#include "daemon/dns.h"
#include "daemon/log.h"
#include "irc.h"

void (*irc_privmsg_cb)(struct ircstate *, const struct privmsg *) = NULL;
void (*irc_disconnect_cb)(struct ircstate *) = NULL;

static struct io_plan *irc_connected(struct io_conn *conn, struct lightningd_state *dstate, struct ircstate *state);
static void irc_disconnected(struct io_conn *conn, struct ircstate *state);

bool irc_send_msg(struct ircstate *state, struct privmsg *m)
{
	return irc_send(state, "PRIVMSG", "%s :%s", m->channel, m->msg);
}

/* Send a raw irccommand to the IRC server. */
bool irc_send(struct ircstate *state, const char *command, const char *fmt, ...)
{
	va_list ap;
	struct irccommand *c = tal(state, struct irccommand);

	c->prefix = NULL;

	if (!state->connected)
		return false;

	va_start(ap, fmt);
	c->command = tal_strdup(c, command);
	c->params = tal_vfmt(c, fmt, ap);
	va_end(ap);

	list_add_tail(&state->writequeue, &c->list);
	io_wake(state);
	return true;
}

/* Write buffered irccommands to the IRC connection. Commands can be
   buffered using irc_send. */
static struct io_plan *irc_write_loop(struct io_conn *conn, struct ircstate *state)
{
	state->writebuffer = tal_free(state->writebuffer);

	struct irccommand *m = list_pop(&state->writequeue, struct irccommand, list);
	if (m == NULL)
		return io_out_wait(conn, state, irc_write_loop, state);

	bool hasprefix = m->prefix == NULL;
	state->writebuffer = tal_fmt(
		state, "%s%s%s %s\r\n",
		hasprefix ? "" : m->prefix,
		hasprefix ? "" : " ",
		m->command,
		m->params);

	tal_free(m);

	log_debug(state->log, "Sending: \"%s\"", state->writebuffer);

	return io_write(
		       conn,
		       state->writebuffer, strlen(state->writebuffer),
		       irc_write_loop, state
		       );
}

/* 
 * Called by the read loop to handle individual lines. This splits the
 * line into a struct irccommand and passes it on to the specific
 * handlers for the irccommand type. It silently drops any irccommand
 * that has an unhandled type.
 */
static void handle_irc_command(struct ircstate *state, const char *line)
{
	log_debug(state->log, "Received: \"%s\"", line);

	struct irccommand *m = talz(state, struct irccommand);
	char** splits = tal_strsplit(m, line, " ", STR_NO_EMPTY);
	int numsplits = tal_count(splits) - 1;

	if (numsplits > 2 && strstarts(splits[0], ":")) {
		m->prefix = splits[0];
		splits++;
	}
	m->command = splits[0];
	m->params = tal_strjoin(m, splits + 1, " ", STR_NO_TRAIL);

	if (streq(m->command, "PING")) {
		irc_send(state, "PONG", "%s", m->params);

	} else if (streq(m->command, "PRIVMSG")) {
		struct privmsg *pm = talz(m, struct privmsg);
		pm->sender = m->prefix;
		pm->channel = splits[1];
		pm->msg = tal_strjoin(m, splits + 2, " ", STR_NO_TRAIL);
		irc_privmsg_cb(state, pm);
	}
	tal_free(m);
}

/* 
 * Read incoming data and split it along the newline boundaries. Takes
 * care of buffering incomplete lines and passes the lines to the
 * handle_irc_command handler.
 */
static struct io_plan *irc_read_loop(struct io_conn *conn, struct ircstate *state)
{

	size_t len = state->readlen + state->buffered;
	char *start = state->buffer, *end;

	while ((end = memchr(start, '\n', len)) != NULL) {
		/* Strip "\r\n" from lines. */
		const char *line = tal_strndup(state, start, end - 1 - start);
		handle_irc_command(state, line);
		tal_free(line);
		len -= (end + 1 - start);
		start = end + 1;
	}

	/* Move any partial data back down. */
	memmove(state->buffer, start, len);
	state->buffered = len;

	return io_read_partial(conn, state->buffer + state->buffered,
			       sizeof(state->buffer) - state->buffered,
			       &state->readlen, irc_read_loop, state);
}

static void irc_failed(struct lightningd_state *dstate, struct ircstate *state)
{
	irc_disconnected(state->conn, state);
	state->connected = false;
}

static void irc_disconnected(struct io_conn *conn, struct ircstate *state)
{
	log_debug(state->log, "Lost connection to IRC server");
	state->connected = false;
	state->conn = NULL;
	state->readlen = 0;
	state->buffered = 0;
	memset(state->buffer, 0, sizeof(state->buffer));

	/* Clear any pending commands, they're no longer useful */
	while (!list_empty(&state->writequeue))
		tal_free(list_pop(&state->writequeue, struct irccommand, list));

	/* Same goes for partially written commands */
	state->writebuffer = tal_free(state->writebuffer);

	if (irc_disconnect_cb != NULL)
		irc_disconnect_cb(state);
}

void irc_connect(struct ircstate *state)
{
	state->connected = false;
	list_head_init(&state->writequeue);

	log_debug(state->log, "Connecting to IRC server %s", state->server);
	dns_resolve_and_connect(state->dstate, state->server, "6667", irc_connected, irc_failed, state);
}

static struct io_plan *irc_connected(struct io_conn *conn, struct lightningd_state *dstate, struct ircstate *state)
{
	io_set_finish(conn, irc_disconnected, state);
	state->conn = conn;
	state->connected = true;
	irc_send(state, "USER", "%s 0 * :A lightning node", state->nick);
	irc_send(state, "NICK", "%s", state->nick);
	irc_send(state, "JOIN", "#lightning-nodes");

	return io_duplex(conn,
			 io_read_partial(conn,
					 state->buffer, sizeof(state->buffer),
					 &state->readlen, irc_read_loop, state),
			 irc_write_loop(conn, state));
}