// native_bridge/src/NativeInstrumentEngine.cpp
#include "NativeInstrumentEngine.h"

#include <fluidsynth.h>
#include <sfizz.hpp>
#include "Vst3Instrument.h"

// void* members keep fluid types out of the public header; cast here.
#define FS_SYNTH (static_cast<fluid_synth_t*>(synth))
#define FS_SETTINGS (static_cast<fluid_settings_t*>(settings))

#include <cstring>
#include <iostream>

// -----------------------------------------------------------------
// 1. SOUNDFONT ENGINE (.SF2 / .SF3) VIA FLUIDSYNTH C API
// -----------------------------------------------------------------
FluidSynthInstrument::FluidSynthInstrument()
    : settings(nullptr), synth(nullptr), sfontId(-1) {
    for (uint32_t i = 0; i < 16; ++i) bank_[i] = 0;
}

FluidSynthInstrument::~FluidSynthInstrument() {
    if (synth) delete_fluid_synth(FS_SYNTH);
    if (settings) delete_fluid_settings(FS_SETTINGS);
}

bool FluidSynthInstrument::loadSoundFontFile(const std::string& path, double sampleRate) {
    if (synth) { delete_fluid_synth(FS_SYNTH); synth = nullptr; }
    if (settings) { delete_fluid_settings(FS_SETTINGS); settings = nullptr; }
    settings = new_fluid_settings();
    fluid_settings_setnum(FS_SETTINGS, "synth.sample-rate", sampleRate);
    fluid_settings_setint(FS_SETTINGS, "synth.polyphony", 256);
    fluid_settings_setint(FS_SETTINGS, "synth.verbose", 0);
    synth = new_fluid_synth(FS_SETTINGS);
    if (!synth) return false;
    sfontId = fluid_synth_sfload(FS_SYNTH, path.c_str(), 1);
    if (sfontId == -1) return false;
    // Reset all channels to font preset 0 (spec §VII: bank0/prog0 piano)
    for (uint32_t ch = 0; ch < 16; ++ch) {
        bank_[ch] = 0;
        fluid_synth_program_select(FS_SYNTH, ch, sfontId, 0, 0);
    }
    return true;
}

bool FluidSynthInstrument::init(double sampleRate, uint32_t maxBlockSize) {
    return synth != nullptr;
}

void FluidSynthInstrument::selectProgram(uint32_t channel, uint32_t bank, uint32_t program) {
    if (!synth || channel >= 16) return;
    bank_[channel] = bank;
    fluid_synth_program_select(FS_SYNTH, channel, sfontId, bank, program);
}

void FluidSynthInstrument::noteOn(uint32_t channel, uint32_t pitch, float velocity, uint32_t sampleOffset) {
    if (!synth) return;
    int velInt = static_cast<int>(velocity * 127.0f);
    fluid_synth_noteon(FS_SYNTH, channel, pitch, velInt);
}

void FluidSynthInstrument::noteOff(uint32_t channel, uint32_t pitch, uint32_t sampleOffset) {
    if (!synth) return;
    fluid_synth_noteoff(FS_SYNTH, channel, pitch);
}

void FluidSynthInstrument::controlChange(uint32_t channel, uint32_t cc, uint32_t value) {
    if (!synth || channel >= 16) return;
    // Bank select MSB/LSB (A12): CC0 = (bank>>7)&0x7F, CC32 = bank&0x7F
    if (cc == 0)       bank_[channel] = (bank_[channel] & 0x7Fu) | ((value & 0x7Fu) << 7);
    else if (cc == 32) bank_[channel] = (bank_[channel] & ~0x7Fu) | (value & 0x7Fu);
    fluid_synth_cc(FS_SYNTH, channel, cc, value);
}

void FluidSynthInstrument::programChange(uint32_t channel, uint32_t program) {
    if (!synth || channel >= 16) return;
    // Dung bank da nhan tu CC0/CC32 — bank hardcode 0 lam preset o bank != 0
    // khong duoc chon (fluid giu preset cu -> ra piano sai).
    fluid_synth_program_select(FS_SYNTH, channel, sfontId, bank_[channel], program);
}

void FluidSynthInstrument::pitchBend(uint32_t channel, uint32_t bend14) {
    if (!synth) return;
    // fluid_synth_pitch_bend takes the raw 14-bit value (center 8192).
    fluid_synth_pitch_bend(FS_SYNTH, channel, bend14);
}

bool FluidSynthInstrument::openGUI(void* parentWindowHandle) {
    return false; // SoundFont uses Web GUI Manager / Reskinned Knobs
}

void FluidSynthInstrument::closeGUI() {}

void FluidSynthInstrument::processAudioBlock(float* outputL, float* outputR, uint32_t numSamples) {
    if (!synth) return;
    fluid_synth_write_float(FS_SYNTH, numSamples, outputL, 0, 1, outputR, 0, 1);
}

// -----------------------------------------------------------------
// 2. SFZ ENGINE (.SFZ) VIA SFIZZ C++ API
// -----------------------------------------------------------------
bool SfizzInstrument::loadSfzFile(const std::string& path, double sampleRate) {
    sfizzSynth.setSampleRate(sampleRate);
    return sfizzSynth.loadSfzFile(path);
}

bool SfizzInstrument::init(double sampleRate, uint32_t maxBlockSize) {
    sfizzSynth.setSampleRate(sampleRate);
    sfizzSynth.setSamplesPerBlock(maxBlockSize);
    return true;
}

void SfizzInstrument::selectProgram(uint32_t channel, uint32_t bank, uint32_t program) {}

void SfizzInstrument::noteOn(uint32_t channel, uint32_t pitch, float velocity, uint32_t sampleOffset) {
    sfizzSynth.hdNoteOn(sampleOffset, pitch, velocity);
}

void SfizzInstrument::noteOff(uint32_t channel, uint32_t pitch, uint32_t sampleOffset) {
    sfizzSynth.hdNoteOff(sampleOffset, pitch, 0.0f);
}

void SfizzInstrument::controlChange(uint32_t channel, uint32_t cc, uint32_t value) {
    sfizzSynth.cc(0, static_cast<int>(cc), static_cast<int>(value));
}

void SfizzInstrument::programChange(uint32_t channel, uint32_t program) {
    sfizzSynth.programChange(0, static_cast<int>(program));
}

void SfizzInstrument::pitchBend(uint32_t channel, uint32_t bend14) {
    sfizzSynth.pitchWheel(0, static_cast<int>(bend14));
}

bool SfizzInstrument::openGUI(void* parentWindowHandle) { return false; }
void SfizzInstrument::closeGUI() {}

void SfizzInstrument::processAudioBlock(float* outputL, float* outputR, uint32_t numSamples) {
    float* channels[2] = { outputL, outputR };
    sfizzSynth.renderBlock(channels, numSamples, 1); // numOutputs=1 = stereo L/R pair (2 ch)
}

// -----------------------------------------------------------------
// 3. MULTI-CHANNEL INSTRUMENT MANAGER (A10)
// -----------------------------------------------------------------
std::unique_ptr<INativeInstrument> InstrumentEngineManager::create_instrument(InstrumentType type) {
    switch (type) {
        case InstrumentType::SOUNDFONT_SF2_SF3: return std::make_unique<FluidSynthInstrument>();
        case InstrumentType::SFZ:                return std::make_unique<SfizzInstrument>();
        case InstrumentType::VST3:               return std::make_unique<Vst3Instrument>();
        // ponytail: VST2 host (VST2.4 SDK, Steinberg discontinued) not implemented.
        case InstrumentType::VST2:
        default:                                 return nullptr;
    }
}

bool InstrumentEngineManager::assign(uint32_t channel, InstrumentType type,
                                     const std::string& path, double sampleRate,
                                     uint32_t blockSize) {
    if (channel >= 16) return false;
    auto inst = create_instrument(type);
    if (!inst) return false;
    bool loaded = false;
    if (type == InstrumentType::SOUNDFONT_SF2_SF3)
        loaded = static_cast<FluidSynthInstrument*>(inst.get())->loadSoundFontFile(path, sampleRate);
    else if (type == InstrumentType::SFZ)
        loaded = static_cast<SfizzInstrument*>(inst.get())->loadSfzFile(path, sampleRate);
    else if (type == InstrumentType::VST3)
        loaded = static_cast<Vst3Instrument*>(inst.get())->loadPlugin(path, sampleRate);
    if (!loaded) return false;
    // init() AFTER load: FluidSynth creates its synth inside loadSoundFontFile.
    if (!inst->init(sampleRate, blockSize)) return false;
    // Replacing an existing instrument drops its voices with the old engine.
    // Load may run on a detached thread (VST3 init is slow): only the map
    // write is under the mutex so renderAll on the audio loop never stalls.
    // CRITICAL: the OLD instrument is destroyed AFTER mu_ is released. VST3
    // teardown (setProcessing(false) / terminate / view removed) can block;
    // under the lock it would stall renderAll -> bridge Not Responding,
    // transport stop hangs, notes never turn off (must kill daw_engine).
    std::unique_ptr<INativeInstrument> oldInst;
    {
        std::lock_guard<std::mutex> lock(mu_);
        auto it = channels_.find(channel);
        if (it != channels_.end()) oldInst = std::move(it->second);
        channels_[channel] = std::move(inst);
    }
    oldInst.reset();
    return true;
}

INativeInstrument* InstrumentEngineManager::get(uint32_t channel) {
    std::lock_guard<std::mutex> lock(mu_);
    auto it = channels_.find(channel);
    return it == channels_.end() ? nullptr : it->second.get();
}

void InstrumentEngineManager::setReloading(uint32_t channel, bool on) {
    std::lock_guard<std::mutex> lock(mu_);
    auto it = channels_.find(channel);
    if (it != channels_.end()) it->second->setReloading(on);
}

void InstrumentEngineManager::allNotesOff() {
    std::lock_guard<std::mutex> lock(mu_);
    for (auto& [ch, inst] : channels_) {
        for (uint32_t n = 0; n < 128; ++n) inst->noteOff(ch, n, 0);
    }
}

void InstrumentEngineManager::renderAll(float* outputL, float* outputR, uint32_t numSamples) {
    std::lock_guard<std::mutex> lock(mu_);
    std::memset(outputL, 0, numSamples * sizeof(float));
    std::memset(outputR, 0, numSamples * sizeof(float));
    if (channels_.empty()) return;
    if (scratchL_.size() < numSamples) {
        scratchL_.resize(numSamples);
        scratchR_.resize(numSamples);
    }
    for (auto& [ch, inst] : channels_) {
        std::memset(scratchL_.data(), 0, numSamples * sizeof(float));
        std::memset(scratchR_.data(), 0, numSamples * sizeof(float));
        inst->processAudioBlock(scratchL_.data(), scratchR_.data(), numSamples);
        for (uint32_t i = 0; i < numSamples; ++i) {
            outputL[i] += scratchL_[i];
            outputR[i] += scratchR_[i];
        }
    }
}
