/* cmdsocket.c */

#include <sys/time.h>
#include <dev/wscons/wsconsio.h>

#include <fcntl.h>
#include <unistd.h>
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include "vm.h"

#define PORT 15674
#define CMD_MAXSIZE 1024
#define PREFIX_MAXSIZE 1024

#define CMDSOCKET_BANNER "lightscript cmdsocket ready\r\n"

int cmd_sock = 0;
int cmd_active = 0;
int cmd_ignore;
int cmd_bytes;
int cmdsocket_initialised = 0;
char cmd_buf[CMD_MAXSIZE];
char cmd_prefix[PREFIX_MAXSIZE];

int cmdsocket_init(void)
{
	cmdsocket_initialised = 1;
    	cmd_bytes = 0;
    	cmd_ignore = 0;
	cmd_prefix[0] = '\0';

	return 1;
}

void cmdsocket_close(void)
{
	if (cmd_active)
		close(cmd_active);
	if (cmd_sock)
		close(cmd_sock);
	cmdsocket_initialised = 0;
}

void cmdsocket_prefix(char *prefix)
{
	if (!cmdsocket_initialised)
		return;
	if (strlen(prefix) >= PREFIX_MAXSIZE-1)
		return;
	strncpy(cmd_prefix, prefix, PREFIX_MAXSIZE);
}

#define ERROR(x) do { warn((x)); goto error; } while (0)

#define BACKLOG 0

int cmdsocket_listen(int port) {
	int sockopt_on = 1;
	struct sockaddr_in my_addr;

	cmd_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (cmd_sock == -1)
		ERROR("can't open socket");
	if (setsockopt(cmd_sock, SOL_SOCKET, SO_REUSEADDR, &sockopt_on,
	    sizeof(int)) == -1)
		ERROR("can't set socket options");
	memset((char *) &my_addr, 0, sizeof(my_addr));
	my_addr.sin_family = PF_INET;
	my_addr.sin_port = htons(port);
	my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	if (bind(cmd_sock, (struct sockaddr *)&my_addr, sizeof(my_addr)) == -1)
		ERROR("can't bind socket");
	if (listen(cmd_sock, BACKLOG) == -1)
		ERROR("can't listen");
	vm_register_signal_fd(cmd_sock, VM_CMDLISTENQ);

	return 1;

error:
	if (cmd_sock)
	    close(cmd_sock);
	cmd_sock = 0;
	return 0;
}


int cmdsocket_accept(void) {
	struct sockaddr_in client_addr;
	socklen_t client_addr_size = sizeof(client_addr);
	int c;

	if (!cmdsocket_initialised)
		return 0;
	if (cmd_sock == 0)
		return 0;

	/* We only accept one active connection at a time */
	/* XXX can't just return here */
	if (cmd_active)
		return 0;

	c = accept(cmd_sock, (struct sockaddr *)&client_addr,
	    &client_addr_size);
	if (c == -1) {
		if (errno == EAGAIN)
			return 0;
		warn("Error accepting connection");
		close(cmd_sock);
		cmd_sock = 0;
		return 0;
	}

	cmd_active = c;
	write(cmd_active, CMDSOCKET_BANNER, sizeof(CMDSOCKET_BANNER)-1);

	vm_register_signal_fd(cmd_active, VM_CMDREADQ);
	vm_wakeup(VM_CMDREADQ);
	return 1;
}

void cmd_parse(char *cmd) {
	char *sp;
	int arg = 0;
	char function[CMD_MAXSIZE + PREFIX_MAXSIZE];

	*strchr(cmd, '\n') = '\0';
//	printf("DEBUG: Received command: %s\n", cmd);
//	fflush(stdout);

	sp = strtok(cmd, " \t\r");
	sp = strtok(NULL, " \t\r");
	if (sp) {
	    arg = atoi(sp);
	}

	/* cmd_prefix is guaranteed to be terminated */
	strcpy(function, cmd_prefix);
	strncat(function, cmd, CMD_MAXSIZE);

//	printf("DEBUG: function: %s, arg %d\n", function, arg);
//	fflush(stdout);
	if (vm_spawn_args(function, 1, arg)) {
		/* The ack gets sent by the script */
	} else {
		/* Write an error, if it's possible. Don't worry about
		   missing characters and buffering for now, but we
		   should eventually */
		write(cmd_active, "ERROR\n", strlen("ERROR\n"));
	}

//	printf("Received command: %s\n", cmd);
}

int cmdsocket_read(void)
{
	if (!cmdsocket_initialised)
		return 0;

	while (1) {
		int rv;
		int left = sizeof(cmd_buf) - cmd_bytes;
		char *p;
		if (!cmd_active)
			return 0;

		rv = recv(cmd_active, ((char *)&cmd_buf) + cmd_bytes, left, 0);
		if (rv == -1) {
			if (errno == EAGAIN)
				return 0;
			printf("Error reading from socket\n");
			vm_unregister_signal_fd(cmd_active);
			close(cmd_active);
			cmd_active = 0;
			vm_wakeup(VM_CMDLISTENQ);
			return 0;
		}
		if (rv == 0) {
			vm_unregister_signal_fd(cmd_active);
			close(cmd_active);
			cmd_active = 0;
			vm_wakeup(VM_CMDLISTENQ);
			return 0;
		}
		cmd_bytes += rv;
		if ((p = memchr(cmd_buf, '\n', cmd_bytes))) {
		    if (!cmd_ignore)
			cmd_parse(cmd_buf);
		    memmove(cmd_buf, p+1, cmd_bytes - (p - cmd_buf - 1));
		    cmd_bytes = 0;
		    cmd_ignore = 0;
		}
		if (cmd_bytes == sizeof(cmd_buf)) {
			cmd_bytes = 0;
			cmd_ignore = 1;
			/* Overflow */
		}
	}
}

/*
 * Returns offset to restart at. If off == len, then we have finished.
 * If the return value is the same as off passed in, then the caller
 * should sleep.
 */
int cmdsocket_write(char *buffer, int len, int off)
{
//	printf("cmdsocket_write called with %p, %d, %d\n", buffer, len, off);
	if (!cmdsocket_initialised)
		return 0;
	if (!cmd_active)
		return 0;

	while (off < len) {
		int r;
		r = write(cmd_active, buffer + off, len - off);
		if (r == -1) {
			if (errno == EAGAIN)
				return off;
			warn("error writing packet");
		}
		if (r == 0)
			return off;
		off += r;
	}
	return off;
}
