#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) {
        LOG_ERROR("Application pointer is null");
        R_FALSE
    }
    
    app_ = app;

    if (app_ == nullptr) {
        LOG_ERROR("Application pointer is null");
        R_FALSE
    }

    direct_3d_ = app_->get_direct_3d();
    if (!direct_3d_) {
        LOG_ERROR("Direct3D pointer is null");
        R_FALSE
    }
    
    R_TRUE
}

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

bool scene_manager::save_scene_as() {
    LOG_INFO("Saving scene as...");
    
    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();
        LOG_INFO("Scene saved as: " + scene_path_);
        R_FALSE
    }
    
    R_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) {
        LOG_WARNING("Load scene canceled or failed");
        R_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()) {
        LOG_ERROR("Invalid scene path");
        R_FALSE
    }
    LOG_INFO("Loading scene from: " + scene_path_);


    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()) {
        LOG_ERROR("Échec de l'ouverture du fichier");
        R_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) {
                    LOG_WARNING("ModelPathComponent missing for entity ID: " + std::to_string(currentEntity->GetID()));
                }

                if (!modelPathComponent || modelPathComponent->GetPath().empty()) {
                    LOG_WARNING("No model path specified for entity ID: " + std::to_string(currentEntity->GetID()));
                }
                
                auto renderComponent = currentEntity->GetComponent<ecs::RenderComponent>();

                if (!renderComponent)
                {
                    LOG_WARNING("RenderComponent missing for entity ID: " + std::to_string(currentEntity->GetID()));
                }

                currentTextures = renderComponent->GetTextureContainerBuffer();

                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)) {
                        LOG_ERROR("Échec du préchargement des textures");
                    }
                        
                    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)) {
                        LOG_ERROR("Échec d'initialisation du modèle");
                    } else {
                        // Ajouter au cache
                        modelCache[modelKey] = model;
                    }
                }
                    
                // Ajouter le composant de rendu avec le modèle
                if (model) renderComponent->InitializeWithModel(model);
            }
            
            
            currentEntity = nullptr;
        }
        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);
            }
        }
    }

    LOG_INFO("Scène chargée avec succès");
    R_TRUE
}


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

    if (scene_path_.empty() && !save_scene_as()) {
        R_FALSE
    }
    std::ofstream outFile(scene_path_);
    if (!outFile.is_open()) {
        LOG_ERROR("Échec de l'ouverture du fichier");
        R_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;
            }
        }

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

    outFile.close();
    LOG_INFO("Scène sauvegardée avec succès: " + scene_path_);
    R_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;
}
