#pragma once
#include "component.h"
#include <unordered_map>
#include <vector>
#include <memory>
#include <algorithm>
#include <cassert>
#include <WICTextureLoader.h>
#include <d3d11.h>

#include "camera_class.h"
#include <Fmod/core/inc/fmod.hpp>

namespace ecs {
    /**
     * Type alias for a unique identifier for an entity.
     */
    using EntityID = uint32_t;

class Entity : public std::enable_shared_from_this<Entity> {
public:
    /**
     * Builder for an Entity with a unique ID.
     */
    explicit Entity(EntityID id) : m_ID(id) {}
    ~Entity() = default;

    /**
     * No copy constructor or assignment operator to prevent copying.
     */
    Entity(const Entity&) = delete;
    Entity& operator=(const Entity&) = delete;

    /**
     * Move constructor and assignment operator to allow moving entities.
     */
    Entity(Entity&&) = default;
    Entity& operator=(Entity&&) = default;

    /**
     * Get the unique identifier for the entity.
     * @return The unique ID of the entity.
     */
    EntityID GetID() const { return m_ID; }

    /**
     * Add a component of type T to the entity.
     * If the component already exists, it returns the existing component.
     * @tparam T 
     * @param args Arguments to construct the component.
     * @return A shared pointer to the added or existing component.
     */
    template<typename T, typename... Args>
    std::shared_ptr<T> AddComponent(Args&&... args) {
        static_assert(std::is_base_of<Component, T>::value, "T must derive from Component");
        
        ComponentTypeID typeID = GetComponentTypeID<T>();
        
        // Vérifier si le composant existe déjà
        if (m_Components.find(typeID) != m_Components.end()) {
            return std::static_pointer_cast<T>(m_Components[typeID]);
        }
        
        // Créer et ajouter le composant
        auto component = std::make_shared<T>(std::forward<Args>(args)...);
        m_Components[typeID] = component;

        component->SetParent(shared_from_this());
        
        
        // Initialiser le composant
        component->Initialize();
        
        return component;
    }

    /**
     * Get a component of type T from the entity.
     * @tparam T 
     * @return A shared pointer to the component if it exists, nullptr otherwise.
     */
    template<typename T>
    std::shared_ptr<T> GetComponent() {
        static_assert(std::is_base_of<Component, T>::value, "T must derive from Component");
        
        ComponentTypeID typeID = GetComponentTypeID<T>();
        
        auto it = m_Components.find(typeID);
        if (it != m_Components.end()) {
            return std::static_pointer_cast<T>(it->second);
        }
        
        return nullptr;
    }

    /**
     * Check if the entity has a component of type T.
     * @tparam T 
     * @return true if the entity has the component, false otherwise.
     */
    template<typename T>
    bool HasComponent() const {
        static_assert(std::is_base_of<Component, T>::value, "T must derive from Component");
        
        ComponentTypeID typeID = GetComponentTypeID<T>();
        return m_Components.find(typeID) != m_Components.end();
    }

    /**
     * Remove a component of type T from the entity.
     * @tparam T 
     */
    template<typename T>
    void RemoveComponent() {
        static_assert(std::is_base_of<Component, T>::value, "T must derive from Component");
        
        ComponentTypeID typeID = GetComponentTypeID<T>();
        auto it = m_Components.find(typeID);
        
        if (it != m_Components.end()) {
            m_Components.erase(it);
        }
    }

    /**
     * Update all components of the entity.
     * @param deltaTime 
     */
    void UpdateComponents(float deltaTime) {
        for (auto& [typeID, component] : m_Components) {
            component->Update(deltaTime);
        }
    }

    /**
     * Get all components of the entity.
     * @return A constant reference to the map of components.
     */
    const std::unordered_map<ComponentTypeID, ComponentPtr>& GetAllComponents() const {
        return m_Components;
    }

    /**
     * Set the main camera of the scene to be used by components like AudioComponent.
     * @param camera 
     */
    void SetCamera(camera_class* camera) {m_camera = camera; }

    /**
     * Get the main camera of the scene.
     * @return A pointer to the main camera.
     */
    camera_class* GetCamera() const { return m_camera; }

    /**
     * Set the FMOD sound system to be used by components like AudioComponent.
     * @param soundSystem 
     */
    void SetSoundSystem(FMOD::System* soundSystem) {m_soundSystem = soundSystem; }
    /**
     * Get the FMOD sound system.
     * @return A pointer to the FMOD sound system.
     */
    FMOD::System* GetSoundSystem() const { return m_soundSystem; }

    /**
     * Release all data associated with the entity.
     * This includes clearing all components and resetting the camera and sound system pointers.
     */
    void release()
    {
        // call shutdown on all components if needed
        for (auto& [typeID, component] : m_Components) {
            // If components had a shutdown method, we would call it here
            component->Shutdown();
        }
        m_Components.clear();
        m_camera = nullptr;
        m_soundSystem = nullptr;
    }

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

private:

    /**
     * Unique identifier for the entity.
     */
    EntityID m_ID;
    /**
     * Map to hold components associated with the entity.
     * The key is the type ID of the component, and the value is a shared pointer to the component.
     */
    std::unordered_map<ComponentTypeID, ComponentPtr> m_Components;

    // camera
    camera_class* m_camera = nullptr;

    // FMOD sound system
    FMOD::System* m_soundSystem = nullptr;

    ID3D11Device* device;
    ID3D11DeviceContext* context;
};

}  // namespace ecs
