#if defined(_WIN32) || defined(__APPLE__) || defined (__linux__)\
	|| defined (__unix__)

#include "pc_interface.h"

#ifdef CURSES_INTERFACE
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <curses.h>

#include "interface.h"

WINDOW *logwinb = NULL, *logwin = NULL, *statuswin = NULL, *gpiowin = NULL,
	*dhtwin = NULL, *lswin = NULL, *aqswin = NULL, *lmwin = NULL,
	*formwin = NULL, *geswin = NULL;
const char *gesturesnames[] =
	{ "None", "Right", "Left", "Up", "Down", "Forward", "Backward"
	, "Clockwise", "CnterClockwise"};

extern uint8_t apins[APINS];
extern bool dpins[DPINS];
extern float temperature;
extern float humidity;
extern float lightlevel;
extern int tvoc;
extern int eco2;
extern uint8_t gesture;
extern bool ledmatrix[8][8];

void title_window(WINDOW *win, const char *title, bool focus)
{
	int lines, cols;
	box(win, focus ? 'H' : 0, focus ? '=' : 0);
	getmaxyx(win, lines, cols);
	mvwprintw(win, 0, cols/2-strlen(title)/2, "%s", title);
	wnoutrefresh(win);
	(void)lines;
}

unsigned oldfocus = 0, focus = 0, subfocus = 0;

void curses_draw_gpio()
{
	bool f = focus == 0;
	wmove(gpiowin, 1, 1);
	wprintw(gpiowin, " D0 ..% *s.. D%u", max(0, DPINS*2-12), " ", DPINS-1);
	wmove(gpiowin, 2, 1);
	for (unsigned i = 0; i<DPINS; i++)
		wprintw(gpiowin, "%s%s",
			f && subfocus == i ? ">" : " ", dpins[i] ? "+" : "_");
	wmove(gpiowin, 3, 1);
	wprintw(gpiowin, "  A0 ..% *s.. A%u", max(0, APINS*4-12), " ", APINS-1);
	wmove(gpiowin, 4, 1);
	for (unsigned i = 0; i<APINS; i++)
		wprintw(gpiowin, "%s% 3u",
			f && subfocus == DPINS+i ? ">" : " ", apins[i]);
	wnoutrefresh(gpiowin);
}

void curses_draw_dht()
{
	bool f = focus == 1;
	mvwprintw(dhtwin, 1, 1, "%sT: % 3.1f C",
		f && subfocus == 0 ? ">" : " ", temperature);
	mvwprintw(dhtwin, 2, 1, "%sH: % 3.1f %%",
		f && subfocus == 1 ? ">" : " ", humidity);
	wnoutrefresh(dhtwin);
}

void curses_draw_lightsensor()
{
	bool f = focus == 2;
	mvwprintw(lswin, 1, 1, "%s% 4.2f lx", f ? ">" : " ", lightlevel);
	wnoutrefresh(lswin);
}

void curses_draw_airqualitysensor()
{
	bool f = focus == 3;
	mvwprintw(aqswin, 1, 1, "%sTVOC: % 4u",
		f && subfocus == 0 ? ">" : " ", tvoc);
	mvwprintw(aqswin, 2, 1, "%seCO2: % 4u",
		f && subfocus == 1 ? ">" : " ", eco2);
	wnoutrefresh(aqswin);
}

void curses_draw_gesturesensor()
{
	bool f = focus == 4;
	mvwprintw(geswin, 1, 1, "%s% 15s", f ? ">" : " ",
		gesturesnames[gesture]);
	wnoutrefresh(geswin);
}

void curses_draw_ledmatrix()
{
	wmove(lmwin, 1, 1);
	for (unsigned i = 0; i<8; i++) {
		wmove(lmwin, 1+i, 1);
		for (unsigned j = 0; j<8; j++)
			wprintw(lmwin, " %s", ledmatrix[i][j] ? "o" : "_");
	}
	wnoutrefresh(lmwin);
}

void curses_focus(bool all)
{
	WINDOW *wins[] = { gpiowin, dhtwin, lswin, aqswin, geswin, lmwin };
	char *titles[] =
		{ "gpio", "dht", "light", "airquality", "gesture", "ledmatrix"};
	void (*fs[])() = {curses_draw_gpio, curses_draw_dht,
		curses_draw_lightsensor, curses_draw_airqualitysensor,
		curses_draw_gesturesensor, curses_draw_ledmatrix};
	if (all) {
		for (unsigned i = 0; i<sizeof(wins)/sizeof(WINDOW *); i++) {
			title_window(wins[i], titles[i], false);
		}
	} else {
		title_window(wins[oldfocus], titles[oldfocus], false);
	}

	title_window(wins[focus], titles[focus], true);
	fs[focus]();
}

bool inform = false;
char buf[21] = {0};
unsigned bufi = 0;
void form_init();
void form_key(int k)
{
	char *ptr;
	float res;
	switch (k) {
	case 'q':
		inform = false;
		break;
	case KEY_BACKSPACE:
		buf[bufi--] = '\0';
		mvwprintw(formwin, 1, 2, buf);
		wnoutrefresh(formwin);
		break;
	case '0': case '1': case '2': case '3': case '4': case '5': case '6':
	case '7': case '8': case '9': case '.': case '-':
		if (bufi <= sizeof(buf)-1) {
			buf[bufi++] = k;
			buf[bufi] = '\0';
		}
		mvwprintw(formwin, 1, 2, buf);
		wnoutrefresh(formwin);
		break;
	case '\n':
		res = strtof(buf, &ptr);
		if (*ptr != '\0') {
			msg_log("unable to parse: %s\n", buf);
			return;
		}
		switch (focus) {
		case 0: //gpio
			if (subfocus < DPINS) {
				dpins[subfocus] = res > 0;
			} else {
				apins[subfocus-DPINS] = (int)res % 256;
			}
			curses_draw_gpio();
			break;
		case 1: //dht
			if (subfocus == 0) {
				temperature = min(100.0, max(res, -100.0));
			} else {
				humidity = min(100.0, max(res, 0.0));
			}
			curses_draw_dht();
			break;
		case 2: //light sensor
			lightlevel = min(54612.5, max(res, 0.0));
			curses_draw_lightsensor();
			break;
		case 3: //airquality sensor
			if (subfocus == 0) {
				tvoc = min(65535.0, max(res, 0.0));
			} else {
				eco2 = min(65535.0, max(res, 0.0));
			}
			curses_draw_airqualitysensor();
			break;
		case 4: //gesture sensor
			gesture = ((int) max(res, 0)) % 9;
			curses_draw_gesturesensor();
			break;
		}
		buf[bufi = 0] = '\0';
		inform = false;
		form_init();
		break;
	}
}

void form_init()
{
	if (formwin) {
		werase(formwin);
//		wnoutrefresh(formwin);
		delwin(formwin);
		curses_focus(true);
		wnoutrefresh(logwin);
		wnoutrefresh(logwinb);
		wnoutrefresh(statuswin);
	}

	if (inform) {
		int lines, cols;
		getmaxyx(stdscr, lines, cols);

		formwin = newwin(3, 20, lines/2-1, cols/2-10);
		title_window(formwin, "new value", false);
		form_key(ERR);
	}
}

#ifndef NOMOUSE
void curses_click(int x, int y, mmask_t bst)
{
	WINDOW *wins[] = { gpiowin, dhtwin, lswin, aqswin, geswin, lmwin };
	int bx, by, mx, my;
	unsigned i;
	for (i = 0; i<sizeof(wins)/sizeof(WINDOW *); i++) {
		getbegyx(wins[i], by, bx);\
		getmaxyx(wins[i], my, mx);\
		if ( x >= bx && x < bx+mx && y >= by && y < by+my) {
			x -= bx;
			y -= by;
			break;
		}
	}
	oldfocus = focus;

	switch (i) {
	case 0: // gpio
		if (y == 1 || y == 2) {//dig
			subfocus = x / 2;
		} else { // ana
			subfocus = DPINS + x / 4;
		}
		focus = 0;
		break;
	case 1: // dht
		subfocus = y == 1 ? 0 : 1;
		focus = 1;
		break;
	case 2: // light
		subfocus = 0;
		focus = 2;
		break;
	case 3: // aqs
		subfocus = y == 1 ? 0 : 1;
		focus = 3;
		break;
	case 4: // gesture
		subfocus = 0;
		focus = 4;
		break;
	default:
		return;
	}

	if (bst & BUTTON1_DOUBLE_CLICKED) {
		inform = !inform;
		form_init();
	}
	curses_focus(false);
}
#endif /* NOMOUSE */

void curses_key(int k)
{
	switch (k) {
	case KEY_LEFT:
		focus = focus == 0 ? 4 : focus-1;
		subfocus = 0;
		curses_focus(false);
		break;
	case KEY_RIGHT:
		focus = focus == 4 ? 0 : focus+1;
		subfocus = 0;
		curses_focus(false);
		break;
	case KEY_UP:
		switch (focus) {
		case 0:
			subfocus = subfocus+1 >= APINS+DPINS ? 0 : subfocus+1;
			break;
		case 1: //dht
		case 3: //aqs
			subfocus = 1-subfocus;
			break;
		}
		curses_focus(false);
		break;
	case KEY_DOWN:
		switch (focus) {
		case 0:
			subfocus = subfocus <= 0 ? APINS+DPINS-1 : subfocus-1;
			break;
		case 1: //dht
		case 3: //aqs
			subfocus = 1-subfocus;
			break;
		}
		curses_focus(false);
		break;
	case ' ':
		if (focus == 0 && subfocus < DPINS)
			dpins[subfocus] = !dpins[subfocus];
		curses_draw_gpio();
		break;
	case '\n':
		inform = !inform;
		form_init();
		break;
	}
}

#define PLINES 4
#define PCOLS 20

void curses_init(int mtaskport)
{
	if (stdscr) {
		if (formwin)
			delwin(formwin);
		delwin(logwin);
		delwin(logwinb);
		delwin(gpiowin);
		delwin(dhtwin);
		delwin(lswin);
		delwin(aqswin);
		delwin(geswin);
		delwin(lmwin);
		delwin(statuswin);
		endwin();
		refresh();
	} else {
		initscr();
		clear();
		cbreak();
		keypad(stdscr, true);
		#ifndef NOMOUSE
			mousemask(ALL_MOUSE_EVENTS, NULL);
		#endif /* NOMOUSE */
		noecho();
		nodelay(stdscr, true);
	}

	int lines, cols;

	char title[100];
	sprintf(title,
		"mTask client at localhost:%d "
		"| ARROWS to move "
		"| ENTER to edit "
		"| SPACE to toggle"
		"| Q or CTRL+C to quit"
		, mtaskport);
	title_window(stdscr, title, false);

	mvwaddstr(stdscr, 0, COLS-3, "[x]");

	int gpiowidth = min(2*PCOLS, COLS-max(DPINS*2+1, APINS*4+1)-4);
	logwinb = derwin(stdscr, LINES-1, min(COLS/2-2, gpiowidth), 1, 0);
	title_window(logwinb, "log", false);
	getmaxyx(logwinb, lines, cols);
	logwin = derwin(logwinb, lines-2, cols-2, 1, 1);
	scrollok(logwin, true);
	idlok(logwin, true);

	statuswin = derwin(stdscr, LINES-1, COLS-cols, 1, cols);
	title_window(statuswin, "peripherals", false);

	getmaxyx(statuswin, lines, cols);

	int cline = 1, ccol = 1;

	gpiowin = derwin(statuswin, 6, cols-2, cline, ccol);

	cline += 6;
	ccol = 1;

	dhtwin = derwin(statuswin, PLINES, PCOLS, cline, ccol);

#define inccolline {\
	if (ccol + 2*PCOLS > cols) {\
		ccol = 1;\
		cline += PLINES;\
	} else {\
		ccol += PCOLS;\
	}\
}
	inccolline;

	lswin = derwin(statuswin, PLINES, PCOLS, cline, ccol);

	inccolline;

	aqswin = derwin(statuswin, PLINES, PCOLS, cline, ccol);

	inccolline;

	geswin = derwin(statuswin, PLINES, PCOLS, cline, ccol);

	inccolline;

	lmwin = derwin(statuswin, max(10, PLINES), PCOLS, cline, ccol);

	curses_draw_gpio();
	curses_draw_dht();
	curses_draw_lightsensor();
	curses_draw_airqualitysensor();
	curses_draw_gesturesensor();
	curses_draw_ledmatrix();
	curses_focus(true);

	if (inform)
		form_init();
	doupdate();
}

void curses_winch_handler(int sig)
{
	msg_log("WINCH: %d handled\n", sig);
	(void)sig;
//	curses_init();
}

void pc_init(int mtaskport)
{
#ifndef _WIN32
	if (signal(SIGWINCH, curses_winch_handler) == SIG_ERR)
		pdie("signal");
#endif /* _WIN32 */

	curses_init(mtaskport);
}

void pc_msg_log(const char *fmt, va_list ap)
{

	// Disregard the message if the log window is not yet initialized
	if (logwin == NULL) {
		return;
	}

	vw_printw(logwin, fmt, ap);
	wnoutrefresh(logwin);
	form_init();
}

void pc_reset(void)
{
	endwin();
}

void pc_exit(void)
{
	endwin();
}

void pc_yield(void)
{
#ifndef NOMOUSE
	MEVENT event;
#endif /* NOMOUSE */
	int k = getch();
	oldfocus = focus;
	switch (k) {
	case 'q':
	case 3: //EoT is sometimes sent when pressing CTRL+C on windows
		killHandler(SIGTERM);
		break;
	case KEY_RESIZE:
		curses_winch_handler(28);
		break;
	case ERR:
		break;
#ifndef NOMOUSE
	case KEY_MOUSE:
		if (
			#ifdef PDCURSES
				nc_getmouse(&event)
			#else /* PDCURSES */
				getmouse(&event)
			#endif /* PDCURSES */
				 == OK) {
			if (event.y == 0 && event.x >= COLS-3) {
				killHandler(SIGTERM);
			} else if (inform) {
				int by, bx, my, mx;
				getbegyx(formwin, by, bx);
				getmaxyx(formwin, my, mx);
				if ( event.x < bx || event.x >= bx + mx ||
					  event.y < by || event.y >= by + my)
					inform = !inform;
			} else {
				curses_click(event.x, event.y, event.bstate);
			}
		}
		break;
#endif /* NOMOUSE */
	default:
		if (inform) {
			form_key(k);
		} else {
			curses_key(k);
		}
	}
	doupdate();
}

void pc_write_dpin(uint16_t i, bool b)
{
	curses_draw_gpio();
	(void)i;
	(void)b;
}
void pc_read_dpin(uint16_t i)
{
	(void)i;
}

void pc_write_apin(uint16_t i, uint8_t a)
{
	curses_draw_gpio();
	(void)i;
	(void)a;
}

void pc_read_apin(uint16_t i)
{
	(void)i;
}

void pc_set_pinmode(uint16_t p, enum PinMode mode)
{
	msg_log("set pinmode of %d to %s\n", p,
		mode == PMInput_c ? "input" :
		mode == PMOutput_c ? "output" : "input_pullup");
	(void)p;
	(void)mode;
}

void pc_humidity(struct DHTInfo dht) { (void)dht; }
void pc_temperature(struct DHTInfo dht) { (void)dht; }
void pc_light(void) { }
void pc_tvoc(void) { }
void pc_co2(void) { }

void pc_lmdot(uint8_t x, uint8_t y, bool s)
{
	curses_draw_ledmatrix();
	(void)x;
	(void)y;
	(void)s;
}

void pc_lmintensity(uint8_t intensity)
{
	msg_log("ledmatrix intensity: %u\n", intensity);
	(void)intensity;
}

void pc_lmclear() { }
void pc_lmdisplay() { }

#endif /* CURSES_INTERFACE */
#endif /* defined(_WIN32) || defined(__APPLE__) || defined (__linux__)\
	|| defined (__unix__)*/

