#include "BeefySysLib/Common.h"
#include "BfObjects.h"
#include "Thread.h"
//#include "ThreadLocalStorage.h"
#include "StompAlloc.h"
//#include <crtdbg.h>

//#define USE_STOMP_ALLOC

#undef MemoryBarrier

using namespace bf::System;
using namespace bf::System::Threading;

#ifdef BF_THREAD_TLS
BF_TLS_DECLSPEC Thread* Thread::sCurrentThread;
#endif

static volatile int gLiveThreadCount;
static volatile int gBackgroundThreadCount;
static Beefy::SyncEvent gThreadsDoneEvent;

#ifdef BF_PLATFORM_WINDOWS
extern DWORD gBfTLSKey;
#else
extern pthread_key_t gBfTLSKey;
#endif

bf::System::Threading::Thread* BfGetCurrentThread()
{
#ifdef BF_THREAD_TLS
	return Thread::sCurrentThread;
#else
    Thread* internalThread = (Thread*)BfpTLS_GetValue(BfTLSManager::sInternalThreadKey);
	return internalThread;
#endif
}

void Thread::Suspend()
{
	BfpThread_Suspend(GetInternalThread()->mThreadHandle, NULL);
}

void Thread::Resume()
{
	BfpThread_Resume(GetInternalThread()->mThreadHandle, NULL);
}

void Thread::SetJoinOnDelete(bool joinOnDelete)
{
	auto internalThread = GetInternalThread();
	Beefy::AutoCrit autoCrit(internalThread->mCritSect);
	internalThread->mJoinOnDelete = joinOnDelete;
}

int Thread::GetPriorityNative()
{
	return (int)BfpThread_GetPriority(GetInternalThread()->mThreadHandle, NULL) + 2;
}

void Thread::SetPriorityNative(int priority)
{
	return BfpThread_SetPriority(GetInternalThread()->mThreadHandle, (BfpThreadPriority)(priority - 2), NULL);
}

bool Thread::GetIsAlive()
{
	if (GetInternalThread() == NULL)
		return false;
	bool success = BfpThread_WaitFor(GetInternalThread()->mThreadHandle, 0);
	return !success;
}

bool Thread::GetIsThreadPoolThread()
{
	return false;
}

bool Thread::JoinInternal(int millisecondsTimeout)
{
	auto internalThread = GetInternalThread();
	if (internalThread == NULL)
		return true;
	bool success = BfpThread_WaitFor(internalThread->mThreadHandle, millisecondsTimeout);
	return success;
}

void Thread::SleepInternal(int millisecondsTimeout)
{
	BfpThread_Sleep(millisecondsTimeout);
}

void Thread::SpinWaitInternal(int iterations)
{
	BF_COMPILER_FENCE();
	BF_SPINWAIT_NOP();
}

bool Thread::YieldInternal()
{
	return BfpThread_Yield();
}

Thread* Thread::GetCurrentThreadNative()
{
    return BfGetCurrentThread();
}

unsigned long Thread::GetProcessDefaultStackSize()
{
	return 0;
}


static void BF_CALLTYPE CStartProc(void* threadParam)
{
	Thread* thread = (Thread*)threadParam;
#ifdef BF_THREAD_TLS
	Thread::sCurrentThread = thread;
#else
    BfpTLS_SetValue(BfTLSManager::sInternalThreadKey, thread);
#endif

	auto internalThread = thread->GetInternalThread();

	// Hold lock until we get ThreadStarted callback
	internalThread->mCritSect.Lock();

	internalThread->mStartedEvent.Set(true);
	internalThread->mThreadHandle = BfpThread_GetCurrent();
	internalThread->mStackStart = (intptr)&thread;
	internalThread->ThreadStarted();

	bool isAutoDelete = gBfRtCallbacks.Thread_IsAutoDelete(thread);
	gBfRtCallbacks.Thread_ThreadProc(thread);
	
	if (internalThread->mIsBackground)
		BfpSystem_InterlockedExchangeAdd32((uint32*)&gBackgroundThreadCount, -1);

	bool isLastThread = (int32)BfpSystem_InterlockedExchangeAdd32((uint32*)&gLiveThreadCount, -1) <= gBackgroundThreadCount + 1;
		
    //printf("Stopping thread\n");

	bool wantsDelete = false;
	//
	{
		internalThread->ThreadStopped();

		Beefy::AutoCrit autoCrit(internalThread->mCritSect);
		if (isAutoDelete)
			gBfRtCallbacks.Thread_AutoDelete(thread);

		internalThread->mDone = true;

		if (internalThread->mThread == NULL)
		{
			// If the thread was already deleted then we need to delete ourselves now
			wantsDelete = true;
		}
	}

	if (wantsDelete)
		delete internalThread;

	if (isLastThread)
		gThreadsDoneEvent.Set(false);	

    //printf("Thread stopped\n");
}

void BfInternalThread::WaitForAllDone()
{
	if ((gBfRtFlags & BfRtFlags_NoThreadExitWait) != 0)
		return;

	while (gLiveThreadCount > gBackgroundThreadCount)
	{
		// Clear out any old done events
		gThreadsDoneEvent.WaitFor();
	}
}

BfInternalThread* Thread::SetupInternalThread()
{
	BfInternalThread* internalThread;
	internalThread = new BfInternalThread();
	SetInternalThread(internalThread);
	return internalThread;
}

void Thread::ManualThreadInit()
{
#ifdef BF_THREAD_TLS
	sCurrentThread = this;
#else
	BfpTLS_SetValue(BfTLSManager::sInternalThreadKey, this);
#endif

	BfInternalThread* internalThread = SetupInternalThread();
	internalThread->ManualThreadInit(this);
}

void Thread::StartInternal()
{
	BfpSystem_InterlockedExchangeAdd32((uint32*)&gLiveThreadCount, 1);

	BfInternalThread* internalThread = SetupInternalThread();

	Beefy::AutoCrit autoCrit(internalThread->mCritSect);
	internalThread->mStarted = true;
	internalThread->mThread = this;
#ifdef _WIN32
	internalThread->mThreadHandle = BfpThread_Create(CStartProc, (void*)this, GetMaxStackSize(), (BfpThreadCreateFlags)(BfpThreadCreateFlag_StackSizeReserve | BfpThreadCreateFlag_Suspended), &internalThread->mThreadId);
	SetInternalThread(internalThread);
	BfpThread_Resume(internalThread->mThreadHandle, NULL);
#else
	internalThread->mThreadHandle = BfpThread_Create(CStartProc, (void*)this, GetMaxStackSize(), (BfpThreadCreateFlags)(BfpThreadCreateFlag_StackSizeReserve), &internalThread->mThreadId);
	SetInternalThread(internalThread);
#endif
}

void Thread::RequestExitNotify()
{
	// Do we already have implicit exiting notification?
	if (BfGetCurrentThread() != NULL)
		return;

#ifdef BF_PLATFORM_WINDOWS
	FlsSetValue(gBfTLSKey, (void*)&gBfRtCallbacks);
#else
	pthread_setspecific(gBfTLSKey, (void*)&gBfRtCallbacks);
#endif
}

void Thread::ThreadStarted()
{
	auto internalThread = GetInternalThread();
	internalThread->mCritSect.Unlock();
}

intptr Thread::GetThreadId()
{
	return GetInternalThread()->mThreadId;
}

void Thread::SetStackStart(void* ptr)
{
	GetInternalThread()->mRunning = true;
	GetInternalThread()->mStackStart = (intptr)ptr;
}

void Thread::InternalFinalize()
{
	auto internalThread = GetInternalThread();
	if (internalThread == NULL)
		return;

	bool wantsJoin = false;

	bool started = false;
	//
	{
		Beefy::AutoCrit autoCrit(internalThread->mCritSect);
		started = internalThread->mStarted;
	}

	if (started)
		internalThread->mStartedEvent.WaitFor();

	//
	{
		Beefy::AutoCrit autoCrit(internalThread->mCritSect);
		if ((!internalThread->mDone) && (internalThread->mJoinOnDelete))
		{
			if (this != BfGetCurrentThread())
			{
				wantsJoin = true;
			}
		}
	}

	if (wantsJoin)
		JoinInternal(0);

	bool wantsDelete = false;
	//
	{
		Beefy::AutoCrit autoCrit(internalThread->mCritSect);

		if (!internalThread->mDone)
		{
			// We need to let the internal thread delete itself when it's done...
			internalThread->mThread = NULL;
		}
		else
		{
			wantsDelete = true;
		}
		SetInternalThread(NULL);
	}

	if (internalThread->mIsManualInit)
		wantsDelete = true;

	if (wantsDelete)
		delete internalThread;
}

bool Thread::IsBackgroundNative()
{
	auto internalThread = GetInternalThread();
	if (internalThread == NULL)
		return false;
	return internalThread->mIsBackground;
}

void Thread::SetBackgroundNative(bool isBackground)
{
	auto internalThread = GetInternalThread();
	if (internalThread == NULL)
		return;

	if (isBackground != internalThread->mIsBackground)
	{
		internalThread->mIsBackground = isBackground;		
		BfpSystem_InterlockedExchangeAdd32((uint32*)&gBackgroundThreadCount, isBackground ? 1 : -1);
	}	
}

int Thread::GetThreadStateNative()
{
	return 0;
}

void Thread::InformThreadNameChange(String* name)
{
	Beefy::String nameStr;
	if (name != NULL)
		nameStr = name->ToStringView();
	BfpThread_SetName(GetInternalThread()->mThreadHandle, nameStr.c_str(), NULL);
}

void Thread::MemoryBarrier()
{
	BF_FULL_MEMORY_FENCE();
}
