///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
///////////////////////////////////////////////////////////////////////////////

#include "TestThread.h"
#include <EATest/EATest.h>
#include <EAStdC/EAStopwatch.h>
#include <EAStdC/EASprintf.h>
#include <EAStdC/EAString.h>
#include <EASTL/set.h>
#include <eathread/eathread_thread.h>
#include <eathread/eathread_sync.h>
#include <eathread/eathread_condition.h>

#ifdef EA_PLATFORM_LINUX
	#include <unistd.h>
#endif

#ifdef EA_PLATFORM_MICROSOFT
	EA_DISABLE_ALL_VC_WARNINGS()
	#include <Windows.h>
	EA_RESTORE_ALL_VC_WARNINGS()
#endif

#include <atomic>

using namespace EA::Thread;


const int           kMaxConcurrentThreadCount = EATHREAD_MAX_CONCURRENT_THREAD_COUNT;
static unsigned int sThreadTestTimeMS = 2000; // We potentially change this value below.
static AtomicInt32  sThreadCount = 0;
static AtomicInt32  sShouldGo = 0;


static intptr_t TestFunction1(void*)
{
	ThreadTime nTimeEnd = EA::Thread::GetThreadTime() + sThreadTestTimeMS;

	while(EA::Thread::GetThreadTime() < nTimeEnd)
		ThreadSleep();
	return 0;
}


static intptr_t TestFunction3(void*)
{
	// This following code should produce NULL pointer access violation exception
	// EA::UnitTest::ReportVerbosity(1, "Throw NULL pointer Exception.\n");
	// char* pTest = NULL;
	// *pTest = 1;
	return 0;
}


static intptr_t TestFunction4(void* arg)
{
	const intptr_t returnValue = (intptr_t)arg;

	EA::UnitTest::ThreadSleepRandom(0, 5);

	return returnValue;
}


#if !defined(EA_PLATFORM_MOBILE)
	static intptr_t TestFunction6(void* arg)
	{
		const intptr_t returnValue = (intptr_t)arg;

		sThreadCount++;

		while(sShouldGo == 0)
			ThreadSleep(10);

		EA::UnitTest::ThreadSleepRandom(3, 8);

		sThreadCount--;

		return returnValue;
	}
#endif


static intptr_t TestFunction7(void*)
{
	while(sShouldGo == 0)
		ThreadSleep(10);

	return 0;
}


static intptr_t TestFunction8(void*)
{
	while(sShouldGo == 0)
		ThreadSleep(10);

	return 0;
}

static intptr_t TestFunction9(void* arg)
{
	return (intptr_t)GetThreadProcessor();
}

static intptr_t TestFunction10(void* arg)
{
	ThreadSleep(10);
	return TestFunction9(arg);
}

static intptr_t TestFunction12(void* arg)
{    
	ThreadSleep(10);    
	return TestFunction9(arg);
}

static intptr_t TestFunction11(void* arg)
{
	int requestedCore = *(static_cast<int*>(arg));
	int coreIndex = (requestedCore % EA::Thread::GetProcessorCount());

	SetThreadAffinityMask(UINT64_C(1) << coreIndex);  // set the highest processor available.

	for (int retryCount = 100; retryCount && (GetThreadProcessor() != coreIndex); retryCount--)
		ThreadSleep(1);

	return (intptr_t)GetThreadProcessor();
}

static intptr_t TestFunction13(void* arg)
{    
	ThreadSleep(10);    
	ThreadEnd(42);  // 42 is a magic number we will verify gets passed through the user.
	return 0; 
}

static intptr_t TestFunction3ExceptionWrapper(RunnableFunction defaultRunnableFunction, void* pContext)
{
	return defaultRunnableFunction(pContext);
}


class TestRunnable1 : public IRunnable
{
	intptr_t Run(void*)
	{
		const ThreadTime nTimeEnd = EA::Thread::GetThreadTime() + sThreadTestTimeMS;
		while (EA::Thread::GetThreadTime() < nTimeEnd)
			ThreadSleep();
		return 0;
	}
} gTestRunnable1;


class TestRunnable2 : public IRunnable
{
	intptr_t Run(void*)
	{
		const ThreadTime nTimeEnd = EA::Thread::GetThreadTime() + sThreadTestTimeMS;

		while(EA::Thread::GetThreadTime() < nTimeEnd)
		{
			ThreadSleep();
		}
		return 0;
	}
} gTestRunnable2;

static intptr_t TestRunnable3ExceptionWrapper(IRunnable* defaultRunnableFunction, void* pContext)
{
	return defaultRunnableFunction->Run(pContext);
}


class TestRunnable3 : public IRunnable
{
	intptr_t Run(void*)
	{
		// This following code should produce NULL pointer access violation exception
		// EA::UnitTest::ReportVerbosity(1, "Throw NULL pointer Exception.\n");
		// char* pTest = NULL;
		// *pTest = 1;
		ThreadSleep(500);
		return 0;
	}
} gTestRunnable3;

class TestRunnable4 : public IRunnable
{
	intptr_t Run(void*)
	{
		// IRunnable object that returns the thread id that executed on.
		return TestFunction9(NULL); 
	}
} gTestRunnable4;


int TestThreadAffinityMask()
{
	int nErrorCount = 0;
	const int MAX_ITERATIONS = 16;
	const int nAvailableProcessors = EA::Thread::GetProcessorCount();

	auto VERIFY_AFFINITY_RESULT = [&](intptr_t in_result, int count)
	{
#if EATHREAD_THREAD_AFFINITY_MASK_SUPPORTED
		auto result = static_cast<int>(in_result);
		EATEST_VERIFY_F(result == (count % nAvailableProcessors),
						"Thread failure: SetAffinityMask not working properly. Thread ran on: %d/%d <=> Expected: %d\n",
						result, nAvailableProcessors, (count % nAvailableProcessors));
#endif
	};

	int count = MAX_ITERATIONS;
	while(--count)
	{
		// Test Thread Affinity Masks (thread parameters) 
		Thread     thread;            
		ThreadParameters params;

		params.mnAffinityMask = INT64_C(1) << (count % nAvailableProcessors);
		params.mnProcessor = kProcessorAny;
		thread.Begin(TestFunction10, NULL, &params);

		intptr_t result = 0;
		thread.WaitForEnd(GetThreadTime() + 30000, &result);

		VERIFY_AFFINITY_RESULT(result, count);
	}

	count = MAX_ITERATIONS;    
	while(--count)
	{
		// Test Thread Affinity Masks (thread object)
		ThreadParameters params;
		params.mnProcessor = kProcessorAny;

		Thread     thread;            
		thread.Begin(TestFunction12, NULL, &params);  // sleeps then grabs the current thread id. 
		thread.SetAffinityMask(INT64_C(1) << (count % nAvailableProcessors));

		intptr_t   result = 0;
		thread.WaitForEnd(GetThreadTime() + 30000, &result);

		VERIFY_AFFINITY_RESULT(result, count);
	}    
	
	count = MAX_ITERATIONS;
	while(--count)
	{
		// Test Thread Affinity Masks (global functions)       
		ThreadParameters params;
		params.mnProcessor = kProcessorAny;

		Thread     thread;            
		thread.Begin(TestFunction11, &count, &params);  

		intptr_t   result = 0;
		thread.WaitForEnd(GetThreadTime() + 30000, &result);

		VERIFY_AFFINITY_RESULT(result, count);
	}

	count = MAX_ITERATIONS;    
	while(--count)
	{
		// Test Thread Affinity Masks (thread parameters) - For IRunnable variant of the Thread::Begin function
		ThreadParameters params;
		params.mnProcessor = kProcessorAny;
		params.mnAffinityMask = INT64_C(1) << (count % nAvailableProcessors);
		params.mnProcessor = kProcessorAny;

		Thread     thread;            
		thread.Begin(&gTestRunnable4, NULL, &params);  // sleeps then grabs the current thread id. 

		intptr_t   result = 0;
		thread.WaitForEnd(GetThreadTime() + 30000, &result);

		VERIFY_AFFINITY_RESULT(result, count);
	}    

	return nErrorCount;
} 


int TestThreadPriorities()
{
	int nErrorCount = 0;

	if(!IsSuperUser())
	{
		EA::EAMain::Report("Skipping Thread Priority test because we don't have sufficient system priviliages.\n");
		return nErrorCount;
	}

	// Verify that thread priorities act as expected.
	// Threads with higher priorities should execute instead of or before threads of lower priorities.
	// On some platforms (e.g. Windows), lower priority threads do get some execution time, so we have to recognize that.

	// Create 20 threads of very high priority, 20 threads of high priority, and 20 threads of regular priority.
	// Start the 20 very high priority threads first. 
	// Wait a bit then start the other 40 threads.
	// Quit all the very high priority threads. 
	// Wait a bit, while having the 40 threads measure how much time they execute.
	// Quit the remaining 40 threads.
	// Verify that the 20 high priority threads executed much more than the regular threads.

	struct PriorityTestThread : public EA::Thread::IRunnable
	{
		EA::Thread::Thread           mThread;
		EA::Thread::ThreadParameters mParameters;
		EA::Thread::Semaphore        mSemaphore;
		char                         mThreadName[16];
		volatile uint64_t            mCounter;
		volatile bool                mbShouldRun;
		char						 mPadd[EA_CACHE_LINE_SIZE];// make sure that these structures end up on different cachelines

		PriorityTestThread() : mThread(), mParameters(), mSemaphore(0), mCounter(0), mbShouldRun(true) {}
		PriorityTestThread(const PriorityTestThread&){}
		void operator=(const PriorityTestThread&){}

		intptr_t Run(void*)
		{
			mSemaphore.Wait();

			while(mbShouldRun)
			{
				//char buffer[64];
				//EA::StdC::Sprintf(buffer, "%f", (double)(mCounter*1.23)); // Just waste time.
				mCounter++;
				EAReadBarrier();
			}

			return 0;
		}
	};

	if((EA::Thread::GetProcessorCount() >= 4))
	{
		const int                kThreadCount = 4;
		PriorityTestThread       threadHighestPriority[kThreadCount];
		PriorityTestThread       threadRegularPriority[kThreadCount];
		PriorityTestThread       threadHighPriority[kThreadCount];
		EA::StdC::LimitStopwatch limitStopwatch(EA::StdC::Stopwatch::kUnitsSeconds);
		const EA::Thread::ThreadAffinityMask kCommonAffinityMask = 0xf; // first 4 cores only

		EA::Thread::ThreadParameters commonParams;
		commonParams.mbDisablePriorityBoost = true; // we can disable boosting if we want a better simulation of console-like behavior 
		commonParams.mnAffinityMask = kCommonAffinityMask;
		commonParams.mnProcessor = kProcessorAny;

#if defined(EA_PLATFORM_MICROSOFT)
		// Due to how windows thread priorities work we need to further increase the thread priority of the high threads
		// If this is not done the test will randomly have the regular priority have a higher count than the high threads. 
		static const int kHigherPriorityDelta = kThreadPriorityMax - 2;
#else
		static const int kHigherPriorityDelta = 0;
#endif
		EA::Thread::SetThreadPriority(EA::Thread::kThreadPriorityDefault + 3);
		for (int i = 0; i < kThreadCount; i++)
		{
			{
				PriorityTestThread& highestThread = threadHighestPriority[i];
				highestThread.mParameters = commonParams;
				highestThread.mParameters.mnPriority = (EA::Thread::kThreadPriorityDefault + 2 + kHigherPriorityDelta);
				EA::StdC::Sprintf(highestThread.mThreadName, "Highest%d", i);
				highestThread.mParameters.mpName = highestThread.mThreadName;
				highestThread.mThread.Begin(&highestThread, NULL, &highestThread.mParameters);
			}
			{
				PriorityTestThread& regularThread = threadRegularPriority[i];
				regularThread.mParameters = commonParams;
				regularThread.mParameters.mnPriority = (EA::Thread::kThreadPriorityDefault);
				EA::StdC::Sprintf(regularThread.mThreadName, "Reg%d", i);
				regularThread.mParameters.mpName = regularThread.mThreadName;
				regularThread.mThread.Begin(&regularThread, NULL, &regularThread.mParameters);
			}

			{
				PriorityTestThread& highThread = threadHighPriority[i];
				highThread.mParameters = commonParams;
				highThread.mParameters.mnPriority = (EA::Thread::kThreadPriorityDefault + 1 + kHigherPriorityDelta);
				EA::StdC::Sprintf(highThread.mThreadName, "High%d", i);
				highThread.mParameters.mpName = highThread.mThreadName;
				highThread.mThread.Begin(&highThread, NULL, &highThread.mParameters);
			}
		}

		limitStopwatch.SetTimeLimit(1, true); 
		while(!limitStopwatch.IsTimeUp())
			{ /* Do nothing. Don't even sleep, as some platform might not give us the CPU back. */ }

		for(int i = 0; i < kThreadCount; i++)
		{
			threadHighestPriority[i].mSemaphore.Post(1); 
			threadHighPriority[i].mSemaphore.Post(1); 
			threadRegularPriority[i].mSemaphore.Post(1);
		}

		limitStopwatch.SetTimeLimit(3, true); 
		while(!limitStopwatch.IsTimeUp())
			{ /* Do nothing. Don't even sleep, as some platform might not give us the CPU back. */ }

		for(int i = 0; i < kThreadCount; i++)
			threadHighestPriority[i].mbShouldRun = false;
		EAWriteBarrier();

		limitStopwatch.SetTimeLimit(3, true); 
		while(!limitStopwatch.IsTimeUp())
			{ /* Do nothing. Don't even sleep, as some platform might not give us the CPU back. */ }

		for(int i = 0; i < kThreadCount; i++)
		{
			threadHighPriority[i].mbShouldRun = false;
			threadRegularPriority[i].mbShouldRun = false;
		}
		EAWriteBarrier();

		limitStopwatch.SetTimeLimit(3, true); 
		while(!limitStopwatch.IsTimeUp())
			{ /* Do nothing. Don't even sleep, as some platform might not give us the CPU back. */ }

		EA::Thread::SetThreadPriority(EA::Thread::kThreadPriorityDefault);

		uint64_t highPriorityCount    = 0;
		uint64_t regularPriorityCount = 0;

		for(int i = 0; i < kThreadCount; i++)
			highPriorityCount += threadHighPriority[i].mCounter;
		for(int i = 0; i < kThreadCount; i++)
			regularPriorityCount += threadRegularPriority[i].mCounter;

		// Verify that the higher priority threads always got priority over the regular priority threads.
		EATEST_VERIFY_F(highPriorityCount > regularPriorityCount, "Priority execution failure: highPriorityCount: %I64u, regularPriorityCount: %I64u", highPriorityCount, regularPriorityCount);

		// Wait for the threads to end before continuing.
		for(int i = 0; i < kThreadCount; i++)
		{
			threadHighestPriority[i].mThread.WaitForEnd();
			threadHighPriority[i].mThread.WaitForEnd();
			threadRegularPriority[i].mThread.WaitForEnd();
		}
	}
	
	return nErrorCount;
}

int TestSetThreadProcessConstants()
{
	int nErrorCount = 0;

	// testing a user reported regression of negative value shifts
	for(auto k : { kProcessorDefault, kProcessorAny })
	{
		Thread t;

		t.Begin([](void* param) -> intptr_t
				{
					int kConstant = *(int*)param;
					SetThreadProcessor(kConstant);
					return 0;
				}, &k);
		t.WaitForEnd();
	}

	return nErrorCount;
}

int TestNullThreadNames()
{
	int nErrorCount = 0;

	{
		ThreadParameters threadParams;
		threadParams.mpName = nullptr;

		Thread t;
		t.Begin([](void*) -> intptr_t { return 0; }, NULL, &threadParams);
		t.WaitForEnd();
	}

	return nErrorCount;
}

int TestLambdaThreads()
{
	int nErrorCount = 0;

	{ // test rvalue
		int foo = 0;
		MakeThread([&]
				   {
					   EATEST_VERIFY(foo == 0);
					   foo = 42;
				   })
			.WaitForEnd();
		EATEST_VERIFY(foo == 42);
	}

	{ // test lvalue
		int foo = 0;

		auto callme = [&]
		{
			EATEST_VERIFY(foo == 0);
			foo = 42;
		};

		MakeThread(callme).WaitForEnd();
		EATEST_VERIFY(foo == 42);
	}

	{ // test thread parameters
		const char* MY_THREAD_NAME = "my thread name";
		ThreadParameters params;
		params.mpName = MY_THREAD_NAME;

		MakeThread(
			[&] { EATEST_VERIFY(strncmp(MY_THREAD_NAME, GetThreadName(), EATHREAD_NAME_SIZE) == 0); }, params)
				.WaitForEnd();
	}

	return nErrorCount;
}

int TestThreadDynamicData()
{
	int nErrorCount = 0;

	const int kOverflowDynamicDataCount = 256; // Must be greater than EA::Thread::kMaxThreadDynamicDataCount.

	for(int i = 0; i < kOverflowDynamicDataCount; i++)
	{
		EA::Thread::ThreadId id = 0;
		EA::Thread::SysThreadId sysId = 0;

		MakeThread([&]
				{
					id = EA::Thread::GetThreadId();
					sysId = EA::Thread::GetSysThreadId();
				})
		.WaitForEnd();

		EATEST_VERIFY(FindThreadDynamicData(id) == nullptr);
		EATEST_VERIFY(FindThreadDynamicData(sysId) == nullptr);
	}

	return nErrorCount;
}

int TestSetThreadProcessor()
{
	// Exercise EA::Thread::GetThreadId, EA::Thread::GetSysThreadId, EAThreadGetUniqueId, SetThreadProcessor, GetThreadProcessor.

	// Create and start N threads paused.
	// Release all threads to run at once.
	// Have each of the threads record its EAThreadGetUniqueId value and exit.
	// Verify that there were no collisions in the recorded id values.
	int nErrorCount = 0;

	struct IdTestThread : public EA::Thread::IRunnable
	{
		EA::Thread::Thread         mThread;                 // The Thread object.
		EA::Thread::Semaphore      mSemaphore;              // Used to pause the thread after it starts.
		EA::Thread::ThreadUniqueId mUniqueId;               // The EAThreadUniqueId that this thread gets assigned by the OS.
		EA::Thread::ThreadId       mThreadId;               // The EAThreadUniqueId that this thread gets assigned by the OS.
		EA::Thread::SysThreadId    mSysThreadId;            // The EAThreadUniqueId that this thread gets assigned by the OS.
		int                        mAssignedProcessorId;    // The processor id that we ask the OS to run this thread on.
		int                        mProcessorId;            // The processor id that this thread gets assigned by the OS. Should equal mAssignedProcessorId.

		IdTestThread() : mThread(), mSemaphore(0), mUniqueId(), mThreadId(), mSysThreadId(), mAssignedProcessorId(), mProcessorId() {}
		IdTestThread(const IdTestThread&){} // Avoid compiler warnings.
		void operator=(const IdTestThread&){}     // Avoid compiler warnings.

		intptr_t Run(void*)
		{
			mSemaphore.Wait();
			EAThreadGetUniqueId(mUniqueId);
			mThreadId    = EA::Thread::GetThreadId();
			mSysThreadId = EA::Thread::GetSysThreadId();
			mProcessorId = EA::Thread::GetThreadProcessor();
			EAWriteBarrier();
			return 0;
		}
	};

	#if defined(EA_PLATFORM_DESKTOP)
	  const int kThreadCount = 100;
	#elif (EA_PLATFORM_WORD_SIZE == 8)
	  const int kThreadCount = 50;
	#else
	  const int kThreadCount = 16;
	#endif
	IdTestThread                thread[kThreadCount];
	ThreadParameters            threadParams;
	EA::UnitTest::RandGenT<int> random(EA::UnitTest::GetRandSeed());
	const int                   processorCount = EA::Thread::GetProcessorCount();

	for(int i = 0; i < kThreadCount; i++)
	{
		threadParams.mnProcessor = random(processorCount);
		threadParams.mpName = "IdTest";
		thread[i].mAssignedProcessorId = threadParams.mnProcessor;
		thread[i].mThread.Begin(&thread[i], NULL, &threadParams);
	}

	EA::UnitTest::ThreadSleep(1000);

	for(int i = 0; i < kThreadCount; i++)
		thread[i].mSemaphore.Post(1); 

	EA::UnitTest::ThreadSleep(1000);

	for(int i = 0; i < kThreadCount; i++)
		thread[i].mThread.WaitForEnd();
	EAReadBarrier();

	EA::Thread::ThreadUniqueId uniqueIdArray[kThreadCount];
	EA::Thread::ThreadId       idArray[kThreadCount];
	EA::Thread::SysThreadId    sysIdArray[kThreadCount];

	// Problem: We don't have an EAThreadEqual(const ThreadId&, const ThreadId&) function, but could use one.
	// If we had such a thing, then we wouldn't need the odd code below and could probably use an eastl::set.
	for(int i = 0; i < kThreadCount; i++)
	{
		memset(&uniqueIdArray[i], 0, sizeof(EA::Thread::ThreadUniqueId));
		memset(&idArray[i],       0, sizeof(EA::Thread::ThreadId));
		memset(&sysIdArray[i],    0, sizeof(EA::Thread::SysThreadId));
	}

	for(int i = 0; i < kThreadCount; i++)
	{
		for(int j = 0; j < i; j++)
			EATEST_VERIFY(memcmp(&uniqueIdArray[j], &thread[i].mUniqueId, sizeof(EA::Thread::ThreadUniqueId)) != 0);
		uniqueIdArray[i] = thread[i].mUniqueId;

		for(int j = 0; j < i; j++)
			EATEST_VERIFY(memcmp(&idArray[j], &thread[i].mThreadId, sizeof(EA::Thread::ThreadId)) != 0);
		idArray[i] = thread[i].mThreadId;

		for(int j = 0; j < i; j++)
			EATEST_VERIFY(memcmp(&sysIdArray[j], &thread[i].mSysThreadId, sizeof(EA::Thread::SysThreadId)) != 0);
		sysIdArray[i] = thread[i].mSysThreadId;

		// The following will fail on some platforms, as they don't support assigning 
		// thread affinity (e.g. PS3) or don't respect the assigned thread affinity (e.g. Windows). 
		// To consider: make a define which identifies which platforms rigidly follow thread processor assignments.
		// On Windows, EAThread doesn't use SetThreadAffinityMask but rather uses SetThreadIdealProcessor,
		// which doesn't guarantee which processor the thread will run on and rather is a hint.
		#if defined(EA_PLATFORM_CONSOLE) && defined(EA_PLATFORM_MICROSOFT) // To do: add platforms to this list appropriately.
			EATEST_VERIFY_F(thread[i].mProcessorId == thread[i].mAssignedProcessorId, 
							 "    Error: Thread assigned to run on processor %d, found to be running on processor %d.", 
							 thread[i].mAssignedProcessorId, thread[i].mProcessorId);
		#endif
	}
	
	return nErrorCount;
}


int TestThreadDisablePriorityBoost()
{
	int nErrorCount = 0;

#if defined(EA_PLATFORM_WINDOWS) || defined(EA_PLATFORM_XBOXONE)
	{
		Thread thread;
		ThreadParameters params;

		auto priorityBoostTester = [&](BOOL expectedDisablePriorityBoost)
		{
			thread.Begin(
			    [](void* pInExpectedDisablePriorityBoost) -> intptr_t
			    {
				    BOOL* pExpectedDisablePriorityBoost = (BOOL*)pInExpectedDisablePriorityBoost;
				    int nErrorCount = 0;
				    PBOOL pDisablePriorityBoost = nullptr;

				    auto result = GetThreadPriorityBoost(GetCurrentThread(), pDisablePriorityBoost);
				    EATEST_VERIFY_MSG(result != 0, "GetThreadPriorityBoost failed\n");
				    EATEST_VERIFY_MSG((result != 0) && *pDisablePriorityBoost == *pExpectedDisablePriorityBoost,
				                      "Thread Priority Boost was not disabled\n");

				    return nErrorCount;
				},
			    &expectedDisablePriorityBoost, &params);

			intptr_t threadErrorCount = 0;
			thread.WaitForEnd(GetThreadTime() + 30000, &threadErrorCount);
			nErrorCount += (int)threadErrorCount;
		};


		params.mbDisablePriorityBoost = true;
		priorityBoostTester(TRUE);

		params.mbDisablePriorityBoost = false;
		priorityBoostTester(FALSE);
	}
#endif

	return nErrorCount;
}


int TestThreadParameters()
{ // Test ThreadParameters
	int nErrorCount = 0;
	Thread::Status   status;
	const int        kThreadCount(kMaxConcurrentThreadCount - 1);
	ThreadId         threadId[kThreadCount];
	Thread           thread[kThreadCount];
	int              i;
	ThreadParameters threadParameters;

	const int nOriginalPriority = GetThreadPriority();

	// Set our thread priority to match that of the threads we will be creating below.
	SetThreadPriority(kThreadPriorityDefault + 1);

	for(i = 0; i < kThreadCount; i++)
	{
		status = thread[i].GetStatus();
		EATEST_VERIFY_MSG(status == Thread::kStatusNone, "Thread failure: Thread should have kStatusNone (2).\n");

		// ThreadParameters
		threadParameters.mnStackSize = 32768;  
		threadParameters.mnPriority  = kThreadPriorityDefault + 1;
		threadParameters.mpName      = "abcdefghijklmnopqrstuvwxyz"; // Make an overly large name.

		threadId[i] = thread[i].Begin(TestFunction1, NULL, &threadParameters);

		EATEST_VERIFY_MSG(threadId[i] != (ThreadId)kThreadIdInvalid, "Thread failure: ThreadBegin failed.\n");

		// It turns out that you can't really do such a thing as set lower priority in native Linux, not with SCHED_OTHER, at least.
		#if ((!defined(EA_PLATFORM_UNIX) && !defined(__APPLE__)) || defined(__CYGWIN__)) && !EA_USE_CPP11_CONCURRENCY
			EATEST_VERIFY_MSG(thread[i].GetPriority() == threadParameters.mnPriority, "Thread failure: Thread Priority not set correctly (2).\n");
			 
		#endif

		if(i > 0)
			EATEST_VERIFY_MSG(threadId[i] != threadId[i-1], "Thread failure: Thread id collision (2).\n");
	}

	ThreadSleep(200);

	for(i = 0; i < kThreadCount; i++)
	{
		if(threadId[i] != kThreadIdInvalid)
			thread[i].SetName("0123456789012345678901234567890"); // Make an overly large name.
	}

	ThreadSleep(200);

	// It turns out that you can't really do such a thing as set lower priority in native Linux.
	#if ((!defined(EA_PLATFORM_UNIX) || defined(__CYGWIN__)) && !defined(__APPLE__) && !EA_USE_CPP11_CONCURRENCY)
		int nPriority;

		if(threadId[0] != kThreadIdInvalid)
		{
			nPriority = thread[0].GetPriority();

			thread[0].SetPriority(nPriority - 1);
			EATEST_VERIFY_MSG(thread[0].GetPriority() == nPriority - 1, "Thread failure: Thread Priority not set correctly (3.1).\n");

			thread[0].SetPriority(nPriority);
			EATEST_VERIFY_MSG(thread[0].GetPriority() == nPriority, "Thread failure: Thread Priority not set correctly (3.2).\n");
			 
		}
	#endif

	for(i = 0; i < kThreadCount; i++)
	{
		if(threadId[i] != kThreadIdInvalid)
			thread[i].WaitForEnd(GetThreadTime() + 30000);
	}

	SetThreadPriority(kThreadPriorityDefault);

	for(i = 0; i < kThreadCount; i++)
	{
		status = thread[i].GetStatus();

		if(threadId[i] != kThreadIdInvalid)
			EATEST_VERIFY_MSG(status == Thread::kStatusEnded, "Thread failure: Thread should have kStatusEnded (2).\n");
		else
			EATEST_VERIFY_MSG(status == Thread::kStatusNone, "Thread failure: Thread should have kStatusNone (2).\n");
	}
	
	SetThreadPriority(nOriginalPriority);
	return nErrorCount;
}

int TestThreadEnd()
{
	int nErrorCount(0);
	EA::Thread::Thread thread;
	Thread::Status   status;

	thread.Begin(TestFunction13); 
	ThreadSleep(20);
	
	intptr_t returncode;
	thread.WaitForEnd();
	status = thread.GetStatus(&returncode);
	
	EATEST_VERIFY_MSG(returncode == 42, "Thread return code failure:  Expected return code 42.");
	EATEST_VERIFY_MSG(status == Thread::kStatusEnded, "Thread failure: Thread should have kStatusEnded.\n");
 
	return nErrorCount;
}

int TestThreadThread()
{
	int nErrorCount(0);

	sThreadTestTimeMS = (gTestLengthSeconds * 1000) / 2; // '/2' because this test doesn't need so much time.

	nErrorCount += TestThreadAffinityMask();
	nErrorCount += TestThreadEnd();

	{
		ThreadId threadId = GetThreadId();

		EATEST_VERIFY_MSG(threadId != kThreadIdInvalid, "GetThreadId failure.\n");

		SysThreadId sysThreadId = GetSysThreadId(threadId);

		EATEST_VERIFY_MSG(sysThreadId != kSysThreadIdInvalid, "GetSysThreadId failure.\n");

		#if (defined(EA_PLATFORM_MICROSOFT) || defined(EA_PLATFORM_UNIX) || defined(EA_PLATFORM_PS4)) && !EA_USE_CPP11_CONCURRENCY
			const void*    pStackBase = GetThreadStackBase();
			const void*    pStackTop  = &pStackBase;
			const intptr_t stackSize  = ((char*)pStackBase - (char*)pStackTop);

			// Verify that pStackBase is non-NULL and that the stack size is less than N MB.
			EATEST_VERIFY_MSG(pStackBase && (stackSize < 10485760), "GetThreadStackBase failure.\n");
			
		#endif

		// We disable this test for now on Kettle because although we have 7 cores available 
		// there is no guaranty the their ID are 0..6 and the system takes core ID 7
		#if !defined(EA_PLATFORM_PS4)

			int processorCount = GetProcessorCount();
			int processor      = GetThreadProcessor();
			
			// This isn't much of a test, but it at least exercizes the function.
			EATEST_VERIFY_F(processor < processorCount, "    Error: GetThreadProcessor [%d] >= GetProcessorCount [%d].\n", processor, processorCount);
		#endif

		// To do: Test this:
		// void SetThreadProcessor(int nProcessor);
	}

	{ // Test Current thread functionality
		const int nOriginalPriority = GetThreadPriority();
		int nPriority = kThreadPriorityDefault;

		EATEST_VERIFY_MSG(nPriority >= kThreadPriorityMin, "Thread priority failure (1).\n");
		EATEST_VERIFY_MSG(nPriority <= kThreadPriorityMax, "Thread priority failure (2).\n");

		// It turns out that you can't really do such a thing as set lower priority with most Unix threading subsystems. 
		// You can do so with Cygwin because it is just a pthreads API running on Windows OS/threading.
		// C++11 thread libraries also provide no means to set or query thread priority.
		#if (!defined(EA_PLATFORM_UNIX) || defined(__CYGWIN__)) && !EA_USE_CPP11_CONCURRENCY
			int  nPriority1;
			bool bResult;

			bResult = SetThreadPriority(nPriority);
			EATEST_VERIFY_MSG(bResult, "Thread priority failure (3).\n");

			bResult = SetThreadPriority(nPriority - 1);
			EATEST_VERIFY_MSG(bResult, "Thread priority failure (4).\n");

			nPriority1 = GetThreadPriority();
			EATEST_VERIFY_MSG(nPriority1 == nPriority - 1, "Thread priority failure (5).\n");

			bResult = SetThreadPriority(nPriority + 1);
			EATEST_VERIFY_MSG(bResult, "Thread priority failure (6).\n");

			nPriority1 = GetThreadPriority();
			EATEST_VERIFY_MSG(nPriority1 == nPriority + 1, "Thread priority failure (7).\n");

			bResult = SetThreadPriority(kThreadPriorityDefault);
			EATEST_VERIFY_MSG(bResult, "Thread priority failure (8).\n");

			nPriority1 = GetThreadPriority();
			EATEST_VERIFY_MSG(nPriority1 == kThreadPriorityDefault, "Thread priority failure (9).\n");
			 
		#endif

		SetThreadPriority(nOriginalPriority);
		ThreadSleep(kTimeoutImmediate);
		ThreadSleep(500);
	}


	#if defined(EA_PLATFORM_WINDOWS) && !EA_USE_CPP11_CONCURRENCY
	{ // Try to reproduce Windows problem with Thread::GetStatus returning kStatusEnded when it should return kStatusRunning.
		// On my current work machine (WinXP32, Single P4 CPU) this problem doesn't occur. But it might occur with others.
		Thread::Status status;
		Thread         threadBackground[8];
		Thread         thread;

		for(int i = 0; i < 8; i++)
			threadBackground[i].Begin(TestFunction1);

		EA::Thread::SetThreadPriority(kThreadPriorityDefault + 2);
		thread.Begin(TestFunction1);
		status = thread.GetStatus();
		EA::Thread::SetThreadPriority(kThreadPriorityDefault);

		EATEST_VERIFY_MSG(status == Thread::kStatusRunning, "Thread failure: Thread should have kStatusRunning.\n");

		thread.WaitForEnd();
	}
	#endif


	EA::Thread::Thread::SetGlobalRunnableFunctionUserWrapper(TestFunction3ExceptionWrapper);
	EA::Thread::Thread::SetGlobalRunnableClassUserWrapper(TestRunnable3ExceptionWrapper);

	{ // Test thread creation functionality.
		Thread::Status status;
		const int      kThreadCount(kMaxConcurrentThreadCount - 1);
		Thread         thread[kThreadCount];
		ThreadId       threadId[kThreadCount];
		int            i;

		for(i = 0; i < kThreadCount; i++)
		{
			status = thread[i].GetStatus();
			EATEST_VERIFY_MSG(status == Thread::kStatusNone, "Thread failure: Thread should have kStatusNone (1).\n");

			threadId[i] = thread[i].Begin(TestFunction1);
			EATEST_VERIFY_MSG(threadId[i] != kThreadIdInvalid, "Thread failure: ThreadBegin(TestFunction1) failed.\n");

			threadId[i] = thread[i].Begin(TestFunction3);
			EATEST_VERIFY_MSG(threadId[i] != kThreadIdInvalid, "Thread failure: ThreadBegin(TestFunction3) failed.\n");

			threadId[i] = thread[i].Begin(&gTestRunnable3);
			EATEST_VERIFY_MSG(threadId[i] != kThreadIdInvalid, "Thread failure: ThreadBegin(&gTestRunnable3) failed.\n");

			if(i > 0)
				EATEST_VERIFY_MSG(threadId[i] != threadId[i-1], "Thread failure: Thread id collision (1).\n");
		}

		// It turns out that you can't really do such a thing as set lower priority in native Linux.
		// C++11 threads also have no support for priorities
		#if (!defined(EA_PLATFORM_UNIX) || defined(__CYGWIN__)) && !EA_USE_CPP11_CONCURRENCY
			int nPriority;

			nPriority = thread[0].GetPriority();
			thread[0].SetPriority(nPriority - 1);

			nPriority = thread[0].GetPriority();
			thread[0].SetPriority(nPriority + 1);
		#endif

		ThreadSleep(200);

		for(i = 0; i < kThreadCount; i++)
		{
			if(threadId[i] != kThreadIdInvalid)
				thread[i].WaitForEnd(GetThreadTime() + 30000);
		}

		for(i = 0; i < kThreadCount; i++)
		{
			if(threadId[i] != kThreadIdInvalid)
			{
				status = thread[i].GetStatus();

				EATEST_VERIFY_MSG(status == Thread::kStatusEnded, "Thread failure: Thread should have kStatusEnded (1).\n");
			}
		}
	}


	nErrorCount += TestThreadParameters();

	{
		// Test if we can set and retrieve a custom thread name
		Thread::Status status;
		Thread         thread;
		ThreadId       threadId;
		const char*    threadName = "Test_Thread";
		const char*    defaultName = "DEFAULT";
		ThreadParameters threadParameters;

		threadParameters.mpName = defaultName;

		sThreadTestTimeMS = 10000;
		threadId = thread.Begin(TestFunction1, NULL, &threadParameters);

		EATEST_VERIFY_MSG(threadId != kThreadIdInvalid, "Thread failure: ThreadBegin(TestFunction1) failed.\n");
		EATEST_VERIFY_MSG(strncmp(defaultName, thread.GetName(), EATHREAD_NAME_SIZE) == 0, "Thread failure: GetName should return the name used when initializing the thread.");

		thread.SetName(threadName);

		EATEST_VERIFY_MSG(strncmp(threadName, thread.GetName(), EATHREAD_NAME_SIZE) == 0, "Thread failure: GetName should return the name set in SetName.");

		ThreadSleep(sThreadTestTimeMS + 1000);

		if(threadId != kThreadIdInvalid)
		{
			thread.WaitForEnd(GetThreadTime() + 30000);
			status = thread.GetStatus();
			EATEST_VERIFY_MSG(status == Thread::kStatusEnded, "Thread failure: Thread should have kStatusEnded (3).\n");
		}
	}

	// Test the free standing function variant of the GetName/SetName API
	{
		// Test if we can set and retrieve a custom thread name
		Thread::Status status;
		Thread         thread;
		ThreadId       threadId;
		const char*    threadName = "Test_Thread";
		const char*    defaultName = "DEFAULT";

		ThreadParameters threadParameters;
		threadParameters.mpName = defaultName;
		threadParameters.mnProcessor = EA::Thread::kProcessorAny;
		threadParameters.mnAffinityMask = EA::Thread::kThreadAffinityMaskAny;

		static volatile std::atomic<bool> sbThreadStarted;
		static volatile std::atomic<bool> sbThreadTestDone;
		sbThreadStarted = false;
		sbThreadTestDone = false;

		threadId = thread.Begin( [](void*) -> intptr_t
			{
				sbThreadStarted = true;
				while (!sbThreadTestDone)
					ThreadSleep();
				return 0;
			},
			NULL, &threadParameters);

		while(!sbThreadStarted) // Wait for thread to start up
			ThreadSleep();

		EATEST_VERIFY_MSG(threadId != kThreadIdInvalid, "Thread failure: ThreadBegin(TestFunction1) failed.\n");
		EATEST_VERIFY_MSG(strncmp(defaultName, GetThreadName(threadId), EATHREAD_NAME_SIZE) == 0, "Thread failure: GetName should return the name used when initializing the thread.");

		SetThreadName(threadId, threadName);
		EATEST_VERIFY_MSG(strncmp(threadName, GetThreadName(threadId), EATHREAD_NAME_SIZE) == 0, "Thread failure: GetName should return the name set in SetName.");

		sbThreadTestDone = true;  // signal that test is completed and the thread can shutdown 
		if(threadId != kThreadIdInvalid)
		{
			thread.WaitForEnd(GetThreadTime() + 30000);
			status = thread.GetStatus();
			EATEST_VERIFY_MSG(status == Thread::kStatusEnded, "Thread failure: Thread should have kStatusEnded (3).\n");
		}
	}


	{
		// Test the creation+destruction of many threads to make sure resources are recycled properly

		#if defined(EA_PLATFORM_DESKTOP)
		const int kThreadCount(200);
		#else
		const int kThreadCount(20);
		#endif
		Thread    thread;
		ThreadId  threadId;
		intptr_t  returnValue;

		EA::UnitTest::ReportVerbosity(1, "Creating many threads and then WaitForEnd()\n");

		for(int i = 0; i < kThreadCount; i++)
		{
			threadId = thread.Begin(TestFunction4, reinterpret_cast<void*>((uintptr_t)i));

			Thread::Status status = thread.GetStatus(&returnValue);
			if(status != Thread::kStatusEnded)
				thread.WaitForEnd(GetThreadTime() + 30000, &returnValue);
			EA::UnitTest::ReportVerbosity(1, "Thread ended.\n");

			EATEST_VERIFY_F(returnValue == i, "Thread failure: Thread return code is wrong (1). threadId: %s, status: %d, returnValue: %d\n", EAThreadThreadIdToString(threadId), (int)status, (int)returnValue);
			if(returnValue != i)
				EA::UnitTest::ReportVerbosity(1, "    Expected: %u, actual: %" PRId64 ".\n", (unsigned)i, (int64_t)returnValue);

			// Get the status again to make sure it returns the correct status.
			thread.GetStatus(&returnValue);
			EATEST_VERIFY_F(returnValue == i, "Thread failure: Thread return code is wrong (2). threadId: %s, status: %d, returnValue: %d\n", EAThreadThreadIdToString(threadId), (int)status, (int)returnValue);
			if(returnValue != i)
				EA::UnitTest::ReportVerbosity(1, "    Expected: %u, actual: %" PRId64 ".\n", (unsigned)i, (int64_t)returnValue);
		}

		
		EA::UnitTest::ReportVerbosity(1, "Creating many threads and then repeat GetStatus() until they finish\n");
		for(int i = 0; i < kThreadCount + 1; i++)
		{
			// Windows will get stuck in infinite loop if return value is 259 (STILL_ACTIVE)
			if(i == 259)
				 ++i;           

			threadId = thread.Begin(TestFunction4, reinterpret_cast<void*>((uintptr_t)i));

			const ThreadTime nGiveUpTime = EA::Thread::GetThreadTime() + 20000; // Give up after N milliseconds.

			// Test to see if GetStatus() will recycle system resource properly
			while(thread.GetStatus(&returnValue) != Thread::kStatusEnded)
			{
				ThreadSleep(100);

				if(EA::Thread::GetThreadTime() > nGiveUpTime)
				{
					EA::UnitTest::ReportVerbosity(1, "Thread failure: GetStatus failed.\n");
					break;
				}
			}

			EATEST_VERIFY_MSG(returnValue == i, "Thread failure: Thread return code is wrong (3).\n");
			if(returnValue != i)
				EA::UnitTest::ReportVerbosity(1, "    Expected: %u, actual: %" PRId64 ".\n", (unsigned)i, (int64_t)returnValue);

			// Get the status again to make sure it returns the correct status.
			thread.GetStatus(&returnValue);
			EATEST_VERIFY_MSG(returnValue == i, "Thread failure: Thread return code is wrong (4).\n");
			if(returnValue != i)
				EA::UnitTest::ReportVerbosity(1, "    Expected: %u, actual: %" PRId64 ".\n", (unsigned)i, (int64_t)returnValue);

			// See if calling WaitForEnd() now will result in a crash
			thread.WaitForEnd(GetThreadTime() + 30000, &returnValue);
			EATEST_VERIFY_MSG(returnValue == i, "Thread failure: Thread return code is wrong (5).\n");
		}
	}


	{
		// Test the creation of many threads

		#if defined(EA_PLATFORM_DESKTOP)
			Thread::Status status;
			const int      kThreadCount(96);
			Thread         thread[kThreadCount];
			ThreadId       threadId[kThreadCount];
			int            i;

			sThreadTestTimeMS = 10000;

			for(i = 0; i < kThreadCount; i++)
			{
				threadId[i] = thread[i].Begin(TestFunction1);
				EATEST_VERIFY_MSG(threadId[i] != kThreadIdInvalid, "Thread failure: ThreadBegin(TestFunction1) failed.\n");
			}

			ThreadSleep(sThreadTestTimeMS + 1000);

			for(i = 0; i < kThreadCount; i++)
			{
				if(threadId[i] != kThreadIdInvalid)
					thread[i].WaitForEnd(GetThreadTime() + 30000);
			}

			for(i = 0; i < kThreadCount; i++)
			{
				if(threadId[i] != kThreadIdInvalid)
				{
					status = thread[i].GetStatus();
					EATEST_VERIFY_MSG(status == Thread::kStatusEnded, "Thread failure: Thread should have kStatusEnded (3).\n");
				}
			}
		#endif
	}

	{
		// Regression of Thread dtor behaviour - the dtor should not wait for created threads
		// We reuse the atomic shouldgo to figure out what is really happening setting it to 0
		// initially and then incrementing it when the thread callback completes, allowing
		// us to detect whether the dtor completed before the thread callback.
		sShouldGo = 0;
		{
			// Create a scope for our thread object
			Thread thread;
			
			// Get our thread going. It will sleep immediately for 2 seconds and then increment sShouldGo
			ThreadId threadId = thread.Begin(TestFunction8);
			EATEST_VERIFY_MSG(threadId != kThreadIdInvalid, "Thread failure: thread.Begin(TestFunction8) failed.\n");

			// Now we exit our scope while our thread in theory still has a second before it completes
		} // NOTE(rparolin): If your test hangs here, its most likely due to a semantic change in the thread object that is waiting for threads to complete.

		// Signal to the thread it is allowed to complete.
		sShouldGo = 1;
	}


	{
		int nOriginalPriority = EA::Thread::GetThreadPriority();
		EA::Thread::SetThreadPriority(kThreadPriorityDefault);

		// Tests setting and getting thread priority while the thread is active.
		Thread::Status  status;
		Thread          thread;
		ThreadId        threadId;
		sShouldGo       = 0;

		threadId = thread.Begin(TestFunction7);
		EATEST_VERIFY_MSG(threadId != kThreadIdInvalid, "Thread failure: ThreadBegin(TestFunction7) failed.\n");
		
		// It turns out that you can't really do such a thing as set lower priority in native Linux.
		#if ((!defined(EA_PLATFORM_LINUX) && !defined(__APPLE__) && !defined(EA_PLATFORM_BSD)) || defined(__CYGWIN__)) && !EA_USE_CPP11_CONCURRENCY
			int nPriority = thread.GetPriority();

			thread.SetPriority(nPriority - 1);
			if(EATEST_VERIFY_MSG(thread.GetPriority() == nPriority - 1, "Thread failure: Thread Priority not set correctly (2).\n"))
				 nErrorCount++;

			thread.SetPriority(nPriority);
			EATEST_VERIFY_MSG(thread.GetPriority() == nPriority, "Thread failure: Thread Priority not set correctly (3).\n");
		#endif

		sShouldGo = 1;
		thread.WaitForEnd(GetThreadTime() + 30000);

		status = thread.GetStatus();
		EATEST_VERIFY_MSG(status == Thread::kStatusEnded, "Thread failure: Thread should have kStatusEnded.\n");
		EA::Thread::SetThreadPriority(nOriginalPriority);
	}

	nErrorCount += TestThreadDynamicData();
	nErrorCount += TestThreadPriorities();
	nErrorCount += TestSetThreadProcessor();
	nErrorCount += TestSetThreadProcessConstants();
	nErrorCount += TestNullThreadNames();
	nErrorCount += TestLambdaThreads();

	{
		// Test SetDefaultProcessor
		Thread::SetDefaultProcessor(kProcessorAny);
	}


	#if !defined(EA_PLATFORM_MOBILE)
		// This test does not execute correctly on Android. The 'while(sThreadCount
		// < kThreadCount)' loop sometimes hangs indefinitely because the spawned
		// threads exit often before the loop continues, so the spawned
		// thread count never gets up to kThreadCount. I guess the inner loop
		// should be testing addedThreadCount too. (?) But, just disable for now,
		// since I guess it's working on other platforms.
		{
			// Test very many threads starting and quitting, recycling
			// The PS3 code had problems with leaking threads in this case.
			// Create a bunch of threads.
			// Every N ms quit one of them and create a new one.

			// This test tends to be quite taxing on system resources, so cut it back for 
			// certain platforms. To do: use EATest's platform speed metrics.
			const int kThreadCount(48); // This needs to be greater than the eathread_thread.cpp kMaxThreadDynamicDataCount value.
			const int kTotalThreadsToRun(500);

			Thread    thread;
			ThreadId  threadId;
			int       addedThreadCount = 0;
			int       i = 0;

			sThreadCount = 0;
			sShouldGo    = 0;

			// Create threads.
			for(i = 0; i < kThreadCount; i++)
			{
				threadId = thread.Begin(TestFunction6, reinterpret_cast<void*>((uintptr_t)i));
				EATEST_VERIFY(threadId != kThreadIdInvalid);
			}

			// Wait until they are all created and started.
			while(sThreadCount < kThreadCount)
				ThreadSleep(500);

			// Let them run and exit as they go.
			sShouldGo = 1;

			// Add new threads as existing ones leave.
			while(addedThreadCount < kTotalThreadsToRun)
			{
				if(sThreadCount < kThreadCount)
				{
					threadId = thread.Begin(TestFunction6, reinterpret_cast<void*>((uintptr_t)i++));

					EATEST_VERIFY(threadId != kThreadIdInvalid);

					addedThreadCount++;
					
					#if defined(EA_PLATFORM_DESKTOP)
						// The created threads will not get any time slices on weaker platforms.
						// thus making the test create threads until the system is out of resources
						ThreadSleep(kTimeoutYield);

						// Sometimes it will never exit this loop.. because it will leave faster then it is made
						if(addedThreadCount >= kTotalThreadsToRun)
							break;
					#endif
				}
				else
					ThreadSleep(10);
			}

			// Wait until they have all exited.
			while(sThreadCount != 0) // While there are threads...
				ThreadSleep(500);

			// On some platforms it seems that thread entry/exit is so slow that the thread count might not necessarily
			// reflect the actual number of threads running.  This was causing an issue where the function was
			// exiting before all threads completed, so I added this wait.  It may be worth considering 
			// keeping track of all created threads in an array, and then using a thread-join operation here
			// instead
			ThreadSleep(1000);
		}

	#endif

	return nErrorCount;
}









