// Audio.cpp - Windows Media Foundation MP3 decoding
#include "audio/Audio.h"
#include <SDL3/SDL.h>
#include <cstdio>
#include <algorithm>
#include <fstream>
#include <cstring>
#include <vector>
#include <chrono>
#include <thread>
#ifdef _WIN32
#include <windows.h>
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <objbase.h>
#include <wrl/client.h>
#pragma comment(lib, "mfplat.lib")
#pragma comment(lib, "mfreadwrite.lib")
#pragma comment(lib, "mfuuid.lib")
#pragma comment(lib, "ole32.lib")
using Microsoft::WRL::ComPtr;
#ifdef max
#undef max
#endif
#ifdef min
#undef min
#endif
#elif defined(__APPLE__)
#include <AudioToolbox/AudioToolbox.h>
#include <CoreFoundation/CoreFoundation.h>
#endif

Audio& Audio::instance(){ static Audio inst; return inst; }

bool Audio::init(){ if(outSpec.freq!=0) return true; outSpec.format=SDL_AUDIO_S16; outSpec.channels=outChannels; outSpec.freq=outRate; 
#ifdef _WIN32
 if(!mfStarted){ if(FAILED(MFStartup(MF_VERSION))) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] MFStartup failed"); } else mfStarted=true; }
#endif
 return true; }

#if defined(_WIN32)
static bool decodeMP3(const std::string& path, std::vector<int16_t>& outPCM, int& outRate, int& outCh){
  outPCM.clear(); outRate=44100; outCh=2; 
  ComPtr<IMFSourceReader> reader; 
  wchar_t wpath[MAX_PATH]; int wlen = MultiByteToWideChar(CP_UTF8,0,path.c_str(),-1,wpath,MAX_PATH); if(!wlen) return false;
  if(FAILED(MFCreateSourceReaderFromURL(wpath,nullptr,&reader))) return false;
  // Request PCM output
  ComPtr<IMFMediaType> outType; MFCreateMediaType(&outType); outType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio); outType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM); outType->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, 2); outType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, 44100); outType->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, 4); outType->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, 44100*4); outType->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, 16); outType->SetUINT32(MF_MT_AUDIO_CHANNEL_MASK, 3); reader->SetCurrentMediaType((DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, NULL, outType.Get());
  reader->SetStreamSelection(MF_SOURCE_READER_FIRST_AUDIO_STREAM, TRUE);
  while(true){ DWORD flags=0; ComPtr<IMFSample> sample; if(FAILED(reader->ReadSample(MF_SOURCE_READER_FIRST_AUDIO_STREAM,0,nullptr,&flags,nullptr,&sample))) break; if(flags & MF_SOURCE_READERF_ENDOFSTREAM) break; if(!sample) continue; ComPtr<IMFMediaBuffer> buffer; if(FAILED(sample->ConvertToContiguousBuffer(&buffer))) continue; BYTE* data=nullptr; DWORD maxLen=0, curLen=0; if(SUCCEEDED(buffer->Lock(&data,&maxLen,&curLen)) && curLen){ size_t samples = curLen/2; size_t oldSz = outPCM.size(); outPCM.resize(oldSz + samples); std::memcpy(outPCM.data()+oldSz, data, curLen); } if(data) buffer->Unlock(); }
  outRate=44100; outCh=2; return !outPCM.empty(); }
#elif defined(__APPLE__)
// Decode MP3 files using macOS AudioToolbox so music works on Apple builds.
static bool decodeMP3(const std::string& path, std::vector<int16_t>& outPCM, int& outRate, int& outCh){
    outPCM.clear();
    outRate = 44100;
    outCh = 2;

    CFURLRef url = CFURLCreateFromFileSystemRepresentation(nullptr, reinterpret_cast<const UInt8*>(path.c_str()), path.size(), false);
    if (!url) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] Failed to create URL for %s", path.c_str());
        return false;
    }

    ExtAudioFileRef audioFile = nullptr;
    OSStatus status = ExtAudioFileOpenURL(url, &audioFile);
    CFRelease(url);
    if (status != noErr || !audioFile) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] ExtAudioFileOpenURL failed (%d) for %s", static_cast<int>(status), path.c_str());
        return false;
    }

    AudioStreamBasicDescription clientFormat{};
    clientFormat.mSampleRate = 44100.0;
    clientFormat.mFormatID = kAudioFormatLinearPCM;
    clientFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
    clientFormat.mBitsPerChannel = 16;
    clientFormat.mChannelsPerFrame = 2;
    clientFormat.mFramesPerPacket = 1;
    clientFormat.mBytesPerFrame = (clientFormat.mBitsPerChannel / 8) * clientFormat.mChannelsPerFrame;
    clientFormat.mBytesPerPacket = clientFormat.mBytesPerFrame * clientFormat.mFramesPerPacket;

    status = ExtAudioFileSetProperty(audioFile, kExtAudioFileProperty_ClientDataFormat, sizeof(clientFormat), &clientFormat);
    if (status != noErr) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] Failed to set client format (%d) for %s", static_cast<int>(status), path.c_str());
        ExtAudioFileDispose(audioFile);
        return false;
    }

    const UInt32 framesPerBuffer = 4096;
    std::vector<int16_t> buffer(framesPerBuffer * clientFormat.mChannelsPerFrame);
    while (true) {
        AudioBufferList abl{};
        abl.mNumberBuffers = 1;
        abl.mBuffers[0].mNumberChannels = clientFormat.mChannelsPerFrame;
        abl.mBuffers[0].mDataByteSize = framesPerBuffer * clientFormat.mBytesPerFrame;
        abl.mBuffers[0].mData = buffer.data();

        UInt32 framesToRead = framesPerBuffer;
        status = ExtAudioFileRead(audioFile, &framesToRead, &abl);
        if (status != noErr) {
            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] ExtAudioFileRead failed (%d) for %s", static_cast<int>(status), path.c_str());
            ExtAudioFileDispose(audioFile);
            return false;
        }

        if (framesToRead == 0) {
            break; // EOF
        }

        size_t samplesRead = static_cast<size_t>(framesToRead) * clientFormat.mChannelsPerFrame;
        outPCM.insert(outPCM.end(), buffer.data(), buffer.data() + samplesRead);
    }

    ExtAudioFileDispose(audioFile);
    outRate = static_cast<int>(clientFormat.mSampleRate);
    outCh = static_cast<int>(clientFormat.mChannelsPerFrame);
    return !outPCM.empty();
}

#else
static bool decodeMP3(const std::string& path, std::vector<int16_t>& outPCM, int& outRate, int& outCh){
    (void)outPCM; (void)outRate; (void)outCh; (void)path;
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] MP3 unsupported on this platform: %s", path.c_str());
    return false;
}
#endif

void Audio::addTrack(const std::string& path){ AudioTrack t; t.path=path; 
        if(decodeMP3(path, t.pcm, t.rate, t.channels)) t.ok=true; else SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] Failed to decode %s", path.c_str());
    tracks.push_back(std::move(t)); }

void Audio::shuffle(){ 
    std::lock_guard<std::mutex> lock(tracksMutex);
    std::shuffle(tracks.begin(), tracks.end(), rng); 
}

bool Audio::ensureStream(){
    if(audioStream) return true;
    // Ensure audio spec is initialized
    if (!init()) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] Failed to initialize audio spec before opening device stream");
        return false;
    }
    audioStream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &outSpec, &Audio::streamCallback, this);
    if(!audioStream){
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] SDL_OpenAudioDeviceStream failed: %s", SDL_GetError());
        return false;
    }
    // Ensure the device is running so SFX can be heard even before music starts
    SDL_ResumeAudioStreamDevice(audioStream);
    return true;
}

void Audio::start(){
    if(!ensureStream()) return;
    // If no track is selected yet, try to select one now (in case tracks loaded after initial start)
    if(current < 0) {
        nextTrack();
    }
    SDL_ResumeAudioStreamDevice(audioStream);
    playing = true;
}

void Audio::skipToNextTrack(){
    if(!ensureStream()) return;

    // If menu music is active, just restart the looped menu track
    if(isMenuMusic){
        if(menuTrack.ok){
            menuTrack.cursor = 0;
            playing = true;
            SDL_ResumeAudioStreamDevice(audioStream);
        }
        return;
    }

    if(tracks.empty()) return;
    nextTrack();
    playing = true;
    SDL_ResumeAudioStreamDevice(audioStream);
}

void Audio::toggleMute(){ muted=!muted; }
void Audio::setMuted(bool m){ muted=m; }

bool Audio::isMuted() const { return muted; }

void Audio::nextTrack(){
    if(tracks.empty()) { current = -1; return; }
    // Try every track once to find a decodable one
    int start = current;
    for(size_t i=0;i<tracks.size(); ++i){
        current = (current + 1) % (int)tracks.size();
        if(tracks[current].ok){ tracks[current].cursor=0; return; }
    }
    current=-1;
}

void Audio::feed(Uint32 bytesWanted, SDL_AudioStream* stream){
  if(bytesWanted==0) return;
    // Prepare a buffer of int16 samples for the output device
    const size_t outSamples = bytesWanted / sizeof(int16_t);
    std::vector<int16_t> mix(outSamples, 0);

    // 1) Mix music into buffer (if not muted)
    // 1) Mix music into buffer (if not muted)
    if(!muted && playing){
        size_t cursorBytes = 0;
        while(cursorBytes < bytesWanted){
            AudioTrack* trk = nullptr;
            
            if (isMenuMusic) {
                if (menuTrack.ok) trk = &menuTrack;
            } else {
                if (current >= 0 && current < (int)tracks.size()) trk = &tracks[current];
            }
            
            if (!trk) break;

            size_t samplesAvail = trk->pcm.size() - trk->cursor; // samples (int16)
            if(samplesAvail == 0){ 
                if (isMenuMusic) {
                    trk->cursor = 0; // Loop menu music
                    continue;
                } else {
                    nextTrack(); 
                    if(current < 0) break; 
                    continue; 
                }
            }
            
            size_t samplesNeeded = (bytesWanted - cursorBytes) / sizeof(int16_t);
            size_t toCopy = (samplesAvail < samplesNeeded) ? samplesAvail : samplesNeeded;
            if(toCopy == 0) break;
            
            // Mix add with clamp
            size_t startSample = cursorBytes / sizeof(int16_t);
            for(size_t i=0;i<toCopy;++i){
                int v = (int)mix[startSample+i] + (int)trk->pcm[trk->cursor+i];
                if(v>32767) v=32767; if(v<-32768) v=-32768; mix[startSample+i] = (int16_t)v;
            }
            trk->cursor += toCopy;
            cursorBytes += (Uint32)(toCopy * sizeof(int16_t));
            
            if(trk->cursor >= trk->pcm.size()) {
                if (isMenuMusic) {
                    trk->cursor = 0; // Loop menu music
                } else {
                    nextTrack();
                }
            }
        }
    }

    // 2) Mix active SFX
    {
        std::lock_guard<std::mutex> lock(sfxMutex);
        for(size_t si=0; si<activeSfx.size(); ){
            auto &s = activeSfx[si];
            size_t samplesAvail = s.pcm.size() - s.cursor;
            if(samplesAvail == 0){ activeSfx.erase(activeSfx.begin()+si); continue; }
            size_t toCopy = (samplesAvail < outSamples) ? samplesAvail : outSamples;
            for(size_t i=0;i<toCopy;++i){
                int v = (int)mix[i] + (int)s.pcm[s.cursor+i];
                if(v>32767) v=32767; if(v<-32768) v=-32768; mix[i] = (int16_t)v;
            }
            s.cursor += toCopy;
            ++si;
        }
    }

    // Submit mixed audio
    if(!mix.empty()) SDL_PutAudioStreamData(stream, mix.data(), (int)bytesWanted);
}

void Audio::playSfx(const std::vector<int16_t>& pcm, int channels, int rate, float volume){
    if(pcm.empty()) return;
    if(!ensureStream()) return;
    // Convert input to device format (S16, stereo, 44100)
    SDL_AudioSpec src{}; src.format=SDL_AUDIO_S16; src.channels=(Uint8)channels; src.freq=rate;
    SDL_AudioSpec dst{}; dst.format=SDL_AUDIO_S16; dst.channels=(Uint8)outChannels; dst.freq=outRate;
    SDL_AudioStream* cvt = SDL_CreateAudioStream(&src, &dst);
    if(!cvt) return;
    // Apply volume while copying into a temp buffer
    std::vector<int16_t> volBuf(pcm.size());
    for(size_t i=0;i<pcm.size();++i){
        int v = (int)(pcm[i] * volume);
        if(v>32767) v=32767; if(v<-32768) v=-32768; volBuf[i]=(int16_t)v;
    }
    SDL_PutAudioStreamData(cvt, volBuf.data(), (int)(volBuf.size()*sizeof(int16_t)));
    SDL_FlushAudioStream(cvt);
    int bytes = SDL_GetAudioStreamAvailable(cvt);
    if(bytes>0){
        std::vector<int16_t> out(bytes/2);
        SDL_GetAudioStreamData(cvt, out.data(), bytes);
        std::lock_guard<std::mutex> lock(sfxMutex);
        activeSfx.push_back(SfxPlay{ std::move(out), 0 });
    }
    SDL_DestroyAudioStream(cvt);
}

void SDLCALL Audio::streamCallback(void* userdata, SDL_AudioStream* stream, int additional, int total){ Uint32 want = additional>0 ? (Uint32)additional : (Uint32)total; if(!want) want=4096; reinterpret_cast<Audio*>(userdata)->feed(want, stream); }

void Audio::addTrackAsync(const std::string& path) {
    std::lock_guard<std::mutex> lock(pendingTracksMutex);
    pendingTracks.push_back(path);
}

void Audio::startBackgroundLoading() {
    // If a previous loading thread exists but has finished, join it so we can start anew
    if (loadingThread.joinable()) {
        if (loadingComplete) {
            loadingThread.join();
        } else {
            // Already running
            return;
        }
    }
    loadingAbort = false;
    loadingComplete = false;
    loadedCount = 0;
    loadingThread = std::thread(&Audio::backgroundLoadingThread, this);
}

void Audio::backgroundLoadingThread() {
#ifdef _WIN32
    // Initialize COM and MF for this thread
    HRESULT hrCom = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
    HRESULT hrMF = MFStartup(MF_VERSION);
    bool mfInitialized = SUCCEEDED(hrMF);
    
    if (!mfInitialized) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] Failed to initialize MF on background thread");
    }
#endif

    while (true) {
        if (loadingAbort.load()) {
            break;
        }
        std::string path;
        {
            std::lock_guard<std::mutex> lock(pendingTracksMutex);
            if (pendingTracks.empty()) break;
            path = std::move(pendingTracks.front());
            pendingTracks.erase(pendingTracks.begin());
            if (loadingAbort.load()) {
                break;
            }
        }
        AudioTrack t;
        t.path = path;
        if (decodeMP3(path, t.pcm, t.rate, t.channels)) {
            t.ok = true;
        } else {
            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] Failed to decode %s", path.c_str());
        }
        
        // Thread-safe addition to tracks
        if (loadingAbort.load()) {
            break;
        }

        {
            std::lock_guard<std::mutex> lock(tracksMutex);
            tracks.push_back(std::move(t));
        }
        
    loadedCount++;
        
        // Small delay to prevent overwhelming the system (unless abort requested)
        if (!loadingAbort.load()) {
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        } else {
            break;
        }
    }
    
#ifdef _WIN32
    // Cleanup MF and COM for this thread
    if (mfInitialized) {
        MFShutdown();
    }
    if (SUCCEEDED(hrCom)) {
        CoUninitialize();
    }
#endif
    
    loadingComplete = true;
}

void Audio::waitForLoadingComplete() {
    if (loadingThread.joinable()) {
        loadingThread.join();
    }
}

bool Audio::isLoadingComplete() const {
    return loadingComplete;
}

int Audio::getLoadedTrackCount() const {
    return loadedCount;
}

void Audio::setMenuTrack(const std::string& path) {
    menuTrack.path = path;
    if (decodeMP3(path, menuTrack.pcm, menuTrack.rate, menuTrack.channels)) {
        menuTrack.ok = true;
    } else {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] Failed to decode menu track %s", path.c_str());
    }
}

void Audio::playMenuMusic() {
    isMenuMusic = true;
    if (menuTrack.ok) {
        menuTrack.cursor = 0;
    }
    start();
}

void Audio::playGameMusic() {
    isMenuMusic = false;
    // If we were playing menu music, we might want to pick a random track or resume
    if (current < 0 && !tracks.empty()) {
        nextTrack();
    }
    start();
}

void Audio::shutdown(){ 
    // Stop background loading thread first
    loadingAbort = true;
    {
        std::lock_guard<std::mutex> lock(pendingTracksMutex);
        pendingTracks.clear();
    }
    if (loadingThread.joinable()) {
        loadingThread.join();
    }
    loadingComplete = true;
    
    if(audioStream){ SDL_DestroyAudioStream(audioStream); audioStream=nullptr; } 
    tracks.clear(); 
    {
        std::lock_guard<std::mutex> lock(pendingTracksMutex);
        pendingTracks.clear();
    }
    playing=false; 
#ifdef _WIN32
 if(mfStarted){ MFShutdown(); mfStarted=false; }
#endif
}

// IAudioSystem interface implementation
void Audio::playSound(const std::string& name) {
    // This is a simplified implementation - in a full implementation,
    // you would load sound effects by name from assets
    // For now, we'll just trigger a generic sound effect
    // In practice, this would load a sound file and play it via playSfx
}

void Audio::playMusic(const std::string& name) {
    // This is a simplified implementation - in a full implementation,
    // you would load music tracks by name
    // For now, we'll just start the current playlist
    if (!tracks.empty() && !playing) {
        start();
    }
}

void Audio::stopMusic() {
    playing = false;
}

void Audio::setMasterVolume(float volume) {
    m_masterVolume = std::max(0.0f, std::min(1.0f, volume));
}

void Audio::setMusicVolume(float volume) {
    m_musicVolume = std::max(0.0f, std::min(1.0f, volume));
}

void Audio::setSoundVolume(float volume) {
    m_sfxVolume = std::max(0.0f, std::min(1.0f, volume));
}

bool Audio::isMusicPlaying() const {
    return playing;
}
