#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;
    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() {
	Logger::Get().Log("Loading scene from " , __FILE__, __LINE__, Logger::LogLevel::Info);

    object_id_ = app_->get_object_id();
    w_folder_ = app_->get_w_folder();
    direct_3d_ = app_->get_direct_3d();
	std::wstring scenePath = get_scene_path();
    
	if (!scenePath.empty()) {
	    scene_path_ = convert_w_string_to_string(scenePath);
	}
	
	std::ifstream inFile(scene_path_);
	if (!inFile.is_open()) {
		Logger::Get().Log("Failed to open file for loading scene: " + scene_path_, __FILE__, __LINE__, Logger::LogLevel::Error);
		return false;
	}
    
    // Réinitialiser l'ID d'objet le plus élevé
    object_id_ = 0;
    
    // Récupérer l'EntityManager pour créer de nouvelles entités
    auto entity_manager = app_->get_entity_manager();
    
    // Supprimer toutes les entités existantes
    entity_manager->Clear();

    // Sauvegarder le répertoire de travail actuel
    std::wstring currentDirectory = w_folder_;

    std::string line;
    while (std::getline(inFile, line)) {
        std::istringstream iss(line);
        int id;
        std::string name;
        XMFLOAT3 position, rotation, scale;
        std::string modelPath;
        std::string shaderTypeStr;
        float boundingRadius;
        std::string objectTypeStr;
        float mass;
        bool physicsEnabled;

        // Lire les données de base de l'entité
        iss >> id >> name
            >> position.x >> position.y >> position.z
            >> rotation.x >> rotation.y >> rotation.z
            >> scale.x >> scale.y >> scale.z;
        
        // Lire le chemin du modèle - vérifier s'il est vide
        iss >> modelPath;
        
        // Si le chemin du modèle est vide ou commence par une lettre majuscule (probablement un type de shader),
        // c'est qu'il a été omis - utilisez une valeur par défaut
        if (modelPath.empty() || (modelPath[0] >= 'A' && modelPath[0] <= 'Z')) {
            // Reculer le curseur de lecture pour lire le type de shader à la place
            iss.seekg(-static_cast<int>(modelPath.length()), std::ios::cur);
            modelPath = "assets/Model/TXT/cube.txt"; // Valeur par défaut
        }
        
        iss >> shaderTypeStr
            >> boundingRadius >> objectTypeStr
            >> mass >> physicsEnabled;

        if (iss.fail()) {
            Logger::Get().Log("Failed to parse entity data: " + line, __FILE__, __LINE__, Logger::LogLevel::Error);
            continue;
        }

        // Mettre à jour l'ID d'objet le plus élevé si nécessaire
        if (id >= object_id_) {
            object_id_ = id + 1;
        }

        // Convertir le chemin du modèle en wstring
        std::wstring wModelPath(modelPath.begin(), modelPath.end());

        // Vérifier si le chemin est relatif
        if (modelPath != "NoModel" && modelPath.length() > 1 && modelPath[1] != ':') {
            // C'est un chemin relatif, préfixer avec le répertoire de travail
            if (currentDirectory.back() != L'/' && currentDirectory.back() != L'\\') {
                // Ajouter un séparateur si nécessaire
                wModelPath = currentDirectory + L"\\" + wModelPath;
            } else {
                wModelPath = currentDirectory + wModelPath;
            }
        }

        // Créer le conteneur de textures pour stocker les chemins
        TextureContainer objectTextures;
        
        // Vider les conteneurs de chemins de textures
        objectTextures.diffusePaths.clear();
        objectTextures.normalPaths.clear();
        objectTextures.specularPaths.clear();
        objectTextures.alphaPaths.clear();

        // Lire les chemins des textures diffuses
        int diffuseTextureCount;
        iss >> diffuseTextureCount;
        for (int i = 0; i < diffuseTextureCount; i++) {
            std::string texturePath;
            iss >> texturePath;
            std::wstring wTexturePath(texturePath.begin(), texturePath.end());
            objectTextures.diffusePaths.push_back(wTexturePath);
        }

        // Lire les chemins des textures normales
        int normalTextureCount;
        iss >> normalTextureCount;
        for (int i = 0; i < normalTextureCount; i++) {
            std::string texturePath;
            iss >> texturePath;
            std::wstring wTexturePath(texturePath.begin(), texturePath.end());
            objectTextures.normalPaths.push_back(wTexturePath);
        }

        // Lire les chemins des textures spéculaires
        int specularTextureCount;
        iss >> specularTextureCount;
        for (int i = 0; i < specularTextureCount; i++) {
            std::string texturePath;
            iss >> texturePath;
            std::wstring wTexturePath(texturePath.begin(), texturePath.end());
            objectTextures.specularPaths.push_back(wTexturePath);
        }

        // Lire les chemins des textures alpha
        int alphaTextureCount;
        iss >> alphaTextureCount;
        for (int i = 0; i < alphaTextureCount; i++) {
            std::string texturePath;
            iss >> texturePath;
            std::wstring wTexturePath(texturePath.begin(), texturePath.end());
            objectTextures.alphaPaths.push_back(wTexturePath);
        }
        
        // Vérifier si on a un modèle à charger
        if (modelPath == "NoModel") {
            Logger::Get().Log("Skipping entity without model: " + name, __FILE__, __LINE__, Logger::LogLevel::Warning);
            continue;
        }

        // Récupérer le cache de modèles de l'application
        auto& modelCache = app_->get_model_cache();
        std::shared_ptr<model_class> sharedModel;

        // Vérifier si le modèle existe déjà dans le cache
        std::string modelKey = modelPath;
        auto it = modelCache.find(modelKey);
        if (it != modelCache.end()) {
            // Utiliser le modèle existant du cache
            Logger::Get().Log("Using cached model for: " + modelKey, __FILE__, __LINE__, Logger::LogLevel::Info);
            sharedModel = it->second;
        } else {
            // Créer un nouveau modèle
            char modelFilename[256];
            size_t convertedChars = 0;
            wcstombs_s(&convertedChars, modelFilename, sizeof(modelFilename), wModelPath.c_str(), _TRUNCATE);

            Logger::Get().Log("Loading model: " + std::string(modelFilename), __FILE__, __LINE__, Logger::LogLevel::Info);

            // Créer et initialiser le modèle
            auto newModel = std::make_shared<model_class>();
            
            // Précharger les textures
            if (!newModel->PreloadTextures(direct_3d_->get_device(), direct_3d_->get_device_context(), objectTextures)) {
                Logger::Get().Log("Failed to preload textures for: " + name, __FILE__, __LINE__, Logger::LogLevel::Error);
                continue;
            }

            if (!newModel->Initialize(direct_3d_->get_device(), direct_3d_->get_device_context(), modelFilename, objectTextures)) {
                Logger::Get().Log("Failed to initialize model: " + name, __FILE__, __LINE__, Logger::LogLevel::Error);
                continue;
            }

            // Ajouter le modèle au cache
            modelCache[modelKey] = newModel;
            sharedModel = newModel;
        }
        
        // Créer une nouvelle entité avec l'EntityManager
        auto entity = entity_manager->CreateEntity();

        // Ajouter un composant d'identité
        auto identityComponent = entity->AddComponent<ecs::IdentityComponent>(id);
        identityComponent->SetName(name);
        identityComponent->SetType(ecs::IdentityComponent::StringToObjectType(objectTypeStr));
        
        // Ajouter un composant de transformation
        auto transformComponent = entity->AddComponent<ecs::TransformComponent>();
        transformComponent->SetPosition(XMLoadFloat3(&position));
        transformComponent->SetRotation(XMLoadFloat3(&rotation));
        transformComponent->SetScale(XMLoadFloat3(&scale));
        transformComponent->UpdateWorldMatrix();
        
        // Ajouter un composant de rendu avec le modèle
        auto renderComponent = entity->AddComponent<ecs::RenderComponent>();
        renderComponent->InitializeWithModel(sharedModel);
        
        // Ajouter un composant de shader
        auto shaderComponent = entity->AddComponent<ecs::ShaderComponent>();
        shaderComponent->SetActiveShader(ecs::ShaderComponent::StringToShaderType(shaderTypeStr));
        
        // Ajouter un composant de chemin de modèle
        auto modelPathComponent = entity->AddComponent<ecs::ModelPathComponent>();
        modelPathComponent->SetPath(wModelPath);
        
        // Ajouter un composant de physique si nécessaire
        auto physicsComponent = entity->AddComponent<ecs::PhysicsComponent>();
        physicsComponent->Initialize();
        physicsComponent->SetMass(mass);
        physicsComponent->SetBoundingRadius(boundingRadius);
        physicsComponent->SetPhysicsEnabled(physicsEnabled);
        
        Logger::Get().Log("Entity loaded: " + name + " with ID: " + std::to_string(id), __FILE__, __LINE__, Logger::LogLevel::Info);
    }

    // Mettre à jour l'ID global dans l'application
    app_->set_object_id(object_id_);
    
    // Mettre à jour les statistiques après le chargement
    app_->update_stats_after_modification();
	
    Logger::Get().Log("Scene loaded successfully from " + scene_path_, __FILE__, __LINE__, Logger::LogLevel::Info);
    return true;
}

bool scene_manager::save_scene() {

    entity_ = app_->get_entity_manager()->GetAllEntities();
    
    if (scene_path_.empty()) {
        Logger::Get().Log("Scene path is empty. Cannot save scene.", __FILE__, __LINE__, Logger::LogLevel::Error);
        return false;
    }

    std::ofstream outFile(scene_path_);
    if (!outFile.is_open()) {
        Logger::Get().Log("Failed to open file for saving scene", __FILE__, __LINE__, Logger::LogLevel::Error);
        return false;
    }

    for (const auto& object : entity_) {
        XMFLOAT3 position, scale, rotation;
        int id = 0;
        int mass = 0;
        float boundingRadius = 0;
        std::string name = "NONE";
        std::string shaderType = "NONE";
        std::string objectType = "NONE";
        std::wstring model_path = L"";
        bool physics_enabled = false;

        auto transform = object->GetComponent<ecs::TransformComponent>();
        if (transform) {
            // convert XMVECTOR to XMFLOAT3
            XMStoreFloat3(&position, transform->GetPosition());
            XMStoreFloat3(&rotation, transform->GetRotation());
            XMStoreFloat3(&scale, transform->GetScale());
        }

        
        auto identity = object->GetComponent<ecs::IdentityComponent>();
        if (identity) {

            id = identity->GetId();
            name = identity->GetName();
            objectType = identity->ObjectTypeToString(identity->GetType());
            
        }

        auto model_path_component = object->GetComponent<ecs::ModelPathComponent>();
        if (model_path_component) {
            
            model_path = model_path_component->GetPath();
        }

        auto shader = object->GetComponent<ecs::ShaderComponent>();
        if (shader)
        {
            shaderType = shader->ShaderTypeToString(shader->GetActiveShader());
        }

        auto physics = object->GetComponent<ecs::PhysicsComponent>();
        if (physics) {
            physics_enabled = physics->IsPhysicsEnabled();
            mass = physics->GetMass();
            boundingRadius = physics->GetBoundingRadius();
        }
        
        // Écrire les données de base de l'objet
        outFile << id << " "
                << name << " "
                << position.x << " " << position.y << " " << position.z << " "
                << rotation.x << " " << rotation.y << " " << rotation.z << " "
                << scale.x << " " << scale.y << " " << scale.z << " "
                << convert_w_string_to_string(model_path) << " "
                << shaderType << " "
                << boundingRadius << " "
                << objectType << " "
                << mass << " "
                << physics_enabled;
        
        // Sauvegarder les chemins des textures_
        // Format: nombre de textures_ diffuses, puis les chemins
        // Même chose pour les autres types de textures_


        auto render = object->GetComponent<ecs::RenderComponent>();
        if (render)
        {
            const auto& model = render->GetModel();

            const auto& textureContainer = model->GetTextureContainer();

            const auto& diffusePaths = textureContainer.GetPaths(TextureType::Diffuse);
            outFile << " " << diffusePaths.size();
            for (const auto& path : diffusePaths) {
                outFile << " " << convert_w_string_to_string(path);
            }

            const auto& normalPaths = textureContainer.GetPaths(TextureType::Normal);
            outFile << " " << normalPaths.size();
            for (const auto& path : normalPaths) {
                outFile << " " << convert_w_string_to_string(path);
            }
            const auto& specularPaths = textureContainer.GetPaths(TextureType::Specular);
            outFile << " " << specularPaths.size();
            for (const auto& path : specularPaths) {
                outFile << " " << convert_w_string_to_string(path);
            }
            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.close();
    Logger::Get().Log("Scene saved successfully to " + 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;
}

