/*
 * ghost_timer.cpp
 *
 */

#include "ghost.h"

#include "windows.h"
#include "mmsystem.h"

#include <atomic>

struct gh_timer_cx_t
{
	unsigned int l_period;
	unsigned int l_events;
	unsigned int l_counter;
	unsigned int l_slave;
	int l_countdown;

	unsigned int b_active;
	unsigned int b_period;
	unsigned int b_events;
	unsigned int b_counter;

	unsigned int b_div;
	unsigned int b_remainder;

    static void __monitor_invokable_fakebatch() __attribute__((used));
};

static gh_timer_cx_t autoMonitorable_ghtimer;

#define cx autoMonitorable_ghtimer

class GhostMasterTimer
{
public:
	HANDLE hThread;
	DWORD tID;

	HANDLE hLocalEvent;
	HANDLE hBatchEvent;
	unsigned int nEvents;

	HANDLE evnList[4];

	HANDLE clientEvent;

	static DWORD WINAPI ThreadProcTrampoline(LPVOID lpParameter)
	{
		GhostMasterTimer& t=*(GhostMasterTimer*)lpParameter;
		t.ThreadProc();
		return 0;
	}

	void ThreadProc()
	{
		SetThreadPriority(hThread, THREAD_PRIORITY_HIGHEST);

		for(;;)
		{
			DWORD results=WaitForMultipleObjects(nEvents, evnList, FALSE, INFINITE);
			if (results==WAIT_OBJECT_0)
			{
				cx.b_active=1;
				--cx.l_countdown;
			}
			if (results==WAIT_OBJECT_0+1)
			{
				if (clientEvent)
				{
					if (!cx.b_active)
					{
						SetEvent(clientEvent);
					}
					else
					{
						if (cx.l_countdown<=0)
							SetEvent(clientEvent);
						cx.l_countdown=cx.b_div;
					}
				}
			}
		}
	}

	GhostMasterTimer()
	{
		hLocalEvent=CreateEventA(0, FALSE, FALSE, 0);

		//hEvent=CreateEventA(0, FALSE, FALSE, "GHOST_TM_EVN");
		hBatchEvent=CreateEventA(0, FALSE, FALSE, "GHOST_TM_BATCH");

		evnList[0]=hBatchEvent;
		evnList[1]=hLocalEvent;

		nEvents=2;

		hThread=CreateThread(0, 0, ThreadProcTrampoline, this, 0, &tID);
	}



	void batchProgram(float ms)
	{
		timeSetEvent(ms, 0, (LPTIMECALLBACK)hBatchEvent, 0, TIME_CALLBACK_EVENT_SET);
	}

	unsigned long period_ms;
	unsigned long long timenow_ms;
	unsigned int batch_slave;

	void timerStart(unsigned int ms, HANDLE evn)
	{
		clientEvent=evn;
		timeSetEvent(ms, period_ms, (LPTIMECALLBACK)hLocalEvent, 0, TIME_PERIODIC|TIME_CALLBACK_EVENT_SET);
	}

};

static GhostMasterTimer ghostMasterTimer;


void GhostSlaveTimerWait()
{
	if (ghostMasterTimer.hLocalEvent)
	{

		DWORD results=WaitForMultipleObjects(2, ghostMasterTimer.evnList, FALSE, INFINITE);
		if (results==WAIT_OBJECT_0+1)
		{
			ghostMasterTimer.batch_slave=1;
		}

		ghostMasterTimer.timenow_ms+=ghostMasterTimer.period_ms;
		return;
	}
}


unsigned long long GhostTimeNowMs()
{
	return cx.l_counter*10;
}

static void CALLBACK GHostBatchTimerAttachCallback(
		   UINT      /*uTimerID*/,
		   UINT      /*uMsg*/,
		   DWORD_PTR dwUser,
		   DWORD_PTR /*dw1*/,
		   DWORD_PTR /*dw2*/
		)
{
	++cx.b_events;
	SetEvent((HANDLE)dwUser);
}

static void CALLBACK GHostLocalTimerAttachCallback(
		   UINT      /*uTimerID*/,
		   UINT      /*uMsg*/,
		   DWORD_PTR dwUser,
		   DWORD_PTR /*dw1*/,
		   DWORD_PTR /*dw2*/
		)
{
	++cx.l_events;
	if (!cx.b_active)
	{
		SetEvent((HANDLE)dwUser);
	}

}

void GHostLocalTimerBatchSlave(bool enable)
{
	cx.l_slave=enable;
}

void GHostLocalTimerAttach(unsigned int period_ms, void* event_handle)
{
	cx.l_period=period_ms;
	ghostMasterTimer.timerStart(period_ms, event_handle);
   	//timeSetEvent(period_ms, 1, GHostLocalTimerAttachCallback, (DWORD_PTR)event_handle, TIME_PERIODIC);
}


void GHostBatchStartMs(unsigned int ms)
{
	if (ms!=cx.b_period)
	{
		if (cx.l_period)
		{
			float d=float(cx.b_period)/float(cx.l_period);
			if (d>=1)
			{
				cx.b_div=d;
				cx.b_remainder=0;
			}
			else
			{
				cx.b_div=0;
				cx.b_remainder=0;
			}
		}
	}
	if (ms)
	{
		ghostMasterTimer.batchProgram(ms);
	}
}

void GHostBatchEnd()
{
}


void gh_timer_cx_t::__monitor_invokable_fakebatch()
{
	GHostBatchStartMs(5);
}
