#pragma once

#include <cstdint>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <bitset>
#include <memory>
#include <typeinfo>

#include "Archetype.h"
#include "EventBus.h"
#include "System.h"
#include "ComponentMask.h"


using ArchetypeId = std::vector<int>;
using EntityId = uint32_t;

class SystemBase;
class ECS
{
private:

	using SystemsArrayMap = std::unordered_map<std::uint8_t, std::vector<SystemBase*>>;

	using ComponentTypeIdBaseMap = std::unordered_map<size_t, ComponentBase*>;

	struct Record
	{
		Archetype* archetype;
		size_t index;
	};

	using EntityArchetypeMap = std::unordered_map<size_t, Record>;

	using ArchetypesArray = std::vector<Archetype*>;

	using ComponentMasks = std::unordered_map<size_t, ComponentMask>;


public:
	explicit ECS();

	~ECS();

	void Update(const std::uint8_t layer, const std::uint32_t delta);

	void RegisterEntity(const EntityId entityId);

	void DestroyEntity(const EntityId entityId);

	template <class C>
	void RegisterComponent();

	template <class C>
	const bool IsComponentRegistered() const;

	template<class C, typename... Args>
	C* AddComponent(const EntityId entityId, Args&&... args);

	template<class C>
	void RemoveComponent(const EntityId entityId);

	template<class C>
	C* GetComponent(const EntityId entityId);

	const bool HasComponent(const EntityId entityId, size_t compTypeId) const;

	template<class C>
	size_t GetComponentTypeId();

	template<class C>
	const bool HasComponent(const EntityId entityId);

	void RegisterSystem(const std::uint8_t& layer, SystemBase* system);

	void UnregisterSystem(const std::uint8_t& layer, SystemBase* system);

	template<class... Cs>
	std::vector<size_t> GetEntitiesWithComponents();

	Archetype* GetArchetype(const ComponentMask& componentMask);

	EntityId GetNewId();

	std::shared_ptr<EventBus> GetEventBus()
	{
		return eventBus_;
	}

private:
	std::shared_ptr<EventBus> eventBus_;

	EntityId entityIdCounter;

	SystemsArrayMap systems;

	EntityArchetypeMap entityArchetypeMap;

	ArchetypesArray archetypes;

	ComponentMasks componentMasks;

	ComponentTypeIdBaseMap componentMap;

	template<class C, typename... Args>
	C* TransferComponentsAndAdd(const EntityId entityId, Archetype* newArchetype, Archetype* oldArchetype, Args&&... args);

	template<class C>
	void TransferComponentsAndRemove(size_t entityIndex, Archetype* oldArchetype, Archetype* newArchetype);

	size_t GetEntityIndex(const Archetype* archetype, EntityId entityId);

	void UpdateRecord(EntityId entityId, Archetype* archetype, Record& record);

	void UpdateEntitiesInArchetypes(Archetype* oldArchetype, Archetype* newArchetype, EntityId entityId);

	size_t FindCurrentSize(Archetype* archetype, size_t ComponentTypeIndex, size_t index);


	void Reallocate(const size_t newComponentDataSize, Archetype* archetype, size_t index);
	void MoveComponentData(const ComponentBase* componentBase, Archetype* fromArch, Archetype* toArch, size_t fromIndex, size_t toIndex, size_t fromEntityIndex, size_t toEntityIndex);

	void TransferComponentData(const size_t compTypeIndex, Archetype* fromArch, Archetype* toArch, size_t i, size_t j, size_t entityIndex);
	void SwapComponentData(const size_t compTypeIndex, Archetype* arch, size_t index, size_t entityIndex);
	void RemoveAndAllocateComponentData(const size_t compTypeIndex, Archetype* fromArchetype, Archetype* toArchetype, size_t i, size_t j, size_t entityIndex);
};

template<class C>
void ECS::RegisterComponent()
{
	size_t componentTypeId = Component<C>::GetTypeID();

	if (componentMap.contains(componentTypeId))
		return; // can't re-register a type

	componentMap.emplace(componentTypeId, new Component<C>);
};

template<class C>
const bool ECS::IsComponentRegistered() const {
	size_t componentTypeId = Component<C>::GetTypeID();
	return (componentMap.contains(componentTypeId));
}

template<class C, typename... Args>
C* ECS::AddComponent(const EntityId entityId, Args&&... args) {
	// 1. Get the component type ID and check if the component already exists for the entity
	auto newComponentID = GetTypeID<C>();

	if (HasComponent(entityId, newComponentID)) { return nullptr; }

	// 2. Update the component mask for the entity and get the new archetype
	componentMasks[entityId].AddComponent(newComponentID);
	Archetype* newArchetype = GetArchetype(componentMasks[entityId]);

	// 3. Get and store the record of the entity
	Record& record = entityArchetypeMap[entityId];
	Archetype* oldArchetype = record.archetype;

	C* newComponent = nullptr;

	// 4. If the entity has existing components, transfer or add the new component
	if (oldArchetype) {
		newComponent = TransferComponentsAndAdd<C>(entityId, newArchetype, oldArchetype, C(std::forward<Args>(args)...));
	}
	// 5. If the entity has no existing components, allocate and add the new component
	else {
		size_t currentSize = FindCurrentSize(newArchetype, newComponentID, 0);
		newComponent = new (&newArchetype->componentData[0][currentSize]) C(std::forward<Args>(args)...);
		//newComponent = AllocateAndAddComponent<C>(newArchetype, std::forward<Args>(args)...);
	}

	// 6. Update the record and return the new component
	UpdateRecord(entityId, newArchetype, record);
	return newComponent;
}

template<class C>
void ECS::RemoveComponent(const EntityId entityId) {
	auto componentID = GetTypeID<C>();

	if (!IsComponentRegistered<C>() || !HasComponent(entityId, componentID)) {
		return;
	}

	componentMasks[entityId].RemoveComponent(componentID);

	Record& record = entityArchetypeMap[entityId];
	Archetype* oldArchetype = record.archetype;

	if (!oldArchetype) { return; }

	Archetype* newArchetype = GetArchetype(componentMasks[entityId]);
	size_t entityIndex = GetEntityIndex(oldArchetype, entityId);

	TransferComponentsAndRemove<C>(entityIndex, oldArchetype, newArchetype);

	UpdateRecord(entityId, newArchetype, record);
}

template<class C>
C* ECS::GetComponent(const EntityId entityId) {
	size_t componentTypeId = Component<C>::GetTypeID();

	if (!HasComponent(entityId, componentTypeId)) {
		return nullptr; // Component doesn't exist for the entity
	}

	const Archetype* archetype = entityArchetypeMap[entityId].archetype;
	if (!archetype) {
		return nullptr; // Archetype doesn't exist for the entity
	}

	size_t componentIndex = archetype->componentMask.ReturnArchetypeIndex(componentTypeId);

	auto entityIterator = std::find(
		archetype->entityIds.begin(),
		archetype->entityIds.end(),
		entityId
	);

	if (entityIterator == archetype->entityIds.end()) {
		return nullptr;
	}

	size_t entityIndex = entityIterator - archetype->entityIds.begin();
	size_t componentSize = componentMap[componentTypeId]->GetSize();

	C* component = reinterpret_cast<C*>(&archetype->componentData[componentIndex][entityIndex * componentSize]);
	return component;
}

template<class C>
size_t ECS::GetComponentTypeId() {
	auto newComponentId = Component<C>::GetTypeID();
	return newComponentId;
}

template<class C>
const bool ECS::HasComponent(const EntityId entityId) {
	return (componentMasks[entityId]).HasComponent(Component<C>::GetTypeID());
}

template<class... Cs>
std::vector<size_t> ECS::GetEntitiesWithComponents()
{
	std::vector<size_t> entities;

	ComponentMask mask;
	((mask.AddComponent(Component<Cs>::GetTypeID())), ...);

	for (const auto& [entityId, record] : entityArchetypeMap)
	{
		const Archetype* archetype = record.archetype;
		if (!archetype) {
			continue;
		}

		const ComponentMask& entityMask = componentMasks[entityId];

		if (mask.IsSubsetOf(entityMask)) {
			entities.push_back(entityId);
		}
	}
	return entities;
}

template<class C, typename... Args>
C* ECS::TransferComponentsAndAdd(const EntityId entityId, Archetype* newArchetype, Archetype* oldArchetype, Args&&... args) {
	auto newComponentMask = newArchetype->componentMask;
	auto oldComponentMask = oldArchetype->componentMask;
	C* newComponent = nullptr;

	size_t entityIndex = GetEntityIndex(oldArchetype, entityId);

	for (size_t i = 0, fromIndex = 0, toIndex = 0; i < newComponentMask.Size() - 1; ++i) {
		if (newComponentMask.HasComponent(i)) {

			size_t currentSize = FindCurrentSize(newArchetype, i, toIndex);

			if (oldComponentMask.HasComponent(i)) {
				TransferComponentData(i, oldArchetype, newArchetype, fromIndex, toIndex, entityIndex);
				++fromIndex;
			}
			else {
				newComponent = new (&newArchetype->componentData[toIndex][currentSize]) C(std::forward<Args>(args)...);
			}
			toIndex++;
		}
	}
	UpdateEntitiesInArchetypes(oldArchetype, newArchetype, entityId);

	return newComponent;
}

template<class C>
void ECS::TransferComponentsAndRemove(size_t entityIndex, Archetype* oldArchetype, Archetype* newArchetype) {
	auto oldComponentMask = oldArchetype->componentMask;
	auto newComponentMask = newArchetype->componentMask;

	for (size_t i = 0, fromIndex = 0, toIndex = 0; i < oldComponentMask.Size(); ++i) {
		if (oldComponentMask.HasComponent(i)) {
			if (newComponentMask.HasComponent(i)) {
				RemoveAndAllocateComponentData(i, oldArchetype, newArchetype, fromIndex, toIndex, entityIndex);
				toIndex++;
			}
			else {
				SwapComponentData(i, oldArchetype, toIndex, entityIndex);
			}

			fromIndex++;
		}
	}

	std::iter_swap(oldArchetype->entityIds.begin() + entityIndex, oldArchetype->entityIds.end() - 1);
	oldArchetype->entityIds.pop_back();
}
#include "ECS.h"

ECS::ECS() : entityIdCounter(1), eventBus_(std::make_shared<EventBus>())
{}

ECS::~ECS()
{
    // Clean up dynamically allocated memory and resources
    for (auto& archetype : archetypes)
    {
        delete archetype;
    }

    for (auto& component : componentMap)
    {
        delete component.second;
    }

    for (auto& systemLayer : systems)
    {
        for (auto& system : systemLayer.second)
        {
            delete system;
        }
    }
}

void ECS::Update(const std::uint8_t layer, const std::uint32_t delta)
{
    for (SystemBase* system : systems[layer])
    {
        const ComponentMask& key = system->GetComponentMask();

        for (Archetype* archetype : archetypes)
        {
            const ComponentMask& archetypeMask = archetype->componentMask;
            if (key.IsSubsetOf(archetypeMask)) {
                system->Update(delta, archetype);
            }
        }
    }
}

void ECS::RegisterEntity(const EntityId entityId)
{
    entityArchetypeMap[entityId] = Record(nullptr, 0);
}

void ECS::DestroyEntity(const EntityId entityId)
{
    if (!entityArchetypeMap.contains(entityId))
        return; // Entity doesn't exist

    Record& record = entityArchetypeMap[entityId];
    Archetype* oldArchetype = record.archetype;

    if (!oldArchetype)
    {
        entityArchetypeMap.erase(entityId);
        return;
    }

    ComponentMask oldComponentMask = oldArchetype->componentMask;
    size_t entityIndex = record.index;
    size_t lastEntityIndex = oldArchetype->entityIds.size() - 1;

    // Move each component data to the end of the archetype's component data
    for (size_t i = 0; i < oldComponentMask.Size(); ++i)
    {
        if (oldComponentMask.HasComponent(i))
        {
            const ComponentBase* const componentBase = componentMap[i];

            if (componentBase == nullptr)
                continue;

            size_t componentDataSize = componentBase->GetSize();
            auto* componentData = &oldArchetype->componentData[i][entityIndex * componentDataSize];
            auto* lastComponentData = &oldArchetype->componentData[i][lastEntityIndex * componentDataSize];

            //Move the last entity's component data to the position of the entity being removed
            componentBase->MoveData(lastComponentData, componentData);

            //Destroy the component data of the entity being removed
            componentBase->DestroyData(componentData);
        }
    }

    // Swap the entity being removed with the last entity in the archetype
    std::swap(oldArchetype->entityIds[entityIndex], oldArchetype->entityIds[lastEntityIndex]);

    // Pop the last entity from the archetype
    oldArchetype->entityIds.pop_back();

    // Update the entity's record in the entityArchetypeMap
    record.index = oldArchetype->entityIds.size() - 1;
    record.archetype = oldArchetype;

    // If the archetype is empty after removing the entity, remove it from the entityArchetypeMap
    if (oldArchetype->entityIds.empty())
    {
        entityArchetypeMap.erase(entityId);
    }
}

const bool ECS::HasComponent(const EntityId entityId, size_t compTypeId) const {
    auto iter = componentMasks.find(entityId);
    if (iter != componentMasks.end()) {
        return iter->second.HasComponent(compTypeId);
    }
    return false;
}

void ECS::RegisterSystem(const std::uint8_t& layer, SystemBase* system)
{
    systems[layer].push_back(system);
}

void ECS::UnregisterSystem(const std::uint8_t& layer, SystemBase* system) {
    auto systemsArrayIt = systems.find(layer);
    if (systemsArrayIt != systems.end()) {
        std::vector<SystemBase*>& systemsArray = systemsArrayIt->second;

        auto systemIt = std::find(systemsArray.begin(), systemsArray.end(), system);
        if (systemIt != systemsArray.end()) {
            systemsArray.erase(systemIt);
        }
    }
}

Archetype* ECS::GetArchetype(const ComponentMask& compMask)
{
    for (Archetype* archetype : archetypes)
    {
        if (archetype->componentMask.IsExactMatch(compMask)) {
            return archetype;
        }
    }

    Archetype* newArchetype = new Archetype;
    newArchetype->componentMask = compMask;

    for (size_t i = 0; i < compMask.Size(); ++i) {
        if (compMask.HasComponent(i)) {
            size_t componentSize = componentMap[i]->GetSize();
            newArchetype->componentData.push_back(new unsigned char[componentSize]);
            newArchetype->componentDataSize.push_back(componentSize);
        }
    }

    archetypes.push_back(newArchetype);
    return newArchetype;
}

EntityId ECS::GetNewId() {
    return entityIdCounter++;
};

size_t ECS::GetEntityIndex(const Archetype* archetype, EntityId entityId) {
    return std::find(
        archetype->entityIds.begin(),
        archetype->entityIds.end() - 1,
        entityId
    ) - archetype->entityIds.begin();
}

void ECS::UpdateRecord(EntityId entityId, Archetype* archetype, Record& record) {
    archetype->entityIds.push_back(entityId);
    record.index = archetype->entityIds.size() - 1;
    record.archetype = archetype;
}

void ECS::UpdateEntitiesInArchetypes(Archetype* oldArchetype, Archetype* newArchetype, EntityId entityId) {
    auto lastEntity = oldArchetype->entityIds.size() - 1;
    auto entityIndex = GetEntityIndex(oldArchetype, entityId);

    if (!oldArchetype->entityIds.empty() && entityIndex < oldArchetype->entityIds.size()) {
        std::iter_swap(oldArchetype->entityIds.begin() + entityIndex, oldArchetype->entityIds.begin() + lastEntity);
        for (std::size_t i = 0, j = 0, index = 0; i < oldArchetype->componentMask.Size() - 1; ++i) {
            if (oldArchetype->componentMask.HasComponent(i)) {
                const ComponentBase* const componentBase = componentMap[i];
                SwapComponentData(i, oldArchetype, index, entityIndex);
                ++index;
            }
        }

        // swap the entity leaving the archetype with the one at the end of the list before deleting the leaving entity by popping
        oldArchetype->entityIds.pop_back();
    }
}

size_t ECS::FindCurrentSize(Archetype* archetype, size_t ComponentTypeIndex, size_t index) {
    const ComponentBase* componentBase = componentMap[ComponentTypeIndex];
    const std::size_t& newCompDataSize = componentBase->GetSize();

    std::size_t currentSize = archetype->entityIds.size() * newCompDataSize;
    std::size_t newSize = currentSize + newCompDataSize;

    if (newSize > archetype->componentDataSize[index]) {
        Reallocate(newCompDataSize, archetype, index);
    }

    return currentSize;
}

void ECS::Reallocate(const size_t newComponentDataSize, Archetype* archetype, size_t index) {
    const ComponentBase* compbase = componentMap[index];

    archetype->componentDataSize[index] *= 2;
    archetype->componentDataSize[index] += newComponentDataSize;

    unsigned char* newData = new unsigned char[archetype->componentDataSize[index]];

    for (auto e = 0; e < archetype->entityIds.size(); ++e) {
        compbase->MoveData(&archetype->componentData[index][e * newComponentDataSize],
            &newData[e * newComponentDataSize]);
        compbase->DestroyData(&archetype->componentData[index][e * newComponentDataSize]);
    }

    delete[] archetype->componentData[index];
    archetype->componentData[index] = newData;
}

void ECS::MoveComponentData(const ComponentBase* componentBase, Archetype* fromArch, Archetype* toArch, size_t fromIndex, size_t toIndex, size_t fromEntityIndex, size_t toEntityIndex) {
    //const ComponentBase* componentBase = componentMap[compTypeIndex];
    const std::size_t componentSize = componentBase->GetSize();

    componentBase->MoveData(&fromArch->componentData[fromIndex][fromEntityIndex * componentSize],
        &toArch->componentData[toIndex][toEntityIndex * componentSize]);
    componentBase->DestroyData(&fromArch->componentData[fromIndex][fromEntityIndex * componentSize]);
}

void ECS::TransferComponentData(const size_t compTypeIndex, Archetype* fromArch, Archetype* toArch, size_t i, size_t j, size_t entityIndex) {
    const ComponentBase* componentBase = componentMap[compTypeIndex];
    MoveComponentData(componentBase, fromArch, toArch, i, j, entityIndex, toArch->entityIds.size());
}

void ECS::SwapComponentData(const size_t compTypeIndex, Archetype* arch, size_t toIndex, size_t entityIndex) {
    const auto lastEntity = arch->entityIds.size() - 1;
    const ComponentBase* componentBase = componentMap[compTypeIndex];

    MoveComponentData(componentBase, arch, arch, toIndex, toIndex, lastEntity, entityIndex);

    
    //const ComponentBase* componentBase = componentMap[compTypeIndex];
    //const std::size_t componentSize = componentBase->GetSize();
    //
    //const auto lastEntity = arch->entityIds.size() - 1;
    //
    //componentBase->MoveData(&arch->componentData[index][lastEntity * componentSize],
    //    &arch->componentData[index][entityIndex * componentSize]);
    //componentBase->DestroyData(&arch->componentData[index][lastEntity * componentSize]);
}

void ECS::RemoveAndAllocateComponentData(const size_t compTypeIndex, Archetype* fromArchetype, Archetype* toArchetype, size_t i, size_t j, size_t entityIndex) {
    const ComponentBase* const componentBase = componentMap[compTypeIndex];
    const std::size_t componentDataSize = componentBase->GetSize();

    size_t currentSize = FindCurrentSize(toArchetype, compTypeIndex, j);

    const size_t lastEntity = fromArchetype->entityIds.size() - 1;

    componentBase->MoveData(&fromArchetype->componentData[i][entityIndex * componentDataSize],
        &toArchetype->componentData[j][currentSize]);

    if (entityIndex != lastEntity) {
        componentBase->MoveData(&fromArchetype->componentData[i][lastEntity * componentDataSize],
            &fromArchetype->componentData[i][entityIndex * componentDataSize]);
    }

    componentBase->DestroyData(&fromArchetype->componentData[i][lastEntity * componentDataSize]);
}





Is there a way to create a subclass/helper class that can handle the reallocate/transfer/component management?