#include "fluidsynth_backend.hpp"
#include <algorithm>
#include <ipc/common.pb.h>
#include <exception>
#include <filesystem>
#include "file_backend.hpp"
#include <stddef.h>
#include <string.h>
#include <file_backend.hpp>
#include <util.hpp>
#include <filesystem>
#include <limits.h>
namespace fs = std::filesystem;
void FluidSynthBackend::fluidsynth_get_property_list_wrapper(void *udata, const char *name, int type) {
    ((FluidSynthBackend*)udata)->fluidsynth_get_property_list(name, type);
}
void FluidSynthBackend::fluidsynth_get_property_list(const char *name, int type) {
    Property property;
    property.set_path(fmt::format("fluidsynth/{0}", name));
    property.set_name(name);
    switch (type) {
        case FLUID_NO_TYPE:
        case FLUID_NUM_TYPE: {
            property.set_type(PropertyType::Double);
            double min, max;
            if (fluid_settings_getnum_range(settings, name, &min, &max) == FLUID_OK) {
                auto *range = property.mutable_hint()->mutable_range();
                range->set_min(min);
                range->set_max(max);
            }
        } break;
        case FLUID_INT_TYPE: {
            property.set_type(PropertyType::Int);
            int min, max;
            if (fluid_settings_getint_range(settings, name, &min, &max) == FLUID_OK) {
                auto *range = property.mutable_hint()->mutable_range();
                range->set_min(min);
                range->set_max(max);
            }
        } break;
        case FLUID_STR_TYPE: {
            property.set_type(PropertyType::String);
       } break;
       default: {
        throw std::exception();
       } break;
    }
    property.set_id(PropertyId::BackendSpecific);
    fluidsynth_properties.push_back(property);
}
std::vector<Property> FluidSynthBackend::get_property_list() {
	return fluidsynth_properties;
}
void FluidSynthBackend::load(const char *filename) {
	memset(&spec, 0, sizeof(spec));
    current_file = filename;
    spec.format = AUDIO_F32SYS;
    spec.samples = 100;
    spec.channels = 2;
    spec.freq = 96000;
    spec.size = 100 * 2 * sizeof(int16_t);
    File *file = open_file(filename);
    settings = new_fluid_settings();
    fluid_settings_setnum(settings, "synth.sample-rate", 96000.0);
    fluid_settings_setnum(settings, "synth.gain", 0.5);
	fluid_settings_foreach(settings, (void*)this, &fluidsynth_get_property_list_wrapper);
    synth = new_fluid_synth(settings);
    player = new_fluid_player(synth);
    fs::path fpath(filename);
    fpath = fpath.parent_path() / fpath.stem();

    std::string fpath_str = fpath.string();
    std::vector<std::string> try_paths = std::vector<std::string>({
        fpath_str + ".sf2", fpath_str + ".dls"
    });
    bool found = false;
    for (auto &path : try_paths) {
        const char *path_c = path.c_str();
        if (fluid_is_soundfont(path_c)) {
            fluid_synth_sfload(synth, path_c, 1);
            found = true;
            break;
        }
    }
    if (!found) {
        WARNING.writeln("Could not find a valid companion file to use as a sound font. Make sure the extension is all-lowercase, if you're on a case sensitive filesystem (Such as on Linux).");
        throw std::exception();
    }
    file_data = malloc(file->get_len());
    file_len = file->read(file_data, 1, file->get_len());
    delete file;
    file_data = realloc(file_data, file_len);
    fluid_player_add_mem(player, file_data, file_len);
}
extern SDL_AudioSpec obtained;
void FluidSynthBackend::switch_stream(int idx) {
    fluid_player_play(player);
}
void FluidSynthBackend::cleanup() {
    delete_fluid_player(player);
    player = nullptr;
    delete_fluid_synth(synth);
    synth = nullptr;
    delete_fluid_settings(settings);
    settings = nullptr;
    file_len = 0;
    free(file_data);
    file_data = nullptr;
}
size_t FluidSynthBackend::render(void *buf, size_t maxlen) {
    size_t sample_type_len = sizeof(float);
    maxlen /= sample_type_len * 2;
    maxlen *= sample_type_len * 2;
    if (fluid_synth_write_float(synth, maxlen / 2 / sample_type_len, buf, 0, 2, buf, 1, 2) == FLUID_FAILED) {
        return 0;
    }
    return maxlen;
}
bool FluidSynthBackend::is_fluidsynth_setting(std::string path) {
    const char *prefix = "fluidsynth/";
    if (path.length() < strlen(prefix)) return false;
    return path.substr(0, strlen(prefix)) == std::string(prefix);
}
void FluidSynthBackend::seek(double position) {
    fluid_player_seek(player, position);
}
double FluidSynthBackend::get_position() {
    return fluid_player_get_current_tick(player);
}
int FluidSynthBackend::get_stream_idx() {
    return 0;
}
void FluidSynthBackend::set_fluidsynth_property_str(std::string path, std::string val) {
    fluid_settings_setstr(settings, path.c_str(), val.c_str());
}
void FluidSynthBackend::set_fluidsynth_property_num(std::string path, double val) {
    fluid_settings_setnum(settings, path.c_str(), val);
}
void FluidSynthBackend::set_fluidsynth_property_int(std::string path, int val) {
    fluid_settings_setint(settings, path.c_str(), val);
}
std::optional<std::string> FluidSynthBackend::get_fluidsynth_property_str(std::string path) {
    char *tmp;
    if (fluid_settings_dupstr(settings, path.c_str(), &tmp) == FLUID_OK) {
        std::string output = tmp;
        free((void*)tmp);
        return output;
    } else {
        return {};
    }
}
std::optional<double> FluidSynthBackend::get_fluidsynth_property_num(std::string path) {
    double output = NAN;
    if (fluid_settings_getnum(settings, path.c_str(), &output) == FLUID_OK) return output;
    else return {};
}
std::optional<int> FluidSynthBackend::get_fluidsynth_property_int(std::string path) {
    int output = 0;
    if (fluid_settings_getint(settings, path.c_str(), &output) == FLUID_OK) return output;
    else return {};
}
void FluidSynthBackend::reset_fluidsynth_property(std::string path) {
    switch (fluid_settings_get_type(settings, path.c_str())) {
        case FLUID_INT_TYPE: {
            int output;
            if (fluid_settings_getint_default(settings, path.c_str(), &output) == FLUID_OK) {
                fluid_settings_setint(settings, path.c_str(), output);
            }
        } break;
        case FLUID_STR_TYPE: {
            char *val;
            if (fluid_settings_getstr_default(settings, path.c_str(), &val) == FLUID_OK) {
                fluid_settings_setstr(settings, path.c_str(), val);
            }
        } break;
        case FLUID_NUM_TYPE: {
            double val;
            if (fluid_settings_getnum_default(settings, path.c_str(), &val) == FLUID_OK) {
                fluid_settings_setnum(settings, path.c_str(), val);
            }
        } break;
    }
}