#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
//#include <sys/socket.h>
//#include <netinet/in.h>
//#include <arpa/inet.h>

#pragma warning(disable :4101)

#include "compat.h"
#include "utils.h"
#include "eventmanager.h"
#include "actionmanager.h"
#include "monitor.h"

static void schedule(PRUNTIME pRuntime, event_t *e, int hook);

static void *onbegin(PMONITOR_THREAD_PARAMETERS parameters);
static void *onrepeat(PMONITOR_THREAD_PARAMETERS parameters);
static void *onend(PMONITOR_THREAD_PARAMETERS parameters);

static int eventc = 0;

/* holds all the events parsed from the configuration */
static event_t *eventv = NULL;

static mutex_t mutex;

void em_init(PRUNTIME pRuntime)
{
	MUTEX_INIT(mutex);

	OUTPUTDEBUG("[EM] initialized\n");

	return;
}

void em_parseconfig(PRUNTIME pRuntime, json_object *config)
{
	int i;
	json_object *events, *event;
	char *type, *subtype, *v;
	event_t *e;
	struct tm tm;
	time_t now;
	struct stat s;

	MUTEX_LOCK(mutex);

	for (i = 0; i < eventc; i++) 
		if (eventv[i].param) 
			API_FREE(pRuntime, eventv[i].param);

	if (eventv) { API_FREE(pRuntime, eventv); eventv = NULL; }

	if ((eventc = json_get_array(config, "events", &events)) >= 0) 
		eventv = API_CALLOC(pRuntime, eventc, sizeof(event_t));

	OUTPUTDEBUG("[EM] parsing %d events\n", eventc);

	for (i = 0; i < eventc; i++) {
		event = fp_json_object_array_get_idx(events, i);
		e = &eventv[i];

		e->type = EVENT_NONE;

		if (json_get_string(event, "event", &type)) {
			OUTPUTDEBUG("[EM] . event #%d is invalid\n", i);
			continue;
		}

		if (json_get_boolean(event, "enabled", &e->enabled)) e->enabled = 0;
		if (json_get_int32(event, "start", &e->begin)) e->begin = -1;
		if (json_get_int32(event, "repeat", &e->repeat)) e->repeat = -1;
		if (json_get_int32(event, "delay", &e->delay)) e->delay = -1;
		if (json_get_int32(event, "iter", &e->iter)) e->iter = -1;
		if (json_get_int32(event, "end", &e->end)) e->end = -1;

		if (!API_STRCMP(type, "process")) {
			if (!(e->param = API_ALLOC(pRuntime, sizeof(event_process_t)))) continue;
			if (json_get_string(event, "process", &v)) continue;
			if (snprintf(EVENT_PROCESS_P(e)->name, sizeof(EVENT_PROCESS_P(e)->name), "%s", v) < 0) continue;
			if (json_get_boolean(event, "window", &EVENT_PROCESS_P(e)->window)) continue;
			if (json_get_boolean(event, "focus", &EVENT_PROCESS_P(e)->focus)) continue;
			e->type = EVENT_PROCESS;
			OUTPUTDEBUG("[EM] . event #%d: process\n", i);

		}
		/*else if (!strcmp(type, "connection")) {
			if (!(e->param = malloc(sizeof(event_connection_t)))) continue;
			if (json_get_string(event, "ip", &v)) continue;
			EVENT_CONNECTION_P(e)->ip = inet_addr(v);
			if (json_get_string(event, "netmask", &v)) continue;
			EVENT_CONNECTION_P(e)->mask = inet_addr(v);
			if (json_get_int32(event, "port", ((int *)&EVENT_CONNECTION_P(e)->port))) continue;
			e->type = EVENT_CONNECTION;
			DEBUG("[EM] . event #%d: connection\n", i);
		}*/
		else if (!strcmp(type, "timer")) {
			
			if (!(e->param = malloc(sizeof(event_timer_t)))) continue;

			if (json_get_string(event, "subtype", &subtype)) continue;

			/* loop */
			if (!strcmp(subtype, "loop")) {
				EVENT_TIMER_P(e)->daily = 0;
				EVENT_TIMER_P(e)->begin = (time_t)0;
				EVENT_TIMER_P(e)->end = (time_t)0;
			}
			/* daily, which is the same crap of loop */
			else if (!strcmp(subtype, "daily")) {
				EVENT_TIMER_P(e)->daily = 1;
				now = time(NULL);
				//gmtime_r(&now, &tm);
				struct tm * ptm = gmtime(&now);
				memcpy(&tm, ptm, sizeof(tm));
				
				/* event start */
				if (json_get_string(event, "ts", &v)) continue;
				if (!strptime(v, "%H:%M:%S", &tm)) continue;
				EVENT_TIMER_P(e)->begin = mktime(&tm);

				/* event end */
				if (json_get_string(event, "te", &v)) continue;
				if (!strptime(v, "%H:%M:%S", &tm)) continue;
				EVENT_TIMER_P(e)->end = mktime(&tm);
				
				if (EVENT_TIMER_P(e)->end < now) {
					EVENT_TIMER_P(e)->begin += 24 * 3600;
					EVENT_TIMER_P(e)->end += 24 * 3600;
				}
			}
			e->type = EVENT_TIMER;
			DEBUG("[EM] . event #%d: timer\n", i);
		}
		
		/* date */
		else if (!strcmp(type, "date")) {
			if (!(e->param = malloc(sizeof(event_timer_t)))) continue;
			EVENT_TIMER_P(e)->daily = 0;
			if (json_get_string(event, "datefrom", &v)) continue;
			if (!strptime(v, "%Y-%m-%d %H:%M:%S", &tm)) continue;
			EVENT_TIMER_P(e)->begin = mktime(&tm);
			if (json_get_string(event, "dateto", &v)) {
				EVENT_TIMER_P(e)->end = (time_t)0;
			}
			else {
				if (!strptime(v, "%Y-%m-%d %H:%M:%S", &tm)) continue;
				EVENT_TIMER_P(e)->end = mktime(&tm);
			}
			e->type = EVENT_TIMER;
			DEBUG("[EM] . event #%d: timer\n", i);
		}

		/* afterinst */
		//else if (!strcmp(type, "afterinst")) {
		//	if (!(e->param = malloc(sizeof(event_timer_t)))) continue;
		//	EVENT_TIMER_P(e)->daily = 0;
		//	// TODO: get  installation date
		//	//if (stat(".lock", &s)) continue;
		//	if (json_get_int32(event, "days", ((int *)&EVENT_TIMER_P(e)->begin))) continue;
		//	EVENT_TIMER_P(e)->begin = (EVENT_TIMER_P(e)->begin * 24 * 3600) + 0;// s.st_mtime;
		//	EVENT_TIMER_P(e)->end = 0;
		//	e->type = EVENT_TIMER;
		//	DEBUG("[EM] . event #%d: timer\n", i);

		//}

		else if (!API_STRCMP(type, "screensaver")) {
			e->type = EVENT_SCREENSAVER;
			OUTPUTDEBUG("[EM] . event #%d: screensaver\n", i);
		}
		else if (!API_STRCMP(type, "idle")) {
			if (!(e->param = API_ALLOC(pRuntime, sizeof(event_idle_t)))) continue;
			if (json_get_int32(event, "time", &EVENT_IDLE_P(e)->idle)) continue;
			e->type = EVENT_IDLE;
			OUTPUTDEBUG("[EM] . event #%d: idle\n", i);
		}
		else if (!API_STRCMP(type, "quota")) {
			if (!(e->param = API_ALLOC(pRuntime, sizeof(event_quota_t)))) continue;
			if (json_get_int32(event, "quota", &EVENT_QUOTA_P(e)->quota)) continue;
			e->type = EVENT_QUOTA;
			OUTPUTDEBUG("[EM] . event #%d: quota\n", i);
		}
		else if (!API_STRCMP(type, "window")) {
			e->type = EVENT_WINDOW;
			OUTPUTDEBUG("[EM] . event #%d: window\n", i);
		}
		else {
			OUTPUTDEBUG("[EM] . event #%d: unknown (%s)\n", i, type);
		}
	}

	OUTPUTDEBUG("[EM] parsing completed\n");

	MUTEX_UNLOCK(mutex);

	return;
}

void em_scheduleall(PRUNTIME pRuntime)
{
	int i;

	MUTEX_LOCK(mutex);

	for (i = 0; i < eventc; i++) 
		schedule(pRuntime, &eventv[i], EVENT_BEGIN);

	MUTEX_UNLOCK(mutex);

	return;
}

void em_eventenable(PRUNTIME pRuntime, int id)
{
	MUTEX_LOCK(mutex);

	if ((id >= 0) && (id < eventc)) {
		eventv[id].enabled = 1;
		OUTPUTDEBUG("[EM] event #%d enabled\n", id);
	}

	MUTEX_UNLOCK(mutex);

	return;
}

void em_eventdisable(PRUNTIME pRuntime, int id)
{
	MUTEX_LOCK(mutex);

	if ((id >= 0) && (id < eventc)) {
		eventv[id].enabled = 0;
		OUTPUTDEBUG("[EM] event #%d disabled\n", id);
	}

	MUTEX_UNLOCK(mutex);

	return;
}

static void schedule(PRUNTIME pRuntime, event_t *e, int hook)
{

	PMONITOR_THREAD_PARAMETERS parameters = API_ALLOC(pRuntime, sizeof(PMONITOR_THREAD_PARAMETERS));
	parameters->pRuntime = pRuntime;
	parameters->args = e;

	switch (e->type) {

	case EVENT_PROCESS:
		switch (hook) {
		case EVENT_BEGIN:
			if (!EVENT_PROCESS_P(e)->window) {
				if (MONITOR_PROCESS.addwatch) 
					MONITOR_PROCESS.addwatch(pRuntime, onbegin, e, MONITOR_FLAG_NONE, EVENT_PROCESS_P(e)->name);
			}
			else {
				if (MONITOR_WINDOW.addwatch)
					/* done */
					MONITOR_WINDOW.addwatch(pRuntime, onbegin, e, MONITOR_FLAG_NONE, EVENT_PROCESS_P(e)->name);
			}
			break;
		case EVENT_END:
			if (!EVENT_PROCESS_P(e)->window) {
				if (MONITOR_PROCESS.addwatch) 
					MONITOR_PROCESS.addwatch(pRuntime, onend, e, MONITOR_FLAG_NEG, EVENT_PROCESS_P(e)->name);
			}
			else {
				if (MONITOR_WINDOW.addwatch) 
					/* done */
					MONITOR_WINDOW.addwatch(pRuntime, onend, e, MONITOR_FLAG_NEG, EVENT_PROCESS_P(e)->name);
			}
			break;
		}
		break;

	case EVENT_CONNECTION:
		switch (hook) {
		case EVENT_BEGIN:
			if (MONITOR_CONNECTION.addwatch) 
				MONITOR_CONNECTION.addwatch(pRuntime, onbegin, e, MONITOR_FLAG_NONE, EVENT_CONNECTION_P(e)->ip, EVENT_CONNECTION_P(e)->mask, EVENT_CONNECTION_P(e)->port);
			break;
		case EVENT_END:
			if (MONITOR_CONNECTION.addwatch) 
				MONITOR_CONNECTION.addwatch(pRuntime, onend, e, MONITOR_FLAG_NEG, EVENT_CONNECTION_P(e)->ip, EVENT_CONNECTION_P(e)->mask, EVENT_CONNECTION_P(e)->port);
			break;
		}
		break;

	case EVENT_TIMER:
		switch (hook) {
		case EVENT_BEGIN:
			if (!EVENT_TIMER_P(e)->end || (EVENT_TIMER_P(e)->end > API_TIME(NULL))) {
				
				if (MONITOR_TIME.addwatch) 
					MONITOR_TIME.addwatch(pRuntime, onbegin, e, MONITOR_FLAG_NONE, EVENT_TIMER_P(e)->begin, 0, 0);
			}
			break;
		case EVENT_END:
			if (EVENT_TIMER_P(e)->end) {
				
				if (MONITOR_TIME.addwatch) 
					MONITOR_TIME.addwatch(pRuntime, onend, e, MONITOR_FLAG_NONE, EVENT_TIMER_P(e)->end, 0, 0);
				
				if (EVENT_TIMER_P(e)->daily) {
					EVENT_TIMER_P(e)->begin += 24 * 3600;
					EVENT_TIMER_P(e)->end += 24 * 3600;
				}
			}
			break;
		}
		break;

	case EVENT_SCREENSAVER:
		switch (hook) {
		case EVENT_BEGIN:
			if (MONITOR_SCREENSAVER.addwatch) 
				MONITOR_SCREENSAVER.addwatch(pRuntime, onbegin, e, MONITOR_FLAG_NONE);
			break;
		case EVENT_END:
			if (MONITOR_SCREENSAVER.addwatch) 
				MONITOR_SCREENSAVER.addwatch(pRuntime, onend, e, MONITOR_FLAG_NEG);
			break;
		}
		break;

	case EVENT_IDLE:
		switch (hook) {
		case EVENT_BEGIN:
			if (MONITOR_IDLE.addwatch) 
				MONITOR_IDLE.addwatch(pRuntime, onbegin, e, MONITOR_FLAG_NONE, EVENT_IDLE_P(e)->idle);
			break;
		case EVENT_END:
			if (MONITOR_IDLE.addwatch)
				MONITOR_IDLE.addwatch(pRuntime, onend, e, MONITOR_FLAG_NEG, EVENT_IDLE_P(e)->idle);
			break;
		}
		break;

	case EVENT_QUOTA:
		switch (hook) {
		case EVENT_BEGIN:
			if (MONITOR_QUOTA.addwatch) 
				MONITOR_QUOTA.addwatch(pRuntime, onbegin, e, MONITOR_FLAG_NONE, EVENT_QUOTA_P(e)->quota);
			break;
		case EVENT_END:
			if (MONITOR_QUOTA.addwatch) 
				MONITOR_QUOTA.addwatch(pRuntime, onend, e, MONITOR_FLAG_NEG, EVENT_QUOTA_P(e)->quota);
			break;
		}
		break;

	case EVENT_WINDOW:
		switch (hook) {
		case EVENT_BEGIN:
			if (MONITOR_WINDOW.addwatch) 
				MONITOR_WINDOW.addwatch(pRuntime, onbegin, e, MONITOR_FLAG_NONE);
			break;
		case EVENT_END:
			if (MONITOR_WINDOW.addwatch)
				MONITOR_WINDOW.addwatch(pRuntime, onend, e, MONITOR_FLAG_NEG);
			break;
		}
		break;

	}

	return;
}

static void *onbegin(PMONITOR_THREAD_PARAMETERS parameters)
{
	PRUNTIME pRuntime = parameters->pRuntime;
	event_t *e = parameters->args;

	if (e->active) return NULL;

	MUTEX_LOCK(mutex);

	e->active = 1;

	if (e->repeat >= 0) {
		if (e->rp && MONITOR_TIME.delwatch) 
		{ 
			MONITOR_TIME.delwatch(pRuntime, e->rp); e->rp = NULL; 
		}
		
		if (MONITOR_TIME.addwatch) 
			e->rp = MONITOR_TIME.addwatch(pRuntime, onrepeat, e, MONITOR_FLAG_PERM, time(NULL) + e->delay, e->delay, e->iter);
	}

	schedule(pRuntime, e, EVENT_END);

	if (e->enabled && (e->begin >= 0)) {
		am_queueaction(e->begin);
	}

	MUTEX_UNLOCK(mutex);

	return NULL;
}

static void *onrepeat(PMONITOR_THREAD_PARAMETERS parameters)
{
	PRUNTIME pRuntime = parameters->pRuntime;
	event_t *e = parameters->args;

	MUTEX_LOCK(mutex);

	if (e->enabled && (e->repeat >= 0)) {
		am_queueaction(e->repeat);
	}

	MUTEX_UNLOCK(mutex);

	return NULL;
}

static void *onend(PMONITOR_THREAD_PARAMETERS parameters)
{
	PRUNTIME pRuntime = parameters->pRuntime;
	event_t *e = parameters->args;

	if (!e->active) return NULL;

	MUTEX_LOCK(mutex);

	e->active = 0;

	if (e->rp && MONITOR_TIME.delwatch) 
	{ 
		MONITOR_TIME.delwatch(pRuntime, e->rp); e->rp = NULL;
	}

	schedule(pRuntime, e, EVENT_BEGIN);

	if (e->enabled && (e->end >= 0)) {
		am_queueaction(e->end);
	}

	MUTEX_UNLOCK(mutex);

	return NULL;
}
