// SPDX-License-Identifier: Unlicense

#include "sfx.h"

#ifdef SHR3D_SFX

#include "base64.h"
#include "global.h"
#include "string_.h"
#include <string.h>

#ifdef SHR3D_SFX_CORE
#include "sfxCore.h"
#endif // SHR3D_SFX_CORE

#ifdef SHR3D_SFX_CORE_EXTENSION_V2
#include "sfxCore/sfxCoreExtensionV2/api.h"
#endif // SHR3D_SFX_CORE_EXTENSION_V2

#ifdef SHR3D_SFX_PLUGIN_CLAP
#include "sfxPluginClap.h"
#endif // SHR3D_SFX_PLUGIN_CLAP

#ifdef SHR3D_SFX_PLUGIN_VST
#include "sfxPluginVst.h"
#endif // SHR3D_SFX_PLUGIN_VST

#ifdef SHR3D_SFX_PLUGIN_VST3
#include "sfxPluginVst3.h"
#endif // SHR3D_SFX_PLUGIN_VST3

std::map<SfxSystem, std::vector<std::string>> Sfx::names{
#ifdef SHR3D_SFX_CORE
  {
    SfxSystem::core,
    {
#ifdef SHR3D_SFX_CORE_NEURALAMPMODELER
      "Neural Amp Modeler",
#endif // SHR3D_SFX_CORE_NEURALAMPMODELER
#ifdef SHR3D_SFX_CORE_HEXFIN
      "Hexfin",
#endif // SHR3D_SFX_CORE_HEXFIN
    }
  },
#ifdef SHR3D_SFX_CORE_AIRWINDOWS
  {
    SfxSystem::coreAirWindows,
    {
      "Acceleration",
      "Acceleration2",
      "ADClip7",
      "ADClip8",
      "ADT",
      "Air",
      "Air2",
      "Air3",
      "Apicolypse",
      "AQuickVoiceClip",
      "AtmosphereBuss",
      "AtmosphereChannel",
      "Aura",
      "AutoPan",
      "Average",
      "AverMatrix",
      "Balanced",
      "BassAmp",
      "BassDrive",
      "BassKit",
      "Baxandall",
      "Baxandall2",
      "Beam",
      "BigAmp",
      "Biquad",
      "Biquad2",
      "BiquadDouble",
      "BiquadHiLo",
      "BiquadNonLin",
      "BiquadOneHalf",
      "BiquadPlus",
      "BiquadStack",
      "BiquadTriple",
      "Bite",
      "BitGlitter",
      "BitShiftGain",
      "BitShiftPan",
      "BlockParty",
      "BrassRider",
      "BrightAmbience",
      "BrightAmbience2",
      "BrightAmbience3",
      "BuildATPDF",
      "BussColors4",
      "ButterComp",
      "ButterComp2",
      "C5RawBuss",
      "C5RawChannel",
      "Cabs",
      "Calibre",
      "Capacitor",
      "Capacitor2",
      "Chamber",
      "Chamber2",
      "Channel4",
      "Channel5",
      "Channel6",
      "Channel7",
      "Channel8",
      "Channel9",
      "Chorus",
      "ChorusEnsemble",
      "ChromeOxide",
      "Cider",
      "ClearCoat",
      "ClipOnly2",
      "ClipSoftly",
      "CloudCoat",
      "Coils",
      "Coils2",
      "Cojones",
      "Compresaturator",
      "Console0Buss",
      "Console0Channel",
      "Console4Buss",
      "Console4Channel",
      "Console5Buss",
      "Console5Channel",
      "Console5DarkCh",
      "Console6Buss",
      "Console6Channel",
      "Console7Buss",
      "Console7Cascade",
      "Console7Channel",
      "Console7Crunch",
      "Console8BussHype",
      "Console8BussIn",
      "Console8BussOut",
      "Console8ChannelHype",
      "Console8ChannelIn",
      "Console8ChannelOut",
      "Console8LiteBuss",
      "Console8LiteChannel",
      "Console8SubHype",
      "Console8SubIn",
      "Console8SubOut",
      "Console9Buss",
      "Console9Channel",
      "ConsoleLABuss",
      "ConsoleLAChannel",
      "ConsoleMCBuss",
      "ConsoleMCChannel",
      "ConsoleMDBuss",
      "ConsoleMDChannel",
      "ConsoleXBuss",
      "ConsoleXChannel",
      "ContentHideD",
      "CreamCoat",
      "Creature",
      "CrickBass",
      "CrunchCoat",
      "CrunchyGrooveWear",
      "Crystal",
      "CStrip",
      "CStrip2",
      "curve",
      "Dark",
      "DarkNoise",
      "DCVoltage",
      "DeBess",
      "Deckwrecka",
      "DeEss",
      "DeHiss",
      "Density",
      "Density2",
      "DeRez",
      "DeRez2",
      "DeRez3",
      "Desk",
      "Desk4",
      "DigitalBlack",
      "Dirt",
      "Discontinuity",
      "Distance",
      "Distance2",
      "Distance3",
      "Distortion",
      "Ditherbox",
      "DitherFloat",
      "DitherMeDiskers",
      "DitherMeTimbers",
      "Doublelay",
      "DoublePaul",
      "Drive",
      "DrumSlam",
      "DubCenter",
      "Dubly",
      "DubSub",
      "DustBunny",
      "Dynamics",
      "Dyno",
      "Edge",
      "EdIsDim",
      "Elation",
      "ElectroHat",
      "Energy",
      "Energy2",
      "Ensemble",
      "EQ",
      "EveryConsole",
      "EverySlew",
      "EveryTrim",
      "Exciter",
      "Facet",
      "FathomFive",
      "FinalClip",
      "FireAmp",
      "Flipity",
      "Floor",
      "Flutter",
      "Focus",
      "Fracture",
      "Fracture2",
      "FromTape",
      "Galactic",
      "Galactic2",
      "GalacticVibe",
      "Gatelope",
      "GlitchShifter",
      "GoldenSlew",
      "Golem",
      "GrindAmp",
      "Gringer",
      "GrooveWear",
      "GuitarConditioner",
      "HardVacuum",
      "Hermepass",
      "HermeTrim",
      "HighGlossDither",
      "HighImpact",
      "Highpass",
      "Highpass2",
      "Holt",
      "Holt2",
      "Hombre",
      "Huge",
      "Hull",
      "Hull2",
      "Hype",
      "Hypersonic",
      "HypersonX",
      "Infinity",
      "Infinity2",
      "Inflamer",
      "Infrasonic",
      "Interstage",
      "IronOxide5",
      "IronOxideClassic",
      "IronOxideClassic2",
      "Isolator",
      "Isolator2",
      "Kalman",
      "kCathedral",
      "kCathedral2",
      "kCathedral3",
      "kChamberAR",
      "kGuitarHall",
      "kPlate140",
      "kPlate240",
      "kPlateA",
      "kPlateB",
      "kPlateC",
      "kPlateD",
      "LeadAmp",
      "LeftoMono",
      "LilAmp",
      "Logical4",
      "Loud",
      "Lowpass",
      "Lowpass2",
      "LRFlipTimer",
      "Luxor",
      "MackEQ",
      "Mackity",
      "MatrixVerb",
      "Melt",
      "MidAmp",
      "MidSide",
      "Mojo",
      "Monitoring",
      "Monitoring2",
      "Monitoring3",
      "MoNoam",
      "MSFlipTimer",
      "MultiBandDistortion",
      "MV",
      "MV2",
      "NaturalizeDither",
      "NCSeventeen",
      "Neverland",
      "Nikola",
      "NodeDither",
      "Noise",
      "NonlinearSpace",
      "NotJustAnotherCD",
      "NotJustAnotherDither",
      "OneCornerClip",
      "OrbitKick",
      "Overheads",
      "Pafnuty",
      "Pafnuty2",
      "Parametric",
      "PaulDither",
      "PaulWide",
      "PDBuss",
      "PDChannel",
      "PeaksOnly",
      "Pear",
      "Pear2",
      "PhaseNudge",
      "PitchDelay",
      "PitchNasty",
      "PlatinumSlew",
      "PocketVerbs",
      "Pockey",
      "Pockey2",
      "Podcast",
      "PodcastDeluxe",
      "Point",
      "Pop",
      "Pop2",
      "Pop3",
      "PowerSag",
      "PowerSag2",
      "Precious",
      "Preponderant",
      "Pressure4",
      "Pressure5",
      "PurestAir",
      "PurestConsole2Buss",
      "PurestConsole2Channel",
      "PurestConsole3Buss",
      "PurestConsole3Channel",
      "PurestConsoleBuss",
      "PurestConsoleChannel",
      "PurestDrive",
      "PurestEcho",
      "PurestFade",
      "PurestGain",
      "PurestSquish",
      "PurestWarm",
      "PurestWarm2",
      "Pyewacket",
      "RawGlitters",
      "RawTimbers",
      "Recurve",
      "Remap",
      "ResEQ",
      "ResEQ2",
      "Reverb",
      "Righteous4",
      "RightoMono",
      "SampleDelay",
      "Shape",
      "ShortBuss",
      "SideDull",
      "Sidepass",
      "Silhouette",
      "Sinew",
      "SingleEndedTriode",
      "Slew",
      "Slew2",
      "Slew3",
      "SlewOnly",
      "SlewSonic",
      "Smooth",
      "SoftGate",
      "SpatializeDither",
      "Spiral",
      "Spiral2",
      "Srsly",
      "Srsly2",
      "Srsly3",
      "StarChild",
      "StarChild2",
      "StereoChorus",
      "StereoDoubler",
      "StereoEnsemble",
      "StereoFX",
      "Stonefire",
      "StoneFireComp",
      "StudioTan",
      "SubsOnly",
      "SubTight",
      "Surge",
      "SurgeTide",
      "Sweeten",
      "Swell",
      "Tape",
      "TapeDelay",
      "TapeDelay2",
      "TapeDither",
      "TapeDust",
      "TapeFat",
      "Texturize",
      "TexturizeMS",
      "Thunder",
      "ToneSlant",
      "ToTape5",
      "ToTape6",
      "ToVinyl4",
      "TPDFDither",
      "TPDFWide",
      "TransDesk",
      "Tremolo",
      "TremoSquare",
      "Trianglizer",
      "TripleSpread",
      "Tube",
      "Tube2",
      "TubeDesk",
      "uLawDecode",
      "uLawEncode",
      "Ultrasonic",
      "UltrasonicLite",
      "UltrasonicMed",
      "UltrasonX",
      "UnBox",
      "VariMu",
      "Verbity",
      "Verbity2",
      "Vibrato",
      "VinylDither",
      "VoiceOfTheStarship",
      "VoiceTrick",
      "Weight",
      "Wider",
      "Wolfbot",
      "XBandpass",
      "XHighpass",
      "XLowpass",
      "XNotch",
      "XRegion",
      "YBandpass",
      "YHighpass",
      "YLowpass",
      "YNotBandpass",
      "YNotch",
      "YNotHighpass",
      "YNotLowpass",
      "YNotNotch",
      "ZBandpass",
      "ZBandpass2",
      "ZHighpass",
      "ZHighpass2",
      "ZLowpass",
      "ZLowpass2",
      "ZNotch",
      "ZNotch2",
      "ZOutputStage",
      "ZRegion",
      "ZRegion2",

    }
  },
#endif // SHR3D_SFX_CORE_AIRWINDOWS
#ifdef SHR3D_SFX_CORE_RAKARRACK
  {
    SfxSystem::coreRakarrack,
    {

      "Reverb", // works
      "Echo", // works
      "Chorus", // works
      "Flanger", // works
      "Phaser", // works
#ifdef SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
      "Overdrive", // fails
      "Distorsion", // fails
#endif // SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
      "EQ", // fails
      "Parametric EQ", // fails
      "Cabinet", // fails
      "Compressor", // not sure
      "WahWah", // works
      "AlienWah", // works
      "Pan", // works
#ifdef SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
#if 0
      "Harmonizer", // fails
#endif
#endif // SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
      "Musical Delay", // works
      "NoiseGate", // works
#ifdef SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
      "Derelict", // fails
#endif // SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
      "Analog Phaser", // works
      "Valve", // works
      "DualFlange", // fails
      "Ring", // fails
      "Exciter", // works
#ifdef SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
      "DistBand", // fails
#endif // SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
      "Arpie", // works
      "Expander", // works
      "Shuffle", // works
      "Synthfilter", // works
      "VaryBand", // works
#ifdef SHR3D_SFX_CORE_RAKARRACK_CONVOLOTRON
      "Convolotron", // works
#endif // SHR3D_SFX_CORE_RAKARRACK_CONVOLOTRON
      "MuTroMojo", // works
      "Echoverse", // works
      "Coil Crafter", // works
      "Shelf Boost", // works
#ifdef SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
      "Vocoder", // fails
#endif // SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
      "Sustainer", // works
#ifdef SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
      "Sequence", // fails
      "Shifter", // fails
      "StompBox", // fails
      "Reverbtron", // fails
#endif // SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
      "Echotron", // works
#ifdef SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
      "StereoHarm", // fails
#endif // SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
      "CompBand", // fails
      "OpticalTrem", // works
      "Vibe", // works
    }
  }
#endif // SHR3D_SFX_CORE_RAKARRACK
#endif // SHR3D_SFX_CORE
};

SfxSystem Sfx::name2SfxSystem(const char* name)
{
#ifdef SHR3D_SFX_CORE
  if (strcmp(reinterpret_cast<const char*>(name), "Core") == 0)
    return SfxSystem::core;
#endif // SHR3D_SFX_CORE

#ifdef SHR3D_SFX_CORE_AIRWINDOWS
  if (strcmp(reinterpret_cast<const char*>(name), "CoreAirWindows") == 0)
    return SfxSystem::coreAirWindows;
#endif // SHR3D_SFX_CORE_AIRWINDOWS

#ifdef SHR3D_SFX_CORE_RAKARRACK
  if (strcmp(reinterpret_cast<const char*>(name), "CoreRakarrack") == 0)
    return SfxSystem::coreRakarrack;
#endif // SHR3D_SFX_CORE_RAKARRACK

#ifdef SHR3D_SFX_CORE_EXTENSION_V2
  if (strcmp(reinterpret_cast<const char*>(name), "ExtensionV2") == 0)
    return SfxSystem::coreExtensionV2;
#endif // SHR3D_SFX_CORE_EXTENSION_V2

#ifdef SHR3D_SFX_PLUGIN_CLAP
  if (strcmp(reinterpret_cast<const char*>(name), "CLAP") == 0)
    return SfxSystem::clap;
#endif // SHR3D_SFX_PLUGIN_CLAP

#ifdef SHR3D_SFX_PLUGIN_LV2
  if (strcmp(reinterpret_cast<const char*>(name), "LV2") == 0)
    return SfxSystem::lv2;
#endif // SHR3D_SFX_PLUGIN_LV2

#ifdef SHR3D_SFX_PLUGIN_VST
  if (strcmp(reinterpret_cast<const char*>(name), "VST") == 0)
    return SfxSystem::vst;
#endif // SHR3D_SFX_PLUGIN_VST

#ifdef SHR3D_SFX_PLUGIN_VST3
  if (strcmp(reinterpret_cast<const char*>(name), "VST3") == 0)
    return SfxSystem::vst3;
#endif // SHR3D_SFX_PLUGIN_VST3

  return SfxSystem::empty;
}

const char* Sfx::sfxSystem2Name(SfxSystem sfxSystem)
{
  switch (sfxSystem)
  {
#ifdef SHR3D_SFX_CORE
  case SfxSystem::core:
    return "Core";
#endif // SHR3D_SFX_CORE

#ifdef SHR3D_SFX_CORE_AIRWINDOWS
  case SfxSystem::coreAirWindows:
    return "CoreAirWindows";
#endif // SHR3D_SFX_CORE_AIRWINDOWS

#ifdef SHR3D_SFX_CORE_RAKARRACK
  case SfxSystem::coreRakarrack:
    return "CoreRakarrack";
#endif // SHR3D_SFX_CORE_RAKARRACK

#ifdef SHR3D_SFX_CORE_EXTENSION_V2
  case SfxSystem::coreExtensionV2:
    return "ExtensionV2";
#endif // SHR3D_SFX_CORE_EXTENSION_V2

#ifdef SHR3D_SFX_PLUGIN_CLAP
  case SfxSystem::clap:
    return "CLAP";
#endif // SHR3D_SFX_PLUGIN_CLAP

#ifdef SHR3D_SFX_PLUGIN_LV2
  case SfxSystem::lv2:
    return "LV2";
#endif // SHR3D_SFX_PLUGIN_LV2

#ifdef SHR3D_SFX_PLUGIN_VST
  case SfxSystem::vst:
    return "VST";
#endif // SHR3D_SFX_PLUGIN_VST

#ifdef SHR3D_SFX_PLUGIN_VST3
  case SfxSystem::vst3:
    return "VST3";
#endif // SHR3D_SFX_PLUGIN_VST3
  default:
    unreachable();
  }
}

static SfxId sfxIdFromSettings(const std::string& settingsValue)
{
  SfxId sfxId{};

  const std::vector<std::string> sfxSystemNameSplit = String::split(settingsValue, ',');
  if (sfxSystemNameSplit.size() == 2)
  {
    const SfxSystem sfxSystem = Sfx::name2SfxSystem(sfxSystemNameSplit[0].c_str());

    for (SfxIndex i = 0; i < SfxIndex(Sfx::names[sfxSystem].size()); ++i)
    {
      if (sfxSystemNameSplit[1] == Sfx::names[sfxSystem][i])
      {
        sfxId.system = sfxSystem;
        sfxId.sfxIndex = i;
        break;
      }
    }
  }

  return sfxId;
}

void Sfx::init()
{
#ifdef SHR3D_SFX_CORE
  SFXCore::init();
#endif // SHR3D_SFX_CORE

#ifdef SHR3D_SFX_PLUGIN_CLAP
  SfxPluginClap::init(Sfx::names[SfxSystem::clap]);
#endif // SHR3D_SFX_PLUGIN_CLAP

#ifdef SHR3D_SFX_PLUGIN_LV2
  SfxPluginLv2::init(Sfx::names);
#endif // SHR3D_SFX_PLUGIN_LV2

#ifdef SHR3D_SFX_PLUGIN_VST
  SfxPluginVst::init(Sfx::names[SfxSystem::vst]);
#endif // SHR3D_SFX_PLUGIN_VST

#ifdef SHR3D_SFX_PLUGIN_VST3
  SfxPluginVst3::init(Sfx::names[SfxSystem::vst3]);
#endif // SHR3D_SFX_PLUGIN_VST3

  Global::tunerPlugin = sfxIdFromSettings(Settings::tunerPlugin);
  Global::tunerMidiPlugin = sfxIdFromSettings(Settings::tunerMidiPlugin);

  //  for (const auto& [sfxSystem, sfxNames] : Sfx::names)
  //  {
  //    for (SfxIndex i = 0; i < i32(names.size()); ++i)
  //    {
  //#ifdef SHR3D_SFX_PLUGIN_VST
  //      if (sfxSystem == SfxSystem::vst && tunerMidiPluginName == sfxNames[i]) // Midi From Audio Plugin
  //      {
  //        const char* parametersBase64 = reinterpret_cast<const char*>(&Settings::tunerMidiPlugin.c_str()[tunerMidiPluginNameSplitPos + 1]);
  //        const u64 parametersBase64Length = Settings::tunerMidiPlugin.size() - tunerMidiPluginNameSplitPos - 1;
  //        const std::vector<u8> decodedData = Base64::decode(parametersBase64, parametersBase64Length);
  //        const std::string decodedParameters(reinterpret_cast<const char*>(decodedData.data()), decodedData.size());
  //        loadParameters({ sfxSystem, i }, 0, decodedParameters);
  //        SfxPluginVst::setMidiFromAudioCallback(i, 0, true);
  //        //Global::tunerMidiPlugin.index = i;
  //      }
  //#endif // SHR3D_SFX_PLUGIN_VST
  //    }
  //  }
}

#ifdef SHR3D_SFX_PLUGIN
bool Sfx::hasSfxPluginWindow(const SfxId sfxId)
{
  //ASSERT(index >= 0);

  switch (sfxId.system)
  {
#ifdef SHR3D_SFX_PLUGIN_CLAP
  case SfxSystem::clap:
    return SfxPluginClap::hasSfxPluginWindow(sfxId.sfxIndex);
#endif // SHR3D_SFX_PLUGIN_CLAP

#ifdef SHR3D_SFX_PLUGIN_LV2
  case SfxSystem::lv2:
    return SfxPluginLv2::hasSfxPluginWindow(sfxId.sfxIndex);
#endif // SHR3D_SFX_PLUGIN_LV2

#ifdef SHR3D_SFX_PLUGIN_VST
  case SfxSystem::vst:
    return SfxPluginVst::hasSfxPluginWindow(sfxId.sfxIndex);
#endif // SHR3D_SFX_PLUGIN_VST

#ifdef SHR3D_SFX_PLUGIN_VST3
  case SfxSystem::vst3:
    return SfxPluginVst3::hasSfxPluginWindow(sfxId.sfxIndex);
#endif // SHR3D_SFX_PLUGIN_VST3
  default:
    unreachable();
  }
}

void Sfx::openSfxPluginWindow(const SfxId sfxId, i32 instance, Shr3DWindow parentWindow)
{
  //ASSERT(index >= 0);

  switch (sfxId.system)
  {
#ifdef SHR3D_SFX_PLUGIN_CLAP
  case SfxSystem::clap:
    SfxPluginClap::openWindow(sfxId.sfxIndex, instance); // parenting is done later
    return;
#endif // SHR3D_SFX_PLUGIN_CLAP

#ifdef SHR3D_SFX_PLUGIN_LV2
  case SfxSystem::lv2:
    SfxPluginLv2::openWindow(sfxId.sfxIndex, instance, parentWindow);
    return;
#endif // SHR3D_SFX_PLUGIN_LV2

#ifdef SHR3D_SFX_PLUGIN_VST
  case SfxSystem::vst:
    SfxPluginVst::openWindow(sfxId.sfxIndex, instance, parentWindow);
    return;
#endif // SHR3D_SFX_PLUGIN_VST

#ifdef SHR3D_SFX_PLUGIN_VST3
  case SfxSystem::vst3:
    SfxPluginVst3::openWindow(sfxId.sfxIndex, instance, parentWindow);
    return;
#endif // SHR3D_SFX_PLUGIN_VST3
  default:
    unreachable();
  }
}

Size Sfx::getSfxPluginWindowSize(const SfxId sfxId, i32 instance)
{
  //ASSERT(index >= 0);

  switch (sfxId.system)
  {
#ifdef SHR3D_SFX_PLUGIN_CLAP
  case SfxSystem::clap:
    return SfxPluginClap::getSfxPluginWindowSize(sfxId.sfxIndex, instance);
#endif // SHR3D_SFX_PLUGIN_CLAP

#ifdef SHR3D_SFX_PLUGIN_LV2
  case SfxSystem::lv2:
    return SfxPluginLv2::getSfxPluginWindowSize(sfxId.sfxIndex, instance);
#endif // SHR3D_SFX_PLUGIN_LV2

#ifdef SHR3D_SFX_PLUGIN_VST
  case SfxSystem::vst:
    return SfxPluginVst::getSfxPluginWindowSize(sfxId.sfxIndex, instance);
#endif // SHR3D_SFX_PLUGIN_VST

#ifdef SHR3D_SFX_PLUGIN_VST3
  case SfxSystem::vst3:
    return SfxPluginVst3::getSfxPluginWindowSize(sfxId.sfxIndex, instance);
#endif // SHR3D_SFX_PLUGIN_VST3
  default:
    unreachable();
  }
}

void Sfx::closeSfxPluginWindow(const SfxId sfxId, i32 instance)
{
  //ASSERT(index >= 0);

  switch (sfxId.system)
  {
#ifdef SHR3D_SFX_PLUGIN_CLAP
  case SfxSystem::clap:
    SfxPluginClap::closeWindow(sfxId.sfxIndex, instance);
    return;
#endif // SHR3D_SFX_PLUGIN_CLAP

#ifdef SHR3D_SFX_PLUGIN_LV2
  case SfxSystem::lv2:
    SfxPluginLv2::closeWindow(sfxId.sfxIndex, instance);
    return;
#endif // SHR3D_SFX_PLUGIN_LV2

#ifdef SHR3D_SFX_PLUGIN_VST
  case SfxSystem::vst:
    SfxPluginVst::closeWindow(sfxId.sfxIndex, instance);
    return;
#endif // SHR3D_SFX_PLUGIN_VST

#ifdef SHR3D_SFX_PLUGIN_VST3
  case SfxSystem::vst3:
    SfxPluginVst3::closeWindow(sfxId.sfxIndex, instance);
    return;
#endif // SHR3D_SFX_PLUGIN_VST3
  default:
    unreachable();
  }
}

i32 Sfx::numParams(const SfxId sfxId, i32 instance)
{
  switch (sfxId.system)
  {
#ifdef SHR3D_SFX_PLUGIN_CLAP
  case SfxSystem::clap:
    return SfxPluginClap::numParams(sfxId.sfxIndex, instance);
#endif // SHR3D_SFX_PLUGIN_CLAP

#ifdef SHR3D_SFX_PLUGIN_LV2
  case SfxSystem::lv2:
    return SfxPluginLv2::numParams(sfxId.sfxIndex, instance);
#endif // SHR3D_SFX_PLUGIN_LV2

#ifdef SHR3D_SFX_PLUGIN_VST
  case SfxSystem::vst:
    return SfxPluginVst::numParams(sfxId.sfxIndex, instance);
#endif // SHR3D_SFX_PLUGIN_VST

#ifdef SHR3D_SFX_PLUGIN_VST3
  case SfxSystem::vst3:
    return SfxPluginVst3::numParams(sfxId.sfxIndex, instance);
#endif // SHR3D_SFX_PLUGIN_VST3

  default:
    unreachable();
  };
}

void Sfx::getParameterProperties(const SfxId sfxId, i32 instance, i32 param, SfxParameterProperties& parameterProperties)
{
  switch (sfxId.system)
  {
#ifdef SHR3D_SFX_PLUGIN_CLAP
  case SfxSystem::clap:
    return SfxPluginClap::getParameterProperties(sfxId.sfxIndex, instance, param, parameterProperties);
#endif // SHR3D_SFX_PLUGIN_CLAP

#ifdef SHR3D_SFX_PLUGIN_LV2
  case SfxSystem::lv2:
    return SfxPluginLv2::getParameterProperties(sfxId.sfxIndex, instance, param, parameterProperties);
#endif // SHR3D_SFX_PLUGIN_LV2

#ifdef SHR3D_SFX_PLUGIN_VST
  case SfxSystem::vst:
    return SfxPluginVst::getParameterProperties(sfxId.sfxIndex, instance, param, parameterProperties);
#endif // SHR3D_SFX_PLUGIN_VST

#ifdef SHR3D_SFX_PLUGIN_VST3
  case SfxSystem::vst3:
    return SfxPluginVst3::getParameterProperties(sfxId.sfxIndex, instance, param, parameterProperties);
#endif // SHR3D_SFX_PLUGIN_VST3

  default:
    unreachable();
  };
}

f32 Sfx::getParameter(const SfxId sfxId, i32 instance, i32 param)
{
  switch (sfxId.system)
  {
#ifdef SHR3D_SFX_PLUGIN_CLAP
  case SfxSystem::clap:
    return SfxPluginClap::getParameter(sfxId.sfxIndex, instance, param);
#endif // SHR3D_SFX_PLUGIN_CLAP

#ifdef SHR3D_SFX_PLUGIN_LV2
  case SfxSystem::lv2:
    return SfxPluginLv2::getParameter(sfxId.sfxIndex, instance, param);
#endif // SHR3D_SFX_PLUGIN_LV2

#ifdef SHR3D_SFX_PLUGIN_VST
  case SfxSystem::vst:
    return SfxPluginVst::getParameter(sfxId.sfxIndex, instance, param);
#endif // SHR3D_SFX_PLUGIN_VST

#ifdef SHR3D_SFX_PLUGIN_VST3
  case SfxSystem::vst3:
    return SfxPluginVst3::getParameter(sfxId.sfxIndex, instance, param);
#endif // SHR3D_SFX_PLUGIN_VST3

  default:
    unreachable();
  };
}

void Sfx::setParameter(const SfxId sfxId, i32 instance, i32 param, f32 value)
{
  switch (sfxId.system)
  {
#ifdef SHR3D_SFX_PLUGIN_CLAP
  case SfxSystem::clap:
    return SfxPluginClap::setParameter(sfxId.sfxIndex, instance, param, value);
#endif // SHR3D_SFX_PLUGIN_CLAP

#ifdef SHR3D_SFX_PLUGIN_LV2
  case SfxSystem::lv2:
    return SfxPluginLv2::setParameter(sfxId.sfxIndex, instance, param, value);
#endif // SHR3D_SFX_PLUGIN_LV2

#ifdef SHR3D_SFX_PLUGIN_VST
  case SfxSystem::vst:
    return SfxPluginVst::setParameter(sfxId.sfxIndex, instance, param, value);
#endif // SHR3D_SFX_PLUGIN_VST

#ifdef SHR3D_SFX_PLUGIN_VST3
  case SfxSystem::vst3:
    return SfxPluginVst3::setParameter(sfxId.sfxIndex, instance, param, value);
#endif // SHR3D_SFX_PLUGIN_VST3

  default:
    unreachable();
  };
}
#endif // SHR3D_SFX_PLUGIN

// return true if outBlock was written
ProcessBlockResult Sfx::processBlock(const SfxId sfxId, i32 instance, f32** inBlock, f32** outBlock, i32 blockSize)
{
  //ASSERT(index >= 0);

  switch (sfxId.system)
  {
#ifdef SHR3D_SFX_CORE
  case SfxSystem::core:
#ifdef SHR3D_SFX_CORE_AIRWINDOWS
  case SfxSystem::coreAirWindows:
#endif // SHR3D_SFX_CORE_AIRWINDOWS
#ifdef SHR3D_SFX_CORE_RAKARRACK
  case SfxSystem::coreRakarrack:
#endif // SHR3D_SFX_CORE_RAKARRACK
    return SFXCore::processBlock(sfxId, instance, inBlock, outBlock, blockSize);
#endif // SHR3D_SFX_CORE

#ifdef SHR3D_SFX_CORE_EXTENSION_V2
  case SfxSystem::coreExtensionV2:
    return Global::sfxCoreExtensionV2SortedByRegistrationOrder[sfxId.sfxIndex]->getInstance(instance)->processBlock(inBlock, outBlock, blockSize);
#endif // SHR3D_SFX_CORE_EXTENSION_V2

#ifdef SHR3D_SFX_PLUGIN_CLAP
  case SfxSystem::clap:
    SfxPluginClap::processBlock(sfxId.sfxIndex, instance, inBlock, outBlock, blockSize);
    return ProcessBlockResult::ProcessedInOutBlock;
#endif // SHR3D_SFX_PLUGIN_CLAP

#ifdef SHR3D_SFX_PLUGIN_LV2
  case SfxSystem::lv2:
    SfxPluginLv2::processBlock(sfxId.sfxIndex, instance, inBlock, outBlock, blockSize);
    return ProcessBlockResult::ProcessedInOutBlock;
#endif // SHR3D_SFX_PLUGIN_LV2

#ifdef SHR3D_SFX_PLUGIN_VST
  case SfxSystem::vst:
    SfxPluginVst::processBlock(sfxId.sfxIndex, instance, inBlock, outBlock, blockSize);
    return ProcessBlockResult::ProcessedInOutBlock;
#endif // SHR3D_SFX_PLUGIN_VST

#ifdef SHR3D_SFX_PLUGIN_VST3
  case SfxSystem::vst3:
    SfxPluginVst3::processBlock(sfxId.sfxIndex, instance, inBlock, outBlock, blockSize);
    return ProcessBlockResult::ProcessedInOutBlock;
#endif // SHR3D_SFX_PLUGIN_VST3
  default:
    unreachable();
  }
}

std::string Sfx::saveParameters(const SfxId sfxId, i32 instance)
{
  //ASSERT(index > 0);

  switch (sfxId.system)
  {
#ifdef SHR3D_SFX_CORE
  case SfxSystem::core:
#ifdef SHR3D_SFX_CORE_AIRWINDOWS
  case SfxSystem::coreAirWindows:
#endif // SHR3D_SFX_CORE_AIRWINDOWS
#ifdef SHR3D_SFX_CORE_RAKARRACK
  case SfxSystem::coreRakarrack:
    return SFXCore::saveParameters(sfxId, instance);
#endif // SHR3D_SFX_CORE_RAKARRACK
#endif // SHR3D_SFX_CORE

#ifdef SHR3D_SFX_CORE_EXTENSION_V2
  case SfxSystem::coreExtensionV2:
  {
    SfxCoreExtensionV2Base* extension = Global::sfxCoreExtensionV2SortedByRegistrationOrder[sfxId.sfxIndex];
    const auto getChunkDataCallback = extension->getChunkDataCallback;
    if (getChunkDataCallback != nullptr)
    {
      const u8* data;
      u64 dataSize;
      getChunkDataCallback(extension, instance, data, dataSize);
      const std::string dataString = Base64::encode(data, dataSize);
      return dataString;
    }
    else
    {
      std::string str;
      const i32 parameterCount = extension->getParameterCount();
      if (parameterCount >= 1)
      {
        const SfxCoreExtensionV2Instance* sfx = extension->getInstance(instance);
        str = F32_To_String(sfx->getParameter(0));
        for (i32 i = 1; i < parameterCount; ++i)
          str += ',' + F32_To_String(sfx->getParameter(i));
      }
      return str;
    }
  }
#endif // SHR3D_SFX_CORE_EXTENSION_V2

#ifdef SHR3D_SFX_PLUGIN_CLAP
  case SfxSystem::clap:
    return SfxPluginClap::saveParameters(sfxId.sfxIndex, instance);
#endif // SHR3D_SFX_PLUGIN_CLAP

#ifdef SHR3D_SFX_PLUGIN_LV2
  case SfxSystem::lv2:
    return SfxPluginLv2::saveParameters(sfxId.sfxIndex, instance);
#endif // SHR3D_SFX_PLUGIN_LV2

#ifdef SHR3D_SFX_PLUGIN_VST
  case SfxSystem::vst:
    return SfxPluginVst::saveParameters(sfxId.sfxIndex, instance);
#endif // SHR3D_SFX_PLUGIN_VST

#ifdef SHR3D_SFX_PLUGIN_VST3
  case SfxSystem::vst3:
    return SfxPluginVst3::saveParameters(sfxId.sfxIndex, instance);
#endif // SHR3D_SFX_PLUGIN_VST3
  default:
    unreachable();
  }
}

void Sfx::loadParameters(const SfxId sfxId, i32 instance, const std::string& parameters)
{
  //ASSERT(index > 0);

  switch (sfxId.system)
  {
#ifdef SHR3D_SFX_CORE
  case SfxSystem::core:
#ifdef SHR3D_SFX_CORE_AIRWINDOWS
  case SfxSystem::coreAirWindows:
#endif // SHR3D_SFX_CORE_AIRWINDOWS
#ifdef SHR3D_SFX_CORE_RAKARRACK
  case SfxSystem::coreRakarrack:
    //if (!parameters.empty())
    SFXCore::loadParameters(sfxId, instance, parameters);
    return;
#endif // SHR3D_SFX_CORE_RAKARRACK
#endif // SHR3D_SFX_CORE

#ifdef SHR3D_SFX_CORE_EXTENSION_V2
  case SfxSystem::coreExtensionV2:
    if (!parameters.empty())
    {
      SfxCoreExtensionV2Base* extension = Global::sfxCoreExtensionV2SortedByRegistrationOrder[sfxId.sfxIndex];
      const auto setChunkDataCallback = extension->setChunkDataCallback;
      if (setChunkDataCallback != nullptr)
      {
        const std::vector<u8> data = Base64::decode(parameters.data(), parameters.size());
        setChunkDataCallback(extension, instance, data.data(), data.size());
      }
      else
      {
        const std::vector<std::string> params = String::split(parameters, ',');
        ASSERT(Global::sfxCoreExtensionV2SortedByRegistrationOrder[sfxId.sfxIndex]->getParameterCount() == params.size());

        SfxCoreExtensionV2Instance* sfx = Global::sfxCoreExtensionV2SortedByRegistrationOrder[sfxId.sfxIndex]->getInstance(instance);
        for (i32 i = 0; i < params.size(); ++i)
          sfx->setParameter(i, f32(atof(reinterpret_cast<const char*>(params[i].c_str()))));
      }
    }
    return;
#endif // SHR3D_SFX_CORE_EXTENSION_V2

#ifdef SHR3D_SFX_PLUGIN_CLAP
  case SfxSystem::clap:
    SfxPluginClap::loadParameters(sfxId.sfxIndex, instance, parameters);
    return;
#endif // SHR3D_SFX_PLUGIN_CLAP

#ifdef SHR3D_SFX_PLUGIN_LV2
  case SfxSystem::lv2:
    SfxPluginLv2::loadParameters(sfxId.sfxIndex, instance, parameters);
    return;
#endif // SHR3D_SFX_PLUGIN_LV2

#ifdef SHR3D_SFX_PLUGIN_VST
  case SfxSystem::vst:
    SfxPluginVst::loadParameters(sfxId.sfxIndex, instance, parameters);
    return;
#endif // SHR3D_SFX_PLUGIN_VST

#ifdef SHR3D_SFX_PLUGIN_VST3
  case SfxSystem::vst3:
    SfxPluginVst3::loadParameters(sfxId.sfxIndex, instance, parameters);
    return;
#endif // SHR3D_SFX_PLUGIN_VST3
  default:
    unreachable();
  }
}

#ifdef SHR3D_SFX_PLUGIN
bool Sfx::sfxSystemIsPlugin(const SfxSystem sfxSystem)
{
  switch (sfxSystem)
  {
#ifdef SHR3D_SFX_PLUGIN_CLAP
  case SfxSystem::clap:
    return true;
#endif // SHR3D_SFX_PLUGIN_CLAP
#ifdef SHR3D_SFX_PLUGIN_LV2
  case SfxSystem::lv2:
    return true;
#endif // SHR3D_SFX_PLUGIN_LV2
#ifdef SHR3D_SFX_PLUGIN_VST
  case SfxSystem::vst:
    return true;
#endif // SHR3D_SFX_PLUGIN_VST
#ifdef SHR3D_SFX_PLUGIN_VST3
  case SfxSystem::vst3:
    return true;
#endif // SHR3D_SFX_PLUGIN_VST3
  default:
    return false;
  }
}
#endif // SHR3D_SFX_PLUGIN

#endif // SHR3D_SFX
