#include "scene_manager.h"
#include "application_class.h"

scene_manager::scene_manager()
{
}

scene_manager::~scene_manager()
{
    shutdown();
}

bool scene_manager::initialize(application_class* app)
{
    if (!app) {
        Logger::Get().Log("Application pointer is null", __FILE__, __LINE__, Logger::LogLevel::Error);
        return false;
    }
    
    app_ = app;

    if (app_ == nullptr) {
        Logger::Get().Log("Application pointer is null", __FILE__, __LINE__, Logger::LogLevel::Error);
        return false;
    }

    direct_3d_ = app_->get_direct_3d();
    if (!direct_3d_) {
        Logger::Get().Log("Direct3D pointer is null", __FILE__, __LINE__, Logger::LogLevel::Error);
        return false;
    }
    
    return true;
}

bool scene_manager::shutdown()
{
    app_ = nullptr;
    return true;
}

bool scene_manager::save_scene_as() {

    Logger::Get().Log("Saving scene as...", __FILE__, __LINE__, Logger::LogLevel::Info);
    
    OPENFILENAME ofn;
    wchar_t szFile[260] = { 0 };

    ZeroMemory(&ofn, sizeof(ofn));
    ofn.lStructSize = sizeof(ofn);
    ofn.hwndOwner = app_->get_hwnd();
    ofn.lpstrFile = szFile;
    ofn.nMaxFile = sizeof(szFile);
    ofn.lpstrFilter = L"Ker Scene\0*.ker\0";
    ofn.nFilterIndex = 1;
    ofn.lpstrFileTitle = NULL;
    ofn.nMaxFileTitle = 0;
    ofn.lpstrInitialDir = NULL;
    ofn.Flags = OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT;
    ofn.lpstrDefExt = L"ker";

    if (GetSaveFileName(&ofn) == TRUE) {
        std::filesystem::path filepath = ofn.lpstrFile;
        
        // Mettre à jour le chemin de scène
        scene_path_ = convert_w_string_to_string(filepath.wstring());
        //object_vec_ = app_->get_kobjects(); // TODO
        
        // Sauvegarder la scène avec le nouveau chemin
        save_scene();
        
        Logger::Get().Log("Scene saved as: " + scene_path_, __FILE__, __LINE__, Logger::LogLevel::Info);
        return false;
    }
    
    return true;
}

bool scene_manager::load_scene() {

    // Ouvrir une boîte de dialogue pour sélectionner un fichier .ker
    OPENFILENAME ofn;
    wchar_t szFile[260] = { 0 };
    ZeroMemory(&ofn, sizeof(ofn));
    ofn.lStructSize = sizeof(ofn);
    ofn.hwndOwner = app_->get_hwnd();
    ofn.lpstrFile = szFile;
    ofn.nMaxFile = sizeof(szFile);
    ofn.lpstrFilter = L"Ker Scene\0*.ker\0";
    ofn.nFilterIndex = 1;
    ofn.lpstrFileTitle = NULL;
    ofn.nMaxFileTitle = 0;
    ofn.lpstrInitialDir = NULL;
    ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
    ofn.lpstrDefExt = L"ker";
    if (GetOpenFileName(&ofn) != TRUE) {
        Logger::Get().Log("Load scene canceled or failed", __FILE__, __LINE__, Logger::LogLevel::Warning);
        return false; // L'utilisateur a annulé la boîte de dialogue
    }
    std::filesystem::path filepath = ofn.lpstrFile;
    scene_path_ = convert_w_string_to_string(filepath.wstring());
    if (scene_path_.empty()) {
        Logger::Get().Log("Invalid scene path", __FILE__, __LINE__, Logger::LogLevel::Error);
        return false;
    }
    Logger::Get().Log("Loading scene from: " + scene_path_, __FILE__, __LINE__, Logger::LogLevel::Info);


    w_folder_ = app_->get_w_folder();
    std::filesystem::current_path(w_folder_);

    
    // S'assurer que les composants sont enregistrés
    EnregistrerTousLesComposants();
    
    std::ifstream inFile(scene_path_);
    if (!inFile.is_open()) {
        Logger::Get().Log("Échec de l'ouverture du fichier", __FILE__, __LINE__, Logger::LogLevel::Error);
        return false;
    }

    auto entity_manager = app_->get_entity_manager();
    entity_manager->Clear();

    std::string line;
    std::shared_ptr<ecs::Entity> currentEntity = nullptr;
    TextureContainer currentTextures;
    
    // Cache de modèle pour éviter de charger plusieurs fois le même modèle
    auto& modelCache = app_->get_model_cache();

    while (std::getline(inFile, line)) {
        line = trim(line);
        
        if (line.substr(0, 7) == "Entity:") {
            // Format: Entity:id:name
            std::stringstream ss(line.substr(7));
            std::string idStr, name;
            std::getline(ss, idStr, ':');
            std::getline(ss, name);

            int id = std::stoi(idStr);
            currentEntity = entity_manager->CreateEntity();
            
            // Ajouter IdentityComponent en premier
            auto identity = currentEntity->AddComponent<ecs::IdentityComponent>(id);
            identity->SetName(name);
            
            // Réinitialiser les données de texture pour cette entité
            currentTextures = TextureContainer();
        }
        else if (line == "EndEntity") {
            // Finaliser l'entité - chargement du modèle si nécessaire
            if (currentEntity) {
                auto modelPathComponent = currentEntity->GetComponent<ecs::ModelPathComponent>();
                if (modelPathComponent && !modelPathComponent->GetPath().empty()) {
                    std::wstring modelPath = modelPathComponent->GetPath();
                    std::string modelKey = convert_w_string_to_string(modelPath);
                    
                    // Vérifier si le modèle existe dans le cache
                    std::shared_ptr<model_class> model;
                    auto it = modelCache.find(modelKey);
                    if (it != modelCache.end()) {
                        model = it->second;
                    } else {
                        // Créer et initialiser un nouveau modèle
                        model = std::make_shared<model_class>();
                        
                        // Précharger les textures
                        if (!model->PreloadTextures(direct_3d_->get_device(), direct_3d_->get_device_context(), currentTextures)) {
                            Logger::Get().Log("Échec du préchargement des textures", __FILE__, __LINE__, Logger::LogLevel::Error);
                        }
                        
                        char modelFilename[256];
                        size_t convertedChars = 0;
                        wcstombs_s(&convertedChars, modelFilename, sizeof(modelFilename), modelPath.c_str(), _TRUNCATE);
                        
                        if (!model->Initialize(direct_3d_->get_device(), direct_3d_->get_device_context(), modelFilename, currentTextures)) {
                            Logger::Get().Log("Échec d'initialisation du modèle", __FILE__, __LINE__, Logger::LogLevel::Error);
                        } else {
                            // Ajouter au cache
                            modelCache[modelKey] = model;
                        }
                    }
                    
                    // Ajouter le composant de rendu avec le modèle
                    if (model) {
                        auto renderComponent = currentEntity->GetComponent<ecs::RenderComponent>();
                        if (!renderComponent) {
                            renderComponent = currentEntity->AddComponent<ecs::RenderComponent>();
                        }
                        renderComponent->InitializeWithModel(model);
                    }
                }
            }
            
            currentEntity = nullptr;
        }
        else if (!line.empty() && line.find("TextureData:") == 0) {
            Logger::Get().Log(line.substr(11), __FILE__, __LINE__, Logger::LogLevel::Info);
            if (currentEntity) {
                std::stringstream ss(line.substr(11));
                std::string token;

                // log the token values for debugging
                Logger::Get().Log( token, __FILE__, __LINE__, Logger::LogLevel::Info);

                std::getline(ss, token, ':');
                // Diffuse
                int diffuseCount = 0;
                if (std::getline(ss, token, ':') && !token.empty())
                    diffuseCount = std::stoi(token);
                for (int i = 0; i < diffuseCount; i++) {
                    if (std::getline(ss, token, ':'))
                        currentTextures.diffusePaths.push_back(std::wstring(token.begin(), token.end()));
                }

                // Normal
                int normalCount = 0;
                if (std::getline(ss, token, ':') && !token.empty())
                    normalCount = std::stoi(token);
                for (int i = 0; i < normalCount; i++) {
                    if (std::getline(ss, token, ':'))
                        currentTextures.normalPaths.push_back(std::wstring(token.begin(), token.end()));
                }
                
                // Textures spéculaires
                int specularCount = 0;
                if (std::getline(ss, token, ':') && !token.empty())
                    specularCount = std::stoi(token);
                for (int i = 0; i < specularCount; i++) {
                    if (std::getline(ss, token, ':'))
                        currentTextures.specularPaths.push_back(std::wstring(token.begin(), token.end()));
                }
                
                // Textures alpha
                int alphaCount = 0;
                if (std::getline(ss, token, ':') && !token.empty())
                    alphaCount = std::stoi(token);
                for (int i = 0; i < alphaCount; i++) {
                    if (std::getline(ss, token, ':'))
                        currentTextures.alphaPaths.push_back(std::wstring(token.begin(), token.end()));
                }
            }
        }
        else if (currentEntity) {
            // Déterminer le type de composant
            std::string componentType = line.substr(0, line.find(':'));
            
            // Créer le composant via la factory et le désérialiser
            auto& factory = ComponentFactory::Get();
            auto component = factory.CreerComposant(componentType, currentEntity);
            
            if (component) {
                component->Deserialize(line);
            }
        }
    }

    Logger::Get().Log("Scène chargée avec succès", __FILE__, __LINE__, Logger::LogLevel::Info);
    return true;
}


bool scene_manager::save_scene() {
    entity_ = app_->get_entity_manager()->GetAllEntities();

    if (scene_path_.empty() && !save_scene_as()) {
        return false;
    }

    std::ofstream outFile(scene_path_);
    if (!outFile.is_open()) {
        Logger::Get().Log("Échec de l'ouverture du fichier", __FILE__, __LINE__, Logger::LogLevel::Error);
        return false;
    }

    for (const auto& entity : entity_) {
        // Récupérer l'ID de l'entité via IdentityComponent
        auto identity = entity->GetComponent<ecs::IdentityComponent>();
        if (!identity) continue;

        // Écrire l'en-tête de l'entité
        outFile << "Entity:" << identity->GetId() << ":" << identity->GetName() << std::endl;

        // Sérialiser tous les composants
        for (const auto& [typeId, component] : entity->GetAllComponents()) {
            std::string serializedData = component->Serialize();
            if (!serializedData.empty()) {
                outFile << serializedData << std::endl;
            }
        }

        // Sérialiser les textures pour les composants de rendu
        auto renderComponent = entity->GetComponent<ecs::RenderComponent>();
        if (renderComponent && renderComponent->GetModel()) {
            const auto& model = renderComponent->GetModel();
            const auto& textureContainer = model->GetTextureContainer();

            outFile << "TextureData:";
            
            // Textures diffuses
            const auto& diffusePaths = textureContainer.GetPaths(TextureType::Diffuse);
            outFile << diffusePaths.size();
            for (const auto& path : diffusePaths) {
                outFile << ":" << convert_w_string_to_string(path);
            }
            
            // Textures normales
            const auto& normalPaths = textureContainer.GetPaths(TextureType::Normal);
            outFile << ":" << normalPaths.size();
            for (const auto& path : normalPaths) {
                outFile << ":" << convert_w_string_to_string(path);
            }
            
            // Textures spéculaires
            const auto& specularPaths = textureContainer.GetPaths(TextureType::Specular);
            outFile << ":" << specularPaths.size();
            for (const auto& path : specularPaths) {
                outFile << ":" << convert_w_string_to_string(path);
            }
            
            // Textures alpha
            const auto& alphaPaths = textureContainer.GetPaths(TextureType::Alpha);
            outFile << ":" << alphaPaths.size();
            for (const auto& path : alphaPaths) {
                outFile << ":" << convert_w_string_to_string(path);
            }
            
            outFile << std::endl;
        }

        outFile << "EndEntity" << std::endl;
    }

    outFile.close();
    Logger::Get().Log("Scène sauvegardée avec succès: " + scene_path_, __FILE__, __LINE__, Logger::LogLevel::Info);
    return true;
}

std::wstring scene_manager::get_scene_path()
{
    OPENFILENAME ofn;
    wchar_t szFile[260];

    ZeroMemory(&ofn, sizeof(ofn));
    ofn.lStructSize = sizeof(ofn);
    ofn.hwndOwner = app_->get_hwnd();
    ofn.lpstrFile = szFile;
    ofn.lpstrFile[0] = '\0';
    ofn.nMaxFile = sizeof(szFile);
    ofn.lpstrFilter = L"Ker Scene\0*.ker\0";
    ofn.nFilterIndex = 1;
    ofn.lpstrFileTitle = NULL;
    ofn.nMaxFileTitle = 0;
    ofn.lpstrInitialDir = NULL;
    ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

    if (GetOpenFileName(&ofn) == TRUE)
    {
        std::filesystem::path filepath = ofn.lpstrFile;
        return filepath.wstring();
    }
    return L"";
}

std::string scene_manager::convert_w_string_to_string(const std::wstring& wstr)
{
    if (wstr.empty()) return std::string();

    int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL);
    std::string str(size_needed, 0);
    WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &str[0], size_needed, NULL, NULL);
    return str;
}

