#pragma once

#include <SFML/Graphics.hpp>
#include <array>
#include <bitset>

#include "ComponentPool.h"
#include "EntityId.h"

class ComponentSystemBase;

class EntitySystem
{

public:

	~EntitySystem()
	{
		for (int i = 0; i < pools.size(); i++)
		{
			delete pools[i];
		}
	}

	static EntitySystem& GetInstance();

	void AddSystem(ComponentSystemBase& system);
	void Update(float deltaSeconds);

	EntityId AddEntity();
	Entity* GetEntityByID(EntityId id);
	bool DestroyEntity(EntityId id);

	template <typename ComponentType>
	ComponentType* AddComponent(EntityId entityId)
	{
		Entity* pEntity = GetEntityByID(entityId);
		if (!pEntity)
		{
			return nullptr;
		}

		int componentId = DataComponent<ComponentType>::GetId();

		if (componentId >= pools.size())
		{
			pools.resize(componentId + 1, nullptr);
			pools[componentId] = new ComponentPool<ComponentType>();
		}

		if (pEntity->addedComponents.test(componentId))
		{
			return nullptr;
		}

		pools[componentId]->AddEntity(entityId);
		pEntity->addedComponents.set(componentId, true);

		for (int i = 0; i < systems.size(); i++)
		{
			std::bitset<32>& entityBitset = pEntity->addedComponents;
			const std::bitset<32>& systemBitset = systems[i]->GetRequiredComponents();

			bool qualified = (entityBitset & systemBitset) == systemBitset;

			if (qualified && !systems[i]->HasEntity(entityId))
			{
				systems[i]->AddEntity(entityId);
			}
		}

		return GetComponent<ComponentType>(entityId);
	}
	
	template <typename ComponentType>
	bool RemoveComponent(EntityId entityId)
	{
		Entity* pEntity = GetEntityByID(entityId);
		if (!pEntity)
		{
			return false;
		}

		int componentId = DataComponent<ComponentType>::GetId();

		if (componentId >= pools.size())
		{
			return false;
		}

		if (!pEntity->addedComponents.test(componentId))
		{
			return false;
		}

		pEntity->addedComponents.set(componentId, false);

		for (int i = 0; i < systems.size(); i++)
		{
			std::bitset<32>& entityBitset = pEntity->addedComponents;
			const std::bitset<32>& systemBitset = systems[i]->GetRequiredComponents();

			bool qualified = (entityBitset & systemBitset) == systemBitset;

			if (!qualified && systems[i]->HasEntity(entityId))
			{
				systems[i]->RemoveEntity(entityId);
			}
		}

		return true;
	}

	template <typename ComponentType>
	bool HasComponent(EntityId entityId)
	{
		return GetComponent<ComponentType>(entityId) != nullptr;
	}

	template <typename ComponentType>
	ComponentType* GetComponent(EntityId entityId)
	{
		Entity* pEntity = GetEntityByID(entityId);
		if (!pEntity)
		{
			return nullptr;
		}

		int componentId = DataComponent<ComponentType>::GetId();
		
		if (componentId >= pools.size())
		{
			return nullptr;
		}
		
		if (!pEntity->addedComponents.test(componentId))
		{
			return nullptr;
		}

		void* pAliasedProperty = pools[componentId]->Get(entityId);
		ComponentType* pProperty = static_cast<ComponentType*>(pAliasedProperty);

		return pProperty;
	}

private:

	static EntitySystem s_Instance;

	IndexType nextInsertionIndex = 1;
	std::array<Entity, std::numeric_limits<IndexType>::max()> entities;

	std::vector<ComponentSystemBase*> systems;
	std::vector<ComponentPoolBase*> pools;
};