#include <ZUtil/ZThread.hpp>
#include <ZUtil/ZLog.hpp>
#include <ZSTL/ZString.hpp>

#ifdef _MSC_VER //begin Microsoft Visual C++ specific code
#include <windows.h>
#undef CreateMutex
#undef CreateEvent
const DWORD MS_VC_EXCEPTION=0x406D1388;

#pragma pack(push,8)
typedef struct tagTHREADNAME_INFO
{
   DWORD dwType;		// Must be 0x1000.
   LPCSTR szName;		// Pointer to name (in user addr space).
   DWORD dwThreadID;	// Thread ID (-1=caller thread).
   DWORD dwFlags;		// Reserved for future use, must be zero.
} THREADNAME_INFO;
#pragma pack(pop)

//This disgusting little hack sets the thread's name in the MSVC debugger
void NativeSetThreadName(const char* threadName)
{
   THREADNAME_INFO info;
   info.dwType = 0x1000;
   info.szName = threadName;
   info.dwThreadID = (DWORD)~0;
   info.dwFlags = 0;

   __try
   {
      RaiseException( MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info );
   }
   __except(EXCEPTION_EXECUTE_HANDLER)
   {
   }
}

#else
#define NativeSetThreadName(name)
#endif  //end Microsoft Visual C++ specific code

/*************************************************************************/

ZThread::ZThread(ZString _threadName)
:
  ThreadName(_threadName),
  bShouldShutdown(false), bShouldPause(false),
  ThreadContext(),
  ThreadLock(),
  InitEvent(),
  PreviousTickCount(0), CurrentTickCount(0),
  bIsRunning(false), bIsInitialized(false)
{

}

/*************************************************************************/

ZThread::~ZThread()
{
	ShutdownThread();
}

/*************************************************************************/

int ZThread::InitThread(void *_runnable)
{
	ZThread *thr = ((ZThread*)_runnable);

	NativeSetThreadName(thr->GetThreadName().Data());

	//TODO - hack for now, need to fix CreateThread
	thr->ThreadContext.ThreadId = ZConcurrency::GetThreadId();

	thr->initThread();

	thr->bIsInitialized = true;

	thr->InitEvent.Notify();

	thr->Run();

	return 0;
}

/*************************************************************************/

void ZThread::Run()
{
	uint64_t dt;

	bIsRunning = true;

	while (!bShouldShutdown)
	{
		while (bShouldPause)
			ZConcurrency::YieldThread();

		ExecuteThreadRequests();

		PreviousTickCount = CurrentTickCount;
		CurrentTickCount = ZConcurrency::GetTicks();
		dt = CurrentTickCount - PreviousTickCount;

		switch (run(dt))
		{
		case ZTR_LOOP:										continue;
		case ZTR_PAUSE:			bShouldPause = true;		break;
		case ZTR_TERMINATE:		bShouldShutdown = true;		break;
		default:				bShouldShutdown = true;		break;
		}
	}

	ExecuteThreadRequests();

	shutdownThread();

	bIsInitialized = false;
	bIsRunning = false;
}

/*************************************************************************/

void ZThread::AddThreadRequest(ZPtr<ZThreadRequest> _request, bool _wait)
{

	//If this is thread adding a request to itself, don't deadlock
	if(IsCallerTheThread()) {
		_request->Execute(this);
		return;
	}

	ThreadLock.Acquire();
	{
		ZTRCallbacks.PushBack(_request);
	}
	ThreadLock.Release();

	if (_wait)
		_request->Wait();
}

/*************************************************************************/

void ZThread::AddCallbackRequest(ZThreadCallbackFunc function, void* arg, bool async)
{
	ZThreadCallbackRequest req;

	//If this is the thread adding a callback to itself, don't deadlock
	if(IsCallerTheThread()) {
		function(this, arg);
		return;
	}

	req.arg = arg;
	req.callback = function;
	req.doneEvent = (async ? NULL : SST_Concurrency_CreateEvent());

	ThreadLock.Acquire();
	{
		Callbacks.PushBack(req);
	}
	ThreadLock.Release();

	//Non-async -> wait for done
	if(!async) {
		SST_Concurrency_WaitEvent(req.doneEvent, SST_WAIT_INFINITE);
		SST_Concurrency_DestroyEvent(req.doneEvent);
	}
}

/*************************************************************************/

void ZThread::ExecuteThreadRequests()
{
	ZList< ZPtr<ZThreadRequest> > persistentRequests;

	ThreadLock.Acquire();

	while (!ZTRCallbacks.Empty())
	{
		//Don't execute if we are told to stop
		if (ZTRCallbacks.Front()->Stop)
		{
			ZTRCallbacks.Front()->Persist = false;
		}
		else
		{
			ZTRCallbacks.Front()->CompletedEvent.Reset();

			ZTRCallbacks.Front()->Execute(this);

			ZTRCallbacks.Front()->CompletedEvent.Notify();
		}

		//If we are to persist, do so
		if (ZTRCallbacks.Front()->Persist)
			persistentRequests.PushBack(ZTRCallbacks.PopFront());
		else
			ZTRCallbacks.PopFront();
	}

	while(!Callbacks.Empty()) {
		ZThreadCallbackRequest& req = Callbacks.Front();

		req.callback(this, req.arg);
		if(req.doneEvent) {
			SST_Concurrency_SignalEvent(req.doneEvent);
		}

		Callbacks.PopFront();
	}


	//Swap back in persistent requests
	ZTRCallbacks.Swap(persistentRequests);

	ThreadLock.Release();
}

/*************************************************************************/

bool ZThread::StartThread()
{
	bShouldShutdown = false;
	bShouldPause = false;

	ThreadContext = ZConcurrency::CreateThread(ZThread::InitThread, this);

	if (!ThreadContext.IsValid())
		return false;

	return true;
}

/*************************************************************************/

void ZThread::ShutdownThread()
{
	bShouldShutdown = true;

	if (ThreadContext.IsValid())
	{
		ZConcurrency::WaitThread(ThreadContext);
		ZConcurrency::DestroyThread(ThreadContext);
		ThreadContext.Invalidate();
	}
}

/*************************************************************************/

void ZThread::PauseThread()
{
	bShouldPause = true;
}

/*************************************************************************/

void ZThread::RestartThread()
{
	bShouldPause = false;
}

/*************************************************************************/

bool ZThread::ThreadRunning()
{
	return bIsRunning;
}

/*************************************************************************/

bool ZThread::ThreadInitialized()
{
	return bIsInitialized;
}

/*************************************************************************/

uint32_t ZThread::GetThreadId()
{
	return ThreadContext.ThreadId;
}

/*************************************************************************/

ZString ZThread::GetThreadName()
{
	ZString name;

	ThreadLock.Acquire();

	name = ThreadName;

	ThreadLock.Release();

	return name;
}

/*************************************************************************/

void ZThread::WaitInitialized()
{
	InitEvent.Wait();
}

/*************************************************************************/

void ZThread::WaitShutdown()
{
	if (ThreadContext.IsValid())
		ZConcurrency::WaitThread(ThreadContext);
}

/*************************************************************************/
bool ZThread::IsCallerTheThread()
{
	uint32_t callerThreadID = SST_Concurrency_GetThreadId();

	return (callerThreadID == this->GetThreadId());
}