//#include <sys/socket.h>
//#include <netinet/in.h>
#include <errno.h>
//#include <stdlib.h>
//#include <stdio.h>
#include <stdarg.h> // TODO killme?
#include <string.h>
#include <unistd.h>
#include <termios.h> // TODO killme
//
//#include <osmocom/core/msgb.h>
//XXX[osmo_counter]#include <osmocom/core/linuxlist.h>
//XXX[osmo_counter]#include <osmocom/core/counter.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/stat_item.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/signal.h>
#include <osmocom/core/select.h>
//
#include <osmocom/vty/prometheus.h>
#include <osmocom/vty/buffer.h>
#include <osmocom/vty/command.h>

#include <osmocom/core/rate_ctr.h>


/* per connection data */
LLIST_HEAD(prom_active_connections);

static void *prom_ctx;

/* Vector which store each vty structure. */
static vector prom_vtyvec;

/* per network data */
static int prom_new_connection(struct osmo_fd *fd, unsigned int what);

static struct osmo_fd server_socket = {
	.when	    = OSMO_FD_READ,
	.cb	    = prom_new_connection,
	.priv_nr    = 0,
};

/*! Create new vty structure. */
//struct prom_vty *vty_create (int vty_sock, void *priv)
//
/*! Create new vty structure. */
// TODO extern struct host prom_host;

/*! Return if this VTY is a shell or not */
//int prom_vty_shell(struct prom_vty *vty)
//{
//	return vty->type == VTY_SHELL ? 1 : 0;
//}


/*! close a telnet connection */
int prom_telnet_close_client(struct osmo_fd *fd)
{
	struct prom_connection *conn = (struct prom_connection*)fd->data;
	char sock_name_buf[OSMO_SOCK_NAME_MAXLEN];
	int rc;

	/* FIXME: getsockname() always fails: "Bad file descriptor" */
	rc = osmo_sock_get_name_buf(sock_name_buf, OSMO_SOCK_NAME_MAXLEN, fd->fd);
	LOGP(DLGLOBAL, LOGL_INFO, "Closing telnet connection %s\n",
	     (rc <= 0) ? "r=NULL<->l=NULL" : sock_name_buf);

	close(fd->fd);
	osmo_fd_unregister(fd);

	if (conn->dbg) {
		log_del_target(conn->dbg);
		talloc_free(conn->dbg);
	}

	llist_del(&conn->entry);
	talloc_free(conn);
	return 0;
}



/*! callback from core VTY code about VTY related events */
void prom_vty_event(enum event event, int sock, struct prom_vty *vty)
{
	struct vty_signal_data sig_data;
	struct prom_connection *connection = vty->priv;
	struct osmo_fd *bfd;

	//if (vty->type != VTY_TERM)
	//	return;

	sig_data.event = event;
	sig_data.sock = sock;
	sig_data.vty = vty;
	osmo_signal_dispatch(SS_L_VTY, S_VTY_EVENT, &sig_data);

	if (!connection)
		return;

	bfd = &connection->fd;

	switch (event) {
	case VTY_READ:
		bfd->when |= OSMO_FD_READ;
		break;
	case VTY_WRITE:
		bfd->when |= OSMO_FD_WRITE;
		break;
	case VTY_CLOSED:
		/* vty layer is about to free() vty */
		prom_telnet_close_client(bfd);
		break;
	default:
		break;
	}
}

int prom_vty_out_va(struct prom_vty *vty, const char *format, va_list ap)
{
	int len = 0;
	int size = 1024;
	char buf[1024];
	char *p = NULL;

	// TODO don't use prom_vty_shell
	//if (prom_vty_shell(vty)) {
	//	vprintf(format, ap);
	//} else {
		va_list args;
		/* Try to write to initial buffer.  */
		va_copy(args, ap);
		len = vsnprintf(buf, sizeof buf, format, args);
		va_end(args);

		/* Initial buffer is not enough.  */
		if (len < 0 || len >= size) {
			while (1) {
				if (len > -1)
					size = len + 1;
				else
					size = size * 2;

				p = talloc_realloc_size(vty, p, size);
				if (!p)
					return -1;

				va_copy(args, ap);
				len = vsnprintf(p, size, format, args);
				va_end(args);

				if (len > -1 && len < size)
					break;
			}
		}

		/* When initial buffer is enough to store all output.  */
		if (!p)
			p = buf;

		/* Pointer p must point out buffer. */
		buffer_put(vty->obuf, (unsigned char *) p, len);

		/* If p is not different with buf, it is allocated buffer.  */
		if (p != buf)
			talloc_free(p);
	//}

	prom_vty_event(VTY_WRITE, vty->fd, vty);

	return len;
}

/*! VTY standard output function
 *  \param[in] vty VTY to which we should print
 *  \param[in] format variable-length format string
 */
int prom_vty_out(struct prom_vty *vty, const char *format, ...)
{
	va_list args;
	int rc;
	va_start(args, format);
	rc = prom_vty_out_va(vty, format, args);
	va_end(args);
	return rc;
}


/*! Initialize telnet based VTY interface listening to 127.0.0.1
 *  \param[in] tall_ctx \ref talloc context
 *  \param[in] priv private data to be passed to callback
 *  \param[in] port TCP port number to bind to
 */
//int telnet_init(void *tall_ctx, void *priv, int port)
//{
//	return telnet_init_dynif(tall_ctx, priv, "127.0.0.1", port);
//}

int osmo_prom_init(void *ctx, int port)
{
	return osmo_prom_init_dynif(ctx, "127.0.0.1", port);
}

/*! Initialize telnet based VTY interface
 *  \param[in] tall_ctx \ref talloc context
 *  \param[in] priv private data to be passed to callback
 *  \param[in] ip IP to listen to ('::1' for localhost, '::0' for all, ...)
 *  \param[in] port TCP port number to bind to
 */
//int telnet_init_dynif(void *tall_ctx, void *priv, const char *ip, int port)

int osmo_prom_init_dynif(void *ctx, const char *host, int port)
{
	int rc;

	prom_ctx = talloc_named_const(ctx, 1,
			"prometheus_connection");

	rc = osmo_sock_init_ofd(
			&server_socket,
			AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP,
			host, port, OSMO_SOCK_F_BIND
			);

	server_socket.data = NULL; // könnte priv

	if (rc < 0) {
		LOGP(DLGLOBAL, LOGL_ERROR, "Cannot bind telnet at %s %d\n",
		     host, port);
		return -1;
	}

	prom_vtyvec = vector_init(VECTOR_MIN_SIZE);

	LOGP(DLGLOBAL, LOGL_NOTICE, "Available via HTTP %s %d\n", host, port);
	return 0;
}

/*! Unlock the configuration from a given VTY
 *  \param[in] vty VTY from which the configuration shall be unlocked
 *  \returns 0 in case of success
 */
int prom_vty_config_unlock(struct prom_vty *vty)
{
	//if (vty_config == 1 && vty->config == 1) {
	//	vty->config = 0;
	//	vty_config = 0;
	//}
	return vty->config;
}


void prom_vty_close(struct prom_vty *vty)
{
	int i;

	/* VTY_CLOSED is handled by the telnet_interface */
	prom_vty_event(VTY_CLOSED, vty->fd, vty);

	if (vty->obuf)  {
		/* Flush buffer. */
		buffer_flush_all(vty->obuf, vty->fd);

		/* Free input buffer. */
		buffer_free(vty->obuf);
		vty->obuf = NULL;
	}

	/* Free command history. */
	for (i = 0; i < PROM_VTY_MAXHIST; i++)
		if (vty->hist[i])
			talloc_free(vty->hist[i]);

	/* Unset vector. */
	vector_unset(prom_vtyvec, vty->fd);

	/* Close socket (ignore standard I/O streams). */
	if (vty->fd > 2) {
		close(vty->fd);
		vty->fd = -1;
	}

	if (vty->buf) {
		talloc_free(vty->buf);
		vty->buf = NULL;
	}

	/* Check configure. */
	prom_vty_config_unlock(vty);

	/* OK free vty. */
	talloc_free(vty);
}

//XXX[osmo_counter]static int osmo_counter_handler(struct osmo_counter *counter, void *vty_)
//XXX[osmo_counter]{
//XXX[osmo_counter]	struct vty *vty = vty_;
//XXX[osmo_counter]	const char *description = counter->description;
//XXX[osmo_counter]
//XXX[osmo_counter]	if (!counter->description)
//XXX[osmo_counter]		description = counter->name;
//XXX[osmo_counter]
//XXX[osmo_counter]	//vty_out(vty, " %s%s: %8lu%s",
//XXX[osmo_counter]	//	vctx->prefix, description,
//XXX[osmo_counter]	//	osmo_counter_get(counter), VTY_NEWLINE);
//XXX[osmo_counter]
//XXX[osmo_counter]	char buf[1024];
//XXX[osmo_counter]
//XXX[osmo_counter]	// TODO only export current value and let graphing tools sum it up?
//XXX[osmo_counter]
//XXX[osmo_counter]	/* TODO ensure name contains only [a-zA-Z0-] */
//XXX[osmo_counter]	const char *name = description;
//XXX[osmo_counter]
//XXX[osmo_counter]	snprintf(buf, sizeof buf,
//XXX[osmo_counter]		"%s %d\n",
//XXX[osmo_counter]		name,
//XXX[osmo_counter]		osmo_counter_get(counter)
//XXX[osmo_counter]	);
//XXX[osmo_counter]
//XXX[osmo_counter]	prom_vty_out(vty, "%X\r\n", strlen(buf));
//XXX[osmo_counter]	prom_vty_out(vty, "%s\r\n", buf);
//XXX[osmo_counter]
//XXX[osmo_counter]	return 0;
//XXX[osmo_counter]}


void sanitize_name(char *name) {
	for (char c; (c = *name) != '\0'; name++) {
		if ('a' <= c && c <= 'z') continue;
		if ('A' <= c && c <= 'Z') continue;
		if ('0' <= c && c <= '9') continue;
		if (c == ':' || c == '_') continue;
		*name = '_';
	}
}


static int rate_ctr_handler(
	struct rate_ctr_group *ctrg, struct rate_ctr *ctr,
	const struct rate_ctr_desc *desc, void *vty_)
{
	struct vty *vty = vty_;

	char buf[1024];
	char name[1024];

	// TODO only export current value and let graphing tools sum it up?

	strcpy(name, desc->name);
	sanitize_name(name);

	char *group = ctrg->desc->group_name_prefix;
	//char intv_sec_name[1024];
	//char intv_min_name[1024];
	//char intv_hour_name[1024];
	//char intv_day_name[1024];

	//strcpy(intv_sec_name, name);
	//strcpy(intv_min_name, name);
	//strcpy(intv_hour_name, name);
	//strcpy(intv_day_name, name);

	//strcat(intv_sec_name, "_intv_sec");
	//strcat(intv_min_name, "_intv_min");
	//strcat(intv_hour_name, "_intv_hour");
	//strcat(intv_day_name, "_intv_day");

	snprintf(buf, sizeof buf,
		//"%s %d\n"
		//"%s %d\n"
		//"%s %d\n"
		//"%s %d\n"
		"%s{group=\"%s\"} %d\n",
		//intv_sec_name, ctr->intv[RATE_CTR_INTV_SEC].rate,
		//intv_min_name, ctr->intv[RATE_CTR_INTV_MIN].rate,
		//intv_hour_name, ctr->intv[RATE_CTR_INTV_HOUR].rate,
		//intv_day_name, ctr->intv[RATE_CTR_INTV_DAY].rate
		name, group, ctr->current
	);

	prom_vty_out(vty, "%X\r\n", strlen(buf));
	prom_vty_out(vty, "%s\r\n", buf);

	return 0;
}

static int rate_ctr_group_handler(struct rate_ctr_group *ctrg, void *vty_)
{
	struct vty *vty = vty_;

	//TODO
//	if (ctrg->idx)
//		prom_vty_out(vty, "%s (%d):\r\n",
//			ctrg->desc->group_description, ctrg->idx);
//	else
//		prom_vty_out(vty, "%s:\r\n",
//			ctrg->desc->group_description);

	rate_ctr_for_each_counter(ctrg, rate_ctr_handler, vty);

	return 0;
}

static int osmo_stat_item_handler(
	struct osmo_stat_item_group *statg, struct osmo_stat_item *item, void *vty_)
{
	struct vty *vty = vty_;
	const char *unit =
		item->desc->unit != OSMO_STAT_ITEM_NO_UNIT ?
		item->desc->unit : "";

	char buf[1024];
	char name[1024];

	//vty_out(vty, " %s%s: %8" PRIi32 " %s%s",
	//	vctx->prefix, item->desc->description,
	//	osmo_stat_item_get_last(item),
	//	unit, VTY_NEWLINE);

	strcpy(name, item->desc->name);
	sanitize_name(name);

	char *group = statg->desc->group_name_prefix;
	int value = osmo_stat_item_get_last(item);
	// TODO unit
	snprintf(buf, sizeof buf, "%s{group=\"%s\",unit=\"%s\"} %d\n", name, group, unit, value);

	prom_vty_out(vty, "%X\r\n", strlen(buf));
	prom_vty_out(vty, "%s\r\n", buf);

	return 0;
}

static int osmo_stat_item_group_handler(struct osmo_stat_item_group *statg, void *vty_)
{
	struct vty *vty = vty_;

	//if (statg->idx)
	//	vty_out(vty, "%s%s (%d):%s", vctx->prefix,
	//		statg->desc->group_description, statg->idx,
	//		VTY_NEWLINE);
	//else
	//	vty_out(vty, "%s%s:%s", vctx->prefix,
	//		statg->desc->group_description, VTY_NEWLINE);

	osmo_stat_item_for_each_item(statg, osmo_stat_item_handler, vty);

	return 0;
}

/*! Read data via vty socket. */
int prom_vty_read(struct prom_vty *vty)
{
	//int i;
	int nbytes;
	unsigned char buf[VTY_READ_BUFSIZ];

	int vty_sock = vty->fd;

	/* Read raw data from socket */
	if ((nbytes = read(vty->fd, buf, VTY_READ_BUFSIZ)) <= 0) {
		if (nbytes < 0) {
			if (ERRNO_IO_RETRY(errno)) {
				prom_vty_event(VTY_READ, vty_sock, vty);
				return 0;
			}
		}
		buffer_reset(vty->obuf);
		vty->status = PROM_VTY_CLOSE;
	}

	//LOGP(DLGLOBAL, LOGL_NOTICE, "read %d byte(s)\n", nbytes);
	prom_vty_out(vty,
	    "HTTP/1.1 200 OK\r\n"
	    "Content-Type: text/plain\r\n"
	    "Transfer-Encoding: chunked\r\n"
	    // "Content-Length: 6\r\n"
	    "\r\n"
	);
	//vty->status = PROM_VTY_CLOSE;

	//XXX[osmo_counter]osmo_counters_for_each(osmo_counter_handler, vty);
	rate_ctr_for_each_group(rate_ctr_group_handler, vty);
	osmo_stat_item_for_each_group(osmo_stat_item_group_handler, vty);

	/* finish chunked transfer encoding */
	prom_vty_out(vty, "0\r\n\r\n");

	// TODO parse

	/* Check status. */
	if (vty->status == PROM_VTY_CLOSE) {
		prom_vty_close(vty);
		return -EBADF;
	} else {
		prom_vty_event(VTY_WRITE, vty_sock, vty);
		prom_vty_event(VTY_READ, vty_sock, vty);
	}

	return 0;
}


static int client_data(struct osmo_fd *fd, unsigned int what)
{
	struct prom_connection *conn = fd->data;
	int rc = 0;

	if (what & OSMO_FD_READ) {
		conn->fd.when &= ~OSMO_FD_READ;
		// TODO feed server
		rc = prom_vty_read(conn->vty);
	}

	/* vty might have been closed from vithin prom_vty_read() */
	if (rc == -EBADF)
		return rc;

	if (what & OSMO_FD_WRITE) {
		rc = buffer_flush_all(conn->vty->obuf, fd->fd);
		if (rc == BUFFER_EMPTY)
			conn->fd.when &= ~OSMO_FD_WRITE;
	}

	return rc;
}


/*! Allocate a new vty interface structure */
struct prom_vty *prom_vty_new(void)
{
	struct prom_vty *new = talloc_zero(tall_vty_ctx, struct vty);

	if (!new)
		goto out;

	INIT_LLIST_HEAD(&new->parent_nodes);

	new->obuf = buffer_new(new, 0);	/* Use default buffer size. */
	if (!new->obuf)
		goto out_new;
	new->buf = _talloc_zero(new, PROM_HTTP_BUFSIZ, "prom_vty_new->buf");
	if (!new->buf)
		goto out_obuf;

	new->max = PROM_HTTP_BUFSIZ;
	new->fd = -1;

	return new;

out_obuf:
	buffer_free(new->obuf);
out_new:
	talloc_free(new);
	new = NULL;
out:
	return new;
}

struct vty *
prom_vty_create (int vty_sock, void *priv)
{
  struct prom_vty *vty;

	struct termios t = {};

	tcgetattr(vty_sock, &t);
	cfmakeraw(&t);
	tcsetattr(vty_sock, TCSANOW, &t);

  /* Allocate new vty structure and set up default values. */
  vty = prom_vty_new ();
  vty->fd = vty_sock;
  vty->priv = priv;
  //vty->type = VTY_TERM; // TODO kill vty->type
  //if (!password_check)
  //  {
  //    if (prom_host.advanced)
  //      vty->node = ENABLE_NODE;
  //    else
  //      vty->node = VIEW_NODE;
  //  }
  //else
  //  vty->node = AUTH_NODE;
  vty->fail = 0;
  vty->cp = 0;
  // TODO vty_clear_buf (vty);
  vty->length = 0;
  memset (vty->hist, 0, sizeof (vty->hist));
  vty->hp = 0;
  vty->hindex = 0;
  vector_set_index (prom_vtyvec, vty_sock, vty);
  vty->status = PROM_VTY_NORMAL;
  //if (prom_host.lines >= 0)
  //  vty->lines = prom_host.lines;
  //else
  //  vty->lines = -1;

  //if (password_check)
  //  {
  //    /* Vty is not available if password isn't set. */
  //    if (prom_host.password == NULL && prom_host.password_encrypt == NULL)
  //      {
  //        prom_vty_out (vty, "Vty password is not set.%s", VTY_NEWLINE);
  //        vty->status = PROM_VTY_CLOSE;
  //        prom_vty_close (vty);
  //        return NULL;
  //      }
  //  }

  /* Say hello to the world. */
  //prom_vty_hello (vty);
  //if (password_check)
  //  prom_vty_out (vty, "%sUser Access Verification%s%s", VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE);

  /* Setting up terminal. */
  // TODO vty_will_echo (vty);
  //TODO vty_will_suppress_go_ahead (vty);

  // TODO vty_dont_linemode (vty);
  // TODO vty_do_window_size (vty);
  /* vty_dont_lflow_ahead (vty); */

  // TODO vty_prompt (vty);

  /* Add read/write thread. */
  prom_vty_event (VTY_WRITE, vty_sock, vty);
  prom_vty_event (VTY_READ, vty_sock, vty);

  return vty;
}


static int prom_new_connection(struct osmo_fd *fd, unsigned int what)
{
	//LOGP(DLGLOBAL, LOGL_ERROR, "derp\n");
	struct prom_connection *connection;
	struct sockaddr_in sockaddr;
	socklen_t len = sizeof(sockaddr);
	int new_connection = accept(fd->fd, (struct sockaddr*)&sockaddr, &len);
	char sock_name_buf[OSMO_SOCK_NAME_MAXLEN];
	int rc;

	if (new_connection < 0) {
		LOGP(DLGLOBAL, LOGL_ERROR, "HTTP accept failed\n");
		return new_connection;
	}

	rc = osmo_sock_get_name_buf(sock_name_buf, OSMO_SOCK_NAME_MAXLEN, new_connection);
	LOGP(DLGLOBAL, LOGL_INFO, "Accept()ed new HTTP connection %s\n",
	     (rc <= 0) ? "r=NULL<->l=NULL" : sock_name_buf);

	connection = talloc_zero(prom_ctx, struct prom_connection);
	connection->priv = fd->data;
	connection->fd.data = connection;
	connection->fd.fd = new_connection;
	connection->fd.when = OSMO_FD_READ;
	connection->fd.cb = client_data;
	rc = osmo_fd_register(&connection->fd);
	if (rc < 0) {
		talloc_free(connection);
		return rc;
	}
	llist_add_tail(&connection->entry, &prom_active_connections);

	// TODO create server
	connection->vty = prom_vty_create(new_connection, connection);
	if (!connection->vty) {
		LOGP(DLGLOBAL, LOGL_ERROR, "couldn't create VTY\n");
		/* prom_vty_create() is already closing the fd if it returns NULL */
		talloc_free(connection);
		return -1;
	}

	return 0;
}