/*
 * Copyright (C) 2013, 2014 Giorgio Vazzana
 *
 * This file is part of Seren.
 *
 * Seren is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Seren is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "remote.h"
#include "msgbook.h"

#define MODULE "remote"

const struct pc_remote_cmd rcommands[] = {
/* set commands with no parameters */
	{id_hangup,      6, "hangup"},
	{id_accept,      6, "accept"},
	{id_refuse,      6, "refuse"},
/* query/set commands with one optional boolean parameter */
	{id_mute,        4, "mute"},
	{id_loop,        4, "loop"},
	{id_autoaccept, 10, "autoaccept"},
/* query/set commands with one optional float parameter */
	{id_micgain,     7, "micgain"},
/* query/set commands with one optional int parameter */
	{id_bitrate,     7, "bitrate"},
	{id_mode,        4, "mode"},
	{id_algo,        4, "algo"},
/* set commands with one int parameter */
	{id_kill,        4, "kill"},
/* special commands */
	{id_nodegain,    8, "nodegain"}, /* nodegain node [gain] */
	{id_call,        4, "call"},     /* call host [port] */
	{id_unknown,     0, NULL}
};

int    pc_remote_arg_int;
float  pc_remote_arg_float;
char  *pc_remote_arg_string;

void remote_init(struct pc_remote *remote, int *verbose)
{
	memset(remote, 0, sizeof(*remote));
	remote->tcpsockfd    = -1;
	remote->events       = POLLIN;
	remote->verbose      = verbose;
}

int remote_open(struct pc_remote *remote, int listening_sockfd)
{
	int sockfd;
	struct sockaddr_in addr;
	socklen_t addr_len;

	addr_len = sizeof(addr);
	sockfd = accept(listening_sockfd, (struct sockaddr *)&addr, &addr_len);
	if (sockfd == -1) {
		snprintf(msgbuf, MBS, "accept(): %s", strerror(errno));
		msgbook_enqueue(&mb0, MB_TYPE_ERROR, MODULE, msgbuf);
		return -1;
	}

	if (remote->tcpsockfd != -1) {
		if (*remote->verbose >= 1)
			msgbook_enqueue(&mb0, MB_TYPE_VERBOSE, MODULE, "Only one TCP connection is allowed, closing this one");
		close(sockfd);
		return -1;
	}

	remote->tcpsockfd       = sockfd;
	remote->events          = POLLIN;
	remote->readbuf_filled  = 0;
	remote->writebuf_filled = 0;

	if (*remote->verbose >= 1) {
		snprintf(msgbuf, MBS, "TCP connection opened, host = %s:%hu, fd = %d",
			     inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), remote->tcpsockfd);
		msgbook_enqueue(&mb0, MB_TYPE_VERBOSE, MODULE, msgbuf);
	}

	return sockfd;
}

void remote_close(struct pc_remote *remote)
{
	close(remote->tcpsockfd);
	remote->tcpsockfd       = -1;
	remote->events          = POLLIN;
	remote->readbuf_filled  = 0;
	remote->writebuf_filled = 0;

	if (*remote->verbose >= 1)
		msgbook_enqueue(&mb0, MB_TYPE_VERBOSE, MODULE, "TCP connection closed");
}

ssize_t remote_read_socket(struct pc_remote *remote)
{
	ssize_t nr;

	nr = recv(remote->tcpsockfd, remote->readbuf + remote->readbuf_filled, sizeof(remote->readbuf) - remote->readbuf_filled, 0);

	if (nr == -1) {
		snprintf(msgbuf, MBS, "recv(): %s", strerror(errno));
		msgbook_enqueue(&mb0, MB_TYPE_ERROR, MODULE, msgbuf);
	} else if (nr >= 0) {
		/* nr = 0 can also happen if sizeof(remote->readbuf) == remote->readbuf_filled, buffer full */
		remote->readbuf_filled += (size_t)nr;

		if (*remote->verbose >= 2) {
			snprintf(msgbuf, MBS, "TCP connection: %zd bytes read", nr);
			msgbook_enqueue(&mb0, MB_TYPE_DEBUG, MODULE, msgbuf);
		}
	}

	return nr;
}

int remote_extract_line(struct pc_remote *remote, char *buf, size_t len)
{
	char *p;
	size_t i, linelen;

	memset(buf, 0, len);

	/* look for the first '\n' */
	p = NULL;
	for (i = 0; i < remote->readbuf_filled; i++) {
		if (remote->readbuf[i] == '\n') {
			p = &remote->readbuf[i];
			break;
		}
	}
	if (!p)
		return 0;

	p++;
	linelen = (size_t)(p - remote->readbuf);
	if (linelen+1 > len) /* leave 1 for the terminator */
		return -1;

	memcpy(buf, remote->readbuf, linelen);

//	if (linelen < remote->readbuf_filled) {
		memmove(remote->readbuf, p, remote->readbuf_filled - linelen);
		remote->readbuf_filled -= linelen;
//	} else {
//		remote->readbuf_filled = 0;
//	}

	return 1;
}

int remote_parse_command(char *line, unsigned int *cmd_idx, int *nb_args_read)
{
	unsigned int i;
	int ret;
	size_t linelen, l;
	char *p;

	linelen = strlen(line);

	for (i = 0; rcommands[i].name; i++) {
		if (strncmp(line, rcommands[i].name, rcommands[i].namelen) == 0)
			break;
	}
	*cmd_idx      = i;
	*nb_args_read = 0;

	switch (rcommands[i].id) {
	case id_hangup:
	case id_accept:
	case id_refuse:
		break;
	case id_mute:
	case id_loop:
	case id_autoaccept:
		l = rcommands[i].namelen;
		if (linelen == l+3 && line[l] == ' ') {
			pc_remote_arg_int = line[l+1] == '0' ? 0 : 1;
			*nb_args_read = 1;
		}
		break;
	case id_micgain:
		l = rcommands[i].namelen;
		if (linelen >= l+3 && line[l] == ' ') {
			ret = sscanf(line+l+1, "%f", &pc_remote_arg_float);
			*nb_args_read = ret == 1 ? 1 : -1;
		}
		break;
	case id_bitrate:
	case id_mode:
	case id_algo:
		l = rcommands[i].namelen;
		if (linelen >= l+3 && line[l] == ' ') {
			ret = sscanf(line+l+1, "%d", &pc_remote_arg_int);
			*nb_args_read = ret == 1 ? 1 : -1;
		}
		break;
	case id_kill:
		*nb_args_read = -1;
		l = rcommands[i].namelen;
		if (linelen >= l+3 && line[l] == ' ') {
			ret = sscanf(line+l+1, "%d", &pc_remote_arg_int);
			if (ret == 1)
				*nb_args_read = 1;
		}
		break;
	case id_nodegain:
		*nb_args_read = -1;
		l = rcommands[i].namelen;
		if (linelen >= l+3 && line[l] == ' ') {
			ret = sscanf(line+l+1, "%d %f", &pc_remote_arg_int, &pc_remote_arg_float);
			if (ret > 0)
				*nb_args_read = ret;
		}
		break;
	case id_call:
		l = rcommands[i].namelen;
		p = strchr(line+l, ' ');
		if (p) {
			char *start, *end;

			while (*p == ' ')
				p++;
			start = p;

			end = strchr(start, ' ');
			if (end) {
				ret = sscanf(end+1, "%d", &pc_remote_arg_int);
				if (ret == 1)
					*nb_args_read += 1;
			} else {
				end = strchr(start, '\n');
			}

			*end = '\0';
			pc_remote_arg_string = start;
			*nb_args_read += 1;
		}
		if (*nb_args_read == 0)
			*nb_args_read = -1;
		break;
	case id_unknown:
		break;
	}

	if (*nb_args_read == -1)
		return PC_REMOTE_CMD_ARGERROR;

	return PC_REMOTE_CMD_OK;

/*
		} else if (strncmp(buf, "^nodes", 6) == 0) {
			sprintf(resp, "nodes ok %u\n", node_get_count(pctx->nodes));
		} else if (strncmp(buf, "^node", 5) == 0) {
			if (nr >= 7 && buf[5] == ' ') {
				int opt_read;

				opt_read = sscanf(buf+6, "%d", &opt_i);
				if (opt_read == 1) {
					if (opt_i == -1 || (0 <= opt_i && opt_i < PCE_MAX_NODES && pctx->nodes[opt_i]))
						err = 0;
					else
						err = 1;
				} else {
					err = -1;
				}
			}
			if (err)
				strcpy(resp, "node err\n");
			else {
				if (opt_i == -1)
					sprintf(resp, "node ok %d %s %hu %s\n", opt_i, pctx->external_ip[0] ? pctx->external_ip : "127.0.0.1", pctx->udp_port, pctx->nick);
				else
					sprintf(resp, "node ok %d %s %hu %s\n", opt_i, inet_ntoa(pctx->nodes[opt_i]->addr.sin_addr),
					        ntohs(pctx->nodes[opt_i]->addr.sin_port), pctx->nodes[opt_i]->nick);
			}
		} else {
			strcpy(resp, "err unknown command\n");
		}
		resp_len = strlen(resp);
		nw = send(pctx->tcpsockfd1, resp, resp_len, MSG_DONTWAIT);
	}
*/
}

int remote_append_writebuf(struct pc_remote *remote, char *buf, size_t len)
{
	size_t space;

	if (remote->writebuf_filled >= sizeof(remote->writebuf))
		return -1;

	space = sizeof(remote->writebuf) - remote->writebuf_filled;
	if (space < len)
		return -1;

	memcpy(remote->writebuf + remote->writebuf_filled, buf, len);
	remote->writebuf_filled += len;

	return 0;
}

ssize_t remote_write_socket(struct pc_remote *remote)
{
	ssize_t nw;

	nw = send(remote->tcpsockfd, remote->writebuf, remote->writebuf_filled, MSG_DONTWAIT);
	if (nw == -1) {
		if (errno == EAGAIN)
			remote->events = POLLIN | POLLOUT;
	} else {
		if (*remote->verbose >= 2) {
			snprintf(msgbuf, MBS, "TCP connection: %zd bytes written", nw);
			msgbook_enqueue(&mb0, MB_TYPE_DEBUG, MODULE, msgbuf);
		}

		memmove(remote->writebuf, remote->writebuf + (size_t)nw, remote->writebuf_filled - (size_t)nw);
		remote->writebuf_filled -= (size_t)nw;

		if (remote->writebuf_filled == 0)
			remote->events = POLLIN;
		else
			remote->events = POLLIN | POLLOUT;
	}

	return nw;
}

#undef MODULE