#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();
        
        // 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_vec_ = app_->get_kobjects();
    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: ", __FILE__, __LINE__, Logger::LogLevel::Error);
		return false;
	}

    // Nettoyer les objets existants
    for (auto& obj : object_vec_) {
        if (obj) {
            obj->Shutdown();
            delete obj;
        }
    }
    object_vec_.clear();
    object_id_ = 0;

	// 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'objet
        iss >> id >> name
            >> position.x >> position.y >> position.z
            >> rotation.x >> rotation.y >> rotation.z
            >> scale.x >> scale.y >> scale.z
            >> modelPath >> shaderTypeStr
            >> boundingRadius >> objectTypeStr
            >> mass >> physicsEnabled;

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

        // Créer un nouvel objet
        object* newObject = new object();

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

    	// Vérifier si le chemin est relatif (ne commence pas par un disque comme C:)
    	if (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;
        
        // IMPORTANT: 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);
        }

    	// preload texture
    	if (!newObject->PreloadTextures(direct_3d_->get_device(),  direct_3d_->get_device_context(), objectTextures))
    	{
    		// Gérer l'erreur
    		return false;
    	}

        // Initialiser l'objet avec le modèle et les chemins de textures_
        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);

        // NE PAS charger les textures_ avant initialize
        // Laisser la méthode initialize s'en charger à partir des chemins
        if (!newObject->Initialize(direct_3d_->get_device(), direct_3d_->get_device_context(), modelFilename, objectTextures)) {
            Logger::Get().Log("Failed to initialize object: " + name, __FILE__, __LINE__, Logger::LogLevel::Error);
            delete newObject;
            continue;
        }

        // Définir les propriétés de l'objet
        newObject->SetId(id);
        newObject->SetName(name);
        newObject->SetPosition(XMLoadFloat3(&position));
        newObject->SetRotation(XMLoadFloat3(&rotation));
        newObject->SetScale(XMLoadFloat3(&scale));
        newObject->SetModelPath(wModelPath);
        newObject->SetActiveShader(newObject->StringToShaderType(shaderTypeStr));
        newObject->SetBoundingRadius(boundingRadius);
        newObject->SetType(newObject->StringToObjectType(objectTypeStr));
        newObject->SetMass(mass);
        newObject->SetPhysicsEnabled(physicsEnabled);

        // Mettre à jour l'ID global si nécessaire
        if (id >= object_id_) {
            object_id_ = id + 1;
            app_->set_object_id(object_id_);
        }

        object_vec_.push_back(newObject);
        Logger::Get().Log("Loaded object: " + name, __FILE__, __LINE__, Logger::LogLevel::Info);
    }

    // pass the vec to the application
    app_->set_kobjects(object_vec_);
	
    Logger::Get().Log("Scene loaded successfully from ", __FILE__, __LINE__, Logger::LogLevel::Info);
    return true;
}

bool scene_manager::save_scene() {

    
    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 : object_vec_) {
        XMFLOAT3 position, scale, rotation;
        XMStoreFloat3(&position, object->GetPosition());
        XMStoreFloat3(&scale, object->GetScale());
        XMStoreFloat3(&rotation, object->GetRotation());
        
        // Écrire les données de base de l'objet
        outFile << object->GetId() << " "
                << object->GetName() << " "
                << position.x << " " << position.y << " " << position.z << " "
                << rotation.x << " " << rotation.y << " " << rotation.z << " "
                << scale.x << " " << scale.y << " " << scale.z << " "
                << convert_w_string_to_string(object->GetModelPath()) << " "
                << object->ShaderTypeToString(object->GetActiveShader()) << " "
                << object->GetBoundingRadius() << " "
                << object->ObjectTypeToString(object->GetType()) << " "
                << object->GetMass() << " "
                << object->IsPhysicsEnabled();
        
        // Sauvegarder les chemins des textures_
        // Format: nombre de textures_ diffuses, puis les chemins
        // Même chose pour les autres types de textures_
        
        // Textures diffuses
        const auto& diffusePaths = object->GetTextureContainer().GetPaths(TextureType::Diffuse);
        outFile << " " << diffusePaths.size();
        for (const auto& path : diffusePaths) {
            outFile << " " << convert_w_string_to_string(path);
        }
        
        // Textures normales
        const auto& normalPaths = object->GetTextureContainer().GetPaths(TextureType::Normal);
        outFile << " " << normalPaths.size();
        for (const auto& path : normalPaths) {
            outFile << " " << convert_w_string_to_string(path);
        }
        
        // Textures spéculaires
        const auto& specularPaths = object->GetTextureContainer().GetPaths(TextureType::Specular);
        outFile << " " << specularPaths.size();
        for (const auto& path : specularPaths) {
            outFile << " " << convert_w_string_to_string(path);
        }
        
        // Textures alpha
        const auto& alphaPaths = object->GetTextureContainer().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;
}