// Copyright (c) 2021 Giorgos Vougioukas
//
// The license can be found in the LICENSE file.

#include "RSI/plugin.h"

#if defined(_WIN32)
  #define RSI_PLUGIN_EXTENSION ".dll"
#elif defined(__APPLE__)
  #define RSI_PLUGIN_EXTENSION ".dylib"
#elif defined(__linux__)
  #define RSI_PLUGIN_EXTENSION ".so"
#else
  #error "Plugin extension not right"
#endif

#include "WDL/assocarray.h"
#include "WDL/ptrlist.h"
#include "WDL/dirscan.h"
#include "WDL/wdlcstring.h"

static RSI_PluginInfo plugin_info;
static WDL_StringKeyedArray<HINSTANCE> modules;

static int RegisterCalled(const char *name, void *info_struct)
{
  if (name == NULL || info_struct == NULL)
  {
    return 0;
  }

  g_pluginimport.Insert((INT_PTR)info_struct, strdup(name));

  return 1;
}

static void *GetFuncCalled(const char *name)
{
  if (!name) return NULL;
  return g_pluginexport.Get(name); // if not found returns 0
}

static void PrepareFuncCalls()
{
  g_pluginexport.Insert("RSI_GetAudioDeviceSamplerate", (void *)&RSI_GetAudioDeviceSamplerate);
  g_pluginexport.Insert("RSI_GetAudioDeviceBitDepth", (void *)&RSI_GetAudioDeviceBitDepth);
  g_pluginexport.Insert("RSI_GetAudioDeviceOutputChannels", (void *)&RSI_GetAudioDeviceOutputChannels);
  g_pluginexport.Insert("RSI_GetDiskReadMode", (void *)&RSI_GetDiskReadMode);
  g_pluginexport.Insert("RSI_GetDiskWriteMode", (void *)&RSI_GetDiskWriteMode);
  g_pluginexport.Insert("RSI_GetDiskIOPriority", (void *)&RSI_GetDiskIOPriority);
  g_pluginexport.Insert("RSI_GetDiskIOSleepStep", (void *)&RSI_GetDiskIOSleepStep);
  g_pluginexport.Insert("RSI_GetAntidoteBitDepth", (void *)&RSI_GetAntidoteBitDepth);
  g_pluginexport.Insert("RSI_GetAudioSystem", (void *)&RSI_GetAudioSystem);
  g_pluginexport.Insert("RSI_GetResampleMode", (void *)&RSI_GetResampleMode);
  g_pluginexport.Insert("RSI_GetWASAPIExclusiveMode", (void *)&RSI_GetWASAPIExclusiveMode);
}

RSI_IFilePic *CreateFilePic(const char *filename)
{
  for (int i = 0; i < g_pluginimport.GetSize(); i++)
  {
    INT_PTR key;

    if (!strcmp(g_pluginimport.Enumerate(i, &key), "apic"))
    {
      RSI_FilePicRegister *fpr = (RSI_FilePicRegister *)g_pluginimport.ReverseLookup(g_pluginimport.Enumerate(i));

      if (fpr)
      {
        RSI_IFilePic *p = fpr->CreateFromFile(filename);

        if (p)
        {
          return p;
        }
        else
        {
          delete p;
        }
      }
    }
  }

  return NULL;
}

RSI_IFileInput *CreateFileInput(const char *filename)
{
  for (int i = 0; i < g_pluginimport.GetSize(); i++)
  {
    INT_PTR key;

    if (!strcmp(g_pluginimport.Enumerate(i, &key), "input"))
    {
      RSI_FileInputRegister *fir = (RSI_FileInputRegister *)g_pluginimport.ReverseLookup(g_pluginimport.Enumerate(i));

      if (fir)
      {
        RSI_IFileInput *p = fir->CreateFromFile(filename);

        if (p)
        {
          return p;
        }
        else
        {
          delete p;
        }
      }
    }
  }

  return NULL;
}

RSI_IFileTag *CreateFileTag(const char *filename)
{
  for (int i = 0; i < g_pluginimport.GetSize(); i++)
  {
    if (!strcmp(g_pluginimport.Enumerate(i), "tag"))
    {
      RSI_FileTagRegister *ftr = (RSI_FileTagRegister *)g_pluginimport.ReverseLookup(g_pluginimport.Enumerate(i));

      if (ftr)
      {
        RSI_IFileTag *p = ftr->CreateFromFile(filename);

        if (p)
        {
          return p;
        }
        else
        {
          delete p;
        }
      }
    }
  }

  return NULL;
}

RSI_IAudioStreamer *CreateAudioStreamer()
{
  INT_PTR key;

  for (int i = 0; i < g_pluginimport.GetSize(); i++)
  {
    if (!strcmp(g_pluginimport.Enumerate(i, &key), "audio_streamer"))
    {
      RSI_AudioStreamerRegister *asr = (RSI_AudioStreamerRegister *)key;

      if (asr)
      {
        RSI_IAudioStreamer *p = asr->CreateAudioStreamer();

        if (p)
        {
          return p;
        }
        else
        {
          delete p;
        }
      }
    }
  }

  return NULL;
}

RSI_IAudioStreamerDevice *CreateAudioStreamerDevice(const char *name)
{
  INT_PTR key;

  WDL_FastString str("audio_device:");
  str.Append(name);

  for (int i = 0; i < g_pluginimport.GetSize(); i++)
  {
    if (!strcmp(g_pluginimport.Enumerate(i, &key), str.Get()))
    {
      RSI_AudioStreamerDeviceRegister *asdr = (RSI_AudioStreamerDeviceRegister *)key;

      if (asdr)
      {
        RSI_IAudioStreamerDevice *p = asdr->CreateAudioStreamerDevice();

        if (p)
        {
          return p;
        }
        else
        {
          delete p;
        }
      }
    }
  }

  return NULL;
}

RSI_IPitchShift *CreatePitchShift(const char *name)
{
  INT_PTR key;

  WDL_FastString str("pitch_shift:");
  str.Append(name);

  for (int i = 0; i < g_pluginimport.GetSize(); i++)
  {
    if (!strcmp(g_pluginimport.Enumerate(i, &key), str.Get()))
    {
      RSI_PitchShiftRegister *psr = (RSI_PitchShiftRegister *)key;

      if (psr)
      {
        RSI_IPitchShift *p = psr->CreatePitchShift();

        if (p)
        {
          return p;
        }
        else
        {
          delete p;
        }
      }
    }
  }

  return NULL;
}

RSI_IFXProcessor *CreateFXProcessor()
{
  INT_PTR key;

  WDL_FastString str("fx");

  for (int i = 0; i < g_pluginimport.GetSize(); i++)
  {
    if (!strcmp(g_pluginimport.Enumerate(i, &key), str.Get()))
    {
      RSI_FXProcessorRegister *fxpr = (RSI_FXProcessorRegister *)key;

      if (fxpr)
      {
        RSI_IFXProcessor *p = fxpr->CreateFXProcessor();

        if (p)
        {
          return p;
        }
        else
        {
          delete p;
        }
      }
    }
  }

  return NULL;
}

void LoadPlugins()
{
  WDL_ASSERT(g_pluginimport.GetSize() == 0);
  WDL_ASSERT(g_pluginexport.GetSize() == 0);
  WDL_ASSERT(modules.GetSize() == 0);

  plugin_info.caller_version = 120;
  plugin_info.Register = &RegisterCalled;
  plugin_info.GetFunc = &GetFuncCalled;

  PrepareFuncCalls();

  WDL_DirScan dir;
  WDL_FastString fn;

  WDL_FastString path(g_modpath);
  path.Append("ext" WDL_DIRCHAR_STR);

  WDL_PtrList_DeleteOnDestroy<WDL_FastString> file_list;

  if (!dir.First(path.Get()))
  {
    do
    {
      if (!dir.GetCurrentIsDirectory())
      {
        dir.GetCurrentFullFN(&fn);

        if (!strcmp(fn.get_fileext(), RSI_PLUGIN_EXTENSION))
        {
          file_list.Add(new WDL_FastString(fn.Get()));
        }
      }
    }
    while (!dir.Next());
  }

  if (file_list.GetSize() == 0)
  {
    MessageBox(NULL, "RSI could not find any plugin. Press OK to exit RSI.",
      "Plugin error", MB_OK);
    exit(1);
  }

  for (int i = 0; i < file_list.GetSize(); i++)
  {
    WDL_FastString *file = file_list.Get(i);

    if (!file) continue;

    HINSTANCE inst = NULL;
    inst = LoadLibrary(file->Get());

    if (inst)
    {
      modules.Insert(file->Get(), inst);
    }
    else
    {
      WDL_FastString liberr;
      liberr.SetFormatted(1024, "RSI could not load the following library:\n\n%s",
        file->Get());
      MessageBox(NULL, liberr.Get(), "Library error", MB_OK);
    }
  }

  typedef int (*PluginEntry)(RSI_PLUGIN_HINSTANCE instance, RSI_PluginInfo *rec);

  WDL_FastString err;
  bool display_error = false;
  err.Set("RSI could not load the following plugin:\n\n");

  for (int i = 0; i < modules.GetSize(); i++)
  {
    PluginEntry plugin_entry = (PluginEntry)GetProcAddress(modules.Enumerate(i),
      RSI_PLUGIN_ENTRYPOINT_NAME);

#if defined(_WIN32)
    HINSTANCE inst = g_inst;
    //HINSTANCE inst = GetModuleHandle(NULL);
#else
    HINSTANCE inst = NULL;
#endif

    //int success = plugin_entry(inst, &plugin_info);
    int success = plugin_entry(modules.Enumerate(i), &plugin_info);

    if (!success)
    {
      display_error = true;
      err.AppendFormatted(2048, "%s\n",
        WDL_get_filepart(modules.ReverseLookup(modules.Enumerate(i))));
    }
  }

  if (display_error)
  {
    err.Append("\nPress OK to continue with limited functionality.");
    MessageBox(NULL, err.Get(), "Plugin error", MB_OK);
  }

  WDL_ASSERT(g_pluginimport.GetSize() > 0);
  WDL_ASSERT(g_pluginexport.GetSize() > 0);
}

void ReleasePlugins()
{
  for (int i = 0; i < modules.GetSize(); i++)
  {
    FreeLibrary(modules.Enumerate(i));
  }

  modules.DeleteAll();
  g_pluginimport.DeleteAll();
  g_pluginexport.DeleteAll();
}
