#pragma once
#include "entity.h"
#include <vector>
#include <unordered_map>
#include <queue>
#include <d3d11.h>

namespace ecs {

class EntityManager {
public:

    /**
     * @brief Type pour l'ID d'une entité
     */
    EntityManager() : m_NextEntityID(0) {}
    ~EntityManager() = default;

    /**
     * Create a new entity.
     * @return A shared pointer to the newly created entity.
     */
    std::shared_ptr<Entity> CreateEntity() {
        EntityID id;
        
        // Réutiliser les IDs des entités supprimées si possible
        if (!m_FreeIDs.empty()) {
            id = m_FreeIDs.front();
            m_FreeIDs.pop();
        } else {
            id = m_NextEntityID++;
        }
        
        auto entity = std::make_shared<Entity>(id);
        entity->SetCamera(camera_);
        entity->SetSoundSystem(sound_system_);
        entity->SetDevice(device);
        entity->SetContext(context);
        m_Entities[id] = entity;
        
        return entity;
    }

    /**
     * Destroy an entity by its ID and recycle its ID for future use.
     * @param id The ID of the entity to destroy.
     */
    void DestroyEntity(EntityID id) {
        auto it = m_Entities.find(id);
        if (it != m_Entities.end()) {
            it->second->release();
            m_Entities.erase(it);
            m_FreeIDs.push(id); // Recycler l'ID
        }
    }

    /** 
     * Get an entity by its ID.
     * @param id The ID of the entity to retrieve.
     * @return A shared pointer to the entity, or nullptr if it does not exist.
     */
    std::shared_ptr<Entity> GetEntity(EntityID id) {
        auto it = m_Entities.find(id);
        if (it != m_Entities.end()) {
            return it->second;
        }
        return nullptr;
    }

    /**
     * Update all entities by calling their UpdateComponents method.
     * @param deltaTime The time elapsed since the last update.
     */
    void UpdateEntities(float deltaTime) {
        for (auto& [id, entity] : m_Entities) {
            entity->UpdateComponents(deltaTime);
        }
    }

    /**
     * Get all entities managed by the EntityManager.
     * @return A vector of shared pointers to all entities.
     */
    std::vector<std::shared_ptr<Entity>> GetAllEntities() {
        std::vector<std::shared_ptr<Entity>> result;
        result.reserve(m_Entities.size());
        
        for (const auto& [id, entity] : m_Entities) {
            result.push_back(entity);
        }
        
        return result;
    }

    /**
     * Get all entities that have a specific component type.
     * @tparam T The component type to filter entities by.
     * @return A vector of shared pointers to entities that have the specified component.
     */
    template<typename T>
    std::vector<std::shared_ptr<Entity>> GetEntitiesWithComponent() {
        static_assert(std::is_base_of<Component, T>::value, "T must derive from Component");
        
        std::vector<std::shared_ptr<Entity>> result;
        
        for (auto& [id, entity] : m_Entities) {
            if (entity->HasComponent<T>()) {
                result.push_back(entity);
            }
        }
        
        return result;
    }

    /**
     * Get the total number of entities managed by the EntityManager.
     * @return The count of entities.
     */
    size_t GetEntityCount() const {
        return m_Entities.size();
    }

    /**
     * Clear all entities and reset the EntityManager.
     * This will remove all entities and free their IDs for future use.
     */
    void Clear() {
        m_Entities.clear();
        
        // Vider la file des IDs libres
        std::queue<EntityID> empty;
        std::swap(m_FreeIDs, empty);
        
        m_NextEntityID = 0;
    }

    /**
     * Get entity by ID (const version).
     * @param id The ID of the entity to retrieve.
     * @return A shared pointer to the entity, or nullptr if it does not exist.
     */
    std::shared_ptr<Entity> GetEntityByID(EntityID entityID)
    {
        if (entityID < m_Entities.size())
        { 
            return m_Entities[entityID];
        }
        
        return nullptr;

    }

    /**
     * Set the main camera that will be propagate to the entity when creating a new entity.
     * @param camera Pointer to the camera_class instance.
     */
    void SetCamera(camera_class* camera) { camera_ = camera; }

    /**
     * Get the main camera used by entities.
     * @return Pointer to the camera_class instance.
     */
    camera_class* GetCamera() const { return camera_; }

    /**
     * Set the FMOD sound system that will be propagate to the entity when creating a new entity.
     * @param sound_system Pointer to the FMOD::System instance.
     */
    void SetSoundSystem(FMOD::System* sound_system) { sound_system_ = sound_system; }
    /**
     * Get the FMOD sound system used by entities.
     * @return Pointer to the FMOD::System instance.
     */
    FMOD::System* GetSoundSystem() const { return sound_system_; }

    void SetDevice (ID3D11Device* dev) { device = dev; }
    void SetContext(ID3D11DeviceContext* ctx) { context = ctx; }
    ID3D11Device* GetDevice() const { return device; }
    ID3D11DeviceContext* GetContext() const { return context; }

    int GetSkyID() const { return sky_id_; }
    void SetSkyID(int id) { sky_id_ = id; }

private:
    EntityID m_NextEntityID;
    std::unordered_map<EntityID, std::shared_ptr<Entity>> m_Entities;
    std::queue<EntityID> m_FreeIDs; // IDs à réutiliser
    camera_class* camera_ = nullptr;
    FMOD::System* sound_system_ = nullptr;
    ID3D11Device* device;
    ID3D11DeviceContext* context;
    int sky_id_ = -1;
};

}  // namespace ecs
