#pragma once

#include "ehs/EHS.h"
#include "ehs/Serializer.h"
#include "ehs/Array.h"
#include "ehs/Log.h"
#include "ehs/Str.h"

#ifdef EHS_OS_WINDOWS
	#include <processthreadsapi.h>
#endif

namespace ehs
{
	#if defined(EHS_OS_WINDOWS)
		#define EHS_INVALID_THREAD nullptr
		typedef void* THandle;
	#elif defined(EHS_OS_LINUX)
		#define EHS_INVALID_THREAD 0
		typedef UInt_64 THandle;
	#endif

	class EHS_LIB_IO Thread
	{
	private:
		static UInt_32 mainId;

		#ifdef EHS_OS_WINDOWS
			static Handle mainTaskHdl;
			static UInt_32 mainTaskIndex;
		#endif

		UInt_64 stackSize;
		THandle hdl;
		UInt_32 id;

		#ifdef EHS_OS_WINDOWS
			Handle taskHdl;
			UInt_32 taskIndex;
		#endif
		
	public:
        /// Frees any native handles.
	    ~Thread();

        /// Default members initialization.
		Thread(const UInt_64 stackSize = 0);

        /// Copies some members from the given thread object.
        /// @param [in] thread The thread object to copy from.
		Thread(const Thread& thread);

        /// Copies some members from the given thread object.
        /// @param [in] thread The thread object to copy from.
        /// @returns The thread that has been assigned to.
		Thread& operator=(const Thread& thread);

        /// Creates a thread handle and starts it with the given method.
        /// @param cb The function to run on the thread.
        /// @param args Raw data to send over to the thread.
		void Start(UInt_32 (*cb)(void*), void* args);

		/// Blocks the calling thread until the referenced thread is finished.
		/// @param timeout The time to wait for before moving on.
		/// @note Pass "EHS_INFINITE" to wait until the thread is finished.
		bool Join(const unsigned int timeout = EHS_INFINITE);

		/// Detaches the referenced thread, removing ownership.
		void Detach();

		/// Retrieves the given stack size available to the referenced thread.
		/// @returns The stack size.
		UInt_64 GetStackSize() const;

		/// Retrieves the native thread handle.
		/// @returns The native handle.
		THandle GetHandle() const;

		/// Retrieves the thread's id.
		/// @returns The id.
		UInt_32 GetId() const;

		/// Checks whether or not the calling thread is the referenced thread.
		/// @returns The result.
		bool IsCurrent() const;

		bool IsValid() const;

		#ifdef EHS_OS_WINDOWS
			/// Adjusts the thread's performance based on the type of task provided, that's going to be executed.
			/// @param[in] task A task name from the provided list in the registry.
			/// @note A list of tasks are in the registry at HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Multimedia\SystemProfile\Tasks.
			void SetTaskType_32(const Str_32& task);

			/// Adjusts the thread's performance based on the type of task provided, that's going to be executed.
			/// @param[in] task A task name from the provided list in the registry.
			/// @note A list of tasks are in the registry at HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Multimedia\SystemProfile\Tasks.
			void SetTaskType_16(const Str_16& task);

			/// Adjusts the thread's performance based on the type of task provided, that's going to be executed.
			/// @param[in] task A task name from the provided list in the registry.
			/// @note A list of tasks are in the registry at HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Multimedia\SystemProfile\Tasks.
			void SetTaskType_8(const Str_8& task);

			void RevertTaskType();
		#endif

		/// Gets the main thread id.
		static UInt_32 GetMainId();

		/// Retrieves the calling thread's id.
		/// @returns The id.
		static UInt_64 GetCurrentId();

		#ifdef EHS_OS_WINDOWS
			/// Adjusts the main thread's performance based on the type of task provided, that's going to be executed.
			/// @param[in] task A task name from the provided list in the registry.
			/// @note A list of tasks are in the registry at HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Multimedia\SystemProfile\Tasks.
			static void SetMainTaskType_32(const Str_32& task);

			/// Adjusts the main thread's performance based on the type of task provided, that's going to be executed.
			/// @param[in] task A task name from the provided list in the registry.
			/// @note A list of tasks are in the registry at HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Multimedia\SystemProfile\Tasks.
			static void SetMainTaskType_16(const Str_16& task);

			/// Adjusts the main thread's performance based on the type of task provided, that's going to be executed.
			/// @param[in] task A task name from the provided list in the registry.
			/// @note A list of tasks are in the registry at HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Multimedia\SystemProfile\Tasks.
			static void SetMainTaskType_8(const Str_8& task);

			static void RevertMainTaskType();
		#endif

		/// Causes the calling thread to wait until the given time using a while loop and the CPU clock.
		/// @param seconds The time in seconds to wait for.
		/// @returns The total elapsed time slept for due to this not being a 100% accurate.
		/// @warning Use "SleepFor" instead unless accuracy is required because this does not do other tasks while sleeping.
		static float HardSleepFor(const float seconds);

        /// Causes the calling thread to wait until the given time using native sleep calls.
        /// @param miliseconds The time in miliseconds to wait for.
        /// @warning This is not at all accurate.
		static void SleepFor(const UInt_32 miliseconds);

    private:
        static void* Redirect(void* args);
	};
}