#include <stdio.h>
#include <gio/gio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/time.h>
#include <glib.h>
#include <glib/gprintf.h>

#include "response.h"
#include "globals.h"

static int port = 3333;

GMainContext *context=NULL;
GMainLoop *mainLoop=NULL;
GSocketConnection * connection = NULL;
GSocketClient * client = NULL;

char exitTokens[5][7] = {"quit", "exit", "logout", "logoff", "local"};

//helper function to nicely terminate the client
static void terminate(char *message, int exitCode)
{
	g_object_unref(client);
	g_object_unref(connection);
	if (message) {
		g_print("%s\n", message);
	}
	exit(exitCode);
}

//a sort of strncmp but with \n or \r exception
static gboolean Matches(char *src, char* dst, gssize size)
{
	for(; *src != '\0', (size--)>0; src++) {
		if(g_ascii_toupper(*src) != g_ascii_toupper(*dst)) {
			return FALSE;
		}
		dst++;
	}
	for(; *dst != '\0'; dst++)
		if (*dst != '\r' && *dst != '\n') {
			return FALSE;
		}

	return TRUE;
}

#define TIMEOUT 100000 // 100000 useconds, 100ms
#define ONESECONDINUSEC 1000000

//GIOChannel callback
static gboolean
stdinAvailable  (GIOChannel *source, GIOCondition condition, gpointer data)
{
	char tmp[STDBUFSIZE];
	memset(tmp,0,STDBUFSIZE);
	int size = read(0, tmp, STDBUFSIZE);
	if(size<=0) {
		return TRUE;
	}

	// Ignore second \n if two are sent in rapid succession.
	// This happens when the serial input is terminated with \r\n,
	// which getty translates to \n\n. The IO handler splits this
	// into 2 input lines, so we can't use a simple regex to
	// replace it.

	static struct timeval last = { 0, 0 }; // the time this last ran
	static struct timeval now; // the time now
	gettimeofday(&now, NULL );

	if (last.tv_sec != 0) { // shouldn't be zero if we have been in here before
		if (strcmp(tmp, "\n") == 0) { // is this a newline all on it's own?
			if (last.tv_sec == now.tv_sec) {
				// if we're in the same second we can just check the usec field
				if (now.tv_usec - last.tv_usec < TIMEOUT) {
					// ignore this \n
					return TRUE;
				}
			}
			// - check that last and now sec fields are only one second apart
			// - check that the time elapsed in this second is smaller than the
			//   timeout, if it isn't we don't need to check the other second
			// - check that there is less of the last second remaining than the timeout
			else if (now.tv_sec - last.tv_sec == 1 && now.tv_usec < TIMEOUT && (ONESECONDINUSEC - last.tv_usec) < TIMEOUT) {
				// and then check the the time left in the last second
				// plus the time in this second is less than the timeout
				if ((ONESECONDINUSEC - last.tv_usec) + now.tv_usec < TIMEOUT) {
					// ignore this \n
					return TRUE;
				}
			}
		}
	}

	last = now;


	//test if we have an exit word
	int idx=0;
	for(idx=0; idx<sizeof(exitTokens)/sizeof(exitTokens[0]); idx++) {
		if(Matches(exitTokens[idx], tmp, strlen(exitTokens[idx]))) {
			g_socket_close(g_socket_connection_get_socket(connection), NULL);
			terminate("Goodbye", 0);
		}
	}

	//write the message
	gssize written=0;
	GPollableOutputStream* out = (GPollableOutputStream*)data;
	if (g_pollable_output_stream_is_writable(out) == FALSE) {
		g_print("stream is not writable\n");
	}

	GError* error = NULL;

	written = g_pollable_output_stream_write_nonblocking(out, tmp, size, NULL, &error);

	if(error != NULL && error->message) {
		g_print("Got error: %s\n", error->message);
	}

	if (written != size || written <= 0) {
		g_print("Could not write. blocking. Written: %d\n", written);
		return FALSE;
	}

	return TRUE;
}

//callback for server input
static gboolean cbServerInput(gpointer data, gpointer additional)
{
	char buffer[STDBUFSIZE];
	gssize size=0;
	memset(buffer,0,STDBUFSIZE);

	GPollableInputStream* inStream = (GPollableInputStream*)data;
	GError *error = NULL;

	size=g_pollable_input_stream_read_nonblocking(inStream, buffer, STDBUFSIZE, NULL, &error);
	if(size <=0) {
		terminate("Connection to server lost", -1);
	}

	if(buffer[0] != ' ') {
		g_print("%s", buffer);
	}
	return TRUE;
}

int main(int argc, char** argv)
{
	/* create a new connection */
	g_type_init ();

	GOutputStream *out;
	GInputStream *in;
	GError* error = NULL;
	GIOChannel* stdinChannel = NULL;
	client = g_socket_client_new();

	/* connect to the host */
	connection = g_socket_client_connect_to_host (client,
	                (gchar*)"localhost",
	                port,
	                NULL,
	                &error);

	if (error != NULL) {
		g_print("Could not connect to server on port %d\n", port);
		g_object_unref(client);
		return -1;
	}

	out = g_io_stream_get_output_stream (G_IO_STREAM (connection));
	in = g_io_stream_get_input_stream (G_IO_STREAM (connection));

	g_print("\r\nWelcome! - ");

	// send an idn command
	const gchar idncmd[] = "*idn?\n";
	g_output_stream_write(out, (void*) &idncmd, sizeof(idncmd), NULL, NULL );
	g_output_stream_flush(out, NULL, NULL );
	gchar ch;
	gboolean print = TRUE;
	// drain the input stream until the prompt, print out everything until a newline
	while (ch != '>') {
		g_input_stream_read(in, &ch, 1, NULL, NULL );
		if (ch == '\n')
			print = FALSE;
		if (print == TRUE)
			printf("%c", ch);
	}
	printf("\r\n\r\n");

	g_print("> ");

	//register Pollable sources
	GSource *outSource = NULL;
	GSource *inSource = g_pollable_input_stream_create_source((struct GPollableInputStream*)in, NULL);
	outSource = g_pollable_output_stream_create_source((struct GPollableOutputStream*)out, NULL);

	//register stdin channel
	stdinChannel = g_io_channel_unix_new(0);
	if(stdinChannel == NULL) {
		terminate("Could not open STDIN channel", -2);
	}

	//attach the sources
	g_io_add_watch(stdinChannel, G_IO_IN, stdinAvailable, (struct GPollableOutputStream*)out);
	g_source_set_callback(inSource, cbServerInput, (GPollableOutputStream*)in, NULL);

	g_source_attach(inSource, NULL);
	g_main_loop_run (g_main_loop_new (NULL, FALSE));
	return 0;
}