#include "stdafx.h"	// If you use MFC, otherwise delete this line.
#include "AnimTimer.h"
#include "process.h"

//
// AnimTimer
// (c) 2001 David Blume. All rights reserved.
//
// Derived from ScrPlus and AnimTimer (c) 1996-2000 by Lucian Wischik.
// 
//

namespace AnimTimer
{

#define TIMERDATA_PROPNAME ("PROP_ANIMTIMERDATA")

typedef struct {
	HWND		hWnd;				// Set at thread creation
	UINT		nIDEvent;
	CRITICAL_SECTION critsec;		// critsec protects IsHwndValid
	BOOL		IsHwndValid;		// set to FALSE if the window no longer wishes the thread to do stuff
	HANDLE		hChangeEvent;		// in response to a SCRM_CHANGE, to interrupt the sleep
	unsigned int idThread;
	HANDLE		hThread;			// a handle so we can wait for it
	unsigned int nPeriod;
} TThreadData;

void CreateTimerData(HWND hwnd);
void SetTimerData(HWND hwnd, TThreadData *td);
TThreadData *GetTimerData(HWND hwnd);
void RemoveTimerData(HWND hwnd);
unsigned int __stdcall ThreadFunction(void *lParam);



UINT SetTimer(HWND hWnd, UINT nIDEvent, UINT uElapse, TIMERPROC lpNULL)
{
	if (lpNULL || !hWnd)
		return false;

	TThreadData *td;
	td = GetTimerData(hWnd);
	if (td != 0) // If a timer was already there, we leave it
		return false;

	td = new TThreadData;
	SetTimerData(hWnd, td);
	InitializeCriticalSection(&td->critsec);
	td->hChangeEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	if (!td->hChangeEvent) {
		RemoveTimerData(hWnd);
		return false;
	}
	td->hWnd = hWnd;
	td->nIDEvent = nIDEvent;
	td->IsHwndValid = TRUE;
	td->nPeriod = uElapse;
	td->hThread = (HANDLE) _beginthreadex(NULL, 4096, ThreadFunction, td, 0, &(td->idThread));
	if (!td->hThread) {
		CloseHandle(td->hChangeEvent);
		RemoveTimerData(hWnd);
		return false;
	}
	// Give the new thread a lower priority.
	HANDLE hThisT = GetCurrentThread();
	HANDLE hThatT  =(HANDLE)td->hThread;
	int pr = GetThreadPriority(hThisT);
	SetThreadPriority(hThatT, pr-1);
	// Hopefully that will do it, and hopefully it won't matter if we underflow by one.

	return true;
}


BOOL KillTimer(HWND hWnd, UINT uIDEvent)
{ 
	if (hWnd==NULL)
		return FALSE;

	TThreadData *td = GetTimerData(hWnd);
	if (td != NULL) { 
		if (uIDEvent != td->nIDEvent)
			return FALSE;
		EnterCriticalSection(&td->critsec);
		td->IsHwndValid = FALSE;
		LeaveCriticalSection(&td->critsec);
		// That will be enough to ensure that we never get any messages
		// from now on. Note that the thread's send(SCRM_TIMER) is
		// itself bracketed inside this critical section
		// So not even another sendmessage can get through
		// CloseHandle((HANDLE)td->hThread);
		PostThreadMessage(td->idThread, WM_QUIT, 0, 0);
		SetEvent(td->hChangeEvent); // Wake the other thread.
		// Now it's just possible that the other thread is waiting
		// for this thread to consume a WM_TIMER message.
		// Then let's consume it.
		MSG theMsg;
		PeekMessage(&theMsg, hWnd, WM_TIMER, WM_TIMER, PM_REMOVE);
		DWORD res = WaitForSingleObject(td->hThread, 2000); // INFINITE);
		CloseHandle(td->hChangeEvent);
		delete td;
	}
	return TRUE;
}

// This thread spins and sends WM_TIMER messages.
unsigned int __stdcall ThreadFunction(void *lParam)
{
	TThreadData *td = (TThreadData *) lParam;
	HWND hwnd = td->hWnd;
	unsigned int nPeriod = td->nPeriod;
	BOOL mustquit=FALSE;
	BOOL isvalid;
	UINT nIDEvent = td->nIDEvent;
	MSG msg;
	while (!mustquit)
	{
		BOOL res = PeekMessage(&msg, NULL, 0, 0xFFFF, PM_REMOVE);
		if (res) { 
			if (msg.message == WM_QUIT)
				mustquit = TRUE;
		}
		if (!mustquit) { 
			DWORD starttime, endtime; // This only mean something if isvalid.
			EnterCriticalSection(&td->critsec);
			isvalid = td->IsHwndValid;
			LeaveCriticalSection(&td->critsec);
			if (isvalid) {
				starttime = GetTickCount();
				SendMessage(hwnd, WM_TIMER, nIDEvent, 0);
				endtime = GetTickCount();
				// Note: period 0 means no rest for the wicked. Any other period
				// means at least some Sleep(), even if it is just 0 to yield a
				// timeslice.

				// It's not so critical that hwnd is valid for us to sleep
				DWORD sleeptime = 0;
				if (starttime + nPeriod > endtime) 
					sleeptime = starttime + nPeriod - endtime;
				if (sleeptime == 0) 
					Sleep(0);
				else {
					DWORD res = WaitForSingleObject(td->hChangeEvent, sleeptime);
					if (res == WAIT_OBJECT_0) 
						ResetEvent(td->hChangeEvent);
				}
			} // If isvalid
		} // If !mustquit
	}
	RemoveTimerData(hwnd);
	_endthreadex(0);
	return (0);
}


void CreateTimerData(HWND hwnd)
{
	TThreadData **hTD = new (TThreadData *);
	*hTD = NULL;
	SetProp(hwnd, TIMERDATA_PROPNAME, (HANDLE) hTD);
}

void SetTimerData(HWND hwnd, TThreadData *td)
{
	TThreadData **hTD = (TThreadData **) GetProp(hwnd, TIMERDATA_PROPNAME);
	if (!hTD) { 
		CreateTimerData(hwnd);
		hTD = (TThreadData **) GetProp(hwnd, TIMERDATA_PROPNAME);
		if (!hTD) 
			return;
	}
	*hTD = td;
}

TThreadData *GetTimerData(HWND hwnd)
{
	TThreadData **hTD = (TThreadData **) GetProp(hwnd, TIMERDATA_PROPNAME);
	return (hTD) ? *hTD : NULL;
}

void RemoveTimerData(HWND hwnd)
{
	TThreadData **hTD = (TThreadData **) GetProp(hwnd, TIMERDATA_PROPNAME);
	delete hTD;
	if (hTD != NULL) 
		RemoveProp(hwnd, TIMERDATA_PROPNAME);
}

}