#include "synth.h"

#include <stdexcept>

Synth::Synth() {
    try {
        settings = new_fluid_settings();
        if (settings == nullptr) {
            throw new std::runtime_error("Unable to create fluid settings");
        }

        synth = new_fluid_synth(settings);
        if (synth == nullptr) {
            throw new std::runtime_error("Unable to create fluid synth");
        }

        adriver = new_fluid_audio_driver(settings, synth);
        if (adriver == nullptr) {
            throw new std::runtime_error("Unable to create audio driver");
        }
    }
    catch (std::runtime_error& e) {
        // Do a mem-safe cleanup and rethrow.
        // TODO JMT: Do these handle receiving nullptr for deletes?
        delete_fluid_audio_driver(adriver);
        delete_fluid_synth(synth);
        delete_fluid_settings(settings);
        throw;
    }
}

Synth::~Synth() {
    // TODO JMT: Do I need to manually clear all of the soundfonts and programs I have handles for?
    delete_fluid_audio_driver(adriver);
    delete_fluid_synth(synth);
    delete_fluid_settings(settings);
}

bool Synth::loadSoundfont(const std::string& filepath) {
    int soundfontId = fluid_synth_sfload(synth, filepath.c_str(), 1);
    if(soundfontId == FLUID_FAILED) {
        return false;
    }

    fluid_sfont_t* soundfontHandle = fluid_synth_get_sfont_by_id(synth, soundfontId);
    const std::string soundfontName{fluid_sfont_get_name(soundfontHandle)};

    Synth::Soundfont soundfont{soundfontName, soundfontId};
    soundfonts.push_back(soundfont);

    fluid_preset_t* preset = nullptr;
    fluid_sfont_iteration_start(soundfontHandle);
    while ((preset = fluid_sfont_iteration_next(soundfontHandle)) != nullptr) {
        const std::string presetName{fluid_preset_get_name(preset)};
        const int bankNumber = fluid_preset_get_banknum(preset);
        const int programNumber = fluid_preset_get_num(preset);

        Synth::Program program {presetName, soundfontId, bankNumber, programNumber};
        programs.push_back(program);
    }

    return true;
}

bool Synth::loadProgram(const Program& program) {
    // TODO JMT: fix this
    fluid_synth_program_select(synth, 0, program.soundfontId, program.bankNumber, program.programNumber);
    fluid_synth_program_select(synth, 1, program.soundfontId, program.bankNumber, program.programNumber);
    return true;
}

bool Synth::setTuning(const std::array<double, NUM_MIDI_NOTES>& pitches) {
    int result = fluid_synth_activate_key_tuning(
        synth,
        0, 0,
        "default",
        pitches.data(),
        1
    );

    if (result == FLUID_OK) {
        result = fluid_synth_activate_tuning(synth, 0, 0, 0, 1);
        result = fluid_synth_activate_tuning(synth, 1, 0, 0, 1);
    }

    return result == FLUID_OK;
}

bool Synth::resetTuning() {
    int result = fluid_synth_activate_key_tuning(
        synth,
        0, 0,
        "default",
        nullptr,
        1
    );

    return result == FLUID_OK;
}

const std::vector<Synth::Program>& Synth::getPrograms() const {
    return programs;
}

fluid_synth_t* Synth::getSynth() {
    return synth;
}
