#include <panel.h>
#include <form.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netinet/in.h>
#include <stdarg.h>

#define MAX_TRAINS 	32
#define CONSOLE_HEIGHT 	8

#define APP_MAIN	0
#define APP_ADD		1
#define APP_EDIT	2
#define APP_CONFIG	3


WINDOW 	*tWins[MAX_TRAINS], *cWin, *trWin, *pWin;
PANEL	*tPanels[MAX_TRAINS], *top, *console, *tr, *options;
char* 	consoleText[CONSOLE_HEIGHT];
int	curTCount, currentMode, currentTrain;
int	wincols, winrows;

char	serverAddr[128];
int	serverPort;

//train data... more to come i'd imagine.
struct tData {
	char name[16];
	int addr, speed, mspeed, dir, f1, f2, f3, f4;
	PANEL *tp;
	WINDOW *tw;
} trains[MAX_TRAINS];


//create/edit train window forms:
FIELD	*trField[5];
FORM	*trForm;

//program options:
FIELD	*pField[2];
FORM	*pForm;

//magic numbers... will be define'd shortly.
int lines=8,cols=40,y=2,x=4,i;

void addConsoleMessage(char* fmt,...) {
	wclear(cWin);
	box(cWin,0,0);
	mvwprintw(cWin,0,1,"[console]");
	int c;
	for (c = 0; c < (CONSOLE_HEIGHT-1); c++) {
		sprintf(consoleText[c],"%s",consoleText[c+1]);
		mvwprintw(cWin,c+1,3,"%s",consoleText[c]);
	}
	va_list args;
	va_start(args,fmt);
	vsprintf(consoleText[CONSOLE_HEIGHT-1],fmt,args);
	va_end(args);
	mvwprintw(cWin,CONSOLE_HEIGHT,3,"%s",consoleText[CONSOLE_HEIGHT-1]);
}

int sock;
struct sockaddr_in server;
bool connected;

char *strip(char *haystack, char *needles) {
	char *found;
	while ((found = strpbrk(haystack,needles)) != NULL) strcpy(found,found+1);
	return haystack;
}

bool sendMessage(char* fmt,...) {
	char msg[40],disp[40];
	va_list args;
	va_start(args,fmt);
	vsprintf(msg,fmt,args);
	va_end(args);
	sprintf(disp,"%s",msg);
	sprintf(msg,"%s\n",msg);

	if (connected) {
		unsigned int msglen = strlen(msg);
        	if (send(sock, msg, msglen, 0) != msglen) return false;
		else {
			addConsoleMessage("sent: %s",disp);
			return true;
		}
	} else {
		//addConsoleMessage("FAILED send: %s",msg);
		return false;
	}
}

bool initNetworking() {
	if ((sock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0) return false;
	memset(&server,0,sizeof(server));
	server.sin_family=AF_INET;
	server.sin_addr.s_addr = inet_addr(serverAddr);
	server.sin_port = htons(serverPort);
	if (connect(sock,(struct sockaddr *)&server,sizeof(server)) < 0) return false;
	return true;
}

void killNetworking() {
	if (connected) {
		close(sock);
		connected = false;
		addConsoleMessage("Disconnected from server.");
	}
}

void updatePanelTabbing() {
	int p;
	top = NULL;
	PANEL *last = NULL;
	for (p = 0; p < MAX_TRAINS; p++) {
		if (trains[p].tp != NULL) {
			if (top == NULL) {
				top = trains[p].tp;
				last = top;
			} else {
				set_panel_userptr(last, trains[p].tp);
				last = trains[p].tp;
			}
		}
	}
	if (top != NULL && last != NULL) {
		set_panel_userptr(last, top);
	}
}

void initTrain(int idx) {
	sendMessage("INIT 1 GL %d N 1 %d 5",trains[idx].addr,trains[idx].mspeed);
}

void sendTrainData(int idx) {
	sendMessage("SET 1 GL %d %d %d %d %d 1 0 0 0 0",trains[idx].addr,trains[idx].dir,trains[idx].speed,trains[idx].mspeed,trains[idx].dir);
}

void connectToServer() {
	if (strcmp(serverAddr,"") != 0) {
		connected = initNetworking();
		if (connected) {
			addConsoleMessage("Successfully connected to server...");
			if (sendMessage("SET CONNECTIONMODE SRCP COMMAND") && sendMessage("GO")) {
				addConsoleMessage("Sent power commands!");
				for (i=0;i<1;i++) initTrain(i);
			} else addConsoleMessage("Power commands failed!");
		} else addConsoleMessage("Connection failed!");
	} else addConsoleMessage("No server details saved, press F2 to edit them.");
}

void drawTrainWindow(int idx) {
	wclear(trains[idx].tw);
	box(trains[idx].tw,0,0);
	mvwprintw(trains[idx].tw,0,1,"[%s]",trains[idx].name);
	mvwprintw(trains[idx].tw,1,1,"Addr:  %d",trains[idx].addr);
	mvwprintw(trains[idx].tw,2,1,"Speed: %d [%d]",trains[idx].speed,trains[idx].mspeed);
	mvwprintw(trains[idx].tw,3,1,"Dir:   %d",trains[idx].dir);
	mvwprintw(trains[idx].tw,4,1,"Funcs: %d %d %d %d",trains[idx].f1,trains[idx].f2,trains[idx].f3,trains[idx].f4);
	sendTrainData(idx);
}

void updateTrain(int idx, char* name, int addr, int spd, int max, int dir) {
	strcpy(trains[idx].name,name);
	trains[idx].addr = addr;
	trains[idx].speed = spd;
	trains[idx].mspeed = max;
	trains[idx].dir = dir;
	drawTrainWindow(idx);
	initTrain(idx);
	sendTrainData(idx);
}

void dealWithForm() {
	int addr = atoi(field_buffer(trField[1],0));
	int spd = atoi(field_buffer(trField[2],0));
	int max = atoi(field_buffer(trField[3],0));
	int dir = atoi(field_buffer(trField[4],0));
	char *name = strip(field_buffer(trField[0],0)," ");
	if (strcmp(name,"") != 0) {
		updateTrain(currentTrain,name,addr,spd,max,dir);
		currentMode = APP_MAIN;	
		hide_panel(tr);
	} else addConsoleMessage("Please enter a valid train name!");
}

bool createTrain(char* tName,int addr,int speed,int mspeed,int dir,int f1,int f2,int f3,int f4) {
	if (curTCount + 1 >= MAX_TRAINS) return false;

	int t = -1;
	int i = 0;
	do {
		if (trains[i].addr == -1) t=i;
		i++;
	} while (t == -1 && i < MAX_TRAINS);


	sprintf(trains[t].name,"%s",tName);
	trains[t].addr=addr;
	trains[t].speed=speed;
	trains[t].mspeed=mspeed;
	trains[t].dir=dir;
	trains[t].f1=f1;
	trains[t].f2=f2;
	trains[t].f3=f3;
	trains[t].f4=f4;

	tWins[curTCount] = newwin(lines,cols,y+(curTCount*2),x+(curTCount * 10));	
	tPanels[curTCount] = new_panel(tWins[curTCount]);
	wbkgd(tWins[curTCount],COLOR_PAIR(1));

	trains[t].tp = tPanels[curTCount];
	trains[t].tw = tWins[curTCount];

	updatePanelTabbing();

	drawTrainWindow(t);

	currentTrain = t;
	top = trains[currentTrain].tp;

	curTCount++;

	return true;
}

void populateTrainForm() {
	char tmp[10];
	set_field_buffer(trField[0],0,trains[currentTrain].name);
	sprintf(tmp,"%d",trains[currentTrain].addr);
	set_field_buffer(trField[1],0,tmp);
	sprintf(tmp,"%d",trains[currentTrain].speed);
	set_field_buffer(trField[2],0,tmp);
	sprintf(tmp,"%d",trains[currentTrain].mspeed);
	set_field_buffer(trField[3],0,tmp);
	sprintf(tmp,"%d",trains[currentTrain].dir);
	set_field_buffer(trField[4],0,tmp);
}

void saveConfig() {
	strcpy(serverAddr,field_buffer(pField[0],0));
	serverPort = atoi(field_buffer(pField[1],0));
	hide_panel(options);
	currentMode = APP_MAIN;
}

void populateConfig() {
	//set port and addr
}

bool handleInput(int ch) {
	int i;
	switch(currentMode) {
		case APP_MAIN:
			switch(ch) {
				case KEY_F(12):
					if (!connected) connectToServer();
					else killNetworking();
					break;
				case KEY_F(2):
					currentMode = APP_CONFIG;
					form_driver(pForm,REQ_LEFT_CHAR);
					populateConfig();
					show_panel(options);					
					break;
				case KEY_DOWN:
					trains[currentTrain].speed -= 1;
					if (trains[currentTrain].speed < 0) trains[currentTrain].speed = 0;
					drawTrainWindow(currentTrain);
					break;
				case KEY_UP:
					trains[currentTrain].speed += 1;
					if (trains[currentTrain].speed > trains[currentTrain].mspeed) trains[currentTrain].speed = trains[currentTrain].mspeed;
					drawTrainWindow(currentTrain);
					break;
				case 9:
					top = (PANEL *)panel_userptr(top);
					top_panel(top);
					for (i = 0; i < MAX_TRAINS; i++) if (trains[i].tp == top) currentTrain = i;
					break;
				case 44:
					trains[currentTrain].dir=1;
					//if (trains[currentTrain].dir < 0) trains[currentTrain].dir = 1;
					drawTrainWindow(currentTrain);
					break;
				case 46:
					trains[currentTrain].dir=0;
					//if (trains[currentTrain].dir > 1) trains[currentTrain].dir = 0;
					drawTrainWindow(currentTrain);
					break;
				case 99:	
					currentMode = APP_ADD;
					form_driver(trForm,REQ_LEFT_CHAR);
					populateTrainForm();
					show_panel(tr);
					break;
				case 113:
					return true; //quit
			}
			break;
		case APP_ADD:
			switch (ch) {
				case KEY_LEFT:
					form_driver(trForm,REQ_LEFT_CHAR);
					break;
				case KEY_RIGHT:
					form_driver(trForm,REQ_RIGHT_CHAR);
					break;
				case KEY_BACKSPACE:
					form_driver(trForm,REQ_LEFT_CHAR);
					form_driver(trForm,REQ_DEL_CHAR);
					break;
				case 27:
					currentMode = APP_MAIN;
					hide_panel(tr);
					break;
				case 10:
					form_driver(trForm,REQ_FIRST_FIELD);
					dealWithForm();
					break;
				case 9:
				case KEY_DOWN:
					form_driver(trForm,REQ_NEXT_FIELD);
					break;
				case KEY_UP:
					form_driver(trForm,REQ_PREV_FIELD);
					break;
				default:
					form_driver(trForm,ch);
					break;
			}
			break;
		case APP_CONFIG:
			switch (ch) {
				case KEY_LEFT:
					form_driver(pForm,REQ_LEFT_CHAR);
					break;
				case KEY_RIGHT:
					form_driver(pForm,REQ_RIGHT_CHAR);
					break;
				case KEY_BACKSPACE:
					form_driver(pForm,REQ_LEFT_CHAR);
					form_driver(pForm,REQ_DEL_CHAR);
					break;
				case 27:
					currentMode = APP_MAIN;
					hide_panel(options);
					break;
				case 10:
					form_driver(pForm,REQ_FIRST_FIELD);
					saveConfig();
					break;
				case 9:
				case KEY_DOWN:
					form_driver(pForm,REQ_NEXT_FIELD);
					break;
				case KEY_UP:
					form_driver(pForm,REQ_PREV_FIELD);
					break;
				default:
					form_driver(pForm,ch);
					break;
			}
			break;
	}	
	//addConsoleMessage("keypress: %d",ch);
	return false;
}

int main() {
	ESCDELAY=0;
	
	curTCount = 0;
	currentMode = APP_MAIN;

	//write all trains as not-loaded.
	for(i=0;i<MAX_TRAINS;i++) {
		trains[i].addr = -1;
		strcpy(trains[i].name,"");
		trains[i].tp=NULL;
		trains[i].tw=NULL;
	}

	for (i = 0; i < CONSOLE_HEIGHT; i++) {
		consoleText[i] = malloc(120 * sizeof(char));
		sprintf(consoleText[i]," ");
	}

	initscr();
	start_color();
	cbreak();
	noecho();
	keypad(stdscr, TRUE);

	init_pair(1,COLOR_WHITE,COLOR_BLUE);
	init_pair(2,COLOR_WHITE,COLOR_RED);
	init_pair(3,COLOR_YELLOW,COLOR_BLACK);
	init_pair(4,COLOR_YELLOW,COLOR_BLUE);
	init_pair(5,COLOR_WHITE,COLOR_GREEN);

	getmaxyx(stdscr,winrows,wincols);

	cWin = newwin(CONSOLE_HEIGHT+2,wincols,winrows-CONSOLE_HEIGHT-2,0);	
	console = new_panel(cWin);	
	wbkgd(cWin,COLOR_PAIR(4));

	trWin = newwin(16,40,30,60);	
	tr = new_panel(trWin);
	wbkgd(trWin,COLOR_PAIR(2));

	//create form for train adding/editing
	trField[0] = new_field(1,16,4,15,0,0);
	trField[1] = new_field(1, 5,5,15,0,0);
	trField[2] = new_field(1, 5,6,15,0,0);
	trField[3] = new_field(1, 5,7,15,0,0);
	trField[4] = new_field(1, 5,8,15,0,0);
	
	set_field_back(trField[0], A_UNDERLINE);
	field_opts_off(trField[0], O_AUTOSKIP);
	set_field_back(trField[1], A_UNDERLINE);
	set_field_type(trField[1],TYPE_NUMERIC,0,3,128);
	set_field_buffer(trField[1],0,"3");
	field_opts_off(trField[1], O_AUTOSKIP);
	set_field_back(trField[2], A_UNDERLINE);
	set_field_type(trField[2],TYPE_NUMERIC,0,0,128);
	set_field_buffer(trField[2],0,"0");
	field_opts_off(trField[2], O_AUTOSKIP);
	set_field_back(trField[3], A_UNDERLINE);
	set_field_type(trField[3],TYPE_NUMERIC,0,0,128);
	set_field_buffer(trField[3],0,"128");
	field_opts_off(trField[3], O_AUTOSKIP);
	set_field_back(trField[4], A_UNDERLINE);
	set_field_type(trField[4],TYPE_NUMERIC,0,0,128);
	set_field_buffer(trField[4],0,"0");
	field_opts_off(trField[4], O_AUTOSKIP);

	trForm = new_form(trField);
	set_form_win(trForm,trWin);
	post_form(trForm);

	box(trWin,0,0);	
	mvwprintw(trWin,0,1,"[Add Train]");
	mvwprintw(trWin,4,4,"Name:");
	mvwprintw(trWin,5,4,"Address:");
	mvwprintw(trWin,6,4,"Speed:");
	mvwprintw(trWin,7,4,"Max Speed:");
	mvwprintw(trWin,8,4,"Direction:");
	hide_panel(tr);

	pWin = newwin(4,40,winrows/2 - 2,wincols/2 - 20);
	options = new_panel(pWin);
	wbkgd(pWin,COLOR_PAIR(5));

	pField[0] = new_field(1,16,1,18,0,0);
	pField[1] = new_field(1, 5,2,18,0,0);

	set_field_back(pField[0], A_UNDERLINE);
	field_opts_off(pField[0], O_AUTOSKIP);
	set_field_buffer(pField[0],0,"192.168.32.128");
	set_field_back(pField[1], A_UNDERLINE);
	set_field_type(pField[1],TYPE_NUMERIC,0,3,128);
	set_field_buffer(pField[1],0,"4303");
	field_opts_off(pField[1], O_AUTOSKIP);

	pForm = new_form(pField);
	set_form_win(pForm,pWin);
	post_form(pForm);

	box(pWin,0,0);	
	mvwprintw(pWin,0,1,"[Configuration]");
	mvwprintw(pWin,1,2,"Server Address:");
	mvwprintw(pWin,2,2,"Port Number:");
	hide_panel(options);

	wbkgd(stdscr,COLOR_PAIR(3));

	//char tName[18];
	//for(i=0;i<5;i++) {
	//	sprintf(tName,"train%d",i);
		createTrain("twilightexpress",3,55,128,1,1,0,0,0);
	//}

	addConsoleMessage("Welcome to trainControl v%f ... please wait ...",0.1f);

	update_panels();
	doupdate();

	connected = false;

	update_panels();
	doupdate();

	int ch;
	bool quit=false;
	while (!quit && ((ch = getch()) != KEY_F(1))) {
		quit = handleInput(ch);
		update_panels();
		doupdate();
	}
	printf("hi");
	//unpost_form(pForm); //crashing!?!
	free_form(pForm);
	for (i=0;i<2;i++)free_field(pField[i]);

	//unpost_form(trForm);
	free_form(trForm);
	for (i=0;i<4;i++)free_field(trField[i]);

	killNetworking();

	endwin();
}
