#include "AudioEngine.h"
#include <iostream>

// Static member definitions
ma_engine AudioEngine::s_Engine;
std::unordered_map<uint64_t, ma_sound*>   AudioEngine::s_SoundMap;
std::unordered_map<uint64_t, PlayingInfo> AudioEngine::s_PlayingInfoMap;


std::vector<float> AudioEngine::s_MasterVU;


bool AudioEngine::Init() {
    ma_engine_config config = ma_engine_config_init();
    config.onProcess        = AudioEngine::EngineProcessCallback;
    config.pProcessUserData = nullptr;

    ma_result r = ma_engine_init(&config, &s_Engine);
    if (r != MA_SUCCESS) {
        Logger::LogError("AudioEngine: ma_engine_init failed (%d)", r);
        return false;
    }

    Logger::LogOk("AudioEngine Core");

    return true;
}


void AudioEngine::Shutdown() {
    // Uninit & delete every sound
    for (auto& kv : s_SoundMap) {
        ma_sound_uninit(kv.second);
        delete kv.second;
    }
    s_SoundMap.clear();
    s_PlayingInfoMap.clear();
    ma_engine_uninit(&s_Engine);
}

void AudioEngine::Play(uint64_t uaid, bool loop) {
    // 1) Tear down existing instance (if any)
    Cleanup(uaid);

    // 2) Fetch and validate asset
    const AssetInfo* asset = AssetManager::GetAssetByID(uaid);
    if (!asset || !asset->loaded || asset->type != AssetType::Audio) {
        Logger::LogError("AudioEngine::Play invalid asset %llu", uaid);
        return;
    }

    // 3) Heap-allocate and init the sound from file
    ma_sound* sound = new ma_sound;
    if (ma_sound_init_from_file(
            &s_Engine,
            asset->path.c_str(),
            /*flags=*/0,
            /*pGroup=*/nullptr,
            /*pFence=*/nullptr,
            sound
        ) != MA_SUCCESS)
    {
        Logger::LogError("AudioEngine::Play failed to load '%s'", asset->path.c_str());
        delete sound;
        return;
    }

    // 4) Store and start playback
    s_SoundMap[uaid] = sound;
    ma_sound_set_looping(sound, loop ? MA_TRUE : MA_FALSE);
    ma_sound_start(sound);

    // 5) Compute totalTime
    ma_uint64 frames = 0;
    ma_sound_get_length_in_pcm_frames(sound, &frames);
    double sampleRate = ma_engine_get_sample_rate(&s_Engine);

    // 6) Push PlayingInfo
    PlayingInfo info;
    info.uaid       = uaid;
    info.name       = asset->filename;
    info.currentTime= 0.0;
    info.totalTime  = double(frames) / sampleRate;
    info.looping    = loop;
    info.hasStarted = false;
    info.sound      = sound;

    s_PlayingInfoMap[uaid] = info;
}

void AudioEngine::Stop(uint64_t uaid) {
    if (auto it = s_SoundMap.find(uaid); it != s_SoundMap.end()) {
        ma_sound_stop(it->second);
    }
}

void AudioEngine::StopAll() {
    for (auto& kv : s_SoundMap) {
        ma_sound_stop(kv.second);
    }
}

void AudioEngine::Unload(uint64_t uaid) {
    Cleanup(uaid);
}




void AudioEngine::CleanupSound(uint64_t uaid)
{
    auto itSound = s_SoundMap.find(uaid);
    if (itSound != s_SoundMap.end()) {
        ma_sound_uninit(itSound->second);
        delete itSound->second;
        s_SoundMap.erase(itSound);
    }
    
}


void AudioEngine::Update()
{
    for (auto it = s_PlayingInfoMap.begin(); it != s_PlayingInfoMap.end(); )
    {
        uint64_t uaid = it->first;
        PlayingInfo &info = it->second;
        ma_sound* s = info.sound;

        bool shouldErase = (!s) ||
                           (!info.looping && info.hasStarted && !ma_sound_is_playing(s));

        if (shouldErase) {
            CleanupSound(uaid);

            it = s_PlayingInfoMap.erase(it);  
        } else {
            
            if (!info.hasStarted && ma_sound_is_playing(s)) {
                info.hasStarted = true;
            }

            ma_uint64 cursor = 0;
            if (ma_sound_get_cursor_in_pcm_frames(s, &cursor) == MA_SUCCESS) {
                info.currentTime = double(cursor) / ma_engine_get_sample_rate(&s_Engine);
            }

            ++it;
        }
    }
}


void AudioEngine::SetVolume(uint64_t uaid, float v) {
    if (auto it = s_SoundMap.find(uaid); it != s_SoundMap.end()) {
        ma_sound_set_volume(it->second, v);
    }
}

void AudioEngine::SetLooping(uint64_t uaid, bool loop) {
    if (auto it = s_SoundMap.find(uaid); it != s_SoundMap.end()) {
        ma_sound_set_looping(it->second, loop ? MA_TRUE : MA_FALSE);
    }
}

void AudioEngine::Cleanup(uint64_t uaid) {
    if (auto it = s_SoundMap.find(uaid); it != s_SoundMap.end()) {
        ma_sound_uninit(it->second);
        delete it->second;
        s_SoundMap.erase(it);
    }
    s_PlayingInfoMap.erase(uaid);
}

const std::unordered_map<uint64_t, PlayingInfo>& AudioEngine::GetPlayingInfoMap() {
    return s_PlayingInfoMap;
}


void AudioEngine::EngineProcessCallback(
    void*      /*pUserData*/,
    float*     pFramesOut,
    ma_uint64  frameCount)
{
    // Fetch the playback device and its channel count
    ma_device* pDevice = ma_engine_get_device(&s_Engine);
    uint32_t channels  = pDevice->playback.channels;

    // Ensure our master VU vector matches the channel count
    s_MasterVU.assign(channels, 0.0f);

    // Compute peak per channel
    for (ma_uint64 i = 0; i < frameCount; ++i) {
        for (uint32_t ch = 0; ch < channels; ++ch) {
            float v = fabsf(pFramesOut[i * channels + ch]);
            if (v > s_MasterVU[ch]) {
                s_MasterVU[ch] = v;
            }
        }
    }

}


const std::vector<float>& AudioEngine::GetMasterVU()
{
    return s_MasterVU;
}
