#if defined(_WIN32) || defined(__APPLE__) || defined (__linux__)\
	|| defined (__unix__)
#ifdef _WIN32
#ifndef WINVER
#define WINVER 0x0501
#endif
#endif

#include <signal.h>
#include <setjmp.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#endif

#include "interface.h"
#include "symbols.h"
#include "mem.h"
#include "task.h"
#include "symbols_peripherals.h"

#ifdef CURSES_INTERFACE
#ifdef _WIN32
#include <curses.h>
#else
#include <ncurses.h>
#endif
WINDOW *winp, *winl, *wins;
void curses_init();
void curses_reset();
void curses_show_peripherals();
void curses_show_log();
static int winltw = 80, winlth = 500;
static int winpw, winph, winpx, winpy;
static int winlw, winlh, winlx, winly;
static int winsw, winsh, winsx, winsy;
#else
#define curses_show_peripherals() ;
#define curses_init() ;
#define curses_reset() ;
#endif

#ifndef max
#define max(a, b) ((a) > (b) ? (a) : (b))
#endif
#ifndef min
#define min(a, b) ((a) < (b) ? (a) : (b))
#endif

struct timeval tv;

#ifdef _WIN32
SOCKET ListenSocket = INVALID_SOCKET;
SOCKET ClientSocket = INVALID_SOCKET;
#else
int sock_fd = -1;
int fd = -1;
#endif

int gargc;
char **gargv;

uint8_t bt;
bool ping = true;

//Restore from sigfpe
jmp_buf fpe;

void killHandler(int i)
{
	if (i == SIGFPE) {
#ifdef _WIN32
		signal(SIGFPE,  killHandler);
#endif
		longjmp(fpe, 1);
	}
	msg_log("\n%i caught, Bye...\n", i);
#ifdef CURSES_INTERFACE
	curses_reset();
#endif
#ifndef _WIN32
	if (sock_fd != -1)
		close(sock_fd);
	if (fd != -1)
		close(fd);
#endif
	exit(EXIT_FAILURE);
}

void usage(FILE *o, char *arg0)
{
	fprintf(o, "Usage: %s [opts]\n\nOptions\n"
		"-p PORT  Custom port number, default: 8123\n"
		"-i       No Ping\n" , arg0);
}

#ifdef _WIN32
int gettimeofday(struct timeval * tp, void *tzp)
{
	(void)tzp;
	static const uint64_t EPOCH = ((uint64_t) 116444736000000000ULL);

	SYSTEMTIME  system_time;
	FILETIME    file_time;
	uint64_t    time;

	GetSystemTime( &system_time );
	SystemTimeToFileTime( &system_time, &file_time );
	time =  ((uint64_t)file_time.dwLowDateTime )      ;
	time += ((uint64_t)file_time.dwHighDateTime) << 32;

	tp->tv_sec  = (long) ((time - EPOCH) / 10000000L);
	tp->tv_usec = (long) (system_time.wMilliseconds * 1000);
	return 0;
}
#endif

uint64_t getmillis(void)
{
	if (gettimeofday(&tv, NULL) == -1)
		pdie("gettimeofday");
	return tv.tv_sec*1000 + tv.tv_usec/1000;
}

bool input_available(void)
{
#ifdef _WIN32
	u_long bytes_available;
	if (ioctlsocket(ClientSocket, FIONREAD, &bytes_available) != 0) {
		printf("recv failed with error: %d\n", WSAGetLastError());
		WSACleanup();
		die("");
	}
	return bytes_available>0;
#else
	tv.tv_sec=0;
	tv.tv_usec=0;
	fd_set fds;
	FD_ZERO(&fds);
	FD_SET(fd, &fds);
	if (select(fd+1, &fds, NULL, NULL, &tv) == -1)
		pdie("select");
	return FD_ISSET(fd, &fds);
#endif
}

uint8_t read_byte(void)
{
#ifdef _WIN32
	int res = recv(ClientSocket, (char *)&bt, 1, 0);
	if (res == 0) {
		return 255;
	} else if (res < 0) {
		printf("recv failed with error: %d\n", WSAGetLastError());
		closesocket(ClientSocket);
		WSACleanup();
		return 255;
	}
#else
	ssize_t res = read(fd, &bt, 1);
	//Other end closed the socket
	if (res == 0) {
		return 255;
	//Error occured
	} else if (res == -1) {
		perror("read");
		return 255;
	}
#endif
	return bt;
}

void write_byte(uint8_t b)
{
#ifdef _WIN32
	send(ClientSocket, (char *)&b, 1, 0);
#else
	write(fd, &b, 1);
#endif
}

bool dpins[DPINS] = {false};
void write_dpin(uint16_t i, bool b)
{
	dpins[i] = b;
	msg_log("dwrite %d: %d\n", i, b);
	curses_show_peripherals();
}

bool read_dpin(uint16_t i)
{
	msg_log("dread %d: %s\n", i, dpins[i] ? "true" : "false");
	return dpins[i];
}

uint8_t apins[APINS] = {0};
void write_apin(uint16_t i, uint8_t a)
{
	msg_log("awrite %d: %d\n", i, a);
	curses_show_peripherals();
	apins[i] = a;
}

uint8_t read_apin(uint16_t i)
{
	msg_log("aread %d: %u\n", i, apins[i]);
	return apins[i];
}

void set_pinmode(uint16_t p, uint8_t mode)
{
	msg_log("pinmode %d %d\n", p, mode);
	(void)p;
	(void)mode;
}

uint16_t translate_pin(uint16_t pin)
{
	return pin;
}

struct mt_DHTInfo *dht;
void *dht_init(struct mt_DHTInfo a)
{
	dht = malloc(sizeof (struct mt_DHTInfo));
	msg_log("dht init at %p\n", dht);
	*dht = a;
	switch (a.cons) {
	case mt_DHTInfo_DHT_DHT:
		msg_log("init DHT\n");
		break;
	case mt_DHTInfo_DHT_SHT:
		msg_log("init SHT on address: %x\n", a.data.mt_DHT_SHT);
		break;
	}
	return dht;
}

float get_dht_temp(void *p)
{
	msg_log("get dht temp at %p\n", p);
	struct mt_DHTInfo dht = *((struct mt_DHTInfo *)p);
	switch (dht.cons) {
	case mt_DHTInfo_DHT_DHT:
		msg_log("read temp for DHT\n");
		break;
	case mt_DHTInfo_DHT_SHT:
		msg_log("read temp SHT on address: %x\n", dht.data.mt_DHT_SHT);
		break;
	}
	return 42.0;
}

float get_dht_humidity(void *p)
{
	msg_log("get dht humidity at %p\n", p);
	struct mt_DHTInfo dht = *((struct mt_DHTInfo *)p);
	switch (dht.cons) {
	case mt_DHTInfo_DHT_DHT:
		msg_log("read humidity for DHT\n");
		break;
	case mt_DHTInfo_DHT_SHT:
		msg_log("read temp SHT on address: %x\n", dht.data.mt_DHT_SHT);
		break;
	}
	return 38.0;
}

void *i2c_init(uint8_t addr)
{
	return (void *)1;
	(void)addr;
}

uint8_t i2c_abutton(void *st)
{
	return BUTTONNONE;
	(void)st;
}

uint8_t i2c_bbutton(void *st)
{
	return BUTTONNONE;
	(void)st;
}

void *lightsensor_init(uint8_t addr)
{
	return (void *)1;
	(void)addr;
}

float get_light(void *st)
{
	return 4.20;
	(void)st;
}

void *airqualitysensor_init(uint8_t addr) {
	return (void *)1;
	(void)addr;
}

void set_environmental_data(void *st, float humid, float temp) {
	msg_log(SC("set environment: %f, %f\n"), humid, temp);
	(void)humid;
	(void)temp;
	(void)st;
}

uint16_t get_tvoc(void *st)
{
	msg_log(SC("read tvoc\n"));
	return 38;
	(void) st;
}

uint16_t get_co2(void *st)
{
	msg_log(SC("read co2\n"));
	return 42;
	(void) st;
}

bool ledmatrix[8][8] = {{false}};
void *ledmatrix_init(struct mt_LEDMatrixInfo x)
{
	return (void *)1;
	(void) x;
}

void ledmatrix_dot(void *st, uint8_t x, uint8_t y, bool state)
{
	msg_log("ledmatrix dot: %u %u %u\n", x, y, state);
	ledmatrix[x][y] = state;
	curses_show_peripherals();
	(void)st;
}

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

void ledmatrix_clear(void *st)
{
	for (int x = 0; x<8; x++)
		for (int y = 0; y<8; y++)
			ledmatrix[x][y] = false;
	curses_show_peripherals();
	(void)st;
}

void ledmatrix_display(void *st)
{
	(void)st;
}

void msdelay(unsigned long ms)
{
#ifdef _WIN32
	Sleep(ms);
#else
	struct timespec ts = {
		.tv_sec=ms/1000,
		.tv_nsec=(ms % 1000) *1000*1000
	};
	nanosleep(&ts, NULL);
#endif
}

#ifndef _WIN32
struct sigaction act;
#endif

void real_setup(void)
{
	curses_init();
	int port = 8123, opti = 1;

#ifndef _WIN32
	memset(&act, 0, sizeof(act));
	act.sa_handler = killHandler;
	act.sa_flags = SA_NODEFER;
	if (
			sigaction(SIGFPE,  &act, NULL) == -1 ||
			sigaction(SIGINT,  &act, NULL) == -1 ||
			sigaction(SIGTERM, &act, NULL) == -1 ||
			sigaction(SIGPIPE, &act, NULL) == -1)
		pdie("sigaction");
#else
	if (
			signal(SIGFPE,  killHandler) == SIG_ERR ||
			signal(SIGINT,  killHandler) == SIG_ERR ||
			signal(SIGTERM,  killHandler) == SIG_ERR)
		pdie("sigaction");
#endif

	//Command line arguments
	while (opti < gargc) {
		if (strcmp((*gargv)+opti, "-h") == 0) {
			usage(stdout, gargv[0]);
			exit(EXIT_SUCCESS);
		} else if (strcmp(gargv[opti], "-p") == 0 && opti+1<gargc) {
			port = atoi(gargv[++opti]);
			if (port < 1)
				die("Port numbers are > 1\n");
		} else if (strcmp(gargv[opti], "-i") == 0) {
			ping = false;
		} else {
			usage(stderr, gargv[0]);
			exit(EXIT_FAILURE);
		}
		opti++;
	}

#ifdef _WIN32
	WSADATA wsaData;
	struct addrinfo *result = NULL;
	struct addrinfo hints;
	if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) {
		pdie("WSAStartup");
	}
	ZeroMemory(&hints, sizeof(hints));
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;
	hints.ai_flags = AI_PASSIVE;

	char portstr[8] = {0};
	sprintf(portstr, "%u", port);
	if (getaddrinfo(NULL, portstr, &hints, &result) != 0 ) {
		perror("getaddrinfo");
		WSACleanup();
		die("");
	}

	if ((ListenSocket =
			socket(result->ai_family,
			result->ai_socktype,
			result->ai_protocol)) == INVALID_SOCKET) {
	        printf("socket failed with error: %d\n", WSAGetLastError());
		freeaddrinfo(result);
		WSACleanup();
		die("");
	}

	// Setup the TCP listening socket
	if (bind( ListenSocket,
			result->ai_addr,
			(int)result->ai_addrlen) == SOCKET_ERROR) {
	        printf("bind failed with error: %d\n", WSAGetLastError());
		freeaddrinfo(result);
		closesocket(ListenSocket);
		WSACleanup();
		die("");
	}

	freeaddrinfo(result);

	if (listen(ListenSocket, SOMAXCONN) == SOCKET_ERROR) {
		printf("listen failed with error: %d\n", WSAGetLastError());
		closesocket(ListenSocket);
		WSACleanup();
		die("");
	}

	msg_log("Listening on %d\n", port);

	// Accept a client socket
	if ((ClientSocket = accept(
			ListenSocket, NULL, NULL)) == INVALID_SOCKET) {
		printf("accept failed with error: %d\n", WSAGetLastError());
		closesocket(ListenSocket);
		WSACleanup();
		die("");
	}

	// No longer need server socket
	closesocket(ListenSocket);
#else
	//Open file descriptors
	struct sockaddr_in sa;

	memset(&sa, 0, sizeof(sa));
	sa.sin_family = AF_INET;
	sa.sin_addr.s_addr = INADDR_ANY;
	sa.sin_port = htons(port);

	if (sock_fd == -1) {
		if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
			pdie("socket");
		if (bind(sock_fd, (struct sockaddr*)&sa, sizeof(sa)) == -1)
			pdie("bind");
	}
	if (listen(sock_fd, 10) == -1)
		pdie("listen");

	msg_log("Listening on %d\n", port);
	if ((fd = accept(sock_fd, (struct sockaddr *)NULL, NULL)) == -1)
		pdie("accept");
#endif
	msg_log("Accepted incoming connection\n");

}

#if LOGLEVEL > 0
void msg_log(const char *fmt, ...)
{
	va_list ap;
	va_start(ap, fmt);
#ifdef SIMULATE_ARDUINO
	char buf[128];
	vsnprintf(buf, 128, fmt, ap);
	write_byte(MTFDEBUG);
	write_byte(strlen(buf));
	for (uint8_t i = 0; i<128; i++) {
		if (buf[i] == '\0')
			break;
		write_byte(buf[i]);
	}
#else

#ifdef CURSES_INTERFACE
	vw_printw(winl, fmt, ap);
	curses_show_log();
#else
	vfprintf(stderr, fmt, ap);
#endif
#endif
	va_end(ap);
}
#endif

#if LOGLEVEL == 2
void dump_stack(uint8_t *program, uint16_t pc, uint16_t *stack, uint16_t sp,
	uint16_t fp)
{
#ifdef CURSES_INTERFACE
	wclear(wins);
	wmove(wins, 0, 0);
	uint16_t half = ((winsw-3)/3)/2;
	for (int16_t i = max(0, pc-half); i<pc+half; i++)
		wprintw(wins, " %s%02x%s", pc == i ? "|" : "", program[i],
			pc == i ? "|" : "");
	for (uint16_t i = 0; i<sp+4; i++) {
		wmove(wins, winsh-2-(i % (winsh-2)), (i/(winsh-2))*15);
		wprintw(wins, "%c%c %03u: %06x", i == sp ? 's' : ' ',
			i == fp ? 'f' : ' ', i, stack[i]);
	}
	wrefresh(wins);
//	getch();
#else
	msg_debug(SC("sp: %u, pc: %u, fp: %u\n"), sp, pc, fp);
	msg_debug(SC("program[pc]: %x\n"), program[pc]);
	for (uint16_t j = 0; j<sp+4 && mem_rptr(stack+j) < mem_max_sp(); j++) {
		if (j == sp && j == fp)
			msg_debug(SC("sp fp"));
		else if (j == sp)
			msg_debug(SC("sp   "));
		else if (j == fp)
			msg_debug(SC("fp   "));
		else
			msg_debug(SC("     "));
		msg_debug(SC("     stack[%u]: %u\n"), j, stack[j]);
	}
#endif
}
#endif

void pdie(char *s)
{
	perror(s);
	die("");
}

void die(const char *fmt, ...)
{
	va_list ap;
	va_start(ap, fmt);
	vfprintf(stderr, fmt, ap);
	va_end(ap);
	exit(EXIT_FAILURE);
}

extern int main(int argc, char **argv);
void reset()
{
	curses_reset();

#ifdef _WIN32
	if (shutdown(ClientSocket, SD_SEND) == SOCKET_ERROR) {
		perror("shutdown");
		WSACleanup();
		die("");
	}
	closesocket(ClientSocket);
	WSACleanup();
	ListenSocket = INVALID_SOCKET;
	ClientSocket = INVALID_SOCKET;
#else
	close(fd);
	fd = -1;
#endif
	main(gargc, gargv);
}

#ifdef CURSES_INTERFACE
#define MAXPINS 20
void curses_show_peripherals()
{
	int y = 0;
	wmove(winp, y++, 0);
	wprintw(winp, "General Purpose Input/Output Pins");
	//Pins
	wmove(winp, y, 0);
	wprintw(winp, "   ");
	for (int p = 0; p<min(max(DPINS,APINS),MAXPINS); p++)
		wprintw(winp, " % 3u", p);
	wmove(winp, ++y, 0);
	wprintw(winp, "DP:");
	for (int p = 0; p<min(MAXPINS, DPINS); p++)
		wprintw(winp, " % 3u", dpins[p]);
	wmove(winp, ++y, 0);
	wprintw(winp, "AP:");
	for (int p = 0; p<min(MAXPINS, APINS); p++)
		wprintw(winp, " % 3u", apins[p]);
	y++;

	//LED Matrix
#ifdef HAVE_LEDMATRIX
	wmove(winp, ++y, 0);
	y++;
	wprintw(winp, "LED Matrix");
	for (int lx = 0; lx < 8; lx++) {
		wmove(winp, y++, 0);
		for (int ly = 0; ly < 8; ly++)
			wprintw(winp, " %s", ledmatrix[lx][ly] ? "o" : "_");
	}
	y++;
#endif

#ifdef HAVE_DHT
	//TODO
	wmove(winp, ++y, 0);
	y++;
	wprintw(winp, "DHT temp: %d, humidity: %u", dht_temps[2], dht_humidities[2]);
	y++;
#endif
	wrefresh(winp);
}

void curses_show_log()
{
	prefresh(winl, winlth-winlh, 0, winly, winlx, winlh, winlx+winlw);
}

void curses_winch_handler(int sig)
{
	msg_log("WINCH: %d handled\n", sig);
	endwin();
	initscr();
	clear();
	refresh();

	winpw = COLS/2-1;
	winph = LINES/2-1;
	winpx = 0;
	winpy = 0;

	winsw = COLS/2-1;
	winsh = LINES/2;
	winsx = 0;
	winsy = LINES/2;

	winlw = COLS/2;
	winlh = LINES;
	winlx = COLS/2;
	winly = 0;

	msg_log("cols: %u lines: %u\n", COLS, LINES);
	msg_log("winp: w %u h %u x %u y %u\n", winpw, winph, winpx, winpy);
	msg_log("wins: w %u h %u x %u y %u\n", winsw, winsh, winsx, winsy);
	msg_log("winl: w %u h %u x %u y %u\n", winlw, winlh, winlx, winly);

	if (!winl) {
		winl = newpad(winlth, winltw);
		idlok(winl, true);
		scrollok(winl, true);
	}
	if (!winp)
		winp = newwin(winph, winpw, winpx, winpy);
	if (!wins)
		wins = newwin(winsh, winsw, winsx, winsy);

	wmove(winl, winlth-1, 0);
	wresize(winp, winph, winpw);
	wresize(wins, winsh, winsw);
	mvwin(winp, winpy, winpx);
	mvwin(wins, winsy, winsx);

	curses_show_peripherals();
	curses_show_log();
	wrefresh(wins);
	refresh();
}

void curses_init()
{
	initscr();
	cbreak();
	noecho();

#ifndef _WIN32
	if (signal(SIGWINCH, curses_winch_handler) == SIG_ERR)
		pdie("signal");
#endif

	curses_winch_handler(28);
}

void curses_reset()
{
	delwin(winp);
	delwin(winl);
	delwin(wins);
	endwin();
}

#endif

long lastping = 0;
void real_yield(void)
{
	if (ping && getmillis()-lastping > 300) {
		write_byte(MTFPING);
		write_byte('\n');
		lastping = getmillis();
	}
}
#endif
