#ifdef PRECOMPILEDHEADERS
	#include "Utils All.h"
	#include "interface control.h"
#else
	#include <windows.h>
	#include <mmsystem.h>
	#include <string.h>
	#include "stdlib.h"
	#include "debug.h"
	#include "Timer Control.h"
	#include "overhead.h"
	#include "handle items.h"
	#include "worlddef.h"
	#include "renderworld.h"
	#include "interface control.h"
	#include "keymap.h"
#endif

#ifndef WIN32_LEAN_AND_MEAN
	#define WIN32_LEAN_AND_MEAN
#endif

#include "Soldier Control.h"
#include "connect.h"

// Base resolution of callback timer
static INT32 BASETIMESLICE = 10;
const INT32 FASTFORWARDTIMESLICE = 1000;
const LONGLONG FREQUENCY_CONST = 1000000;
static INT32 MIN_NOTIFY_TIME = 16000;
static INT32 UPDATETIMESLICE = 10000;


INT32	giClockTimer = -1;
INT32	giTimerDiag = 0;

UINT32	guiBaseJA2Clock = 0;
UINT32	guiBaseJA2NoPauseClock = 0;

BOOLEAN	gfPauseClock = FALSE;

BOOLEAN  gfHispeedClockMode = FALSE;

const inline UINT32 TIME_US_TO_MS(UINT32 value) { return value / 1000; }
const inline UINT32 TIME_MS_TO_US(UINT32 value) { return value * 1000; }

UINT32   giFastForwardPeriod = FASTFORWARDTIMESLICE;
BOOLEAN giFastForwardMode = FALSE;
INT32   giFastForwardKey = 0;
UINT32  guiTimeSlice = 0;
FLOAT gfClockSpeedPercent = 1.0;
LARGE_INTEGER gliPerfFreq = {0};
LARGE_INTEGER gliPerfCount = {0};
LARGE_INTEGER gliPerfCountNext = {0};


INT32		giTimerIntervals[ NUMTIMERS ] =
{
	5,					// Tactical Overhead
	20,					// NEXTSCROLL
	200,				// Start Scroll
	200,				// Animate tiles
	1000,				// FPS Counter
	80,					// PATH FIND COUNTER
	150,				// CURSOR TIMER
	250,				// RIGHT CLICK FOR MENU
	300,				// LEFT
	30,					// SLIDING TEXT
	200,				// TARGET REFINE TIMER
	150,					// CURSOR/AP FLASH
	60,					// FADE MERCS OUT
	160,				// PANEL SLIDE
	1000,				// CLOCK UPDATE DELAY
	20,					// PHYSICS UPDATE
	100,				// FADE ENEMYS
	20,					// STRATEGIC OVERHEAD
	40,
	500,				// NON GUN TARGET REFINE TIMER
	250,				// IMPROVED CURSOR FLASH
	500,				// 2nd CURSOR FLASH
	400,					// RADARMAP BLINK AND OVERHEAD MAP BLINK SHOUDL BE THE SAME
	400,
	10,					// Music Overhead
	100,				// Rubber band start delay
};

// TIMER COUNTERS
INT32		giTimerCounters[ NUMTIMERS ];

INT32		giTimerAirRaidQuote				= 0;
INT32		giTimerAirRaidDiveStarted = 0;
INT32		giTimerAirRaidUpdate			= 0;
INT32		giTimerCustomizable				= 0;
INT32		giTimerTeamTurnUpdate			= 0;

CUSTOMIZABLE_TIMER_CALLBACK gpCustomizableTimerCallback = NULL;

// Clock Callback event ID
MMRESULT	gTimerID;

TIMECAPS	gtc;

HANDLE		ghClockThread;
DWORD		gdwClockThreadId;
HANDLE		ghClockThreadShutdown;

HANDLE		ghNotifyThread;
DWORD		gdwNotifyThreadId;
HANDLE		ghNotifyThreadEvent;
HANDLE		ghNotifyThreadShutdownComplete;

// GLOBALS FOR CALLBACK
UINT32				gCNT;
SOLDIERTYPE		*gPSOLDIER;

// GLobal for displaying time diff ( DIAG )
UINT32		guiClockDiff = 0;
UINT32		guiClockStart = 0;

// BOB: made global to help track freeze issue
LONGLONG gliTimestampDiff = 0;
LONGLONG gliWaitTime = 0;
LONGLONG giIncrement = 0;
UINT32 giSleepTime = 0;


extern UINT32 guiCompressionStringBaseTime;
extern INT32 giFlashHighlightedItemBaseTime;
//extern INT32 giCompatibleItemBaseTime;//Moa:removed (see HandleMouseInCompatableItemForMapSectorInventory)
extern INT32 giAnimateRouteBaseTime;
extern INT32 giPotHeliPathBaseTime;
extern INT32 giPotMilitiaPathBaseTime;
extern INT32 giClickHeliIconBaseTime;
extern INT32 giExitToTactBaseTime;
extern UINT32 guiSectorLocatorBaseTime;
extern INT32 giCommonGlowBaseTime;
extern INT32 giFlashAssignBaseTime;
extern INT32 giFlashContractBaseTime;
extern UINT32 guiFlashCursorBaseTime;
extern INT32 giPotCharPathBaseTime;

// sevenfm: display overflow detection
extern void MapScreenMessage(UINT16 usColor, UINT8 ubPriority, STR16 pStringA, ...);

typedef void (*TIMER_NOTIFY_CALLBACK) ( INT32 timer, PTR state );
struct TIMER_NOTIFY_ITEM
{
	TIMER_NOTIFY_CALLBACK callback;
	PTR state;
};
typedef std::list<TIMER_NOTIFY_ITEM> TIMER_NOTIFY_ITEM_LIST;
typedef TIMER_NOTIFY_ITEM_LIST::iterator TIMER_NOTIFY_ITEM_ITERATOR;
static TIMER_NOTIFY_ITEM_LIST glNotifyCallbacks;
static CRITICAL_SECTION gcsNotifyLock;

static bool HasTimerNotifyCallbacks( );
static void BroadcastTimerNotify(INT32 );
static BOOLEAN UpdateTimeCounter( INT32 &counter, INT32 &iTimeLeft );
static BOOLEAN UpdateCounter( INT32 counter, INT32 &iTimeLeft);
static void UpdateTimer();
void ResetJA2ClockGlobalTimers(void);

UINT32 InitializeJA2TimerCallback( UINT32 uiDelay, LPTIMECALLBACK TimerProc, UINT32 uiUser );

// CALLBACKS
void CALLBACK FlashItem( UINT uiID, UINT uiMsg, DWORD uiUser, DWORD uiDw1, DWORD uiDw2 );

void CALLBACK TimeProc( UINT uID,	UINT uMsg, DWORD dwUser, DWORD dw1,	DWORD dw2	)
{
	static BOOLEAN fInFunction = FALSE;
	//SOLDIERTYPE		*pSoldier;

	if ( !fInFunction )
	{
		fInFunction = TRUE;

		BOOLEAN timerDone = FALSE;
		BOOLEAN tickTime = FALSE;
		INT32 iTimeLeft = 0;

		// Use QPC to check if BASETIMESLICE (in ms) has passed
		if (IsHiSpeedClockMode())
		{
			// Only advance time when sufficient time has passed to exceed next time
			QueryPerformanceCounter(&gliPerfCount);
			if (gliPerfCount.QuadPart > gliPerfCountNext.QuadPart)
			{
				INT32 iNext = IsFastForwardMode() ? giFastForwardPeriod : UPDATETIMESLICE;
				giIncrement = (iNext * gliPerfFreq.QuadPart) / FREQUENCY_CONST;
				gliPerfCountNext.QuadPart = gliPerfCount.QuadPart + giIncrement;
				iTimeLeft = iNext;
				timerDone = IsFastForwardMode();
				tickTime = TRUE;
			}
		}
		else
		{
			// When using millisecond timer, advance time everytime this function is called
			tickTime = TRUE;
			timerDone = !IsFastForwardMode();
		}
		if (tickTime)
		{
			guiBaseJA2NoPauseClock += BASETIMESLICE;

			if ( !gfPauseClock )
			{
				UINT32 uiOldClock = guiBaseJA2Clock;

				guiBaseJA2Clock += BASETIMESLICE;

				// Terapevt suggested fix
				if ((INT32)guiBaseJA2Clock < 0)
					guiBaseJA2Clock = 0;

				// detect overflow
				if (uiOldClock > guiBaseJA2Clock)
				{
					MapScreenMessage(162, 0, L"guiBaseJA2Clock overflow detected!");
					for (gCNT = 0; gCNT < TOTAL_SOLDIERS; gCNT++)
					{
						if (MercPtrs[gCNT])
							MercPtrs[gCNT]->ResetSoldierChangeStatTimer();
					}
				}

				for ( gCNT = 0; gCNT < NUMTIMERS; gCNT++ )
				{
					timerDone |= UpdateCounter( gCNT, iTimeLeft  );
				}

				// Update some specialized countdown timers...
				timerDone |= UpdateTimeCounter( giTimerAirRaidQuote, iTimeLeft );
				timerDone |= UpdateTimeCounter( giTimerAirRaidDiveStarted, iTimeLeft );
				timerDone |= UpdateTimeCounter( giTimerAirRaidUpdate, iTimeLeft );
				timerDone |= UpdateTimeCounter( giTimerTeamTurnUpdate, iTimeLeft );

				if ( gpCustomizableTimerCallback )
				{
					timerDone |= UpdateTimeCounter( giTimerCustomizable, iTimeLeft );
				}

#ifndef BOUNDS_CHECKER

				// If mapscreen...
				if( guiTacticalInterfaceFlags & INTERFACE_MAPSCREEN )
				{
					// IN Mapscreen, loop through player's team.....
					for ( gCNT = gTacticalStatus.Team[ gbPlayerNum ].bFirstID; gCNT <= gTacticalStatus.Team[ gbPlayerNum ].bLastID; gCNT++ )
					{
						gPSOLDIER = MercPtrs[ gCNT ];
						timerDone |= UpdateTimeCounter( gPSOLDIER->timeCounters.PortraitFlashCounter, iTimeLeft );
						timerDone |= UpdateTimeCounter( gPSOLDIER->timeCounters.PanelAnimateCounter, iTimeLeft );
					}
				}
				else
				{
					// Set update flags for soldiers
					////////////////////////////
					for ( gCNT = 0; gCNT < guiNumMercSlots; gCNT++ )
					{
						gPSOLDIER = MercSlots[ gCNT ];

						if ( gPSOLDIER != NULL )
						{
							timerDone |= UpdateTimeCounter( gPSOLDIER->timeCounters.UpdateCounter, iTimeLeft );
							timerDone |= UpdateTimeCounter( gPSOLDIER->timeCounters.DamageCounter, iTimeLeft );
							timerDone |= UpdateTimeCounter( gPSOLDIER->timeCounters.ReloadCounter, iTimeLeft );
							timerDone |= UpdateTimeCounter( gPSOLDIER->timeCounters.FlashSelCounter, iTimeLeft );
							timerDone |= UpdateTimeCounter( gPSOLDIER->timeCounters.BlinkSelCounter, iTimeLeft );
							timerDone |= UpdateTimeCounter( gPSOLDIER->timeCounters.PortraitFlashCounter, iTimeLeft );
							timerDone |= UpdateTimeCounter( gPSOLDIER->timeCounters.AICounter, iTimeLeft );
							timerDone |= UpdateTimeCounter( gPSOLDIER->timeCounters.FadeCounter, iTimeLeft );
							timerDone |= UpdateTimeCounter( gPSOLDIER->timeCounters.NextTileCounter, iTimeLeft );
							timerDone |= UpdateTimeCounter( gPSOLDIER->timeCounters.PanelAnimateCounter, iTimeLeft );
#ifdef JA2UB
							timerDone |= UpdateTimeCounter( gPSOLDIER->GetupFromJA25StartCounter, iTimeLeft );
#endif
						}
					}
				}
#endif
			}
		}

		if (timerDone)
			SetEvent(ghNotifyThreadEvent);
		fInFunction = FALSE;
	}
}

static UINT32 MIN_TIMER(UINT32 timer, UINT32 other)
{
	UINT32 value = TIME_MS_TO_US(timer);
	return ( value && value < other ? value : other );
}

// checks if the clock based on QueryPerformanceCounter is using sane values
static inline bool TimerSanityCheck() {
	// quick and drity check for messed up gliPerfCountNext - if the high part is ahead by more than 2, something very bad is going on.
	return !( gliPerfCountNext.HighPart > gliPerfCount.HighPart + 1L );
}

// Returns the smallest time interval for a counter currently in use
UINT32 GetNextCounterDoneTime(void)
{
	QueryPerformanceCounter(&gliPerfCount);
	gliTimestampDiff = gliPerfCountNext.QuadPart - gliPerfCount.QuadPart;
	gliWaitTime = (gliTimestampDiff * FREQUENCY_CONST) / gliPerfFreq.QuadPart;

	// if the wait time is too long, or the "missed" step gets too long, re-evaluate the timer and try waiting 125ms
	if (gliWaitTime > 15000 || gliWaitTime < -15000) {
		gliWaitTime = 125; // in mili-seconds

		QueryPerformanceFrequency(&gliPerfFreq);
		gliPerfCountNext.QuadPart = gliPerfCount.QuadPart + ((125 * gliPerfFreq.QuadPart) / FREQUENCY_CONST);
	}

	return (UINT32)((gliWaitTime > 0) ? gliWaitTime : 0);
}

// Function to test if there are any outstanding timers.  Used in fast forward routines
BOOLEAN IsTimerActive(void)
{
	return GetNextCounterDoneTime() <= FASTFORWARDTIMESLICE ? TRUE : FALSE;
}

DWORD WINAPI JA2ClockThread( LPVOID lpParam ) 
{
	__try
	{
		for(;;) 
		{
			TimeProc(0, 0, 0, 0, 0);

			DWORD dwResult = WaitForSingleObject(ghClockThreadShutdown, 0);
			if (dwResult == WAIT_OBJECT_0 || dwResult == WAIT_ABANDONED)
				break;
			YieldProcessor();

			// Sleep for a couple of milliseconds if not in fast forward mode
			if (!IsFastForwardMode()) {
				
				giSleepTime = TIME_US_TO_MS(GetNextCounterDoneTime());

				// monitor the returned sleep times, if we try to sleep for more than 2 secs then somehing must be very wrong.
				if (giSleepTime > 2000) {
					giSleepTime = 250;
				}

				Sleep(giSleepTime);
			}
		} 
	}
	__except( EXCEPTION_EXECUTE_HANDLER  )
	{
		// Unhandled exception just exit
		__debugbreak();
	}
	return 0L;
}

DWORD WINAPI JA2NotifyThread( LPVOID lpParam )
{
	HANDLE waitHandles[] = {ghClockThreadShutdown, ghNotifyThreadEvent};
	for(;;) 
	{
		DWORD dwResult = WaitForSingleObject(ghClockThreadShutdown, 0);
		if (dwResult == WAIT_OBJECT_0 || dwResult == WAIT_ABANDONED)
			break;

		DWORD waitTime = (!IsFastForwardMode()) ? max(TIME_US_TO_MS(MIN_NOTIFY_TIME), TIME_US_TO_MS( GetNextCounterDoneTime() ) ) : 0;
		dwResult = WaitForMultipleObjectsEx(_countof(waitHandles), waitHandles, FALSE, waitTime, FALSE);
		if (dwResult == WAIT_OBJECT_0)
			break;
		if (dwResult >= WAIT_ABANDONED_0 && dwResult <= (WAIT_ABANDONED_0 + _countof(waitHandles)))
			break;
		if ( dwResult == WAIT_FAILED || dwResult == WAIT_TIMEOUT || (dwResult-WAIT_OBJECT_0) == 1)
		{
			if (HasTimerNotifyCallbacks())
			{
				BroadcastTimerNotify(-1);
			}
		}
		else
		{
			// unexpected failure
			DebugMsg(TOPIC_JA2, DBG_LEVEL_3, "JA2NotifyThread failed!");
			__debugbreak();
		}
	} 
	SetEvent(ghNotifyThreadShutdownComplete);
	return 0L;
}



BOOLEAN InitializeJA2Clock()
{
#ifdef CALLBACKTIMER
	MMRESULT	mmResult;
	INT32			cnt;

	// Init timer delays
	for ( cnt = 0; cnt < NUMTIMERS; cnt++ )
	{
		giTimerCounters[ cnt ] = giTimerIntervals[ cnt ];
	}


	// First get timer resolutions
	mmResult = timeGetDevCaps( &gtc, sizeof( gtc ) );

	if ( mmResult != TIMERR_NOERROR )
	{
		DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "Could not get timer properties");
	}

	if ( !QueryPerformanceFrequency(&gliPerfFreq) )
	{
		DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "Could not get performance frequency");
	}
	if ( !QueryPerformanceCounter(&gliPerfCount) )
	{
		DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "Could not get performance frequency");
	}

	timeBeginPeriod(gtc.wPeriodMin);

	InitializeCriticalSection(&gcsNotifyLock);

	if (IsHiSpeedClockMode())
	{
		ghClockThreadShutdown = CreateEvent(NULL, TRUE, FALSE, NULL);
		ghNotifyThreadEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
		ghNotifyThreadShutdownComplete = CreateEvent(NULL, TRUE, FALSE, NULL);
		ghClockThread = CreateThread( 
			NULL,              // default security attributes
			0,                 // use default stack size  
			JA2ClockThread,    // thread function 
			NULL,              // argument to thread function 
			0,                 // use default creation flags 
			&gdwClockThreadId);// returns the thread identifier 
		ghNotifyThread = CreateThread( 
			NULL,              // default security attributes
			0,                 // use default stack size  
			JA2NotifyThread,    // thread function 
			NULL,              // argument to thread function 
			0,                 // use default creation flags 
			&gdwNotifyThreadId);// returns the thread identifier 
	}
	else
	{
		UpdateTimer();
	}
#endif

	return TRUE;
}


void	ShutdownJA2Clock(void)
{
	if (IsHiSpeedClockMode())
	{
		SetEvent(ghClockThreadShutdown);
		WaitForSingleObject(ghNotifyThreadShutdownComplete, 2000);
		HANDLE waitHandles[] = {ghClockThread, ghNotifyThread};
		WaitForMultipleObjects(_countof(waitHandles), waitHandles, TRUE, 1000);
		CloseHandle(ghClockThreadShutdown);
		CloseHandle(ghClockThread);
		CloseHandle(ghNotifyThreadEvent);
		CloseHandle(ghNotifyThread);
		// During ungraceful shutdowns notify lock may be in use in notify thread
		if (TryEnterCriticalSection(&gcsNotifyLock))
		{
			LeaveCriticalSection(&gcsNotifyLock);
			DeleteCriticalSection(&gcsNotifyLock);
		}
	}
	else
	{
		// Make sure we kill the timer
#ifdef CALLBACKTIMER
		timeKillEvent( gTimerID );
#endif
	}

	timeEndPeriod(gtc.wPeriodMin);
}


UINT32 InitializeJA2TimerCallback( UINT32 uiDelay, LPTIMECALLBACK TimerProc, UINT32 uiUser )
{
	MMRESULT	mmResult;
	MMRESULT	TimerID;


	// First get timer resolutions
	mmResult = timeGetDevCaps( &gtc, sizeof( gtc ) );

	if ( mmResult != TIMERR_NOERROR )
	{
		__debugbreak();
		DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "Could not get timer properties");
	}

	// Set timer at lowest resolution. Could use middle of lowest/highest, we'll see how this performs first
	TimerID = timeSetEvent( (UINT)uiDelay, (UINT)uiDelay, TimerProc, (DWORD)uiUser, TIME_PERIODIC );

	if ( !TimerID )
	{
		__debugbreak();
		DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "Could not create timer callback");
	}

	return ( (UINT32)TimerID );
}

void RemoveJA2TimerCallback( UINT32 uiTimer )
{
	timeKillEvent( uiTimer );
}


UINT32 InitializeJA2TimerID( UINT32 uiDelay, UINT32 uiCallbackID, UINT32 uiUser )
{
	switch( uiCallbackID )
	{
	case ITEM_LOCATOR_CALLBACK:

		return( InitializeJA2TimerCallback( uiDelay, FlashItem, uiUser ) );
		break;

	}

	// invalid callback id
	Assert( FALSE );
	return( 0 );
}


//////////////////////////////////////////////////////////////////////////////////////////////
// TIMER CALLBACK S
//////////////////////////////////////////////////////////////////////////////////////////////
void CALLBACK FlashItem( UINT uiID, UINT uiMsg, DWORD uiUser, DWORD uiDw1, DWORD uiDw2 )
{

}


void PauseTime( BOOLEAN fPaused )
{
	gfPauseClock = fPaused;
}

void SetCustomizableTimerCallbackAndDelay( INT32 iDelay, CUSTOMIZABLE_TIMER_CALLBACK pCallback, BOOLEAN fReplace )
{
	if ( gpCustomizableTimerCallback )
	{
		if ( !fReplace )
		{
			// replace callback but call the current callback first
			gpCustomizableTimerCallback();
		}
	}

	RESETTIMECOUNTER( giTimerCustomizable, iDelay );
	gpCustomizableTimerCallback = pCallback;
}

void CheckCustomizableTimer( void )
{
	if ( gpCustomizableTimerCallback )
	{
		if ( TIMECOUNTERDONE( giTimerCustomizable, 0 ) )
		{
			// set the callback to a temp variable so we can reset the global variable
			// before calling the callback, so that if the callback sets up another
			// instance of the timer, we don't reset it afterwards
			CUSTOMIZABLE_TIMER_CALLBACK pTempCallback;

			pTempCallback = gpCustomizableTimerCallback;
			gpCustomizableTimerCallback = NULL;
			pTempCallback();
		}
	}
}



void ResetJA2ClockGlobalTimers( void )
{
	UINT32 uiCurrentTime = GetJA2Clock();

	guiCompressionStringBaseTime = uiCurrentTime;
	giFlashHighlightedItemBaseTime = uiCurrentTime;
	//giCompatibleItemBaseTime = uiCurrentTime;//Moa: removed (see HandleMouseInCompatableItemForMapSectorInventory)
	giAnimateRouteBaseTime = uiCurrentTime;
	giPotHeliPathBaseTime = uiCurrentTime;
	giPotMilitiaPathBaseTime = uiCurrentTime;
	giClickHeliIconBaseTime = uiCurrentTime;
	giExitToTactBaseTime = uiCurrentTime;
	guiSectorLocatorBaseTime = uiCurrentTime;

	giCommonGlowBaseTime = uiCurrentTime;
	giFlashAssignBaseTime = uiCurrentTime;
	giFlashContractBaseTime = uiCurrentTime;
	guiFlashCursorBaseTime = uiCurrentTime;
	giPotCharPathBaseTime = uiCurrentTime;
}

void SetTileAnimCounter( INT32 iTime )
{
	giTimerIntervals[ ANIMATETILES ] = iTime;
}

void SetFastForwardPeriod(DOUBLE value)
{
	giFastForwardPeriod = (UINT32)(value);
	if (giFastForwardPeriod <= 1)
		giFastForwardPeriod = 1;
}

void SetFastForwardKey(INT32 key)
{
	giFastForwardKey = key;
}

BOOLEAN IsFastForwardKeyPressed()
{
	// WANNE: In a multiplayer game it is not allowed for the "pure" client to do fast forward
	// Only the server is allowed to do, because the AI is generated on the server
	if (is_networked)
	{
		if (!is_server)				// It is not allowed when we are not the server
			return false;
		else if (gTacticalStatus.ubCurrentTeam != 1)	// It is not allowed, when it is not the enemy turn!
			return false;
	}

	return giFastForwardKey && IsKeyPressed(giFastForwardKey);
}

void SetFastForwardMode(BOOLEAN enable)
{
	giFastForwardMode = enable;
	UpdateTimer();
}

BOOLEAN IsFastForwardMode()
{
	return giFastForwardMode || IsFastForwardKeyPressed();
}

LONGLONG GetJA2Microseconds()
{
	return gliPerfCount.QuadPart * FREQUENCY_CONST / gliPerfFreq.QuadPart;
}

void AddTimerNotifyCallback( TIMER_NOTIFY_CALLBACK callback, PTR state )
{
	EnterCriticalSection(&gcsNotifyLock);
	BOOL addItem = TRUE;
	for (TIMER_NOTIFY_ITEM_ITERATOR itr = glNotifyCallbacks.begin(); itr != glNotifyCallbacks.end(); ++itr) {
		if ( callback == (*itr).callback && state == (*itr).state ){
			addItem = FALSE;
			break;
		}
	}
	if (addItem)
	{
		TIMER_NOTIFY_ITEM item;
		item.callback = callback;
		item.state = state;
		glNotifyCallbacks.push_back(item);
	}
	LeaveCriticalSection(&gcsNotifyLock);
}

void RemoveTimerNotifyCallback( TIMER_NOTIFY_CALLBACK callback, PTR state )
{
	EnterCriticalSection(&gcsNotifyLock);
	for ( TIMER_NOTIFY_ITEM_ITERATOR itr = glNotifyCallbacks.begin(); itr != glNotifyCallbacks.end(); ) 
	{
		if ( callback == (*itr).callback && state == (*itr).state )
			itr = glNotifyCallbacks.erase(itr);
		else
			++itr;
	}
	LeaveCriticalSection(&gcsNotifyLock);
}

void ClearTimerNotifyCallbacks()
{
	// If we cannot get the lock it is likely due to exception while handling notification and we are shutting down
	if ( TryEnterCriticalSection(&gcsNotifyLock) )
	{
		glNotifyCallbacks.clear();
		LeaveCriticalSection(&gcsNotifyLock);
	}
}

static bool HasTimerNotifyCallbacks( )
{
	return !glNotifyCallbacks.empty();
}


// Call timer notify routine
//   Separate the callback notifies with normal try/catch
//   as SEH __try/__except are incompatible with C++ exceptions
static void InnerTimerNotify(INT32 timer)
{
   try
   {
	  for (TIMER_NOTIFY_ITEM_ITERATOR itr = glNotifyCallbacks.begin(); itr != glNotifyCallbacks.end(); ++itr)
	  {
		 if ( NULL != (*itr).callback)
			(*itr).callback( timer, (*itr).state );
	  }
   }
   catch (...)   {
	   __debugbreak();
	   DebugMsg(TOPIC_JA2, DBG_LEVEL_3, "InnerTimerNotify Failed!");
   }
}
static void BroadcastTimerNotify(INT32 timer)
{
	EnterCriticalSection(&gcsNotifyLock);
	__try
	{
		__try { InnerTimerNotify(timer); }
		__except( EXCEPTION_EXECUTE_HANDLER  )
		{ /*  Not sure.  exit? */ 
			__debugbreak();
			DebugMsg(TOPIC_JA2, DBG_LEVEL_3, "BroadcastTimerNotify Failed!");
		}
	}
	__finally
	{
		LeaveCriticalSection(&gcsNotifyLock);
	}
}

BOOLEAN UpdateTimeCounter( INT32 &counter, INT32 &iTimeLeft)
{
	if (counter == 0) {
		return FALSE;
	} else if ( ( counter - BASETIMESLICE ) < 0 ) {
		counter = 0;
		return TRUE;
	} else {
		counter -= BASETIMESLICE;
		if ( counter < iTimeLeft )
			iTimeLeft = counter;
		return FALSE;
	}
	return FALSE;
}

BOOLEAN UpdateCounter( INT32 counterIdx, INT32 &iTimeLeft )
{
	INT32& counter = giTimerCounters[ counterIdx ];
	return UpdateTimeCounter(counter, iTimeLeft);
}

BOOLEAN UpdateCounter( INT32 counterIdx )
{
	INT32 iDummy = 0;
	return UpdateCounter(counterIdx, iDummy);
}

void ResetCounter(INT32 counterIdx)
{
	giTimerCounters[ counterIdx ] = giTimerIntervals[ counterIdx ];
}

BOOLEAN CounterDone(INT32 counterIdx)
{
	return ( giTimerCounters[ counterIdx ] == 0 ) ? TRUE : FALSE;
}

void ResetTimerCounter(INT32 &timer, INT32 value)
{
	timer = value;
}

BOOLEAN TimeCounterDone(INT32 timer)
{
	return ( timer == 0 ) ? TRUE : FALSE;
}

void ZeroTimeCounter(INT32& timer)
{
	timer = 0;
}

BOOLEAN IsJA2TimerThread()
{
	return (GetCurrentThreadId() == gdwClockThreadId);
}

#ifndef GetJA2Clock
UINT32	GetJA2Clock()
{
	return guiBaseJA2Clock;
}
#endif

#ifndef GetJA2NoPauseClock
UINT32	GetJA2NoPauseClock()
{
	return guiBaseJA2NoPauseClock;
}
#endif

void SetHiSpeedClockMode(BOOLEAN enable)
{
	gfHispeedClockMode = enable;
}

BOOLEAN IsHiSpeedClockMode()
{
	return gfHispeedClockMode;
}

void SetNotifyFrequencyKey(INT32 value)
{
	MIN_NOTIFY_TIME = value;
}

void SetClockSpeedPercent(FLOAT value)
{
	gfClockSpeedPercent = value;
	UPDATETIMESLICE = (UINT32)((FLOAT)TIME_MS_TO_US(BASETIMESLICE) * 100.0f / value);
	UpdateTimer();
}

void UpdateTimer()
{
	// Set timer at lowest resolution. Could use middle of lowest/highest, we'll see how this performs first
	if (!IsHiSpeedClockMode())
	{
		UINT uiTimeSlice = giFastForwardMode ? gtc.wPeriodMin : max(gtc.wPeriodMin, TIME_US_TO_MS(UPDATETIMESLICE));
		if (uiTimeSlice != guiTimeSlice)
		{
			guiTimeSlice = uiTimeSlice;
			if (gTimerID != 0) timeKillEvent(gTimerID);
			gTimerID = timeSetEvent( uiTimeSlice, uiTimeSlice, TimeProc, (DWORD)0, TIME_PERIODIC );
			if ( !gTimerID )
			{
				__debugbreak();
				DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "Could not create timer callback");
			}
		}
	}
}

