/*
	Lan Wars.
	Copyright (c) 2009, Darragh Coy.
	All rights reserved.
	Original work(s), source code & author(s) details available at: http://www.darraghcoy.com

	Redistribution and use in source and binary forms, with or without modification, are permitted 
	provided that the following conditions are met:

		* 	Redistributions of source code must retain the above copyright notice, this list of 
			conditions and the following disclaimer.
		* 	Redistributions in binary form must reproduce the above copyright notice, this list of 
			conditions and the following disclaimer in the documentation.
		* 	Neither the name of the author nor the names of contributors may be used to endorse or 
			promote products derived from this software without specific prior written permission.
		* 	The use of any third party libraries, modules or data files provided with the source 
			form of the program must follow and respect the individual licensing terms of these 
			components. You do not have permission to modify or circumvent the licensing terms for 
			provided third party components unless the third party licenses specifically allow this. 
			If you do not agree to the terms of provided third party components then you do not have 
			permission to use them in your derived work(s).
		* 	The terms of this license do not apply to separate (but related) distributions if supplied 
			by the author(s), only to the contents of this source distribution, derived works, and the 
			binaries produced by either modified or unmodified versions of the source code. The terms 
			of this license do not also apply to any third party libraries or components provided with 
			the source distribution. Such third party elements are licensed under their own separate 
			agreements.

	THIS SOFTWARE IS PROVIDED BY DARRAGH COY ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
	BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
	ARE DISCLAIMED. IN NO EVENT SHALL DARRAGH COY BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
	EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 
	OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 
	OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
	IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

//=====================================================================================================================
/// @brief		This static class creates a high resolution timing system. Time values are read off one core exclusively
///				in a created thread because of the inconsitency of timing values across cores in mult-core systems.
///				This is used by the application core to time when frame updates should be applied.
//=====================================================================================================================

#include "Precompiled.h"

using namespace Game;

//=====================================================================================================================
// Statics
//=====================================================================================================================

/// The current time stamp:

LARGE_INTEGER HighResolutionTimer::s_current_time_stamp = {0};

/// The previous time stamp:

LARGE_INTEGER HighResolutionTimer::s_previous_time_stamp = {0};

/// The frequency of the high resolution timer:

LARGE_INTEGER HighResolutionTimer::s_timer_frequency = {0};

/// The timing thread

HANDLE HighResolutionTimer::s_timing_thread = 0;

/// The timing thread id

DWORD HighResolutionTimer::s_timing_thread_id = 0;

/// Mutex for timing values:

HANDLE HighResolutionTimer::s_mutex = 0;

/// Continue running the timing thread ?

bool HighResolutionTimer::s_run_timing_thread = false;

/// The current time delta between calls to Update()

float HighResolutionTimer::s_current_time_delta = 0;

//=====================================================================================================================
/// @brief		Initializes the high resolution timing system: must be called before high resolution timing can be used
///	@pre		The high resolution timer must not already be intialised
/// @remarks	This should be called in the main thread only
//=====================================================================================================================

void HighResolutionTimer::Initialize()
{
	// Make sure not already initialized:
	
	ENSURE( s_mutex == 0 && s_timing_thread == 0 && s_timing_thread_id == 0 , "The high resolution timer must not already be intialised" );

	// Create a mutex for controlling access to the time stamps: this thread holds it initially
	
	s_mutex = CreateMutex( 0 , true , 0 );
	
	// If we do not have a mutex then issue a fatal error:
	
	if ( s_mutex == 0 ) Errors::IssueError("Could not create syncrhonistation mutex. Unable to initialize high resolution timing system!");
	
	// Ok: now create the timing thread itself:
	
	s_timing_thread = CreateThread
	(
		0					,	// secturity attributes
		0x1000				,	// stack size: only use a small stack of 4k
		TimingThreadMain	,	// start function
		0					,	// parameter to pass to that function
		CREATE_SUSPENDED	,	// Create the thread in a suspended state
		&s_timing_thread_id		// Save the thread id here
	);
	
	// If this thread could not be created then bail the program:
	
	if ( s_timing_thread == 0 )
	{
		Errors::IssueError("Could not create the timing thread. Unable to setup high resolution timing system!");
	}
	
	// Force this thread to run on one processor only:
	
	DWORD result = SetThreadAffinityMask(s_timing_thread,0x1);
	
	// See if succeeded:
	
	if ( result == 0 )
	{
		Errors::IssueError("Could not set timing thread affinity mask. Unable to setup high resolution timing system!");
	}
	
	// Signal the timing thread to run:
	
	s_run_timing_thread = true;
	
	// Reset timing values:
	
	s_current_time_stamp.LowPart	= 0;
	s_current_time_stamp.HighPart	= 0;
	
	s_previous_time_stamp.LowPart	= 0;
	s_previous_time_stamp.HighPart	= 0;
	
	// Query the resolution of the timer:
	
	BOOL qpf_result = QueryPerformanceFrequency(&s_timer_frequency);
	
	// If that failed then abort:
	
	if ( qpf_result == 0 || ( s_timer_frequency.LowPart == 0 && s_timer_frequency.HighPart == 0 ) )
	{
		Errors::IssueError("No high resolution timer present in the system. Unable to setup high resolution timing system!");
	}
	
	// Begin running the timing thread:
	
	result = ResumeThread(s_timing_thread);
	
	// See if succeeded:
	
	if ( result == -1 )
	{
		Errors::IssueError("Could not begin timing thread. Unable to setup high resolution timing system!");
	}
	
	// In debug mode print the resolution of the timer:
	
	#ifdef USE_DEBUG_CONSOLE
	
		DebugConsole::Print("\nHigh Resolution timer initialized. Frequency is at ");
		DebugConsole::Print( (float(*((__int64*)(&s_timer_frequency)))) / 1000000.0f );
		DebugConsole::Print(" MHZ.");
	
	#endif
}

//=====================================================================================================================
/// @brief		Shuts down the high resolution timing system
/// @pre		The high resolution timer must be initialized
/// @remarks	This should be called in the main thread only
//=====================================================================================================================

void HighResolutionTimer::Shutdown()
{
	// Make sure we are initialized
	
	ENSURE( s_mutex != 0 && s_timing_thread != 0 && s_timing_thread_id != 0 , "The high resolution timer must be intialised" );
	
	// Signal the timing thread to end:
	
	s_run_timing_thread = false;
	
	// Release the time stamp mutex:
	
	ReleaseMutex(s_mutex);
	
	// Wait for the timing thread to finish:
	
	WaitForSingleObject(s_timing_thread,INFINITE);
	
	// Free up the thread:
	
	CloseHandle(s_timing_thread); s_timing_thread = 0; s_timing_thread_id = 0;
	
	// Free up the time stamp mutex:
	
	CloseHandle(s_mutex); s_mutex = 0;
	
	// Reset timing values:
	
	s_current_time_stamp.LowPart	= 0;
	s_current_time_stamp.HighPart	= 0;
	
	s_previous_time_stamp.LowPart	= 0;
	s_previous_time_stamp.HighPart	= 0;
}

//=====================================================================================================================
/// @brief		Updates the current time stamp and saves the previous stamp, thus allowing the time delta between calls 
///				to this function to be obtained. 
/// @remarks	Call GetTimeDelta() to calculate the difference in seconds between the current and last time values
//=====================================================================================================================

void HighResolutionTimer::Update()
{
	// Make sure we are initialized
	
	ENSURE( s_mutex != 0 && s_timing_thread != 0 && s_timing_thread_id != 0 , "The high resolution timer must be intialised" );
	
	// Save the current time stamp as the previous:
	
	s_previous_time_stamp = s_current_time_stamp;
	
	// Release the timing mutex: thus allowing the timing thread to update the current time stamp
	
	ReleaseMutex(s_mutex);
	
	// Reaccquire the timing mutex: preventing the timing thread from updating the current time stamp continously
	
	WaitForSingleObject( s_mutex , INFINITE );
	
	// Calculate the difference between the two time stamps:
	
	__int64 difference = (*((__int64*)(&s_current_time_stamp))) - (*((__int64*)(&s_previous_time_stamp)));
	
	// Convert to a high precision double:
	
	long double difference_d = (long double)(difference);
	
	// Now get the frequency of the timer:
	
	long double frequency_d = (long double)(*((__int64*)(&s_timer_frequency)));
	
	// Calculate the time elapsed:
	
	s_current_time_delta = float( difference_d / frequency_d );
}

//=====================================================================================================================
/// @brief		Updates both the current and previous time stamps with the current time stamp value, thus invalidating 
///				any time passed.
/// @pre		The high resolution timer must be initialized
/// @remarks	This function can be used where the application has been paused or minimized for a long period of time and 
///				whereby you want to ignore time passed whilst in the paused state.
//=====================================================================================================================

void HighResolutionTimer::Reset()
{
	// Make sure we are initialized
	
	ENSURE( s_mutex != 0 && s_timing_thread != 0 && s_timing_thread_id != 0 , "The high resolution timer must be intialised" );
	
	// Release the timing mutex: thus allowing the timing thread to update the current time stamp
	
	ReleaseMutex(s_mutex);
	
	// Reaccquire the timing mutex: preventing the timing thread from updating the current time stamp continously
	
	WaitForSingleObject( s_mutex , INFINITE );
	
	// Set the current time stamp as the previous time stamp to discount passed time:
	
	s_previous_time_stamp = s_current_time_stamp;
	
	// Zero the time delta:
	
	s_current_time_delta = 0;
}

//=====================================================================================================================
/// @brief		Obtains the time in seconds between the last two calls to Update(), or returns 0 if Reset() was 
///				called last. 
/// @pre		The high resolution timer must be initialized
/// @remarks	This function does not modify time stamps. You must call either Reset() or Update() to achieve this.
/// @post		The returned time delta can never be less than zero! Time cannot run backwards !!
//=====================================================================================================================

float HighResolutionTimer::GetTimeDelta()
{
	// Make sure we are initialized
	
	ENSURE( s_mutex != 0 && s_timing_thread != 0 && s_timing_thread_id != 0 , "The high resolution timer must be intialised" );
	
	// In debug mode make sure time does not run backwards:
	
	ENSURE( s_current_time_delta >= 0 , "The returned time delta can never be less than zero! Time cannot run backwards !!");

	// Return the delta:

	return s_current_time_delta;
}

//=====================================================================================================================
/// @brief		Main loop for the timing thread: continuosly updates the timing values
/// @param		user_data - custom data that is passed to the thread entry point. Unused here.
/// @returns	An error code indicating success or failure
//=====================================================================================================================

// Visual Studio: Don't show this warning:

#pragma warning(disable:4100)

DWORD WINAPI HighResolutionTimer::TimingThreadMain( void * user_data )
{
	// Wait for the mutex to become available:
	
	WaitForSingleObject( s_mutex , INFINITE );

	// Continue forever until the timing thread is told to stop
	
	while ( s_run_timing_thread )
	{
		// Update current time stamp counter:
		
		QueryPerformanceCounter(&s_current_time_stamp);			
		
		// Release the timing mutex:
		
		ReleaseMutex(s_mutex);
		
		// Wait for the mutex to become available:
		
		WaitForSingleObject( s_mutex , INFINITE );		
	}	
	
	// Release the timing mutex:
	
	ReleaseMutex(s_mutex);

	// Return zero for success:
	
	return 0;
}

// Visual Studio: Renable this warning:

#pragma warning(default:4100)
