// SPDX-License-Identifier: Unlicense

#include "sfxPluginVst.h"

#ifdef SHR3D_SFX_PLUGIN_VST

#include "file.h"
#include "global.h"
#include "opengl.h"
#include "string_.h"
#include "version.h"

#include <string.h>
#include <filesystem>

#ifdef SHR3D_WINDOW_SDL
#include <SDL.h>
#include <SDL_syswm.h>
#endif // SHR3D_WINDOW_SDL

#ifdef PLATFORM_WINDOWS
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#ifdef SHR3D_SFX_PLUGIN_VST_BENCHMARK
#include <Psapi.h>
#endif // SHR3D_SFX_PLUGIN_VST_BENCHMARK
typedef HMODULE dlhandle;
typedef HWND VstWindow;
#define SO_EXT ".dll"
#define dlsym(x, y) (void*)GetProcAddress(x, y)
#else // PLATFORM_WINDOWS
#include <dlfcn.h>
#include <X11/Xlib.h>
typedef ::Window VstWindow; // there is a "class Window" in rubberband
typedef void* dlhandle;
#define SO_EXT ".so"
#ifdef SHR3D_SFX_PLUGIN_VST_BENCHMARK
#include <malloc.h>
#endif // SHR3D_SFX_PLUGIN_VST_BENCHMARK
#endif // PLATFORM_WINDOWS

static std::mutex loadSfxPluginVstInstanceMutex;

enum struct AudioMasterOpcode : i32
{
  automate = 0,
  version = 1,
  currentId = 2,
  idle = 3,
  pinConnected = 4,
  wantMidi = 6,
  getTime = 7,
  processEvents = 8,
  setTime = 9,
  tempoAt = 10,
  getNumAutomatableParameters = 11,
  getParameterQuantization = 12,
  ioChanged = 13,
  needIdle = 14,
  sizeWindow = 15,
  getSampleRate = 16,
  getBlockSize = 17,
  getInputLatency = 18,
  getOutputLatency = 19,
  getPreviousPlug = 20,
  getNextPlug = 21,
  willReplaceOrAccumulate = 22,
  getCurrentProcessLevel = 23,
  getAutomationState = 24,
  offlineStart = 25,
  offlineRead = 26,
  offlineWrite = 27,
  offlineGetCurrentPass = 28,
  offlineGetCurrentMetaPass = 29,
  setOutputSampleRate = 30,
  getSpeakerArrangement = 31,
  getVendorString = 32,
  getProductString = 33,
  getVendorVersion = 34,
  vendorSpecific = 35,
  setIcon = 36,
  canDo = 37,
  getLanguage = 38,
  openWindow = 39,
  closeWindow = 40,
  getDirectory = 41,
  updateDisplay = 42,
  beginEdit = 43,
  endEdit = 44,
  openFileSelector = 45,
  closeFileSelector = 46,
  editFile = 47,
  getChunkFile = 48,
  getInputSpeakerArrangement = 49,
};

enum struct EffFlags
{
  HasEditor = 1,
  HasClip = 2,
  HasVu = 4,
  CanMono = 8,
  CanReplacing = 1 << 4,
  ProgramChunks = 1 << 5,
  IsSynth = 1 << 8,
  NoSoundInStop = 1 << 11,
  ExtIsAsync = 1 << 10,
  ExtHasBuffer = 1 << 11,
  CanDoubleReplacing = 1 << 12
} BIT_FLAGS(EffFlags);

enum struct EffOpcode : i32
{
  Open = 0,
  Close = 1,
  SetProgram = 2,
  GetProgram = 3,
  SetProgramName = 4,
  GetProgramName = 5,
  GetParamLabel = 6,
  GetParamDisplay = 7,
  GetParamName = 8,
  GetVu = 9,
  SetSampleRate = 10,
  SetBlockSize = 11,
  MainsChanged = 12,
  EditGetRect = 13,
  EditOpen = 14,
  EditClose = 15,
  EditDraw = 16,
  EditMouse = 17,
  EditKey = 18,
  EditIdle = 19,
  EditTop = 20,
  EditSleep = 21,
  Identify = 22,
  GetChunk = 23,
  SetChunk = 24,
  ProcessEvents = 25,
  CanBeAutomated = 26,
  String2Parameter = 27,
  GetNumProgramCategories = 28,
  GetProgramNameIndexed = 29,
  CopyProgram = 30,
  ConnectInput = 31,
  ConnectOutput = 32,
  GetInputProperties = 33,
  GetOutputProperties = 34,
  GetPlugCategory = 35,
  GetCurrentPosition = 36,
  GetDestinationBuffer = 37,
  OfflineNotify = 38,
  OfflinePrepare = 39,
  OfflineRun = 40,
  ProcessVarIo = 41,
  SetSpeakerArrangement = 42,
  SetBlockSizeAndSampleRate = 43,
  SetBypass = 44,
  GetEffectName = 45,
  GetErrorText = 46,
  GetVendorString = 47,
  GetProductString = 48,
  GetVendorVersion = 49,
  VendorSpecific = 50,
  CanDo = 51,
  GetTailSize = 52,
  Idle = 53,
  GetIcon = 54,
  SetViewPosition = 55,
  GetParameterProperties = 56,
  KeysRequired = 57,
  GetVstVersion = 58,
  EditKeyDown = 59,
  EditKeyUp = 60,
  SetEditKnobMode = 61,
  GetMidiProgramName = 62,
  GetCurrentMidiProgram = 63,
  GetMidiProgramCategory = 64,
  HasMidiProgramsChanged = 65,
  GetMidiKeyName = 66,
  BeginSetProgram = 67,
  EndSetProgram = 68,
  GetSpeakerArrangement = 69,
  ShellGetNextPlugin = 70,
  StartProcess = 71,
  StopProcess = 72,
  SetTotalSampleToProcess = 73,
  SetPanLaw = 74,
  BeginLoadBank = 75,
  BeginLoadProgram = 76,
  SetProcessPrecision = 77,
  GetNumMidiInputChannels = 78,
  GetNumMidiOutputChannels = 79,
  NumOpcodes = 80
};

struct AEffect
{
  i32 magic;
  intptr_t(*dispatcher)(AEffect*, EffOpcode, i32, intptr_t, void*, f32);
  void (*process)(AEffect*, f32**, f32**, i32);
  void (*setParameter)(AEffect*, i32, f32);
  f32(*getParameter)(AEffect*, i32);
  i32 numPrograms;
  i32 numParams;
  i32 numInputs;
  i32 numOutputs;
  EffFlags flags;
  void* ptr1;
  void* ptr2;
  i32 initialDelay;
  i32 empty3a;
  i32 empty3b;
  f32 unkown_float;
  void* ptr3;
  void* user;
  i32 uniqueID;
  i32 version;
  void (*processReplacing)(AEffect*, f32**, f32**, i32);
};

typedef intptr_t(*audioMasterCallback)(AEffect*, AudioMasterOpcode, int32_t, intptr_t, void*, f32);
typedef AEffect* (*vstPluginMain)(audioMasterCallback audioMaster);

struct VstPlugin
{
  std::vector<AEffect*> aEffect; // allow multiple instances
  i32 vstVersion;
  std::string name;
  std::string vendor;
  i32 version;
  bool interactive;
  u32 audioIns;
  u32 audioOuts;
  i32 midiIns;
  i32 midiOuts;
  bool automatable;

#ifdef SHR3D_WINDOW_SDL
  //SDL_Window* window;
#endif // SHR3D_WINDOW_SDL
#ifdef SHR3D_WINDOW_WIN32
  //HWND window; // Can't use SDL_Window on Windows because of this bug: https://github.com/libsdl-org/SDL/issues/2942
#endif // SHR3D_WINDOW_WIN32

  //Rect* windowRect;
  vstPluginMain pluginMain;
};

static std::vector<VstPlugin> vstPlugins;


#define CCONST(a, b, c, d)( ( ( (i32) a ) << 24 ) |      \
            ( ( (i32) b ) << 16 ) |    \
            ( ( (i32) c ) << 8 ) |     \
            ( ( (i32) d ) << 0 ) )

const i32 kEffectMagic = CCONST('V', 's', 't', 'P');
const i32 kVstLangEnglish = 1;
const i32 kVstMidiType = 1;

const i32 kVstNanosValid = 1 << 8;
const i32 kVstPpqPosValid = 1 << 9;
const i32 kVstTempoValid = 1 << 10;
const i32 kVstBarsValid = 1 << 11;
const i32 kVstCyclePosValid = 1 << 12;
const i32 kVstTimeSigValid = 1 << 13;
const i32 kVstSmpteValid = 1 << 14;   // from Ardour
const i32 kVstClockValid = 1 << 15;   // from Ardour

const i32 kVstTransportPlaying = 1 << 1;
const i32 kVstTransportCycleActive = 1 << 2;
const i32 kVstTransportChanged = 1;

static intptr_t handlePluginMidiOutput(void* ptr)
{
  if (ptr != nullptr)
  {
    enum struct VstMidiEventType : i32
    {
      kVstMidiType = 1
    };

    enum struct VstMidiEventFlags : i32
    {
      kVstMidiEventIsRealtime = 1 << 0	///< means that this event is played life (not in playback)
    };

    struct VstMidiEvent
    {
      VstMidiEventType type;			///< #kVstMidiType
      i32 byteSize;		///< sizeof (VstMidiEvent)
      i32 deltaFrames;	///< sample frames related to the current block start sample position
      VstMidiEventFlags flags;			///< @see VstMidiEventFlags
      i32 noteLength;	///< (in sample frames) of entire note, if available, else 0
      i32 noteOffset;	///< offset into note from note start if available, else 0
      u8 midiData[4];		///< 1 to 3 MIDI bytes; midiData[3] is reserved (zero)
      i8 detune;			///< -64 to +63 cents; for scales other than 'well-tempered' ('microtuning')
      u8 noteOffVelocity;	///< Note Off Velocity [0, 127]
      u8 reserved1;			///< zero (Reserved for future use)
      u8 reserved2;			///< zero (Reserved for future use)
    };
    static_assert(sizeof(VstMidiEvent) == 32);

    struct VstEvent
    {
      VstMidiEventType type;			///< @see VstEventTypes
      i32 byteSize;		///< size of this event, excl. type and byteSize
      i32 deltaFrames;	///< sample frames related to the current block start sample position
      VstMidiEventFlags flags;			///< generic flags, none defined yet

      u8 data[16];			///< data size may vary, depending on event type
    };
    static_assert(sizeof(VstEvent) == 32);

    struct VstEvents
    {
      i32 numEvents;		///< number of Events in array
      void* reserved;		///< zero (Reserved for future use)
      VstEvent* events[2];	///< event pointer array, variable size
    };

    const VstEvents* events = reinterpret_cast<const VstEvents*>(ptr);
    {
      const std::lock_guard lock(Global::playedNotesFromAudioMutex);

      for (i32 i = 0; i < events->numEvents; ++i)
      {
        ASSERT(events->events[i]->type == VstMidiEventType::kVstMidiType);
        ASSERT(events->events[i]->byteSize == 32);
        ASSERT(events->events[i]->deltaFrames == 0);
        //ASSERT(events->events[i]->flags == VstMidiEventFlags::kVstMidiEventIsRealtime);

        const VstMidiEvent* midiEvent = reinterpret_cast<const VstMidiEvent*>(events->events[i]);

        ASSERT(midiEvent->noteLength == 0);
        ASSERT(midiEvent->noteOffset == 0);
        ASSERT(midiEvent->detune == 0);
        ASSERT(midiEvent->noteOffVelocity == 0);
        ASSERT(midiEvent->reserved1 == 0);
        ASSERT(midiEvent->reserved2 == 0);

        ASSERT(Global::playedNotesFromAudioIndex < Const::playedNotesFromAudioMaxCount);
        {
          Global::playedNotesFromAudio[Global::playedNotesFromAudioIndex].status = MidiStatus(midiEvent->midiData[0]);
          Global::playedNotesFromAudio[Global::playedNotesFromAudioIndex].note = MidiNote(midiEvent->midiData[1]);
          Global::playedNotesFromAudio[Global::playedNotesFromAudioIndex].velocity = midiEvent->midiData[2];
          ASSERT(midiEvent->midiData[3] == 0);
          ++Global::playedNotesFromAudioIndex;
        }
      }
    }
  }

  return 0;
}

static intptr_t AudioMaster(AEffect* effect, const AudioMasterOpcode opcode, const int32_t /*index*/, intptr_t /*value*/, void* ptr, const f32 /*opt*/)
{
  //VstPlugin* vst = (effect ? (VstPlugin*)effect->ptr2 : nullptr);

  switch (opcode)
  {
  case AudioMasterOpcode::version:
    return (intptr_t)2400;

  case AudioMasterOpcode::currentId:
    return 0;

  case AudioMasterOpcode::getVendorString:
    strcpy((char*)ptr, "Shr3D");
    return 1;

  case AudioMasterOpcode::getProductString:
    strcpy((char*)ptr, "Shr3D");
    return 1;

  case AudioMasterOpcode::getVendorVersion:
    return (intptr_t)(VERSION_MAJOR << 24 | VERSION_MINOR << 16 | VERSION_PATCH << 8 | 0);

  case AudioMasterOpcode::needIdle:
    return 0;

  case AudioMasterOpcode::updateDisplay:
    return 0;

  case AudioMasterOpcode::getTime:
    return 0;

  case AudioMasterOpcode::ioChanged:
    return 0;

  case AudioMasterOpcode::getSampleRate:
    return (intptr_t)sampleRate();

  case AudioMasterOpcode::idle:
    return 1;

  case AudioMasterOpcode::getCurrentProcessLevel:
    return 0;

  case AudioMasterOpcode::getLanguage:
    return kVstLangEnglish;

  case AudioMasterOpcode::willReplaceOrAccumulate:
    return 1;

  case AudioMasterOpcode::sizeWindow:
    return 1;

  case AudioMasterOpcode::canDo:
  {
    char* s = reinterpret_cast<char*>(ptr);
    if (strcmp(s, "acceptIOChanges") == 0 ||
      strcmp(s, "sendVstTimeInfo") == 0 ||
      strcmp(s, "startStopProcess") == 0 ||
      strcmp(s, "shellCategory") == 0 ||
      strcmp(s, "sizeWindow") == 0)
    {
      return 1;
    }
    return 0;
  }

  case AudioMasterOpcode::beginEdit:
  case AudioMasterOpcode::endEdit:
    return 0;

  case AudioMasterOpcode::automate:
    return 0;

  case AudioMasterOpcode::pinConnected:
    return 0;
  case AudioMasterOpcode::wantMidi:
    return 0;

  case AudioMasterOpcode::processEvents:
  {
    intptr_t(*processEventsCallback)(void*) = reinterpret_cast<intptr_t(*)(void*)>(effect->user); // only handles MidiFromAudio. When set it will call handlePluginMidiOutput. Can be extended in the future.
    if (processEventsCallback != nullptr)
      return processEventsCallback(ptr);
    return 0;
  }
  }

  //ASSERT(false);
  return 0;
}

static std::string getString(AEffect* aEffect, const EffOpcode opcode, i32 index = 0)
{
  char buf[256]{};

  aEffect->dispatcher(aEffect, opcode, index, 0, buf, 0.0);

  return buf;
}

static void loadPluginInstance(VstPlugin& vstPlugin, const i32 blockSize, const i32 sampleRate)
{
  const i32 instance = i32(vstPlugin.aEffect.size());
  vstPlugin.aEffect.push_back(vstPlugin.pluginMain(AudioMaster));
  vstPlugin.aEffect[instance]->ptr2 = &vstPlugin;

  vstPlugin.aEffect[instance]->dispatcher(vstPlugin.aEffect[instance], EffOpcode::SetSampleRate, 0, 0, nullptr, f32(sampleRate));
  vstPlugin.aEffect[instance]->dispatcher(vstPlugin.aEffect[instance], EffOpcode::SetBlockSize, 0, blockSize, nullptr, 0);
  vstPlugin.aEffect[instance]->dispatcher(vstPlugin.aEffect[instance], EffOpcode::Identify, 0, 0, nullptr, 0);

  vstPlugin.aEffect[instance]->dispatcher(vstPlugin.aEffect[instance], EffOpcode::Open, 0, 0, nullptr, 0.0);

  vstPlugin.aEffect[instance]->dispatcher(vstPlugin.aEffect[instance], EffOpcode::BeginSetProgram, 0, 0, nullptr, 0.0);
  vstPlugin.aEffect[instance]->dispatcher(vstPlugin.aEffect[instance], EffOpcode::SetProgram, 0, 0, nullptr, 0.0);
  vstPlugin.aEffect[instance]->dispatcher(vstPlugin.aEffect[instance], EffOpcode::EndSetProgram, 0, 0, nullptr, 0.0);

  vstPlugin.aEffect[instance]->dispatcher(vstPlugin.aEffect[instance], EffOpcode::MainsChanged, 0, 1, nullptr, 0.0);

  const i32 vstVersion = i32(vstPlugin.aEffect[instance]->dispatcher(vstPlugin.aEffect[instance], EffOpcode::GetVstVersion, 0, 0, nullptr, 0)); // might not be needed
  if (vstVersion >= 2)
    vstPlugin.aEffect[instance]->dispatcher(vstPlugin.aEffect[instance], EffOpcode::StartProcess, 0, 0, nullptr, 0.0);

  vstPlugin.aEffect[instance]->dispatcher(vstPlugin.aEffect[instance], EffOpcode::SetSampleRate, 0, 0, nullptr, f32(sampleRate)); // already set. Is this needed?
  vstPlugin.aEffect[instance]->dispatcher(vstPlugin.aEffect[instance], EffOpcode::SetBlockSize, 0, blockSize, nullptr, 0.0); // already set. Is this needed?
}

#define INT32_SWAP(val) \
   ((i32) ( \
    (((u32) (val) & (u32) 0x000000ffU) << 24) | \
    (((u32) (val) & (u32) 0x0000ff00U) <<  8) | \
    (((u32) (val) & (u32) 0x00ff0000U) >>  8) | \
    (((u32) (val) & (u32) 0xff000000U) >> 24)))

void SfxPluginVst::init(std::vector<std::string>& sfxNames)
{
  const std::vector<std::string> paths = String::split(Settings::pathVst, '|');
  for (const std::string& path : paths)
  {
    if (!File::exists(path.c_str()))
      continue;

    for (const auto& file : File::filesInDirectory(path.c_str()))
    {
      if (File::extension(file.c_str()) != std::string((const char*)SO_EXT))
        continue;

#ifdef PLATFORM_WINDOWS
      void* hinstLib = LoadLibrary(String::ws_ExpandEnvironmentStrings(String::s2ws(file.c_str(), file.size())).c_str());
#else // PLATFORM_WINDOWS
      void* hinstLib = dlopen(reinterpret_cast<const char*>(file.c_str()), RTLD_LAZY | RTLD_LOCAL);
#endif // PLATFORM_WINDOWS

      ASSERT(hinstLib != nullptr);

      VstPlugin& vstPlugin = vstPlugins.emplace_back();

      vstPlugin.pluginMain = (vstPluginMain)dlsym((dlhandle)hinstLib, "VSTPluginMain");
      if (vstPlugin.pluginMain == nullptr)
        vstPlugin.pluginMain = (vstPluginMain)dlsym((dlhandle)hinstLib, "main"); // some older vst plugins use "main"
      ASSERT(vstPlugin.pluginMain != nullptr);

      {
        const std::unique_lock<std::mutex> guard(loadSfxPluginVstInstanceMutex);
#ifdef SHR3D_SFX_PLUGIN_VST_BENCHMARK
        const auto start = std::chrono::high_resolution_clock::now();
#ifdef PLATFORM_WINDOWS
        const HANDLE process = GetCurrentProcess();
        PROCESS_MEMORY_COUNTERS pmcBefore;
        GetProcessMemoryInfo(process, &pmcBefore, sizeof(pmcBefore));
#endif // PLATFORM_WINDOWS
#ifdef PLATFORM_LINUX
        const struct mallinfo2 mallInfoBefore = mallinfo2();
#endif // PLATFORM_LINUX
#endif // SHR3D_SFX_PLUGIN_VST_BENCHMARK
        loadPluginInstance(vstPlugin, blockSize(), sampleRate());
#ifdef SHR3D_SFX_PLUGIN_VST_BENCHMARK
        SfxBenchmarkResult& benchmarkResult = Global::sfxPluginVstBenchmarkResults.emplace_back();
        benchmarkResult.duration = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now() - start).count();
#ifdef PLATFORM_WINDOWS
        PROCESS_MEMORY_COUNTERS pmcAfter;
        GetProcessMemoryInfo(process, &pmcAfter, sizeof(pmcAfter));
        benchmarkResult.memoryUsageInBytes = pmcAfter.WorkingSetSize - pmcBefore.WorkingSetSize;
#endif // PLATFORM_WINDOWS
#ifdef PLATFORM_LINUX
        const struct mallinfo2 mallInfoAfter = mallinfo2();
        benchmarkResult.memoryUsageInBytes = mallInfoAfter.arena - mallInfoBefore.arena;
#endif // PLATFORM_LINUX
#endif // SHR3D_SFX_PLUGIN_VST_BENCHMARK
      }

      vstPlugin.vstVersion = i32(vstPlugin.aEffect[0]->dispatcher(vstPlugin.aEffect[0], EffOpcode::GetVstVersion, 0, 0, nullptr, 0));

      if (vstPlugin.aEffect[0]->magic == kEffectMagic &&
        !(to_underlying_(vstPlugin.aEffect[0]->flags & EffFlags::IsSynth)) &&
        to_underlying_(vstPlugin.aEffect[0]->flags & EffFlags::CanReplacing))
      {
        if (vstPlugin.vstVersion >= 2)
        {
          vstPlugin.name = getString(vstPlugin.aEffect[0], EffOpcode::GetEffectName);
          if (vstPlugin.name.length() == 0)
          {
            vstPlugin.name = getString(vstPlugin.aEffect[0], EffOpcode::GetProductString);
          }
        }
        if (vstPlugin.name.length() == 0)
        {
          vstPlugin.name = reinterpret_cast<const char*>(File::filename(file.c_str()));
        }

        if (vstPlugin.vstVersion >= 2)
        {
          vstPlugin.vendor = getString(vstPlugin.aEffect[0], EffOpcode::GetVendorString);
          vstPlugin.version = INT32_SWAP(vstPlugin.aEffect[0]->dispatcher(vstPlugin.aEffect[0], EffOpcode::GetVendorVersion, 0, 0, nullptr, 0));
        }
        if (vstPlugin.version == 0)
        {
          vstPlugin.version = INT32_SWAP(vstPlugin.aEffect[0]->version);
        }

        if (to_underlying_(vstPlugin.aEffect[0]->flags & EffFlags::HasEditor) || vstPlugin.aEffect[0]->numParams != 0)
        {
          vstPlugin.interactive = true;
        }

        vstPlugin.audioIns = vstPlugin.aEffect[0]->numInputs;
        vstPlugin.audioOuts = vstPlugin.aEffect[0]->numOutputs;

        vstPlugin.midiIns = 0;
        vstPlugin.midiOuts = 0;

        vstPlugin.automatable = false;
        for (i32 i = 0; i < vstPlugin.aEffect[0]->numParams; i++)
        {
          if (vstPlugin.aEffect[0]->dispatcher(vstPlugin.aEffect[0], EffOpcode::CanBeAutomated, 0, i, nullptr, 0.0))
          {
            vstPlugin.automatable = true;
            break;
          }
        }
      }
      sfxNames.push_back(vstPlugin.name);
    }
  }
}

bool SfxPluginVst::hasSfxPluginWindow(const SfxIndex index)
{
  ASSERT(index >= 0);
  ASSERT(index < vstPlugins.size());

  return bool(vstPlugins[index].aEffect[0]->flags & EffFlags::HasEditor);
}

void SfxPluginVst::openWindow(const SfxIndex index, const i32 instance, Shr3DWindow parentWindow)
{
  ASSERT(index >= 0);
  ASSERT(index < vstPlugins.size());
  VstPlugin& vstPlugin = vstPlugins[index];
  ASSERT(instance >= 0);
  ASSERT(instance < vstPlugin.aEffect.size());

#ifndef SHR3D_WINDOW_SDL
  auto* windowHandle = parentWindow;
  intptr_t display = 0;
#else // SHR3D_WINDOW_SDL
  SDL_SysWMinfo wmInfo;
  SDL_VERSION(&wmInfo.version);
  SDL_GetWindowWMInfo(parentWindow, &wmInfo);
  auto* windowHandle = reinterpret_cast<void*>(wmInfo.info.x11.window);
  const auto display = reinterpret_cast<intptr_t>(wmInfo.info.x11.display);
#endif // SHR3D_WINDOW_SDL

  {
    // some vst plugins misbehave and corrupt our glContext
#ifdef SHR3D_WINDOW_SDL
    SDL_GL_MakeCurrent(Global::window, nullptr); // When running into problems try to create a new context instead of passing nullptr here.
#else // SHR3D_WINDOW_SDL
    wglMakeCurrent(Global::hdc, Global::pluginGlContext);
#endif // SHR3D_WINDOW_SDL

    vstPlugin.aEffect[instance]->dispatcher(vstPlugin.aEffect[instance], EffOpcode::EditOpen, 0, display, windowHandle, 0.0);

#ifdef SHR3D_WINDOW_SDL
    SDL_GL_MakeCurrent(Global::window, Global::glContext);
#else // SHR3D_WINDOW_SDL
    wglMakeCurrent(Global::hdc, Global::glContext);
#endif // SHR3D_WINDOW_SDL

#ifdef SHR3D_OPENGL_ERROR_CHECK
    ASSERT(glGetError() == 0); // plugin corrupted opengl context
#endif // SHR3D_OPENGL_ERROR_CHECK
  }
}

Size SfxPluginVst::getSfxPluginWindowSize(const SfxIndex index, const i32 instance)
{
  ASSERT(index >= 0);
  ASSERT(index < vstPlugins.size());
  ASSERT(instance >= 0);
  ASSERT(instance < vstPlugins[index].aEffect.size());
  AEffect* aeffect = vstPlugins[index].aEffect[instance];

  Rect* windowRect;
  const bool ok = aeffect->dispatcher(aeffect, EffOpcode::EditGetRect, 0, 0, &windowRect, 0.0);
  ASSERT(ok);

  const Size size = {
    .width = u32(windowRect->right - windowRect->left),
    .height = u32(windowRect->bottom - windowRect->top)
  };

  return size;
}

void SfxPluginVst::closeWindow(const SfxIndex index, const i32 instance)
{
  ASSERT(index >= 0);
  ASSERT(index < vstPlugins.size());
  ASSERT(instance >= 0);
  ASSERT(instance < vstPlugins[index].aEffect.size());
  AEffect* aeffect = vstPlugins[index].aEffect[instance];

#ifdef SHR3D_WINDOW_SDL
  SDL_GL_MakeCurrent(Global::window, nullptr); // When running into problems try to create a new context instead of passing nullptr here.
#endif // SHR3D_WINDOW_SDL

  aeffect->dispatcher(aeffect, EffOpcode::EditClose, 0, 0, nullptr, 0.0);

#ifdef SHR3D_WINDOW_SDL
  SDL_GL_MakeCurrent(Global::window, Global::glContext);
#endif // SHR3D_WINDOW_SDL
}

i32 SfxPluginVst::numParams(const SfxIndex index, const i32 instance)
{
  ASSERT(index >= 0);
  ASSERT(index < vstPlugins.size());
  ASSERT(instance >= 0);
  ASSERT(instance < vstPlugins[index].aEffect.size());
  const AEffect* aeffect = vstPlugins[index].aEffect[instance];

  return aeffect->numParams;
}

void SfxPluginVst::getParameterProperties(const SfxIndex index, const i32 instance, i32 param, SfxParameterProperties& parameterProperties)
{
  ASSERT(index >= 0);
  ASSERT(index < vstPlugins.size());
  ASSERT(instance >= 0);
  ASSERT(instance < vstPlugins[index].aEffect.size());
  AEffect* aeffect = vstPlugins[index].aEffect[instance];

  //struct {
  //  f32 stepFloat;
  //  f32 smallStepFloat;
  //  f32 largeStepFloat;
  //  char label[64];
  //  u32 flags;
  //  i32 minInteger;
  //  i32 maxInteger;
  //  i32 stepInteger;
  //  i32 largeStepInteger;
  //  char shortLabel[8];
  //  u16 displayIndex;
  //  u16 category;
  //  u16 numParametersInCategory;
  //  u16 reserved;
  //  char categoryLabel[24];
  //  char future[16];
  //} vstParameterProperties{};
  //if (vstPlugins[index].aEffect[instance]->dispatcher(vstPlugins[index].aEffect[instance], EffOpcode::GetParameterProperties, 0, 0, &vstParameterProperties, 0.0f) != 0)
  //{
  //  ASSERT(false); // I have not seen a vst plugin that uses this.
  //}
  //else
  //{
  aeffect->dispatcher(aeffect, EffOpcode::GetParamName, param, 0, parameterProperties.name, 0.0f);
  aeffect->dispatcher(aeffect, EffOpcode::GetParamLabel, param, 0, parameterProperties.label, 0.0f);
  aeffect->dispatcher(aeffect, EffOpcode::GetParamDisplay, param, 0, parameterProperties.display, 0.0f);
  //}
}

f32 SfxPluginVst::getParameter(const SfxIndex index, const i32 instance, const i32 param)
{
  ASSERT(index >= 0);
  ASSERT(index < vstPlugins.size());
  ASSERT(instance >= 0);
  ASSERT(instance < vstPlugins[index].aEffect.size());
  AEffect* aeffect = vstPlugins[index].aEffect[instance];

  return aeffect->getParameter(aeffect, param);
}

void SfxPluginVst::setParameter(const SfxIndex index, const i32 instance, const i32 param, const f32 value)
{
  ASSERT(index >= 0);
  ASSERT(index < vstPlugins.size());
  ASSERT(instance >= 0);
  ASSERT(instance < vstPlugins[index].aEffect.size());
  AEffect* aeffect = vstPlugins[index].aEffect[instance];

  return aeffect->setParameter(aeffect, param, value);
}

void SfxPluginVst::processBlock(const SfxIndex index, const i32 instance, f32** inBlock, f32** outBlock, const i32 blockSize)
{
  ASSERT(index >= 0);
  ASSERT(index < vstPlugins.size());

  // After the start of the application one instance is created for each plugin.
  // When a plugin will be used twice or more in a effect chain an additional instance is needed.
  // Instead of pausing the audio, creating an new instance, unpausing the audio. It is done here.
  // Right now there is no way to pause the audio output. It might need to be changed if this does not work as expected.
  {
    const std::unique_lock<std::mutex> guard(loadSfxPluginVstInstanceMutex);
    while (vstPlugins[index].aEffect.size() <= instance)
      loadPluginInstance(vstPlugins[index], blockSize, sampleRate());
  }

  if (blockSize >= 0)
    vstPlugins[index].aEffect[instance]->processReplacing(vstPlugins[index].aEffect[instance], inBlock, outBlock, blockSize);


  //struct VstMidiEvent
  //{
  //  i32 type;
  //  i32 byteSize;
  //  i32 deltaFrames;
  //  i32 flags;
  //  i32 noteLength;
  //  i32 noteOffset;
  //  i8 midiData[4];
  //  i8 detune;
  //  i8 noteOffVelocity;
  //  i8 reserved1;
  //  i8 reserved2;
  //};

  //struct VstEvent
  //{
  //  char dump[sizeof(VstMidiEvent)];
  //};

  //struct VstEvents
  //{
  //  // 00
  //  int numEvents;
  //  // 04
  //  void* reserved;
  //  // 08
  //  VstEvent* events[1];
  //};

  //VstMidiEvent midiEvent;
  //memset(&midiEvent, 0, sizeof(VstMidiEvent));
  //midiEvent.type = kVstMidiType;
  //midiEvent.byteSize = sizeof(midiEvent);
  //midiEvent.deltaFrames = 0;
  //midiEvent.flags = 0;
  //midiEvent.noteOffset = 0;
  //midiEvent.noteLength = 0;
  //midiEvent.midiData[0] = 0x90;
  //midiEvent.midiData[1] = 60;
  //midiEvent.midiData[2] = 100;
  //midiEvent.midiData[3] = 0;
  //midiEvent.detune = 0;
  //midiEvent.noteOffVelocity = 0;
  //midiEvent.reserved1 = 0;
  //midiEvent.reserved2 = 0;

  //VstEvents* mEventList;
  //mEventList = (VstEvents*)new char[sizeof(mEventList->numEvents) + sizeof(mEventList->reserved) + sizeof(mEventList->events[0]) * 512]; //kMaxEventsPerSlice = 512
  //mEventList->numEvents = 1;
  //mEventList->reserved = 0;
  //mEventList->events[0] = (VstEvent*)&midiEvent;
  //callDispatcher(vstPlugins[index].aEffect[instance], EffOpcode::ProcessEvents, 0, 0, mEventList, 0.0f);
}

std::string SfxPluginVst::saveParameters(const SfxIndex index, const i32 instance)
{
  ASSERT(index >= 0);
  ASSERT(index < vstPlugins.size());
  ASSERT(instance >= 0);
  ASSERT(instance < vstPlugins[index].aEffect.size());
  AEffect* aeffect = vstPlugins[index].aEffect[instance];

  u8* chunk = nullptr;
  if (to_underlying_(aeffect->flags & EffFlags::ProgramChunks))
  {
    const i64 len = aeffect->dispatcher(aeffect, EffOpcode::GetChunk, 1, 0, &chunk, 0.0f);
    const std::string parameters(chunk, chunk + len);
    return parameters;
  }
  else
  {
    const i32 paramCount = numParams(index, instance);
    std::string parameters;
    if (paramCount >= 1)
    {
      const f32 paramValue0 = getParameter(index, instance, 0);
      parameters += F32_To_String(paramValue0);
      for (i32 i = 1; i < paramCount; ++i)
      {
        const f32 paramValue = getParameter(index, instance, i);
        parameters += ',' + F32_To_String(paramValue);
      }
    }
    return parameters;
  }
  return {};
}

void SfxPluginVst::loadParameters(const SfxIndex index, const i32 instance, const std::string& parameters)
{
  ASSERT(index >= 0);
  ASSERT(index < vstPlugins.size());
  ASSERT(instance >= 0);
  ASSERT(instance < vstPlugins[index].aEffect.size());
  AEffect* aeffect = vstPlugins[index].aEffect[instance];

  {
    const std::unique_lock<std::mutex> guard(loadSfxPluginVstInstanceMutex);
    if (instance == vstPlugins[index].aEffect.size())
      loadPluginInstance(vstPlugins[index], blockSize(), sampleRate()); // plugin is loaded multiple times, create another instance
  }

  if (to_underlying_(aeffect->flags & EffFlags::ProgramChunks))
  {
    aeffect->dispatcher(aeffect, EffOpcode::BeginSetProgram, 0, 0, nullptr, 0.0f);
    aeffect->dispatcher(aeffect, EffOpcode::SetChunk, 1, parameters.size(), (void*)parameters.data(), 0.0f);
    aeffect->dispatcher(aeffect, EffOpcode::EndSetProgram, 0, 0, nullptr, 0.0f);
  }
  else
  {
    const std::vector<std::string> paramValues = String::split(parameters, ',');
    //ASSERT(paramValues.size() == numParams(index, instance));
    for (i32 i = 0; i < paramValues.size(); ++i)
    {
      const f32 paramValue = atof(reinterpret_cast<const char*>(paramValues[i].c_str()));
      setParameter(index, instance, i, paramValue);
    }
  }
}

void SfxPluginVst::setMidiFromAudioCallback(const SfxIndex index, const i32 instance, const bool enabled)
{
  ASSERT(index >= 0);
  ASSERT(index < vstPlugins.size());
  ASSERT(instance >= 0);
  ASSERT(instance < vstPlugins[index].aEffect.size());
  AEffect* aeffect = vstPlugins[index].aEffect[instance];

  aeffect->user = enabled ? reinterpret_cast<void*>(handlePluginMidiOutput) : nullptr;
}

#endif // SHR3D_SFX_PLUGIN_VST
