#include "pch.h"
#include "ThreadPool.h"
#include "Engine/Engine.h"
#include "TaskManager.h"
#include "Game/Game.h"
#include "Commands/Command.h"

//Static variables used by the engine to signal a quit event
std::mutex anx::ThreadPool::QuitMutex{};
bool anx::ThreadPool::m_Quit = false;
std::mutex anx::ThreadPool::m_GroupMutex{};
std::condition_variable anx::ThreadPool::m_GroupCV{};

//The destructor makes all threads exit and joins them before returning.
anx::ThreadPool::~ThreadPool()
{
	m_Quit = true;
	TaskManager::GetInstance().Quit();
	for (size_t i = 0; i < m_Threads.size(); ++i)
	{
		TaskManager::GetInstance().Quit();
		Engine::GetInstance().Quit();
		m_Threads.at(i).join();
	}
}

//Initialization function that created worker threads depending on the number of physical cores in the system
void anx::ThreadPool::Init()
{
	m_NrThreads = std::thread::hardware_concurrency() - 1;

	for (int i = 0; i < m_NrThreads; ++i)
	{
		m_Threads.emplace_back(std::thread{ &ThreadPool::InfiniteLoop, this });
	}
}

anx::ThreadPool::ThreadPool()
	:m_NrThreads(0)
{
}

//Inifite loop function that gets executed by every worker thread until the game notifies the engine quit
void anx::ThreadPool::InfiniteLoop()
{
	anx::TaskManager& manager = anx::TaskManager::GetInstance();
	anx::Engine& engine = anx::Engine::GetInstance();
	Task currentTask;

	while(!engine.GetQuit())
	{
		//Get a job request from the task manager
		bool hasTask = manager.GetJobRequest(currentTask);
		//If we got a task, execute it depending on it's stage and component type
		if(hasTask)
		{
			int size = currentTask.range.second - currentTask.range.first;
			switch (currentTask.stage)
			{
			case Stage::UPDATE:
				{
					auto& components = manager.GetComponents(currentTask.key);
					for (int i = currentTask.range.first; i < currentTask.range.second; ++i)
					{
						components.at(i)->Update();
					}
					manager.ReduceCounter(currentTask.key, size);
				}
				break;
				//Physics init stage adds all collider components to the scene's quad tree
			case Stage::PHYSICSINIT:
				{
					auto& components = manager.GetComponents(currentTask.key);
					auto* physScene = Engine::GetInstance().GetCurrentGame()->GetCurrentScene()->GetPhysicsScene();
					physScene->Insert(currentTask.range.first, currentTask.range.second);
					manager.ReduceCounter(currentTask.key, size);
				}
				break;
				//The main physics stage is going to process each node and check for collisions between collider components.
				//This information is passed to the collider components by a adding a reference to either the overlapping or blocking entity list of the collider component
			case Stage::PHYSICSMAIN:
				{
					auto* physScene = Engine::GetInstance().GetCurrentGame()->GetCurrentScene()->GetPhysicsScene();
					auto& nodes = physScene->GetTree()->GetNotEmptyNodes();
					for (int i = currentTask.range.first; i < currentTask.range.second; ++i)
					{
						physScene->ProcessNode(nodes[i]);
					}
					manager.ReduceCounter(currentTask.key, size);
				}

				break;
			case Stage::LATEUPDATE:
				{
					auto& components = manager.GetComponents(currentTask.key);
					for (int i = currentTask.range.first; i < currentTask.range.second; ++i)
					{
						components.at(i)->LateUpdate();
					}
					manager.ReduceCounter(currentTask.key, size);
				}

				break;
			case Stage::SWAPPF:
				{
					auto& components = manager.GetComponents(currentTask.key);
					for (int i = currentTask.range.first; i < currentTask.range.second; ++i)
					{
						components.at(i)->SwapPF();
					}
					manager.ReduceCounter(currentTask.key, size);
				}
				break;
				//Command and Loading tasks don't use the component map of the current scene
			case Stage::COMMAND:
				currentTask.pCommand->Execute();
				break;
			case Stage::LOADING:
				{
					auto game = Engine::GetInstance().GetCurrentGame();
					game->SetLoadingScreen();
					while (!game->IsLoadingScreen()) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); }
					game->Load();
				}
				break;
			default: ;
			}
				
		}
	}
}