/*
 * DISTRHO Cardinal Plugin
 * Copyright (C) 2021-2023 Filipe Coelho <falktx@falktx.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * For a full copy of the GNU General Public License see the LICENSE file.
 */

#include "rack.hpp"
#include "plugin.hpp"

#include "DistrhoUtils.hpp"

// Cardinal (built-in)
#include "Cardinal/src/plugin.hpp"

// Fundamental
#include "Fundamental/src/plugin.hpp"

// Aria
extern Model* modelSpleet;
extern Model* modelSwerge;

// AudibleInstruments
#include "AudibleInstruments/src/plugin.hpp"

// BogaudioModules - integrate theme/skin support
#include <mutex>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#define private public
#include "BogaudioModules/src/skins.hpp"
#undef private

// BogaudioModules
extern Model* modelAD;
extern Model* modelBogaudioLFO;
extern Model* modelBogaudioNoise;
extern Model* modelBogaudioVCA;
extern Model* modelBogaudioVCF;
extern Model* modelBogaudioVCO;
extern Model* modelOffset;
extern Model* modelSampleHold;
extern Model* modelSwitch;
extern Model* modelSwitch18;
extern Model* modelUnison;

// MockbaModular
#include "MockbaModular/src/plugin.hpp"
#include "MockbaModular/src/MockbaModular.hpp"
#undef min
#define saveBack ignoreMockbaModular1
#define loadBack ignoreMockbaModular2
#include "MockbaModular/src/MockbaModular.cpp"
#undef saveBack
#undef loadBack
std::string loadBack(int) { return "res/Empty_gray.svg"; }

// surgext
#include "surgext/src/SurgeXT.h"
void surgext_rack_initialize();
void surgext_rack_update_theme();

// ValleyAudio
#include "ValleyAudio/src/Valley.hpp"

// known terminal modules
std::vector<Model*> hostTerminalModels;

// plugin instances
Plugin* pluginInstance__Cardinal;
Plugin* pluginInstance__Fundamental;
Plugin* pluginInstance__Aria;
Plugin* pluginInstance__AudibleInstruments;
Plugin* pluginInstance__BogaudioModules;
Plugin* pluginInstance__MockbaModular;
Plugin* pluginInstance__surgext;
Plugin* pluginInstance__ValleyAudio;

namespace rack {

namespace asset {
std::string pluginManifest(const std::string& dirname);
std::string pluginPath(const std::string& dirname);
}

namespace plugin {

struct StaticPluginLoader {
    Plugin* const plugin;
    FILE* file;
    json_t* rootJ;

    StaticPluginLoader(Plugin* const p, const char* const name)
        : plugin(p),
          file(nullptr),
          rootJ(nullptr)
    {
#ifdef DEBUG
        DEBUG("Loading plugin module %s", name);
#endif

        p->path = asset::pluginPath(name);

        const std::string manifestFilename = asset::pluginManifest(name);

        if ((file = std::fopen(manifestFilename.c_str(), "r")) == nullptr)
        {
            d_stderr2("Manifest file %s does not exist", manifestFilename.c_str());
            return;
        }

        json_error_t error;
        if ((rootJ = json_loadf(file, 0, &error)) == nullptr)
        {
            d_stderr2("JSON parsing error at %s %d:%d %s", manifestFilename.c_str(), error.line, error.column, error.text);
            return;
        }

        // force ABI, we use static plugins so this doesnt matter as long as it builds
        json_t* const version = json_string((APP_VERSION_MAJOR + ".0").c_str());
        json_object_set(rootJ, "version", version);
        json_decref(version);

        // Load manifest
        p->fromJson(rootJ);

        // Reject plugin if slug already exists
        if (Plugin* const existingPlugin = getPlugin(p->slug))
            throw Exception("Plugin %s is already loaded, not attempting to load it again", p->slug.c_str());
    }

    ~StaticPluginLoader()
    {
        if (rootJ != nullptr)
        {
            // Load modules manifest
            json_t* const modulesJ = json_object_get(rootJ, "modules");
            plugin->modulesFromJson(modulesJ);

            json_decref(rootJ);
            plugins.push_back(plugin);
        }

        if (file != nullptr)
            std::fclose(file);
    }

    bool ok() const noexcept
    {
        return rootJ != nullptr;
    }

    void removeModule(const char* const slugToRemove) const noexcept
    {
        json_t* const modules = json_object_get(rootJ, "modules");
        DISTRHO_SAFE_ASSERT_RETURN(modules != nullptr,);

        size_t i;
        json_t* v;
        json_array_foreach(modules, i, v)
        {
            if (json_t* const slug = json_object_get(v, "slug"))
            {
                if (const char* const value = json_string_value(slug))
                {
                    if (std::strcmp(value, slugToRemove) == 0)
                    {
                        json_array_remove(modules, i);
                        break;
                    }
                }
            }
        }
    }
};

static void initStatic__Cardinal()
{
    Plugin* const p = new Plugin;
    pluginInstance__Cardinal = p;

    const StaticPluginLoader spl(p, "Cardinal");
    if (spl.ok())
    {
        p->addModel(modelHostAudio2);
        p->addModel(modelHostCV);
        p->addModel(modelHostMIDI);
        p->addModel(modelHostMIDICC);
        p->addModel(modelHostMIDIGate);
        p->addModel(modelHostMIDIMap);
        p->addModel(modelHostParameters);
        p->addModel(modelHostParametersMap);
        p->addModel(modelHostTime);
        p->addModel(modelTextEditor);
        /* TODO
       #ifdef HAVE_FFTW3F
        p->addModel(modelAudioToCVPitch);
       #else
        */
        spl.removeModule("AudioToCVPitch");
        /*
       #endif
        */
        spl.removeModule("AIDA-X");
        spl.removeModule("AudioFile");
        spl.removeModule("Blank");
        spl.removeModule("Carla");
        spl.removeModule("ExpanderInputMIDI");
        spl.removeModule("ExpanderOutputMIDI");
        spl.removeModule("HostAudio8");
        spl.removeModule("Ildaeil");
        spl.removeModule("MPV");
        spl.removeModule("SassyScope");
        spl.removeModule("glBars");

        hostTerminalModels = {
            modelHostAudio2,
            modelHostCV,
            modelHostMIDI,
            modelHostMIDICC,
            modelHostMIDIGate,
            modelHostMIDIMap,
            modelHostParameters,
            modelHostParametersMap,
            modelHostTime,
        };
    }
}

static void initStatic__Fundamental()
{
    Plugin* const p = new Plugin;
    pluginInstance__Fundamental = p;

    const StaticPluginLoader spl(p, "Fundamental");
    if (spl.ok())
    {
        p->addModel(modelADSR);
        p->addModel(modelLFO);
        p->addModel(modelMerge);
        p->addModel(modelMidSide);
        p->addModel(modelNoise);
        p->addModel(modelQuantizer);
        p->addModel(modelRandom);
        p->addModel(modelScope);
        p->addModel(modelSplit);
        p->addModel(modelSum);
        p->addModel(modelVCA_1);
        p->addModel(modelVCF);
        p->addModel(modelVCMixer);
        p->addModel(modelVCO);
        spl.removeModule("8vert");
        spl.removeModule("Delay");
        spl.removeModule("LFO2");
        spl.removeModule("Mixer");
        spl.removeModule("Mutes");
        spl.removeModule("Octave");
        spl.removeModule("Pulses");
        spl.removeModule("SEQ3");
        spl.removeModule("SequentialSwitch1");
        spl.removeModule("SequentialSwitch2");
        spl.removeModule("VCA");
        spl.removeModule("VCO2");
    }
}

static void initStatic__Aria()
{
    Plugin* const p = new Plugin;
    pluginInstance__Aria = p;

    const StaticPluginLoader spl(p, "AriaModules");
    if (spl.ok())
    {
        p->addModel(modelSpleet);
        p->addModel(modelSwerge);

        spl.removeModule("Aleister");
        spl.removeModule("Arcane");
        spl.removeModule("Atout");
        spl.removeModule("Blank");
        spl.removeModule("Darius");
        spl.removeModule("Grabby");
        spl.removeModule("Pokies4");
        spl.removeModule("Psychopump");
        spl.removeModule("Q");
        spl.removeModule("Qqqq");
        spl.removeModule("Quack");
        spl.removeModule("Quale");
        spl.removeModule("Rotatoes4");
        spl.removeModule("Smerge");
        spl.removeModule("Solomon16");
        spl.removeModule("Solomon4");
        spl.removeModule("Solomon8");
        spl.removeModule("Splirge");
        spl.removeModule("Splort");
        spl.removeModule("Undular");

    }
}

static void initStatic__AudibleInstruments()
{
    Plugin* const p = new Plugin;
    pluginInstance__AudibleInstruments = p;

    const StaticPluginLoader spl(p, "AudibleInstruments");
    if (spl.ok())
    {
        p->addModel(modelPlaits);

        spl.removeModule("Blinds");
        spl.removeModule("Braids");
        spl.removeModule("Branches");
        spl.removeModule("Clouds");
        spl.removeModule("Elements");
        spl.removeModule("Frames");
        spl.removeModule("Kinks");
        spl.removeModule("Links");
        spl.removeModule("Marbles");
        spl.removeModule("Rings");
        spl.removeModule("Ripples");
        spl.removeModule("Shades");
        spl.removeModule("Shelves");
        spl.removeModule("Stages");
        spl.removeModule("Streams");
        spl.removeModule("Tides");
        spl.removeModule("Tides2");
        spl.removeModule("Veils");
        spl.removeModule("Warps");
    }
}

static void initStatic__BogaudioModules()
{
    Plugin* const p = new Plugin;
    pluginInstance__BogaudioModules = p;

    const StaticPluginLoader spl(p, "BogaudioModules");
    if (spl.ok())
    {
        // Make sure to use match Cardinal theme
        Skins& skins(Skins::skins());
        skins._default = settings::preferDarkPanels ? "dark" : "light";

        p->addModel(modelAD);
        p->addModel(modelBogaudioLFO);
        p->addModel(modelBogaudioNoise);
        p->addModel(modelBogaudioVCA);
        p->addModel(modelBogaudioVCF);
        p->addModel(modelBogaudioVCO);
        p->addModel(modelOffset);
        p->addModel(modelSampleHold);
        p->addModel(modelSwitch);
        p->addModel(modelSwitch18);
        p->addModel(modelUnison);

        // cat plugins/BogaudioModules/plugin.json  | jq -r .modules[].slug - | sort
        spl.removeModule("Bogaudio-Additator");
        spl.removeModule("Bogaudio-AddrSeq");
        spl.removeModule("Bogaudio-AddrSeqX");
        spl.removeModule("Bogaudio-ADSR");
        spl.removeModule("Bogaudio-AMRM");
        spl.removeModule("Bogaudio-Analyzer");
        spl.removeModule("Bogaudio-AnalyzerXL");
        spl.removeModule("Bogaudio-Arp");
        spl.removeModule("Bogaudio-ASR");
        spl.removeModule("Bogaudio-Assign");
        spl.removeModule("Bogaudio-Blank3");
        spl.removeModule("Bogaudio-Blank6");
        spl.removeModule("Bogaudio-Bool");
        spl.removeModule("Bogaudio-Chirp");
        spl.removeModule("Bogaudio-Clpr");
        spl.removeModule("Bogaudio-Cmp");
        spl.removeModule("Bogaudio-CmpDist");
        spl.removeModule("Bogaudio-CVD");
        spl.removeModule("Bogaudio-DADSRH");
        spl.removeModule("Bogaudio-DADSRHPlus");
        spl.removeModule("Bogaudio-Detune");
        spl.removeModule("Bogaudio-DGate");
        spl.removeModule("Bogaudio-Edge");
        spl.removeModule("Bogaudio-EightFO");
        spl.removeModule("Bogaudio-EightOne");
        spl.removeModule("Bogaudio-EQ");
        spl.removeModule("Bogaudio-EQS");
        spl.removeModule("Bogaudio-FFB");
        spl.removeModule("Bogaudio-FlipFlop");
        spl.removeModule("Bogaudio-FMOp");
        spl.removeModule("Bogaudio-Follow");
        spl.removeModule("Bogaudio-FourFO");
        spl.removeModule("Bogaudio-FourMan");
        spl.removeModule("Bogaudio-Inv");
        spl.removeModule("Bogaudio-Lgsw");
        spl.removeModule("Bogaudio-LLFO");
        spl.removeModule("Bogaudio-LLPG");
        spl.removeModule("Bogaudio-Lmtr");
        spl.removeModule("Bogaudio-LPG");
        spl.removeModule("Bogaudio-LVCF");
        spl.removeModule("Bogaudio-LVCO");
        spl.removeModule("Bogaudio-Manual");
        spl.removeModule("Bogaudio-Matrix18");
        spl.removeModule("Bogaudio-Matrix44");
        spl.removeModule("Bogaudio-Matrix44Cvm");
        spl.removeModule("Bogaudio-Matrix81");
        spl.removeModule("Bogaudio-Matrix88");
        spl.removeModule("Bogaudio-Matrix88Cv");
        spl.removeModule("Bogaudio-Matrix88M");
        spl.removeModule("Bogaudio-MegaGate");
        spl.removeModule("Bogaudio-Mix1");
        spl.removeModule("Bogaudio-Mix2");
        spl.removeModule("Bogaudio-Mix4");
        spl.removeModule("Bogaudio-Mix4x");
        spl.removeModule("Bogaudio-Mix8");
        spl.removeModule("Bogaudio-Mix8x");
        spl.removeModule("Bogaudio-Mono");
        spl.removeModule("Bogaudio-Mult");
        spl.removeModule("Bogaudio-Mumix");
        spl.removeModule("Bogaudio-Mute8");
        spl.removeModule("Bogaudio-Nsgt");
        spl.removeModule("Bogaudio-OneEight");
        spl.removeModule("Bogaudio-Pan");
        spl.removeModule("Bogaudio-PEQ");
        spl.removeModule("Bogaudio-PEQ14");
        spl.removeModule("Bogaudio-PEQ14XF");
        spl.removeModule("Bogaudio-PEQ6");
        spl.removeModule("Bogaudio-PEQ6XF");
        spl.removeModule("Bogaudio-Pgmr");
        spl.removeModule("Bogaudio-PgmrX");
        spl.removeModule("Bogaudio-PolyCon");
        spl.removeModule("Bogaudio-PolyCon8");
        spl.removeModule("Bogaudio-PolyMult");
        spl.removeModule("Bogaudio-PolyOff16");
        spl.removeModule("Bogaudio-PolyOff8");
        spl.removeModule("Bogaudio-Pressor");
        spl.removeModule("Bogaudio-Pulse");
        spl.removeModule("Bogaudio-Ranalyzer");
        spl.removeModule("Bogaudio-Reftone");
        spl.removeModule("Bogaudio-RGate");
        spl.removeModule("Bogaudio-Shaper");
        spl.removeModule("Bogaudio-ShaperPlus");
        spl.removeModule("Bogaudio-Sine");
        spl.removeModule("Bogaudio-Slew");
        spl.removeModule("Bogaudio-Stack");
        spl.removeModule("Bogaudio-Sums");
        spl.removeModule("Bogaudio-Switch1616");
        spl.removeModule("Bogaudio-Switch44");
        spl.removeModule("Bogaudio-Switch81");
        spl.removeModule("Bogaudio-Switch88");
        spl.removeModule("Bogaudio-UMix");
        spl.removeModule("Bogaudio-VCAmp");
        spl.removeModule("Bogaudio-VCM");
        spl.removeModule("Bogaudio-Velo");
        spl.removeModule("Bogaudio-Vish");
        spl.removeModule("Bogaudio-VU");
        spl.removeModule("Bogaudio-Walk");
        spl.removeModule("Bogaudio-Walk2");
        spl.removeModule("Bogaudio-XCO");
        spl.removeModule("Bogaudio-XFade");
    }
}

static void initStatic__MockbaModular()
{
    Plugin* const p = new Plugin;
    pluginInstance__MockbaModular = p;

    const StaticPluginLoader spl(p, "MockbaModular");
    if (spl.ok())
    {
        p->addModel(modelCZOsc);
        p->addModel(modelFiltah);
        p->addModel(modelMaugOsc);
        p->addModel(modelMixah);
        p->addModel(modelPannah);
        p->addModel(modelReVoltah);
        p->addModel(modelShapah);

        spl.removeModule("Blank");
        spl.removeModule("Comparator");
        spl.removeModule("Countah");
        spl.removeModule("CZDblSine");
        spl.removeModule("CZPulse");
        spl.removeModule("CZReso1");
        spl.removeModule("CZReso2");
        spl.removeModule("CZReso3");
        spl.removeModule("CZSaw");
        spl.removeModule("CZSawPulse");
        spl.removeModule("CZSquare");
        spl.removeModule("Dividah");
        spl.removeModule("DualBUFFER");
        spl.removeModule("DualNOT");
        spl.removeModule("DualOR");
        spl.removeModule("DualNOR");
        spl.removeModule("DualAND");
        spl.removeModule("DualNAND");
        spl.removeModule("DualXOR");
        spl.removeModule("DualXNOR");
        spl.removeModule("Feidah");
        spl.removeModule("FeidahS");
        spl.removeModule("Holdah");
        spl.removeModule("MaugSaw");
        spl.removeModule("MaugSaw2");
        spl.removeModule("MaugShark");
        spl.removeModule("MaugSquare");
        spl.removeModule("MaugSquare2");
        spl.removeModule("MaugSquare3");
        spl.removeModule("MaugTriangle");
        spl.removeModule("Mixah3");
        spl.removeModule("PSelectah");
        spl.removeModule("Selectah");
        spl.removeModule("UDPClockMaster");
        spl.removeModule("UDPClockSlave");
    }
}

static void initStatic__surgext()
{
    Plugin* const p = new Plugin;
    pluginInstance__surgext = p;

    const StaticPluginLoader spl(p, "surgext");
    if (spl.ok())
    {
        p->addModel(modelVCOModern);
        p->addModel(modelVCOSine);
        /*
        p->addModel(modelVCOAlias);
        p->addModel(modelVCOClassic);
        p->addModel(modelVCOFM2);
        p->addModel(modelVCOFM3);
        p->addModel(modelVCOSHNoise);
        p->addModel(modelVCOString);
        p->addModel(modelVCOTwist);
        p->addModel(modelVCOWavetable);
        p->addModel(modelVCOWindow);
        */
        spl.removeModule("SurgeXTOSCAlias");
        spl.removeModule("SurgeXTOSCClassic");
        spl.removeModule("SurgeXTOSCFM2");
        spl.removeModule("SurgeXTOSCFM3");
        spl.removeModule("SurgeXTOSCSHNoise");
        spl.removeModule("SurgeXTOSCString");
        spl.removeModule("SurgeXTOSCTwist");
        spl.removeModule("SurgeXTOSCWavetable");
        spl.removeModule("SurgeXTOSCWindow");

        // Add the ported ones
        p->addModel(modelSurgeLFO);
        p->addModel(modelSurgeMixer);
        p->addModel(modelSurgeMixerSlider);
        p->addModel(modelSurgeModMatrix);
        p->addModel(modelSurgeWaveshaper);
        /*
        p->addModel(modelSurgeDelay);
        p->addModel(modelSurgeDelayLineByFreq);
        p->addModel(modelSurgeDelayLineByFreqExpanded);
        p->addModel(modelSurgeDigitalRingMods);
        p->addModel(modelSurgeVCF);
        */
        spl.removeModule("SurgeXTDelay");
        spl.removeModule("SurgeXTDelayLineByFreq");
        spl.removeModule("SurgeXTDelayLineByFreqExpanded");
        spl.removeModule("SurgeXTDigitalRingMod");
        spl.removeModule("SurgeXTVCF");

        p->addModel(modelFXNimbus);
        spl.removeModule("SurgeXTFXBonsai");
        spl.removeModule("SurgeXTFXChorus");
        spl.removeModule("SurgeXTFXChow");
        spl.removeModule("SurgeXTFXCombulator");
        spl.removeModule("SurgeXTDigitalRingMod");
        spl.removeModule("SurgeXTFXDistortion");
        spl.removeModule("SurgeXTFXExciter");
        spl.removeModule("SurgeXTFXEnsemble");
        spl.removeModule("SurgeXTFXFlanger");
        spl.removeModule("SurgeXTFXFrequencyShifter");
        spl.removeModule("SurgeXTFXNeuron");
        spl.removeModule("SurgeXTFXPhaser");
        spl.removeModule("SurgeXTFXResonator");
        spl.removeModule("SurgeXTFXReverb");
        spl.removeModule("SurgeXTFXReverb2");
        spl.removeModule("SurgeXTFXRingMod");
        spl.removeModule("SurgeXTFXRotarySpeaker");
        spl.removeModule("SurgeXTFXSpringReverb");
        spl.removeModule("SurgeXTFXTreeMonster");
        spl.removeModule("SurgeXTFXVocoder");

        /*
        p->addModel(modelEGxVCA);
        p->addModel(modelQuadAD);
        p->addModel(modelQuadLFO);
        p->addModel(modelUnisonHelper);
        p->addModel(modelUnisonHelperCVExpander);
        */
        spl.removeModule("SurgeXTEGxVCA");
        spl.removeModule("SurgeXTQuadAD");
        spl.removeModule("SurgeXTQuadLFO");
        spl.removeModule("SurgeXTUnisonHelper");
        spl.removeModule("SurgeXTUnisonHelperCVExpander");

        surgext_rack_initialize();
    }
}

/*
static void initStatic__ValleyAudio()
{
    Plugin* const p = new Plugin;
    pluginInstance__ValleyAudio = p;

    const StaticPluginLoader spl(p, "ValleyAudio");
    if (spl.ok())
    {
        p->addModel(modelDexter);
        p->addModel(modelInterzone);

        spl.removeModule("Amalgam");
        spl.removeModule("Feline");
        spl.removeModule("Plateau");
        spl.removeModule("Terrorform");
        spl.removeModule("Topograph");
        spl.removeModule("uGraph");
    }
}
*/

void initStaticPlugins()
{
    initStatic__Cardinal();
    initStatic__Fundamental();
    initStatic__Aria();
    initStatic__AudibleInstruments();
    initStatic__BogaudioModules();
    initStatic__MockbaModular();
    initStatic__surgext();
    /*
    initStatic__ValleyAudio();
    */
}

void destroyStaticPlugins()
{
    for (Plugin* p : plugins)
        delete p;
    plugins.clear();
}

void updateStaticPluginsDarkMode()
{
    const bool darkMode = settings::preferDarkPanels;
    // bogaudio
    {
        Skins& skins(Skins::skins());
        skins._default = darkMode ? "dark" : "light";

        std::lock_guard<std::mutex> lock(skins._defaultSkinListenersLock);
        for (auto listener : skins._defaultSkinListeners) {
            listener->defaultSkinChanged(skins._default);
        }
    }
    // surgext
    {
        surgext_rack_update_theme();
    }
}

}
}
