#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 {


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


    /**
     * Initialize the component by setting up the Direct3D device and context from the parent entity.
     * This method is called when the component is added to an entity.
     */
    void Initialize() override
    {
        if (auto parent = GetParent()) {
            SetDevice(parent->GetDevice());
            SetContext(parent->GetContext());
        }

        texture_container_buffer = TextureContainer();
        
    }
    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) R_FALSE
        m_model = model;
        R_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)) {
                R_FALSE
            }
            g_model_cache[filename] = new_model;
            m_model = new_model;
        }

        m_modelFilePath = modelFilename;
        R_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)) {
                R_FALSE
            }
            texturesContainer.AssignTexture(texturesContainer, texture, texturePath, i);
            i++;
        }

        R_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);
        }
    }

    void RenderOnlyGeometryFromSun(ID3D11DeviceContext* ctx)
    {
        if (m_model && m_is_in_sun_pov) m_model->Render(ctx);
        
    }

    void set_is_sun_visible(bool v) { m_is_in_sun_pov = v; }
    bool is_in_sun_pov() const { return m_is_in_sun_pov; }

    /**
     * Serialize the RenderComponent's state to a string.
     * This method is useful for saving the component's state or debugging.
     * @return A string representation of the RenderComponent's state.
     */
    std::string Serialize() const override {
        if (!m_model) return "RenderComponent:NoModel";

        TextureContainer texture_container_buffer = m_model->GetTextureContainer();

        auto serializePaths = [](const std::vector<std::wstring> &paths) -> std::string {
            std::string result;
            for (size_t i = 0; i < paths.size(); ++i) {
                std::string p = std::string(paths[i].begin(), paths[i].end());
                result += p;
                if (i + 1 < paths.size()) {
                    result += ",";  // virgule entre chemins mais pas après dernier
                }
            }
            return result;
        };

        int diffuse_count = (int)texture_container_buffer.diffuse.size();
        int normal_count = (int)texture_container_buffer.normal.size();
        int specular_count = (int)texture_container_buffer.specular.size();
        int alpha_count = (int)texture_container_buffer.alpha.size();

        std::string diffuse_paths_ = serializePaths(texture_container_buffer.diffusePaths);
        std::string normal_paths_ = serializePaths(texture_container_buffer.normalPaths);
        std::string specular_paths_ = serializePaths(texture_container_buffer.specularPaths);
        std::string alpha_paths_ = serializePaths(texture_container_buffer.alphaPaths);

        std::stringstream ss;
        ss << "RenderComponent:HasModel:"
           << is_shadow_caster_ << ":"
           << diffuse_count << ":" << diffuse_paths_ << ":"
           << normal_count << ":" << normal_paths_ << ":"
           << specular_count << ":" << specular_paths_ << ":"
           << alpha_count << ":" << alpha_paths_;

        return ss.str();
    }


    /**
     * Deserialize the RenderComponent's state from a string.
     * This method parses the string and sets the component's properties accordingly.
     * Note: The model will be loaded separately based on the model file path.
     * @param data The string representation of the RenderComponent's state.
     * @return True if deserialization was successful, otherwise false.
     */
    bool Deserialize(const std::string& data) override {
        std::stringstream ss(data);
        std::string type;

        if (!std::getline(ss, type, ':') || type != "RenderComponent")
            R_FALSE

        std::string token;

        if (!std::getline(ss, token, ':') || token != "HasModel")
            R_FALSE

        std::getline(ss,token, ':');
        is_shadow_caster_ = std::stoi(token);

        int diffuse_count = 0, normal_count = 0, specular_count = 0, alpha_count = 0;

        std::vector<std::wstring> paths_diffuse, paths_normal, paths_specular, paths_alpha;

        auto read_count = [&](int& count) -> bool {
            if (!std::getline(ss, token, ':'))
                R_FALSE
            try {
                count = std::stoi(token);
            }
            catch (...) {
                R_FALSE
            }
            R_TRUE
        };

        auto clean_token = [](std::string& str) {
            // Retirer virgules et espaces
            str.erase(std::remove(str.begin(), str.end(), ','), str.end());
            str.erase(0, str.find_first_not_of(" \t\n\r"));
            str.erase(str.find_last_not_of(" \t\n\r") + 1);
        };

        auto read_paths = [&](int count, std::vector<std::wstring>& paths) -> bool {
            for (int i = 0; i < count; ++i) {
                if (!std::getline(ss, token, ':'))
                    R_FALSE

                clean_token(token);

                if (!token.empty()) {
                    paths.emplace_back(token.begin(), token.end());
                    LOG_INFO("Loaded path: " + std::string(token.begin(), token.end()));
                }
            }
            R_TRUE
        };

        if (!read_count(diffuse_count)) R_FALSE
        if (!read_paths(diffuse_count, paths_diffuse)) R_FALSE

        if (!read_count(normal_count)) R_FALSE
        if (!read_paths(normal_count, paths_normal)) R_FALSE

        if (!read_count(specular_count)) R_FALSE
        if (!read_paths(specular_count, paths_specular)) R_FALSE

        if (!read_count(alpha_count)) R_FALSE
        if (!read_paths(alpha_count, paths_alpha)) R_FALSE

        // Libérer textures existantes
        for (auto& tex : texture_container_buffer.diffuse) if (tex) { tex->Release(); tex = nullptr; }
        for (auto& tex : texture_container_buffer.normal) if (tex) { tex->Release(); tex = nullptr; }
        for (auto& tex : texture_container_buffer.specular) if (tex) { tex->Release(); tex = nullptr; }
        for (auto& tex : texture_container_buffer.alpha) if (tex) { tex->Release(); tex = nullptr; }

        texture_container_buffer.diffuse.clear();
        texture_container_buffer.normal.clear();
        texture_container_buffer.specular.clear();
        texture_container_buffer.alpha.clear();

        texture_container_buffer.diffusePaths = std::move(paths_diffuse);
        texture_container_buffer.normalPaths = std::move(paths_normal);
        texture_container_buffer.specularPaths = std::move(paths_specular);
        texture_container_buffer.alphaPaths = std::move(paths_alpha);

        R_TRUE
    }

    /**
     * Render the ImGui interface for the RenderComponent.
     * This method provides a user interface for inspecting and modifying the component's properties.
     * It includes options to toggle visibility, display model information, and manage textures.
     */
    void OnImGuiRender() override {
        ImGui::Checkbox("Visible", &m_isVisible);
        C_BOX("Is shadow caster" , &is_shadow_caster_);
        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.");
        }

        ImGui::Separator();

        // Définir les types de textures_
        std::vector<std::string> textureCategories = {
            "Diffuse", "Normal", "Specular", "Alpha"
        };

        std::vector<TextureType> textureTypes = {
            TextureType::Diffuse, TextureType::Normal, 
            TextureType::Specular, TextureType::Alpha
        };

        // Créer un espace pour afficher les textures_ avec défilement
        ImGui::BeginChild("TextureChild", ImVec2(0, 200), true, ImGuiWindowFlags_HorizontalScrollbar);
            
        // Pour chaque type de texture
        for (int typeIndex = 0; typeIndex < textureCategories.size(); typeIndex++)
        {
            TextureType type = textureTypes[typeIndex];
            std::string typeName = textureCategories[typeIndex];
                
            // Afficher le titre de la catégorie
            ImGui::Text("%s:", typeName.c_str());
            ImGui::SameLine();

            // Compter combien de textures_ de ce type existent
            int textureCount = 0;
            while (GetTexture(type, textureCount) != nullptr)
            {
                textureCount++;
            }

            // Afficher toutes les textures_ existantes
            ImGui::BeginGroup();
            for (int texIndex = 0; texIndex < textureCount; texIndex++)
            {
                ID3D11ShaderResourceView* texture = GetTexture(type, texIndex);
                if (texture)
                {
                    // ID unique pour chaque bouton de texture
                    std::string buttonId = "tex##" +
                        std::to_string(typeIndex) + "_" +
                        std::to_string(texIndex);
                        
                    if (ImGui::ImageButton(buttonId.c_str(), (ImTextureID)texture, ImVec2(48, 48)))
                    {
                        // Ouvrir une boîte de dialogue pour changer la texture
                        OPENFILENAME ofn;
                        WCHAR szFile[260];
                        ZeroMemory(&ofn, sizeof(ofn));
                        ofn.lStructSize = sizeof(ofn);
                        ofn.hwndOwner = NULL;
                        ofn.lpstrFile = szFile;
                        szFile[0] = '\0';
                        ofn.nMaxFile = sizeof(szFile);
                        ofn.lpstrFilter = L"Texture\0*.png;*.jpg;*.dds\0";
                        ofn.nFilterIndex = 1;
                        ofn.lpstrInitialDir = NULL;
                        ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

                        if (GetOpenFileName(&ofn))
                        {
                            // Changer la texture existante
                            m_model->ChangeTexture(device, context, ofn.lpstrFile, type, texIndex);
                        }
                    }
                        
                    // Afficher l'indice de texture et prévisualisation au survol
                    if (ImGui::IsItemHovered())
                    {
                        ImGui::BeginTooltip();
                        ImGui::Text("%s %d", typeName.c_str(), texIndex);
                        ImGui::Image((ImTextureID)texture, ImVec2(192, 192));
                        ImGui::EndTooltip();
                    }
                        
                    ImGui::SameLine();
                }
            }
                
            // Bouton pour ajouter une nouvelle texture
            std::string addButtonLabel = "+##"  + std::to_string(typeIndex);
            if (ImGui::Button(addButtonLabel.c_str(), ImVec2(48, 48)))
            {
                // Ouvrir une boîte de dialogue pour ajouter une texture
                OPENFILENAME ofn;
                WCHAR szFile[260];
                ZeroMemory(&ofn, sizeof(ofn));
                ofn.lStructSize = sizeof(ofn);
                ofn.hwndOwner = NULL;
                ofn.lpstrFile = szFile;
                szFile[0] = '\0';
                ofn.nMaxFile = sizeof(szFile);
                ofn.lpstrFilter = L"Texture\0*.png;*.jpg;*.dds\0";
                ofn.nFilterIndex = 1;
                ofn.lpstrInitialDir = NULL;
                ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

                if (GetOpenFileName(&ofn))
                {
                    // Ajouter une nouvelle texture
                    m_model->AddTexture(device, context, ofn.lpstrFile, type);
                }
            }
                
            ImGui::EndGroup();
            ImGui::Separator();
        }
            
        ImGui::EndChild();
    }

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

    void SetTextureContainer(const TextureContainer& texContainer) { texture_container_buffer = texContainer; }
    const TextureContainer& GetTextureContainerBuffer() const { return texture_container_buffer; }

    /**
     * Get whether the model casts shadows.
     * @return True if the model casts shadows, false otherwise.
     */
    bool IsShadowCaster() const { return is_shadow_caster_; }
    /**
     * Set whether the model casts shadows.
     * @param isCaster True to make the model cast shadows, false otherwise.
     */
    void SetShadowCaster(bool isCaster) { is_shadow_caster_ = isCaster; }

private:
    std::shared_ptr<model_class> m_model;
    std::string m_modelFilePath;
    bool m_isVisible;
    ID3D11Device* device;
    ID3D11DeviceContext* context;
    TextureContainer texture_container_buffer;
    bool is_shadow_caster_ = true;
    bool m_is_in_sun_pov;
};

}  // namespace ecs
