#include "socket-common.h"
#include "response.h"
#include "lcd.h"
#include "flash.h"
#include "globals.h"
#include "bus.h"
#include "device-functions.h"
#include "monitor.h"
#include "menus.h"
#include "gpib.h"
#include "parser.h"
#include "error_utils.h"

#include <stdlib.h>
#include <ctype.h>
#include <glib.h>
#include <unistd.h>
#include <fcntl.h>
#include <fstab.h>

#define STDIN_BUF_SIZE 1024


static gboolean periodic_poll (void);
static gboolean finish_boot (void);


int port=3333; //port to listen

guint signalMyCb; //signal to register which is used in cbClientInput(), step 10 from requirements
GAsyncQueue** stdinQueue=NULL;
GThread **peers; //actual connected peers


void send_message(gchar* message)
{
	if(NULL == message) {
		return;
	}
	int count=0;
	gssize size = strlen(message);
	if(!size) {
		return;
	}

	//send the final buffer to the queue
	for(count=0; count<MAX_SESSIONS; count++) {
		//allocate on the heap and deallocate in response.c, cbClientOutput()
		char *toSend = realloc(NULL, size+1);
		memcpy(toSend, message, size+1);

		//make sure we send to only valid peers
		if(peers[count] != 0) {
			g_async_queue_push(stdinQueue[count], toSend);
		}
	}
}

/** pulls an integer position from the vector which contains the
	position to use for the stdinQueue vector
	@param id - the thread id
*/
static int pullIndex(GThread* id)
{
	int i=0,ret=-1;
	static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
	g_static_mutex_lock (&mutex);
	for(i=0; i<MAX_SESSIONS; i++)
		if(peers[i] == id) {
			peers[i] = (GThread*)0;
			ret=i;
			break;
		}
	g_static_mutex_unlock (&mutex);
	return ret;
}

/**
 * pushed in the peers vector the thread id for future reference
 * @param id
 */
static int pushIndex(GThread* id)
{
	int i=0,ret=-1;
	static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
	g_static_mutex_lock (&mutex);
	for(i=0; i<MAX_SESSIONS; i++)
		if(peers[i] == 0) {
			peers[i] = id;
			ret=i;
			break;
		}

	g_static_mutex_unlock (&mutex);
	return ret;
}

//handler for incoming connection, closes the connection if maxCon connections are already handled
static gboolean
incomingConnection (GSocketService *service,
                    GSocketConnection      *connection,
                    GSocketListener        *listener,
                    gpointer                user_data)
{
	if(globals.Remote.terminal_connections +1 > MAX_SESSIONS) {
		g_print_debug("Connection closed. Max reached\n");
		return TRUE;
	}
	globals.Remote.terminal_connections++;
	g_print_debug("Incoming connection\n");
	return FALSE;
}


//thread connection handler
static gboolean
handler (GThreadedSocketService *service,
         GSocketConnection      *connection,
         GSocketListener        *listener,
         gpointer                user_data)
{


	GOutputStream *out;
	GInputStream *in;

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

	g_print_debug("Handling %d connections\n", globals.Remote.terminal_connections);

	//register ourselves in the peers vector, use the index obtained in the stdinQueue
	//should not get -1
	GThread* self = g_thread_self();
	int index=pushIndex(self);

	GSource *outSource = NULL;
	GSource *inSource = g_pollable_input_stream_create_source((struct GPollableInputStream*)in, NULL);

	//get a reference to the async queue
	GAsyncQueue *queue = g_async_queue_ref(stdinQueue[index]);

	//input from client
	g_source_set_callback(inSource, cbClientInput, (GPollableOutputStream*)out, NULL);
	g_source_attach(inSource, NULL);

	//keep thread alive, every 1000 microseconds
	while(g_source_is_destroyed(inSource)==FALSE) {
		g_usleep(1000);
		gint elems;

		//verify our queue length and activate/deactivate the "out" Source for this connection
		//if we don't do this, the out Source will be scheduled frequently and will busy loop
		if((elems=g_async_queue_length(queue))>0) {
			if(outSource == NULL) {

				outSource = g_pollable_output_stream_create_source((struct GPollableOutputStream*)out, NULL);
				g_source_set_callback(outSource, cbClientOutput, queue, NULL);
				g_source_attach(outSource, NULL);
			} else {
				g_source_destroy(outSource);
				g_source_unref(outSource);
				outSource = NULL;
			}

		} else {
			if(outSource!= NULL && !g_source_is_destroyed(outSource)) {
				g_print_debug("Destroy source\n");
				g_source_destroy(outSource);
				g_source_unref(outSource);
				outSource = NULL; //added
			}
		}
		//end of activate/deactivate
	}

	//reached the end of the thread
	if (g_output_stream_close(out, NULL, NULL) == FALSE) {
		g_print_debug("out not closed\n");
	}
	if (g_input_stream_close(in, NULL, NULL) == FALSE) {
		g_print_debug("in not closed\n");
	}

	g_print_debug("Thread end\n");
	globals.Remote.terminal_connections--; //keep track of connections
	g_async_queue_unref(queue); //unreference the queue
	pullIndex(g_thread_self()); //unregister from the peers vector
	return TRUE;
}

extern void vxi_main ();

static gpointer vxithreadfunc (gpointer data)
{
	vxi_main ();
	return NULL;
}


static void get_root_mount ()
{
	struct fstab *sys_fstab;

	sys_fstab = getfsfile ("/");
	
	if (sys_fstab == NULL) {
		return;
	} 

	strcpy (globals.HWDetect.remount_point, sys_fstab->fs_spec);

	printf ("root mount = %s\n", globals.HWDetect.remount_point);

	return;
}

int main(int argc, char **argv)
{
	GSocketService *service = NULL;
	GError *error = NULL;
	GIOChannel* stdinChannel = NULL;

	g_type_init ();
	g_thread_init (NULL);

	get_root_mount();	
	bus_init();

	LCD_initialize();
	LCD_write(0,0,"Starting...");

	initFlash (&globals.Flash, FALSE, 0);

	gchar *message = g_strdup_printf ("%s, S/N %s, FW %s, GPIB addr %d. Avtech Electrosystems Ltd.", 
		globals.Flash.model_num, 
		globals.Flash.serial_num,
		globals.HWDetect.firmware,
		globals.Flash.gpib_address);
	LCD_display_extended_message (message, FALSE, FALSE);
	g_free (message);

	int i;
	for (i=0; i<8; i++) {
		set_dac(i,globals.Flash.initial_dac_settings[i]);
	}

	fixFlash(&globals.Flash);

	// count startups
	if (globals.Flash.self_cal) {
		++globals.Flash.self_cal_startups;
		int eprom_loc = (char *) &(globals.Flash.self_cal_startups) - (char *) &(globals.Flash.flash_start);
		writeUserBlock(&globals.Flash, eprom_loc, sizeof(globals.Flash.self_cal_startups));
	}

	GPIB_initialize();

	IO_Setup_RS232(	globals.Flash.baud,
	                globals.Flash.hardhand);

	/* start-up delay */
	LCD_write(3,0,"Warming up, please wait... ");

	globals.Timers.startup_timer_value = sec_timer ();

	Main_Rst();

	for (i=0; i<max_channels; i++) {
		if (globals.Flash.toggle_trig_at_boot[i]) {
			int j;
			for (j=0;j<ampl_polarities;j++) {

				// do each polarity
				Set_Amplitude(0,j,1,0,0,0,0,i,0.0,0);
				Set_Trig_Source(i,source_hold);

				if (globals.Flash.volt_ctrl_pw[i]) {
					Set_Use_Vctrl_PW_Range(i,FALSE);
				}

				// trigger IL710 twice to initialize it
				Set_Output_State(i,output_on);
				Set_Trig_Source(i,source_immediate);
				Set_Trig_Source(i,source_immediate);
				Set_Output_State(i,output_off);
			}
		}
	}

	Main_Rst();

	//register stdin channel
	stdinChannel = g_io_channel_unix_new(0);
	if(stdinChannel == NULL) {
		g_printerr("No io channel\n");
		exit(-1);
	}

	int idx=0;

	//allocate a MAX_SESSIONS queue on the heap
	stdinQueue = malloc(sizeof(struct GAsyncQueue*) * MAX_SESSIONS);
	//allocate peers vector on the heap
	peers = malloc(sizeof(GThread*) * MAX_SESSIONS);
	for(idx=0; idx<MAX_SESSIONS; idx++) {
		peers[idx] = 0;
		stdinQueue[idx] = g_async_queue_new();
	}

	//create a threaded service
	service = g_threaded_socket_service_new (MAX_SESSIONS+1);
	if (!g_socket_listener_add_inet_port (G_SOCKET_LISTENER (service),
	                                      port,
	                                      NULL,
	                                      &error)) {
		g_printerr ("%s: %s\n", argv[0], error->message);
		free(stdinQueue);
		free(peers);
		return 1;
	}

	//init the signal signalMyCb
	initSignals(&signalMyCb);

	g_print_debug("Server listening on port %d\n", port);
	g_signal_connect (service, "run", G_CALLBACK (handler), NULL);
	g_signal_connect (service, "incoming", G_CALLBACK(incomingConnection), NULL);

	GMainLoop *loop = g_main_loop_new (NULL, FALSE);

	g_timeout_add (20, (GSourceFunc) periodic_poll, NULL);
	g_timeout_add (100, (GSourceFunc) finish_boot, NULL);

	if (globals.Flash.vxi_enabled) {
		GThread *vxithread =g_thread_create(vxithreadfunc, NULL, FALSE, NULL);
		if(vxithread == NULL) {
			printf("Couldn't create vxi thread\n");
		}
	}

	g_main_loop_run (loop);

	bus_shutdown();
	free(stdinQueue);
	free(peers);

	return 0;

}


static gboolean finish_boot (void)
{

#define MIN_STARTUP_DELAY 2
#define MAX_STARTUP_DELAY 120

	long on_delay = (long)globals.Flash.turn_on_dly;
	if (on_delay < MIN_STARTUP_DELAY) {
		on_delay = MIN_STARTUP_DELAY;
	}
	if (on_delay > MAX_STARTUP_DELAY) {
		on_delay = MAX_STARTUP_DELAY;
	}

	long timer_count;

	if ((timer_count=sec_timer()-globals.Timers.startup_timer_value) < on_delay) {
		gchar *message = g_strdup_printf ("%ld ", on_delay - timer_count);
		LCD_write(3,27,message);
		g_free (message);
		return TRUE;	// exit and call by timeout again
	}

	LCD_write(3,27,"OK");

	I2C_Setup_Monitor();

	if (globals.Flash.self_cal &&
	                globals.Flash.self_cal_interval &&
	                (globals.Flash.self_cal_startups % globals.Flash.self_cal_interval) == 0 ) {
		int error_num;
		if (error_num=self_cal()) {
			queue_and_broadcast_sensor_alarm(error_num);
		}
	}


	globals.Sys.startup_complete = 1;

	LCD_initialize();	// to fix flaky LCD startup on BBB?

	Show_Main_Menu();

	// report error if GPIB chip not found
	if (!globals.HWDetect.has_gpib) {
		queue_error_and_display_on_LCD(GPIB_missing);
	}

	return FALSE;	// no more calls to this function are needed
}

static gboolean periodic_poll (void)
{
	gboolean power_fail;

	power_fail = bus_getpin (POWER_FAIL);

	if (power_fail) {
		// verify after a short delay (25 ms), to ignore short power glitches
		g_usleep (25e3);
		power_fail = bus_getpin (POWER_FAIL);
	}

	if (power_fail) {

		globals.Sys.shutdown_started = TRUE;

		int i;
		for (i=0;i<max_channels;i++) {
			Set_Output_State(i,output_off);
		}

		while (globals.Sys.flash_write_in_progress) {
			g_usleep(1000);
		}

		// poweroff
		system ("/usr/bin/systemctl poweroff -f");

		exit(0);
	}

	if (globals.Sys.startup_complete) {
		int i, output_on_time_so_far;

		for (i=0; i<(globals.Flash.ChanKey_output_state?globals.Flash.channels:1); ++i) {
			output_on_time_so_far = (int) (sec_timer()-globals.Timers.last_activity_at[i]);
			if (	(globals.Flash.output_timer[i]>0) &&
			                (globals.Timers.last_activity_at[i]>0) &&
			                (output_on_time_so_far > globals.Flash.output_timer[i])) {
				Set_Output_State(i,output_off);
				Show_Main_Menu();
			}
		}

		Update_Main_Menu_If_Visible();

		Menu_Check_Buttons ();

		for (i=0; i<max_channels; i++) {
			if (globals.Flash.monitor_enabled[i]) {
				I2C_Check_Monitors();
			}
		}

		Update_Main_Menu_If_Visible();

		// don't check GPIB interface if a VXI interface is locked,
		// or is currently handling a command

		if ((globals.VxiLocks.locked_network_server == NO_SERVER_LOCKED) &&
		                (globals.VxiLocks.command_in_progress == FALSE)) {

			// tell VXI servers that the 4882 subsystem is busy
			globals.VxiLocks.command_in_progress = TRUE;

			GPIB_check_for_device_clear_signal();

			if (GPIB_check_for_messages(globals.Registers.gpib_input_buffer)) {
				if (GPIB_handle_new_input(globals.Registers.gpib_input_buffer)) {
					Parser_main(globals.Registers.gpib_input_buffer, 0, GPIB_and_VXI_start_query_response, NULL);
				}
			}

			// send response if appropriate
			GPIB_finish_query_response();

			GPIB_check_for_device_clear_signal();

			// tell VXI servers that the 4882 subsystem is available again
			globals.VxiLocks.command_in_progress = FALSE;
		}

		Update_Main_Menu_If_Visible();
	}

	return TRUE;
}