#include "interface.h"

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

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

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#else /* _WIN32 */
#include <sys/time.h>
#endif /* _WIN32 */

#include "pc_interface.h"
#include "communication_interface.h"
#include "interrupts.h"

int gargc;
char **gargv;

//Restore from sigfpe
jmp_buf fpe;

static void real_exit()
{
	cleanup_communication();
	pc_exit();
	exit(EXIT_SUCCESS);
}

void killHandler(int i)
{
	if (i == SIGFPE) {
#ifdef _WIN32
		signal(SIGFPE,  killHandler);
#endif /* _WIN32 */
		longjmp(fpe, 1);
	}
	msg_log("\n%i caught, Bye...\n", i);

	real_exit();
}

static void usage(FILE *o, char *arg0)
{
	fprintf(o, "Usage: %s [opts]\n\nOptions\n"
		"-p PORT             Custom port number\n"
#if defined(COMM_MQTT)
		"-mh HOST            Custom mosquitto host\n"
		"-c                  Use a clean session when connecting to the broker\n"
		"-ka KEEP ALIVE TIME Custom keep alive time\n"
		"-id CLIENT ID       Name of this client\n"
#elif defined(COMM_DIRECT)
		"-pt                 Ping timout in ms, 0 disables\n"
#endif /* COMM_MQTT */
		, 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 /* _WIN32 */

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

bool dpins[DPINS] = {false};
void write_dpin(uint16_t i, bool b)
{
	dpins[i] = b;
	pc_write_dpin(i, b);
}

bool read_dpin(uint16_t i)
{
	pc_read_dpin(i);
	return dpins[i];
}

uint8_t apins[APINS] = {0};
void write_apin(uint16_t i, uint8_t a)
{
	apins[i] = a;
	pc_write_apin(i, a);
}

uint8_t read_apin(uint16_t i)
{
	pc_read_apin(i);
	return apins[i];
}

void set_pinmode(uint16_t p, enum PinMode mode)
{
	pc_set_pinmode(p, mode);
}

uint16_t translate_pin(uint16_t i)
{
	return i>>1;
}

void attach_interrupt(void (*isr)(), uint16_t pin, enum InterruptMode mode)
{
	(void) isr;
	(void) pin;
	(void) mode;
}

void detach_interrupt(uint16_t pin)
{
	(void) pin;
}

void isr() {
	// Left empty on purpose, because interrupts are never triggered on PC
}

void (*get_isr(uint16_t pin, enum InterruptMode mode))()
{
	(void) mode;

	if (pin > MAX_INTERRUPT_PIN) {
		return NULL;
	}

	return isr;
}

static struct DHTInfo *dht;
void *dht_init(struct DHTInfo a)
{
	dht = malloc(sizeof (struct DHTInfo));
	msg_log("dht init at %p\n", dht);
	*dht = a;
	switch (a.cons) {
	case DHT_DHT_c:
		msg_log("init DHT\n");
		break;
	case DHT_SHT_c:
		msg_log("init SHT on address: 0x%x\n", a.data.DHT_SHT);
		break;
	}
	return dht;
}

float temperature = 42.0, humidity = 38.0;
float get_dht_temp(void *p)
{
	pc_temperature(*((struct DHTInfo *)p));
	return temperature;
}

float get_dht_humidity(void *p)
{
	pc_humidity(*((struct DHTInfo *)p));
	return humidity;
}

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

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

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

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

float lightlevel = 4.20;
float get_light(void *st)
{
	pc_light();
	return lightlevel;
	(void)st;
}
uint8_t gesture = 0;
void *gesturesensor_init(uint8_t addr) {
	return (void *)(uintptr_t)addr;
}

uint8_t get_gesture(void *st) {
	return gesture;
	(void)st;
}

int tvoc = 38, eco2 = 42;
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)
{
	pc_tvoc();
	return tvoc;
	(void) st;
}

uint16_t get_co2(void *st)
{
	pc_co2();
	return eco2;
	(void) st;
}

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

void ledmatrix_dot(void *st, uint8_t x, uint8_t y, bool state)
{
	ledmatrix[x][y] = state;
	pc_lmdot(x, y, state);
	(void)st;
}

void ledmatrix_intensity(void *st, uint8_t intensity)
{
	pc_lmintensity(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;
	pc_lmclear();
	(void)st;
}

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

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

void mssleep(unsigned long ms)
{
	msdelay(ms);
}

void force_wake_up() {}

#ifndef _WIN32
static struct sigaction act;
#endif /* !_WIN32 */

void real_setup(void)
{
	int opti = 1;

	// init random number generator
	srand((unsigned) time(NULL));

#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 /* !_WIN32 */
	if (
			signal(SIGFPE,  killHandler) == SIG_ERR ||
			signal(SIGINT,  killHandler) == SIG_ERR ||
			signal(SIGTERM,  killHandler) == SIG_ERR)
		pdie("sigaction");
#endif /* !_WIN32 */

	//Command line arguments
	struct CommSettings cs = get_comm_settings();
	struct LinkSettings ls = get_link_settings();

	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) {
			int port = atoi(gargv[++opti]);
			if (port < 1)
				die("Port numbers are > 1\n");
			ls.port = port;
			msg_log(SC("Port set to %u\n"), ls.port);
#if defined(COMM_DIRECT)
		} else if (strcmp(gargv[opti], "-pt") == 0 && opti+1<gargc) {
			cs.ping = atoi(gargv[++opti]);
			if (cs.ping == 0) {
				msg_log(SC("Ping disabled\n"));
			} else {
				msg_log(SC("Ping timeout set to %u\n"),
					cs.ping);
			}
#elif defined(COMM_MQTT)
		} else if (strcmp(gargv[opti], "-mh") == 0 && opti+1<gargc) {
			ls.host = gargv[++opti];
			msg_log(SC("Host set to %s\n"), ls.host);
		} else if (strcmp(gargv[opti], "-c") == 0) {
			cs.clean_session = true;
			msg_log(SC("Clean session set to true\n"));
		} else if (strcmp(gargv[opti], "-ka") == 0 && opti+1<gargc) {
			cs.keep_alive_time = atoi(gargv[++opti]);
			msg_log(SC("Keep-alive time set to %u\n"),
				cs.keep_alive_time);
		} else if (strcmp(gargv[opti], "-id") == 0 && opti+1<gargc) {
			cs.client_id = gargv[++opti];
			msg_log(SC("Client id set to %s\n"), cs.client_id);
#endif /* COMM_DIRECT */
		} else {
			usage(stderr, gargv[0]);
			exit(EXIT_FAILURE);
		}
		opti++;
	}
	pc_init(ls.port);
	start_communication(cs, ls);
}

#if LOGLEVEL > 0
void msg_log(const char *fmt, ...)
{
	va_list ap;
	va_start(ap, fmt);
	pc_msg_log(fmt, ap);
	va_end(ap);
}
#endif /* LOGLEVEL > 0 */

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);
	real_exit();
}

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

	stop_communication(false);

	struct CommSettings cs = get_comm_settings();
	struct LinkSettings ls = get_link_settings();
	pc_init(ls.port);
	start_communication(cs, ls);
}

void real_yield(void)
{
	pc_yield();
	communication_yield();
}

unsigned int get_random(void)
{
	return (unsigned int) rand();
}

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