// AudioComponent.h
#pragma once
#include "../component.h"
#include <Fmod/core/inc/fmod.hpp>
#include <string>
#include <filesystem>
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <Shlwapi.h>
#pragma comment(lib, "Shlwapi.lib")
#include <commdlg.h> // Pour GetOpenFileName

#include "camera_class.h"
#include "transform_component.h"

namespace ecs {
class AudioComponent : public Component {
public:
    AudioComponent() : m_sound(nullptr), m_channel(nullptr) {}
    ~AudioComponent() override {
        // stop the sound if it's playing
        if (m_channel) {
            bool isPlaying = false;
            m_channel->isPlaying(&isPlaying);
            if (isPlaying) {
                m_channel->stop();
            }
        }
        if (m_sound) m_sound->release();
        // Ne pas libérer m_system ici car il peut être partagé
    }

    /**
     * Shutdown the audio component by releasing the sound and channel.
     * This method is called when the component is removed from an entity or when the entity is destroyed.
     */
    void Shutdown() override {
        if (m_channel) {
            bool isPlaying = false;
            m_channel->isPlaying(&isPlaying);
            if (isPlaying) {
                m_channel->stop();
            }
            m_channel = nullptr;
        }
        if (m_sound) {
            m_sound->release();
            m_sound = nullptr;
        }
        m_system = nullptr; // Ne pas libérer m_system ici car il peut être partagé
        m_camera = nullptr;
    }

    /**
     * Initialize the audio component by setting up the FMOD system and camera from the parent entity.
     * This method is called when the component is added to an entity.
     */
    void Initialize() override
    {

        auto parent = GetParent();
        if (parent && parent->GetCamera())
        {
            SetCamera(parent->GetCamera());
            SetSoundSystem(parent->GetSoundSystem());
        }
        
    }

    /**
     * Load an audio file from the specified path.
     * If the FMOD system is not initialized, it will be initialized first.
     * Creates a sound object with appropriate settings based on the component's properties.
     * @param path The file path to load the audio from.
     * @return True if the audio file was loaded successfully, otherwise false.
     */
    bool Load(const std::string& path) {
        if (!m_system) {
            Initialize();
            if (!m_system) R_FALSE
        }

        LOG_INFO("Loading audio file: " + path);

        m_soundPath = path;

        if (!std::filesystem::exists(path)) {
            m_lastError = "Fichier non trouvé: " + path;
            LOG_ERROR(m_lastError);
            R_FALSE
        }

        if (m_sound) {
            m_sound->release();
            m_sound = nullptr;
        }

        FMOD_MODE mode = FMOD_DEFAULT;
        if (m_spatialized)
            mode |= FMOD_3D;
        if (m_looping)
            mode |= FMOD_LOOP_NORMAL;
        else
            mode |= FMOD_LOOP_OFF;

        std::filesystem::path absolutePath = std::filesystem::absolute(path);
        
        FMOD_RESULT result = m_system->createSound(absolutePath.string().c_str(), mode, nullptr, &m_sound);
        if (result != FMOD_OK) {
            m_lastError = "Échec du chargement du son: " + std::to_string(result) + 
                          " (chemin: " + absolutePath.string() + ")";
            LOG_ERROR(m_lastError);
            R_FALSE
        }

        R_TRUE
    }

    /**
     * Play the loaded audio sound.
     * If the sound is already playing, it will be stopped and restarted.
     * The channel properties such as volume, pan, pitch, mute, priority, and paused state are applied.
     */
    void Play() {
        if (m_system && m_sound) {
            bool isPlaying = false;
            if (m_channel) {
                m_channel->isPlaying(&isPlaying);
            }

            if (isPlaying) {
                m_channel->stop();
            }

            m_system->playSound(m_sound, nullptr, false, &m_channel);

            if (m_channel) {
                m_channel->setVolume(m_volume);
                m_channel->setPan(m_pan);
                m_channel->setPitch(m_pitch);
                m_channel->setMute(m_muted);
                m_channel->setPriority(m_priority);
                m_channel->setPaused(m_paused);
            }
        }
    }

    /**
     * Pause the currently playing audio sound.
     * If the sound is not playing, this method has no effect.
     */
    void Pause() {
        if (m_channel) {
            m_paused = true;
            m_channel->setPaused(true);
        }
    }

    /**
     * Resume the paused audio sound.
     * If the sound is not paused, this method has no effect.
     */
    void Resume() {
        if (m_channel) {
            m_paused = false;
            m_channel->setPaused(false);
        }
    }

    /**
     * Stop the currently playing audio sound.
     * If the sound is not playing, this method has no effect.
     */
    void Stop() {
        if (m_channel)
            m_channel->stop();
    }

    /**
     * Set the camera to be used for 3D audio spatialization.
     * This camera is used to update the listener attributes in FMOD.
     * @param camera Pointer to the camera_class instance.
     */
    void SetCamera(camera_class* camera) {m_camera = camera; }

    /**
     * Update the audio component.
     * @param deltaTime Time since the last update.
     */
    void Update(float deltaTime) override {
        if (!m_spatialized) return;

        if (!m_system || !m_camera) return;

        auto parent = GetParent();
        if (!parent) return;

        auto transform = parent->GetComponent<TransformComponent>();
        if (!transform) return;

        XMVECTOR pos = transform->GetPosition();

        m_position.x = XMVectorGetX(pos);
        m_position.y = XMVectorGetY(pos);
        m_position.z = XMVectorGetZ(pos);

        if (m_channel) {
            FMOD_VECTOR velocity = { 0.f, 0.f, 0.f }; // à améliorer si possible - calculer depuis déplacement temps+position ?
            m_channel->set3DAttributes(&m_position, &velocity);
        }

        // Mise à jour du listener FMOD
        XMFLOAT3 camPos = m_camera->get_position();
        XMFLOAT3 camForward = m_camera->get_forward();
        XMFLOAT3 camUp = m_camera->get_up();

        FMOD_VECTOR listenerPos = { camPos.x, camPos.y, camPos.z };
        FMOD_VECTOR forward = { camForward.x, camForward.y, camForward.z };
        FMOD_VECTOR up = { camUp.x, camUp.y, camUp.z };

        FMOD_VECTOR listenerVel = { 0.f, 0.f, 0.f };
        
        if (m_use_velocity)
        {
            static XMFLOAT3 lastCamPos = camPos;
            XMFLOAT3 currentCamPos = camPos;

            float deltaTimeSec = deltaTime > 0.0001f ? deltaTime : 0.016f; // éviter division par zéro

            XMFLOAT3 listenerVelocity = {
                (currentCamPos.x - lastCamPos.x) / deltaTimeSec,
                (currentCamPos.y - lastCamPos.y) / deltaTimeSec,
                (currentCamPos.z - lastCamPos.z) / deltaTimeSec
            };
            lastCamPos = currentCamPos;

            listenerVel = { listenerVelocity.x, listenerVelocity.y, listenerVelocity.z };
        }
        
        m_system->set3DListenerAttributes(0, &listenerPos, &listenerVel, &forward, &up);
        m_system->update();
    }

    /**
     * ImGui Widget to control the audio component properties.
     * Allows loading/changing/removing audio files, and adjusting properties like volume, pan, pitch, looping, spatialization, etc.
     * Displays error messages if loading fails.
     * This method is called during the ImGui rendering phase.
     */
    void OnImGuiRender() override {
        if (!m_sound) {
            ImGui::Text("No audio file loaded");

             if (ImGui::Button("Load audio file")) {
                OPENFILENAME ofn;
                wchar_t szFile[MAX_PATH] = L"";
                ZeroMemory(&ofn, sizeof(ofn));
                ofn.lStructSize = sizeof(ofn);
                ofn.hwndOwner = NULL; // Renseigner si possible avec handle de la fenêtre
                ofn.lpstrFile = szFile;
                ofn.nMaxFile = MAX_PATH;
                ofn.lpstrFilter = L"Audio Files\0*.mp3;*.wav;*.ogg;*.flac\0All Files\0*.*\0";
                ofn.nFilterIndex = 1;
                ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

                if (GetOpenFileName(&ofn)) {
                    wchar_t exePath[MAX_PATH];
                    GetModuleFileName(NULL, exePath, MAX_PATH);
                    PathRemoveFileSpec(exePath);

                    std::wstring targetDir = std::wstring(exePath) + L"\\assets\\sounds";

                    DWORD ftyp = GetFileAttributes(targetDir.c_str());
                    if (ftyp == INVALID_FILE_ATTRIBUTES) {
                        CreateDirectory((std::wstring(exePath) + L"\\assets").c_str(), NULL);
                        CreateDirectory(targetDir.c_str(), NULL);
                    }

                    const wchar_t* filename = wcsrchr(szFile, L'\\');
                    std::wstring fileNameStr = filename ? (filename + 1) : szFile;

                    std::wstring targetPath = targetDir + L"\\" + fileNameStr;

                    if (!CopyFile(szFile, targetPath.c_str(), FALSE)) {
                        MessageBox(NULL, L"Erreur lors de la copie du fichier audio.", L"Erreur", MB_OK | MB_ICONERROR);
                    } else {
                        int utf8size = WideCharToMultiByte(CP_UTF8, 0, targetPath.c_str(), -1, NULL, 0, NULL, NULL);
                        std::string pathStr(utf8size, 0);
                        WideCharToMultiByte(CP_UTF8, 0, targetPath.c_str(), -1, &pathStr[0], utf8size, NULL, NULL);
                        pathStr.resize(utf8size - 1);

                        if (!Load(pathStr)) {
                            // erreur dans m_lastError
                        }
                    }
                }
            }

            if (!m_lastError.empty()) {
                ImGui::TextColored(ImVec4(1, 0, 0, 1), "Error: %s", m_lastError.c_str());
                LOG_ERROR(m_lastError);
            }
        } else {
            ImGui::Text("Loaded: %s", m_soundPath.c_str());

            if (ImGui::Button("Change audio")) {
                OPENFILENAME ofn;
                wchar_t szFile[MAX_PATH] = L"";
                ZeroMemory(&ofn, sizeof(ofn));
                ofn.lStructSize = sizeof(ofn);
                ofn.hwndOwner = NULL;
                ofn.lpstrFile = szFile;
                ofn.nMaxFile = MAX_PATH;
                ofn.lpstrFilter = L"Audio Files\0*.mp3;*.wav;*.ogg;*.flac\0All Files\0*.*\0";
                ofn.nFilterIndex = 1;
                ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

                if (GetOpenFileName(&ofn)) {
                    wchar_t exePath[MAX_PATH];
                    GetModuleFileName(NULL, exePath, MAX_PATH);
                    PathRemoveFileSpec(exePath);

                    std::wstring targetDir = std::wstring(exePath) + L"\\assets\\sounds";

                    DWORD ftyp = GetFileAttributes(targetDir.c_str());
                    if (ftyp == INVALID_FILE_ATTRIBUTES) {
                        CreateDirectory((std::wstring(exePath) + L"\\assets").c_str(), NULL);
                        CreateDirectory(targetDir.c_str(), NULL);
                    }

                    const wchar_t* filename = wcsrchr(szFile, L'\\');
                    std::wstring fileNameStr = filename ? (filename + 1) : szFile;

                    std::wstring targetPath = targetDir + L"\\" + fileNameStr;

                    if (!CopyFile(szFile, targetPath.c_str(), FALSE)) {
                        MessageBox(NULL, L"Erreur lors de la copie du fichier audio.", L"Erreur", MB_OK | MB_ICONERROR);
                    } else {
                        int utf8size = WideCharToMultiByte(CP_UTF8, 0, targetPath.c_str(), -1, NULL, 0, NULL, NULL);
                        std::string pathStr(utf8size, 0);
                        WideCharToMultiByte(CP_UTF8, 0, targetPath.c_str(), -1, &pathStr[0], utf8size, NULL, NULL);
                        pathStr.resize(utf8size - 1);

                        if (!Load(pathStr)) {
                            // erreur dans m_lastError
                        }
                    }
                }
            }
            ImGui::SameLine();
            if (ImGui::Button("Remove audio")) {
                Stop();
                if (m_sound) m_sound->release();
                m_sound = nullptr;
                m_soundPath.clear();
            }

            ImGui::Separator();

            if (ImGui::Button("Play")) {
                Play();
            }
            ImGui::SameLine();
            if (ImGui::Button(m_paused ? "Resume" : "Pause")) {
                if (m_paused) Resume();
                else Pause();
            }
            ImGui::SameLine();
            if (ImGui::Button("Stop")) {
                Stop();
            }

            ImGui::Separator();

            if (ImGui::SliderFloat("Volume", &m_volume, 0.0f, 1.0f)) {
                if (m_channel) m_channel->setVolume(m_volume);
            }

            if (ImGui::SliderFloat("Pan", &m_pan, -1.0f, 1.0f)) {
                if (m_channel) m_channel->setPan(m_pan);
            }

            if (ImGui::SliderFloat("Pitch", &m_pitch, 0.5f, 2.0f)) {
                if (m_channel) m_channel->setPitch(m_pitch);
            }

            if (ImGui::Checkbox("Looping", &m_looping)) {
                // Recharger le son pour appliquer le mode loop
                Load(m_soundPath);
            }

            if (ImGui::Checkbox("Spatialization 3D", &m_spatialized)) {
                if (!m_soundPath.empty()) {
                    Load(m_soundPath); // Recharger le son avec ou sans 3D selon le choix
                }
            }
            
            // check box to use velocity for doppler effect (only if spatialized)
            if (m_spatialized) {
                ImGui::SameLine();
                ImGui::Checkbox("Use Velocity for Doppler", &m_use_velocity);
            }

            if (ImGui::Checkbox("Mute", &m_muted)) {
                if (m_channel) m_channel->setMute(m_muted);
            }

            if (ImGui::SliderInt("Priority", &m_priority, 0, 256)) {
                if (m_channel) m_channel->setPriority(m_priority);
            }

            ImGui::Separator();

            if (m_channel && m_sound) {
                unsigned int pos = 0, len = 0;
                m_channel->getPosition(&pos, FMOD_TIMEUNIT_MS);
                m_sound->getLength(&len, FMOD_TIMEUNIT_MS);

                float progress = (len > 0) ? (float)pos / len : 0.0f;
                ImGui::ProgressBar(progress, ImVec2(300, 0), nullptr);

                auto to_minsec = [](unsigned int ms) {
                    unsigned int totalSec = ms / 1000;
                    return std::make_pair(totalSec / 60, totalSec % 60);
                };
                auto [curMin, curSec] = to_minsec(pos);
                auto [lenMin, lenSec] = to_minsec(len);

                ImGui::Text("%02u:%02u / %02u:%02u", curMin, curSec, lenMin, lenSec);
            }

            // afficher la position de l'objet et de la camera ainsi que la rotaion de la camera si spatialisé
            if (m_spatialized) {
                auto parent = GetParent();
                if (parent) {
                    auto transform = parent->GetComponent<TransformComponent>();
                    if (transform) {
                        XMVECTOR pos = transform->GetPosition();
                        ImGui::Text("Object Position: (%.2f, %.2f, %.2f)", XMVectorGetX(pos), XMVectorGetY(pos), XMVectorGetZ(pos));
                    }
                }
                if (m_camera) {
                    XMFLOAT3 camPos = m_camera->get_position();
                    XMFLOAT3 camRot = m_camera->get_rotation();
                    ImGui::Text("Camera Position: (%.2f, %.2f, %.2f)", camPos.x, camPos.y, camPos.z);
                    ImGui::Text("Camera Rotation: (%.2f, %.2f, %.2f)", camRot.x, camRot.y, camRot.z);
                }
            }

            if (!m_lastError.empty()) {
                ImGui::TextColored(ImVec4(1,0,0,1), "Error: %s", m_lastError.c_str());
            }
        }
    }

    /**
     * Set the FMOD system to be used by this audio component.
     * This method allows sharing the FMOD system instance across multiple audio components.
     * @param system Pointer to the FMOD::System instance.
     */
    void SetSoundSystem(FMOD::System* system) {
        m_system = system;
    }

    /**
     * Serialize the audio component's state to a string.
     * This includes properties like sound path, volume, pan, pitch, looping, muted, paused, priority, spatialization, and use of velocity.
     * This method is useful for saving the component's state or debugging.
     * @return A string representation of the audio component's state.
     */
    std::string Serialize() const override {
        std::stringstream ss;
        ss << "AudioComponent:"
           << m_soundPath << ";"
           << m_volume << ":"
           << m_pan << ":"
           << m_pitch << ":"
           << m_looping << ":"
           << m_muted << ":"
           << m_paused << ":"
           << m_priority << ":"
           << m_spatialized << ":"
           << m_use_velocity;
        return ss.str();
    }

    /**
     * Deserialize the audio component's state from a string.
     * This method parses the string and sets the component's properties accordingly.
     * If the sound path is valid, it attempts to load the audio file.
     * @param data The string representation of the audio component's state.
     * @return True if deserialization was successful, otherwise false.
     */
    bool Deserialize(const std::string& data) override {
        std::stringstream ss(data);
        std::string type;
        std::getline(ss, type, ':');
        if (type != "AudioComponent") R_FALSE

        std::string s_volume, s_pan, s_pitch, s_looping, s_muted, s_paused, s_priority, s_spatialized, s_use_velocity;

        std::getline(ss, m_soundPath, ';');
        std::getline(ss, s_volume, ':');
        std::getline(ss, s_pan, ':');
        std::getline(ss, s_pitch, ':');
        std::getline(ss, s_looping, ':');
        std::getline(ss, s_muted, ':');
        std::getline(ss, s_paused, ':');
        std::getline(ss, s_priority, ':');
        std::getline(ss, s_spatialized, ':');
        std::getline(ss, s_use_velocity, ':');
        
        m_volume = std::stof(s_volume);
        m_pan = std::stof(s_pan);
        m_pitch = std::stof(s_pitch);
        m_looping = (s_looping == "1");
        m_muted = (s_muted == "1");
        m_paused = (s_paused == "1");
        m_priority = std::stoi(s_priority);
        m_spatialized = (s_spatialized == "1");
        m_use_velocity = (s_use_velocity == "1");

        // Recharger le son si le chemin existe (optional: ou laisser le chargement manuel)
        if (!m_soundPath.empty() && m_system) {
            Load(m_soundPath);
        }

        R_TRUE
    }

private:
    FMOD::System* m_system;
    FMOD::Sound* m_sound;
    FMOD::Channel* m_channel;
    std::string m_soundPath;
    std::string m_lastError;
    
    float m_volume = 1.0f;
    float m_pan = 0.0f;
    float m_pitch = 1.0f;
    bool m_looping = false;
    bool m_muted = false;
    bool m_paused = false;
    int m_priority = 128;
    bool m_spatialized = false;
    bool m_use_velocity = false;

    FMOD_VECTOR m_position = {0.0f, 0.0f, 0.0f};
    camera_class* m_camera = nullptr;
};
}
