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

#if defined(_WIN32) && defined(_DEBUG)
#include <vld.h>
#endif

//#define ENABLE_THA_WAVEFORM_CONSOLE

#include "thalia/thalia_plugin_public.h"
#include "thalia_waveform/waveform_input.h"
#include "thalia_waveform/waveform_apic.h"
#include "thalia_waveform/waveform_tag.h"
#include "thalia_waveform/waveform_entry_point.h"

#include "WDL/wdlcstring.h"

double (*THA_GetAudioDeviceSamplerate)();
void (*THA_GetDiskReadMode)(int *rmode, int *rbufsize, int *rnbufs);
void (*THA_GetResampleMode)(bool *interp, int *filtercnt,
  bool *sinc, int *sinc_size, int *sinc_interpsize);

static HINSTANCE wf_instance;
static HWND wf_main_hwnd;

static char s_last_dll_file[128];

sndfile sf;

sf_count_t sf_filelength(void *user_data)
{
  WDL_FileRead *fr = (WDL_FileRead *)user_data;
  return (sf_count_t)fr->GetSize();
}

sf_count_t sf_seek(sf_count_t offset, int whence, void *user_data)
{
  WDL_FileRead *fr = (WDL_FileRead *)user_data;

  switch (whence)
  {
  case SEEK_SET:
    {
      fr->SetPosition((WDL_INT64)offset);
    } break;
  case SEEK_CUR:
    {
      fr->SetPosition(fr->GetPosition() + (WDL_INT64)offset);
    } break;
  case SEEK_END:
    {
      fr->SetPosition(fr->GetSize() + (WDL_INT64)offset);
    } break;
  default: break;
  }

  return (sf_count_t)fr->GetPosition();
}

sf_count_t sf_read(void *ptr, sf_count_t count, void *user_data)
{
  WDL_FileRead *fr = (WDL_FileRead *)user_data;

  if (fr->GetPosition() + (WDL_INT64)count > fr->GetSize())
  {
    count = (sf_count_t)(fr->GetSize() - fr->GetPosition());
  }

  fr->Read((unsigned char *)ptr, (int)count);
  return count;
}

sf_count_t sf_write(const void *ptr, sf_count_t count, void *user_data)
{
  WDL_FileWrite *fw = (WDL_FileWrite *)user_data;
  fw->Write(ptr, (int)count);
  return count;
}

sf_count_t sf_tell(void *user_data)
{
  WDL_FileRead *fr = (WDL_FileRead *)user_data;
  return (sf_count_t)fr->GetPosition();
}

static bool TryLoad_libsndfile(const char *name)
{
#ifdef _WIN32
  HINSTANCE dll = LoadLibrary(name);
#else
  void *dll = dlopen(name, RTLD_NOW|RTLD_LOCAL);
#endif

  if (!dll) return false;

  int errcnt = 0;
  #ifdef _WIN32
    #define GETITEM(x) if (NULL == (*(void **)&sf.x = GetProcAddress((HINSTANCE)dll,"sf_" #x))) errcnt++;
    #define GETITEM_NP(x) if (NULL == (*(void **)&sf.x = GetProcAddress((HINSTANCE)dll, #x))) errcnt++;
  #else
    #define GETITEM(x) if (NULL == (*(void **)&sf.x = dlsym(dll,"lame_" #x))) errcnt++;
    #define GETITEM_NP(x) if (NULL == (*(void **)&sf.x = dlsym(dll,#x))) errcnt++;
  #endif
  GETITEM(close)
  GETITEM(open)
  GETITEM(wchar_open)
  GETITEM(set_string)
  GETITEM(get_string)
  GETITEM(readf_double)
  GETITEM(open_virtual)

  int errcnt2 = errcnt;

  #undef GETITEM
  #undef GETITEM_NP

  if (errcnt2)
  {
    memset(&sf, 0, sizeof(sf));

#ifdef _WIN32
    FreeLibrary(dll);
#else
    dlclose(dll);
#endif
    return false;
  }

  lstrcpyn_safe(s_last_dll_file, name, sizeof(s_last_dll_file));
#ifdef _WIN32
  //if (!strstr(name,"\\"))
  GetModuleFileName(dll, s_last_dll_file, (DWORD)sizeof(s_last_dll_file));
#else
  //if (!strstr(name,"/"))
  {
    Dl_info inf = {0,};
    dladdr((void*)lame.init, &inf);
    if (inf.dli_fname)
      lstrcpyn_safe(s_last_dll_file, inf.dli_fname, sizeof(s_last_dll_file));
  }
#endif

  return true;
}

THA_IFileInput *CreateFromType(const char *type)
{
  if (!strcmp(type, "WAV") || !strcmp(type, "AIFF"))
  {
    return new THA_WaveFormInput;
  }

  return NULL;
}

THA_IFileInput *CreateFromFile(const char *filename)
{
  if (!wdl_filename_cmp(WDL_get_fileext(filename), ".wav") ||
    !wdl_filename_cmp(WDL_get_fileext(filename), ".aiff"))
  {
    THA_WaveFormInput *p = new THA_WaveFormInput;

    if (p->Open(filename))
    {
      return p;
    }

    delete p;
  }

  return NULL;
}

struct THA_FileInputRegister wf_input_reg =
{
  CreateFromType,
  CreateFromFile,
};

THA_IFileTag *CreateWaveFormTag(const char *filename)
{
  if (!wdl_filename_cmp(WDL_get_fileext(filename), ".wav") ||
    !wdl_filename_cmp(WDL_get_fileext(filename), ".aiff"))
  {
    THA_WaveFormTag *p = new THA_WaveFormTag;

    if (p && p->Open(filename))
    {
      return p;
    }
    else
    {
      delete p;
    }
  }

  return NULL;
}

THA_FileTagRegister wf_tag_reg =
{
  &CreateWaveFormTag
};

THA_IFilePic *CreateWaveFormAPIC(const char *filename)
{
  if (!wdl_filename_cmp(WDL_get_fileext(filename), ".wav") ||
    !wdl_filename_cmp(WDL_get_fileext(filename), ".aiff"))
  {
    THA_WaveFormAPIC *p = new THA_WaveFormAPIC;

    if (p->Open(filename))
    {
      return p;
    }
    else
    {
      delete p;
    }
  }

  return NULL;
}

THA_FilePicRegister wf_apic_reg =
{
  &CreateWaveFormAPIC
};

extern "C"
{
  THA_PLUGIN_EXPORT int THA_PLUGIN_ENTRYPOINT(
    THA_PLUGIN_HINSTANCE instance, THA_PluginInfo *rec)
  {
#if defined(_WIN32) && defined(_DEBUG) && defined(ENABLE_THA_WAVEFORM_CONSOLE)
    // The first will use the console only if
    // the application is started by the command 
    // line. The second one will always enable
    // the console.
    //if (AttachConsole(ATTACH_PARENT_PROCESS))
    if (AttachConsole(ATTACH_PARENT_PROCESS) || AllocConsole())
    {
        freopen("CONOUT$", "w", stdout);
        freopen("CONOUT$", "w", stderr);
    }
#endif

    wf_instance = instance;

    if (rec)
    {
      if (rec->caller_version != THA_PLUGIN_VERSION || !rec->GetFunc)
      {
        return 0;
      }

      wf_main_hwnd = rec->hwnd_main;

      char path[2048];
      GetModuleFileName(wf_instance, path, sizeof(path));
      WDL_FastString modname(path);
      modname.remove_filepart(true);
      modname.Append("libsndfile");
      modname.Append(WDL_DIRCHAR_STR);
      modname.Append("libsndfile-1.dll");

      bool loaded_libsndfile = TryLoad_libsndfile(modname.Get());

      *((void **)&THA_GetAudioDeviceSamplerate) = rec->GetFunc("THA_GetAudioDeviceSamplerate");
      *((void **)&THA_GetDiskReadMode) = rec->GetFunc("THA_GetDiskReadMode");
      *((void **)&THA_GetResampleMode) = rec->GetFunc("THA_GetResampleMode");

      if (!rec->Register || !THA_GetAudioDeviceSamplerate || !THA_GetDiskReadMode ||
        !THA_GetResampleMode || !loaded_libsndfile)
      {
        return 0;
      }

      rec->Register("input", &wf_input_reg);
      rec->Register("tag", &wf_tag_reg);
      //rec->Register("apic", &wf_apic_reg);

      return 1;
    }
    else
    {
      return 0;
    }
  }
}
