#include "deThread_priv.hpp"

#ifdef DESTINY3D_PLATFORM_WIN32

deThreadLock::deThreadLock(u32 spinCount)
:	mLockEvent(0),
	mThreadId(0),
	mCount(-1),
	mDepth(0),
	mSpinCount(spinCount)
{
	mLockEvent = CreateEvent(0, FALSE, FALSE, 0); // auto-reset, unsignaled
	if (!mLockEvent)
	{
		DE_ASSERT(mLockEvent != NULL);
		throw;
	}
}

deThreadLock::~deThreadLock()
{
	if (mLockEvent)
		CloseHandle(mLockEvent);
}

// Locks this object.
// If another thread has this object locked already, then
//   this thread blocks until the other thread relinquishes its lock.
// If this thread has already locked this object, an internal counter is incremented
//   
void deThreadLock::Lock()
{
	LONG threadID = GetCurrentThreadId();
	if(InterlockedIncrement(&mCount) > 0)
	{
		// something has the lock already...
		if (InterlockedCompareExchange(&mThreadId, threadID, threadID) == threadID)
		{
			// ... and it was us, let's remember how many additional times we lock.
			mDepth += 1;
			return;
		}
		else
		{
			// ... and it wasn't us, so we'll have to wait our turn.
			if (mSpinCount > 0)
			{
				// Another thread on a different CPU might relinquish control at any moment.
				// We want to be ready for it if that happens.
				// Spinning for a short while is faster than kernel blocking if the odds of
				//   getting the lock during the spin are good.
				for (int x = mSpinCount; x > 0; --x)
				{
					if (InterlockedCompareExchange(&mCount, 0, 0) == 0)
					{
						// No other CPUs have any locks anymore,
						// We still need to clear that signal though...
						if (WAIT_OBJECT_0 == WaitForSingleObject(mLockEvent, 0))
							break;
					}
				}
			}
			WaitForSingleObject(mLockEvent, INFINITE);
			// Now we've been signaled, and have unblocked from the kernel.
		}
	}
	// We now have the lock, so remember the calling thread and continue.
	InterlockedExchange(&mThreadId, threadID);
}

// Similar to Lock(), except it will spin or return immediately
deBoolean deThreadLock::TryLock(u32 waitTimeInMs)
{
	LONG threadID = GetCurrentThreadId();
	if(InterlockedIncrement(&mCount) == 0)
	{
		// nothing had this locked, we just got it. Remember the calling thread and continue.
		InterlockedExchange(&mThreadId, threadID);
		return deTRUE;
	}
	else
	{
		// something has the lock already...
		if (InterlockedCompareExchange(&mThreadId, threadID, threadID) == threadID)
		{
			// ... and it was us, let's remember how many additional times we lock.
			mDepth += 1;
			return deTRUE;
		}
		else
		{
			DWORD waitResult = WaitForSingleObject(mLockEvent, waitTimeInMs);
			if(waitResult == WAIT_TIMEOUT)
			{
				// forget our attempt to lock
				InterlockedDecrement(&mCount);
				return deFALSE;
			}
			else // WAIT_OBJECT_0 or WAIT_ABANDONED
			{
				// we got the lock!
				InterlockedExchange(&mThreadId, threadID);
				return deTRUE;
			}
		}
	}
}

void deThreadLock::Unlock()
{
	LONG threadID = GetCurrentThreadId();
	if(InterlockedCompareExchange(&mThreadId, threadID, threadID) == threadID)
	{
		// We're operating in the same thread as the one that got the initial lock.
		if(InterlockedDecrement(&mCount) >= 0)
		{
			// Something else has requested a lock...
			if (mDepth > 0)
				mDepth -= 1; // it was just us, remove one level of recursion.
			else
			{
				InterlockedExchange(&mThreadId, 0);
				SetEvent(mLockEvent); // it was something else, signal another thread to wake up.
			}
		}
		// we have now released our lock and can continue execution
	}
	else
		DEBUGRETURNNOVAL("deThreadLock::Unlock() - called from different thread than Lock()");
}

long deThreadLock::Release()
{
	// Ensure that nothing else is using this object when it goes away
	Lock();
	// Destroy!
	delete this;
	return 0;
}

DETHREAD_API IdeThreadLock* IdeThreadLock_CreateThreadLock(u32 spinCount)
{
	IdeThreadLock* lock = NULL;
	try
	{
		static int numCPUs = 0;
		if (numCPUs == 0)
		{
			SYSTEM_INFO sysInfo;
			GetSystemInfo(&sysInfo);
			numCPUs = sysInfo.dwNumberOfProcessors;
		}
		u32 realSpinCount = (numCPUs > 1) ? spinCount : 0;
		lock = new deThreadLock(realSpinCount);
	}
	catch(...)
	{
		// We know this constructor can throw, if it fails to create the event.
		return NULL;
	}
	return lock;
}

#elif defined(DESTINY3D_PLATFORM_LINUX)
#error linux threading system not yet implemented
#else
#error unsupported platform configuration
#endif
