//////////////////////////////////////////////////////////////////////////////
//
// Asura_Platform_Multithread.cpp
//
// Copyright (c) 2006 Rebellion. All rights reserved.
//
// Description:
//
//     PC-specific code for multithreading code
//
//////////////////////////////////////////////////////////////////////////////

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Core/Asura.h"

// This file's header.
#include "Core/Asura_Multithread.h"

// Asura independent
#include "Debugging/Asura_Assert.h"
#include "Debugging/Asura_CPUProfile.h"

#include <process.h>
#include <windows.h>

u_int Asura_Thread_System::s_auStackSizes[]  =	{ 32 * 1024		//ASURA_MULTITHREAD_STACK_SIZE_SMALL
												, 128 * 1024	//ASURA_MULTITHREAD_STACK_SIZE_MEDIUM
												, 0 };			//ASURA_MULTITHREAD_STACK_SIZE_LARGE


// C L A S S   D E C L A R A T I O N S ///////////////////////////////////////
class Asura_PC_ThreadParams
{
public:

	Asura_PC_ThreadParams( Asura_SyncEvent* pxSyncEvent, Asura_ThreadFunc pfnThreadFunc, const void* pUserData, const char* pszThreadName)
		: m_pxSyncEvent( pxSyncEvent )
		, m_pfnThreadFunc (pfnThreadFunc)
		, m_pUserData (pUserData)
		, m_pszThreadName ( pszThreadName)
	{
	}

	// data
	Asura_SyncEvent* m_pxSyncEvent;		// to keep a local variable in scope while the thread is starting up.
	Asura_ThreadFunc m_pfnThreadFunc;
	const void* m_pUserData;
	const char* m_pszThreadName;
};

// Bizarrely, this stuff isn't defined in the Platform SDK headers
struct Asura_PC_ThreadInfo
{
	DWORD  m_uType; // Must be 0x1000
	LPCSTR m_szName;
	DWORD  m_uThreadID; // -1 for caller thread
	DWORD  m_uFlags;
};

// F U N C T I O N S /////////////////////////////////////////////////////////

// Set the thread name in the debugger. See MSDN index entry 'SetThreadName function'
void Asura_PC_SetThreadName( const char* const szThreadName,
	const DWORD uThreadID /*= static_cast< DWORD >(-1)*/ )
{
	Asura_PC_ThreadInfo xInfo;
	xInfo.m_uType = 0x1000;
	xInfo.m_szName = szThreadName;
	xInfo.m_uThreadID = uThreadID;
	xInfo.m_uFlags = 0;

	__try
	{
		const DWORD uNumArgs = sizeof( xInfo ) / sizeof( DWORD );
		const ULONG_PTR* const puPtr = reinterpret_cast< const ULONG_PTR*>( &xInfo );

		RaiseException( 0x406D1388, 0, uNumArgs, puPtr );
	}
	__except( EXCEPTION_CONTINUE_EXECUTION )
	{
	}
}

// S T A T I C   F U N C T I O N S ///////////////////////////////////////////
static u_int WINAPI Asura_PC_ThreadStartup( void* pParam )
{
	const Asura_PC_ThreadParams* pxParams = reinterpret_cast<const Asura_PC_ThreadParams*>(pParam);
	
	ASURA_CPU_PROFILE__START_THREAD( pxParams->m_pszThreadName);

	Asura_PC_SetThreadName( pxParams->m_pszThreadName );

	Asura_ThreadFunc pfnThreadFunc = pxParams->m_pfnThreadFunc;
	const void* pUserData = pxParams->m_pUserData;
	// now we've got copies of the thread startup data, signal the event
	pxParams->m_pxSyncEvent->SetSignal();
	pfnThreadFunc(pUserData);
	
	ASURA_CPU_PROFILE__STOP_THREAD();
	
	return 0;
}

// C L A S S   I M P L E M E N T A T I O N ///////////////////////////////////
u_int Asura_Thread_System::Platform_GetNumHardwareThreads()
{
    SYSTEM_INFO xSystemInfo;
    GetSystemInfo( &xSystemInfo );
    // Return the number of execution pipelines available to the system.
    return static_cast< u_int >( xSystemInfo.dwNumberOfProcessors );
}

bool Asura_Thread_System::Platform_CreateThread( const char* szThreadName, Asura_ThreadFunc pfnThreadFunc, const void* pUserData, const ASURA_MULTITHREAD_STACK_SIZE eStackSize )
{
	Asura_Assert(eStackSize != ASURA_MULTITHREAD_STACK_NUM_SIZES, "Invalid stack size enumeration passed to Platform_CreateThread");
	
	Asura_SyncEvent xSyncEvent("thread creation", false);

	Asura_PC_ThreadParams xParams(&xSyncEvent, pfnThreadFunc, pUserData, szThreadName);

	u_int uThreadID;
	uintptr_t pThreadHandle =	_beginthreadex
							(
								NULL,							// void *security,
								s_auStackSizes[eStackSize],		// unsigned stack_size,
								Asura_PC_ThreadStartup,			// unsigned ( __stdcall *start_address )( void * ),
								&xParams,						// void *arglist,
								0,								// unsigned initflag,
								&uThreadID						// unsigned *thrdaddr 
							);

	xSyncEvent.WaitForSignal();

	Asura_Assert(pThreadHandle != NULL, "CreateThread failed");
	const bool bReturn = (pThreadHandle != NULL);
	
	// MGH 21/4/2008: We aren't going to use the handle further, so we can close it immediately.
	// We need to do this in order to avoid a memory leak.
	if (pThreadHandle != NULL)
	{
		CloseHandle(reinterpret_cast<HANDLE>(pThreadHandle));
	}

	return bReturn;
}

void Asura_Thread_System::Platform_YieldCurrentThread()
{
	//Sleep(0);
	SwitchToThread();
}

void Asura_Thread_System::Platform_DelayCurrentThread(float fTimeInSeconds, bool bAllowCallbacks)
{
	Sleep( static_cast< DWORD >( fTimeInSeconds * 1000.0f ) );
}

Asura_ThreadID Asura_Thread_System::Platform_GetCurrentThreadID()
{
	return GetCurrentThreadId();
}

Asura_ThreadID Asura_Thread_System::Platform_GetInvalidThreadID()
{
	return 0xffffffff;
}

void Asura_Mutex::Platform_Lock()
{
	EnterCriticalSection( &m_xCriticalSection );
}

void Asura_Mutex::Platform_Unlock()
{
	LeaveCriticalSection( &m_xCriticalSection );
}


void Asura_SyncEvent::Platform_WaitForSignal()
{
#ifdef ASURA_ASSERT
	DWORD uRet = 
#endif
	WaitForSingleObject( m_xEventHandle, INFINITE );
	Asura_Assert(uRet == WAIT_OBJECT_0, "unexpected return value from WaitforSingleObject");
}

void Asura_SyncEvent::Platform_SetSignal()
{
#ifdef ASURA_ASSERT
	int iRet = 
#endif
	SetEvent( m_xEventHandle );
	Asura_Assert(iRet != 0, "error in SetEvent()");

}

void Asura_SyncEvent::Platform_ClearSignal()
{
#ifdef ASURA_ASSERT
	int iRet = 
#endif
	ResetEvent( m_xEventHandle );
	Asura_Assert(iRet != 0, "error in ResetEvent()");
}


//////////////////////////////////////////////////////////////////////////
// Semaphore
//////////////////////////////////////////////////////////////////////////
void Asura_Semaphore::Platform_Wait()
{
#if defined ASURA_ASSERT
	const DWORD uRet = 
#endif
		WaitForSingleObject( m_xSemaphoreHandle, INFINITE );

	Asura_Assert( uRet == WAIT_OBJECT_0, "Unexpected return value from WaitforSingleObject()." );
}

bool Asura_Semaphore::Platform_TryWait()
{
	const DWORD uRet = WaitForSingleObject( m_xSemaphoreHandle, 0 );

	if (uRet == WAIT_OBJECT_0)
	{
		return true;
	}

	Asura_Assert( uRet == WAIT_TIMEOUT, "Unexpected return value from WaitforSingleObject()." );
	return false;
}

bool Asura_Semaphore::Platform_Signal()
{
	const DWORD uRet = ReleaseSemaphore( m_xSemaphoreHandle, 1, 0 );

	Asura_Assert( uRet != 0, "Unexpected return value from ReleaseSemaphore()." );
	return (uRet != 0);
}

// EOF //////////////////////////////////////////////////////////////////////////
