#pragma once
#include "../component.h"
#include "model_class.h"
#include <memory>
#include <string>
#include <map>
#include <WICTextureLoader.h>

// Déclaration externe de la variable globale définie dans application_class.h
/**
 * Declaration of the global model cache externe variable from application_class.h .
 * This variable is used to cache models loaded from files to avoid reloading them multiple times.
 */
extern std::map<std::string, std::shared_ptr<model_class>> g_model_cache;

namespace ecs {
    /**
     * Enum for different types of textures used in rendering.
     */
    enum class TextureType
    {
        Diffuse,
        Normal,
        Specular,
        Alpha,
        Reflection
    };

class RenderComponent : public Component {
public:
    /**
     * Builder for the RenderComponent class.
     */
    RenderComponent() : m_model(nullptr), m_isVisible(true) {}
    ~RenderComponent() = default;

    void Initialize() override {}
    void Update(float deltaTime) override {}

    /**
     * Initialize the RenderComponent with a model.
     * This method allows the component to be initialized with an existing model instance.
     * @param model A shared pointer to the model_class instance to use.
     * @return True if initialization was successful, false otherwise.
     */
    bool InitializeWithModel(std::shared_ptr<model_class> model) {
        if (!model) return false;
        m_model = model;
        return true;
    }

    /**
     * Initialize the RenderComponent from a model file.
     * This method checks if the model is already cached; if not, it loads the model from the specified file.
     * @param device The Direct3D device used for rendering.
     * @param deviceContext The Direct3D device context used for rendering.
     * @param modelFilename The path to the model file to load.
     * @param textureContainer The container for textures used by the model.
     * @return True if initialization was successful, false otherwise.
     */
    bool InitializeFromFile(ID3D11Device* device, ID3D11DeviceContext* deviceContext, 
                           const char* modelFilename, TextureContainer& textureContainer) {
        // Vérifier si le modèle existe déjà dans le cache
        std::string filename(modelFilename);
        auto it = g_model_cache.find(filename);
        if (it != g_model_cache.end()) {
            m_model = it->second;
        } else {
            // Créer un nouveau modèle
            auto new_model = std::make_shared<model_class>();
            if (!new_model->Initialize(device, deviceContext, const_cast<char*>(modelFilename), textureContainer)) {
                return false;
            }
            g_model_cache[filename] = new_model;
            m_model = new_model;
        }

        m_modelFilePath = modelFilename;
        return true;
    }

    /**
     * Load textures from a list of file paths into the texture container.
     * This method uses DirectX's WIC texture loader to load textures from the specified paths.
     * @param texturePaths A vector of file paths to the textures to load.
     * @param texturesContainer The container where the loaded textures will be stored.
     * @param device The Direct3D device used for rendering.
     * @param deviceContext The Direct3D device context used for rendering.
     * @return True if all textures were loaded successfully, false otherwise.
     */
    bool LoadTexturesFromPath(std::vector<std::wstring>& texturePaths, TextureContainer& texturesContainer,
                             ID3D11Device* device, ID3D11DeviceContext* deviceContext) {
        HRESULT result;
        
        int i = 0;
        for (const auto& texturePath : texturePaths) {
            ID3D11ShaderResourceView* texture = nullptr;
            result = DirectX::CreateWICTextureFromFile(device, deviceContext, texturePath.c_str(), nullptr, &texture);
            if (FAILED(result)) {
                return false;
            }
            texturesContainer.AssignTexture(texturesContainer, texture, texturePath, i);
            i++;
        }

        return true;
    }

    /**
     * Get the model associated with this RenderComponent.
     * @return A shared pointer to the model_class instance.
     */
    std::shared_ptr<model_class> GetModel() const { return m_model; }
    /**
     * Set the model for this RenderComponent.
     * This method allows the component to be set with an existing model instance.
     * @param model A shared pointer to the model_class instance to set.
     */
    void SetModel(std::shared_ptr<model_class> model) { m_model = model; }

    /**
     * Get the file path of the model associated with this RenderComponent.
     * @return The file path as a string.
     */
    const std::string& GetModelFilePath() const { return m_modelFilePath; }
    /**
     * Set the file path of the model for this RenderComponent.
     * This method allows the component to be set with a specific model file path.
     * @param path The file path to set as a string.
     */
    void SetModelFilePath(const std::string& path) { m_modelFilePath = path; }

    /**
     * Check if the model is currently visible.
     * @return True if the model is visible, false otherwise.
     */
    bool IsVisible() const { return m_isVisible; }
    /**
     * Set the visibility of the model.
     * This method allows the component to control whether the model should be rendered or not.
     * @param visible True to make the model visible, false to hide it.
     */
    void SetVisible(bool visible) { m_isVisible = visible; }

    /** 
     * Get a texture of a specific type by index.
     * This method retrieves the texture from the model based on the specified type and index.
     * @param type The type of texture to retrieve (Diffuse, Normal, Specular, Alpha).
     * @param index The index of the texture to retrieve (default is 0).
     * @return A pointer to the ID3D11ShaderResourceView of the texture, or nullptr if not found.
     */
    ID3D11ShaderResourceView* GetTexture(TextureType type, int index = 0) {
        if (!m_model) return nullptr;
        
        switch (type) {
            case TextureType::Diffuse: 
                return m_model->GetTexture(::TextureType::Diffuse, index);
            case TextureType::Normal: 
                return m_model->GetTexture(::TextureType::Normal, index);
            case TextureType::Specular: 
                return m_model->GetTexture(::TextureType::Specular, index);
            case TextureType::Alpha: 
                return m_model->GetTexture(::TextureType::Alpha, index);
            default: 
                return nullptr;
        }
    }

    /**
     * Get the number of vertices in the model.
     * This method retrieves the vertex count from the model.
     * @return The number of vertices as an integer.
     */
    int GetIndexCount() const {
        return m_model ? m_model->GetIndexCount() : 0;
    }

    /**
     * Render the model using the provided device context.
     * This method calls the Render method of the model if it is initialized and visible.
     * @param deviceContext The Direct3D device context used for rendering.
     */
    void Render(ID3D11DeviceContext* deviceContext) {
        if (m_model && m_isVisible) {
            m_model->Render(deviceContext);
        }
    }

    std::string Serialize() const override {
        if (!m_model) return "RenderComponent:NoModel";
    
        std::stringstream ss;
        ss << "RenderComponent:HasModel";
        return ss.str();
    }

    bool Deserialize(const std::string& data) override {
        std::stringstream ss(data);
        std::string type;
        std::getline(ss, type, ':');
    
        if (type != "RenderComponent") return false;
    
        std::string hasModel;
        std::getline(ss, hasModel);
    
        // Le modèle sera chargé séparément
        return true;
    }

    void OnImGuiRender() override {
        ImGui::Checkbox("Visible", &m_isVisible);
        ImGui::Text("Model File Path: %s", m_modelFilePath.c_str());
        if (m_model) {
            ImGui::Text("Index Count: %d", m_model->GetIndexCount());
        } else {
            ImGui::Text("No model loaded.");
        }
    }

private:
    std::shared_ptr<model_class> m_model;
    std::string m_modelFilePath;
    bool m_isVisible;
};

}  // namespace ecs
