//
// Asura_Platform_Multithread.cpp
//
// Created by KOR - 09 October 2006
//
// Copyright (C) 2006 Rebellion, All Rights Reserved.
//

// Wii-specific code for multithreading code

#include "Core/Asura.h"

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

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

const u_int uASURA_THREAD_WII_STACK_SIZE = 48 * 1024; // 48K, going by what the PSP currently uses.

static OSThread g_xThread;
static char g_acStack[uASURA_THREAD_WII_STACK_SIZE];
static Asura_SyncEvent g_xThreadSyncEvent("ThreadRunning", false);

//TODO: Initialise this array and use it for the stack size
u_int Asura_Thread_System::s_auStackSizes[]  =	{ 0		//ASURA_MULTITHREAD_STACK_SIZE_SMALL
												, 0		//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_Wii_ThreadParams
{
public:

	Asura_Wii_ThreadParams( Asura_SyncEvent* pxSyncEvent, Asura_ThreadFunc pfnThreadFunc, const void* pUserData )
		: m_pxSyncEvent( pxSyncEvent )
		, m_pfnThreadFunc (pfnThreadFunc)
		, m_pUserData (pUserData)
	{
	}

	// 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;
};

// S T A T I C   F U N C T I O N S ///////////////////////////////////////////
static void* Asura_Wii_ThreadStartup( void* pParam )
{
	const Asura_Wii_ThreadParams* pxParams = reinterpret_cast<const Asura_Wii_ThreadParams*>(pParam);
	const 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);
	g_xThreadSyncEvent.ClearSignal();
	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()
{
    return 1;
}

bool Asura_Thread_System::Platform_CreateThread( const char* szThreadName, Asura_ThreadFunc pfnThreadFunc, const void* pUserData, const ASURA_MULTITHREAD_STACK_SIZE eStackSize )
{
	if(g_xThreadSyncEvent.IsSignalled())
	{
		Asura_Assert(0, "We only support one extra (non-main) thread on Wii.");
		return false;
	}
	g_xThreadSyncEvent.SetSignal();

	Asura_SyncEvent xSyncEvent("thread creation", false);
	Asura_Wii_ThreadParams xParams(&xSyncEvent, pfnThreadFunc, pUserData);

	char* pcStackEnd = g_acStack + uASURA_THREAD_WII_STACK_SIZE;

	const bool bCreatedThread =
		OSCreateThread(
			&g_xThread,								// Pointer to thread control block to initialize.
			Asura_Wii_ThreadStartup,				// Pointer to  function from which to start execution.
			&xParams,								// Arguments to be passed to the start function.
			reinterpret_cast<void*>(pcStackEnd),	// Address of initial stack pointer. Note that stacks grow DOWN, so this should be the higher address of the stack
			uASURA_THREAD_WII_STACK_SIZE,			// Size of the stack in bytes.  This is used for sanity checking
			16,										// Base scheduling priority of thread.  0 is the highest priority, 31 is the lowest. The default thread calling the main() function (created by OSInit()) has a priority of 16.
			OS_THREAD_ATTR_DETACH					// OR-ed values of attributes.
			);

	if(bCreatedThread)
	{
		const int iPrevSuspendCount = OSResumeThread(&g_xThread);
		Asura_Assertf(iPrevSuspendCount == 1, ("Expected thread to be resumed on this call. Previous suspend count: %i", iPrevSuspendCount));

		xSyncEvent.WaitForSignal();

		return (iPrevSuspendCount <= 1);
	}
	return false;
}

void Asura_Thread_System::Platform_YieldCurrentThread()
{
	OSYieldThread();
}

Asura_ThreadID Asura_Thread_System::Platform_GetCurrentThreadID()
{
	// KOR - 12/09/2006 - Wii simply references threads by pointer.
	// Therefore, cast pointer to int to get a unique ID. I don't think
	// the thread data will ever get relocated, so this should suffice.
	const OSThread* pxThread = OSGetCurrentThread();
	return reinterpret_cast<int>(pxThread);
}

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

void Asura_Mutex::Platform_Lock()
{
	OSLockMutex(&m_xMutex);
}

void Asura_Mutex::Platform_Unlock()
{
	OSUnlockMutex(&m_xMutex);
}


void Asura_SyncEvent::Platform_WaitForSignal()
{
	OSLockMutex(&m_xMutex);
	while(!m_bSignalledOS)
	{
		OSWaitCond(&m_xCondition, &m_xMutex);
	}
	OSUnlockMutex(&m_xMutex);
}

void Asura_SyncEvent::Platform_SetSignal()
{
	OSLockMutex(&m_xMutex);
	m_bSignalledOS = true;
	OSUnlockMutex(&m_xMutex);
	OSSignalCond(&m_xCondition);
}

void Asura_SyncEvent::Platform_ClearSignal()
{
	OSLockMutex(&m_xMutex);
	m_bSignalledOS = false;
	OSUnlockMutex(&m_xMutex);
}

//////////////////////////////////////////////////////////////////////////
// Semaphore
//////////////////////////////////////////////////////////////////////////
void Asura_Semaphore::Platform_Wait()
{
	OSWaitSemaphore(&m_xSemaphore);
}

bool Asura_Semaphore::Platform_TryWait()
{
	return (OSTryWaitSemaphore(&m_xSemaphore) > 0);
}

bool Asura_Semaphore::Platform_Signal()
{
	OSSignalSemaphore(&m_xSemaphore);
	return true;
}
