/***************************************************************************
*                                                                          *
*  Copyright (C) Golaem S.A.  All Rights Reserved.                         *
*                                                                          *
***************************************************************************/

#pragma once

#if GLM_IS_PLATFORM_WINDOWS
	#define GLM_USE_THREADING_WINDOWS	1
	#define GLM_USE_THREADING_PTHREAD	0
	#define GLM_USE_THREADING_CXX11		0
#elif GLM_IS_PLATFORM_LINUX
	#define GLM_USE_THREADING_WINDOWS	0
	#define GLM_USE_THREADING_PTHREAD	1
	#define GLM_USE_THREADING_CXX11		0
#endif

#if GLM_USE_THREADING_WINDOWS
	#include "glmWindows.h"
#elif GLM_USE_THREADING_PTHREAD
	#include <pthread.h>
#endif

#include "glmAssert.h"

namespace glm
{
	struct GLM_CORE_API threadid_t
	{
#if GLM_USE_THREADING_WINDOWS
		uint64_t _id;
#elif GLM_USE_THREADING_PTHREAD
		pthread_t _id;
#endif
	};

	GLM_FORCE_INLINE bool operator==( const threadid_t& x, const threadid_t& y )
	{
	#if GLM_USE_THREADING_PTHREAD
		return ( pthread_equal(x._id, y._id) != 0 );
	#else
		return ( x._id == y._id );
	#endif
	}

	GLM_FORCE_INLINE bool operator<( const threadid_t& x, const threadid_t& y )
	{
		return ( x._id < y._id );
	}

	void GLM_CORE_API resetThreadId(threadid_t& threadId);

	extern GLM_CORE_API uint64_t convertThreadId(const threadid_t threadId);

	struct GLM_CORE_API threaddef_t
	{
	#if GLM_USE_THREADING_WINDOWS
		HANDLE			_threadHandle;
	#elif GLM_USE_THREADING_PTHREAD
		pthread_t		_pthread;
	#endif
		void (*_start_routine) (void *);
		void *_arg;
		size_t _stackSize;
		threadid_t _threadId;

		threaddef_t() : _start_routine(NULL), _arg(NULL), _stackSize(0)
		{
			resetThreadId(_threadId);
		}
	};

	class GLM_CORE_API TaskBase
	{
	public:
		TaskBase() {}
		virtual ~TaskBase() {};

		virtual void runTask() = 0;
		virtual void requestStopTask() = 0;

		virtual bool isRunningTask() const = 0;

		virtual size_t getRequiredStackSize() const = 0;

		static void startTask(void* taskToStart)
		{
			GLM_ASSERT( taskToStart != NULL );
			TaskBase* task = static_cast<TaskBase*>(taskToStart);
			task->runTask();
		}

	protected:
		static const size_t s_defaultStackSizeInBytes = 1 * 1024 * 1024;
	};

	GLM_FORCE_INLINE const threadid_t getCurrentThreadId()
	{
		threadid_t threadId;
	#if GLM_USE_THREADING_WINDOWS
		#if GLM_IS_64BIT
			threadId._id = reinterpret_cast<uint32_t*>(__readgsqword(48))[18];
		#elif GLM_IS_32BIT
			threadId._id = reinterpret_cast<uint32_t*>(__readfsdword(24))[9];
		#endif
	#elif GLM_USE_THREADING_PTHREAD
		threadId._id = pthread_self();
	#endif
		return threadId;
	}

	GLM_FORCE_INLINE const threadid_t getThreadId(const threaddef_t& thread)
	{
		return thread._threadId;
	}

	//TODO: add support for cpu affinity
	bool GLM_CORE_API createThread(threaddef_t& thread, void (*start_routine) (void *), void *arg, size_t threadStackSize);
	bool GLM_CORE_API joinThread(threaddef_t& thread);

	bool GLM_CORE_API startTask(threaddef_t& thread, TaskBase* task);	//NOTE: will create the thread
	bool GLM_CORE_API stopTask(threaddef_t& thread, TaskBase* task);		//NOTE: will join the thread

	void GLM_CORE_API sleepCurrentThread(uint32_t sleepDurationInMilliseconds);
	void GLM_CORE_API yieldCurrentThread();

	uint32_t GLM_CORE_API getSupportedConcurrentThreadCount();
}
