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

#include "euterpe/plugin.h"

#if defined(_WIN32)
  #define RSE_PLUGIN_EXTENSION ".dll"
#elif defined(__APPLE__)
  #define RSE_PLUGIN_EXTENSION ".dylib"
#elif defined(__linux__)
  #define RSE_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 RSE_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_plugin_import.Insert((INT_PTR)info_struct, strdup(name));

  return 1;
}

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

static void PrepareFuncCalls()
{
  g_plugin_export.Insert("GetHardwareSampleRate", (void *)&GetHardwareSampleRate);
  //g_plugin_export.Insert("GetClientIniPath", (void *)&GetClientIniPath);
  g_plugin_export.Insert("GetClientSettingsPath", (void *)&GetClientSettingsPath);
  g_plugin_export.Insert("GetCreateFileTagger", (void *)&CreateFileTagger);
  g_plugin_export.Insert("GetIniFile", (void *)g_ini_file);
  g_plugin_export.Insert("GetResamplerSinc", (void *)&GetResamplerSinc);
  g_plugin_export.Insert("GetDiskReadMode", (void *)&GetDiskReadMode);
  g_plugin_export.Insert("GetDiskReadBufferSize", (void *)&GetDiskReadBufferSize);
  g_plugin_export.Insert("GetDiskReadBuffers", (void *)&GetDiskReadBuffers);
}

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

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

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

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

  return NULL;
}

RSE_IFileInput *CreateAudioInput(const char *filename)
{
  for (int i = 0; i < g_plugin_import.GetSize(); i++)
  {
    INT_PTR key;

    if (!strcmp(g_plugin_import.Enumerate(i, &key), "pcmsrc"))
    {
      RSE_AudioInputRegister *air = (RSE_AudioInputRegister *)g_plugin_import.ReverseLookup(g_plugin_import.Enumerate(i));

      if (air)
      {
        RSE_IFileInput *p = air->CreateFromFile(filename);

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

  return NULL;
}

RSE_IFileTagger *CreateFileTagger(const char *filename)
{
  for (int i = 0; i < g_plugin_import.GetSize(); i++)
  {
    if (!strcmp(g_plugin_import.Enumerate(i), "tagger"))
    {
      RSE_FileTaggerRegister *ftr = (RSE_FileTaggerRegister *)g_plugin_import.ReverseLookup(g_plugin_import.Enumerate(i));

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

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

  return NULL;
}

RSE_IAudioStreamer *CreateAudioStreamer(const char *name)
{
  INT_PTR key;

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

  for (int i = 0; i < g_plugin_import.GetSize(); i++)
  {
    if (!strcmp(g_plugin_import.Enumerate(i, &key), str.Get()))
    {
      RSE_AudioStreamerRegister *asr = (RSE_AudioStreamerRegister *)key;

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

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

  return NULL;
}

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

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

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

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

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

  return NULL;
}

RSE_IAudioStreamerProperties *CreateAudioStreamerProperties(const char *name)
{
  INT_PTR key;

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

  for (int i = 0; i < g_plugin_import.GetSize(); i++)
  {
    if (!strcmp(g_plugin_import.Enumerate(i, &key), str.Get()))
    {
      RSE_AudioStreamerPropertiesRegister *aspr = (RSE_AudioStreamerPropertiesRegister *)key;

      if (aspr)
      {
        RSE_IAudioStreamerProperties *p = aspr->CreateAudioStreamerProperties();

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

  return NULL;
}

void LoadPlugins()
{
  WDL_ASSERT(g_plugin_import.GetSize() == 0);
  WDL_ASSERT(g_plugin_export.GetSize() == 0);
  WDL_ASSERT(modules.GetSize() == 0);

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

  PrepareFuncCalls();

  WDL_DirScan dir;
  WDL_FastString fn;

  WDL_FastString path(g_module_path);
  path.Append("plugins" 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(), RSE_PLUGIN_EXTENSION))
        {
          file_list.Add(new WDL_FastString(fn.Get()));
        }
      }
    }
    while (!dir.Next());
  }

  if (file_list.GetSize() == 0)
  {
    MessageBox(NULL, "Euterpe could not find any plugin. Press OK to exit Euterpe.",
      "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, "Euterpe could not load the following library:\n\n%s",
        file->Get());
      MessageBox(NULL, liberr.Get(), "Library error", MB_OK);
    }
  }

  typedef int (*PluginEntry)(EUTERPE_PLUGIN_HINSTANCE instance, RSE_PluginInfo *rec);

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

  for (int i = 0; i < modules.GetSize(); i++)
  {
    PluginEntry plugin_entry = (PluginEntry)GetProcAddress(modules.Enumerate(i),
      EUTERPE_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_plugin_import.GetSize() > 0);
  WDL_ASSERT(g_plugin_export.GetSize() > 0);
}

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

  modules.DeleteAll();
  g_plugin_import.DeleteAll();
  g_plugin_export.DeleteAll();
}
