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

#include "thalia_waveform/waveform_input.h"
#include "thalia_waveform/waveform_entry_point.h"

#include "WDL/fpcmp.h"
#include "WDL/pcmfmtcvt.h"
#include "WDL/win32_utf8.h"
#include "WDL/wdlutf8.h"

THA_WaveFormInput::THA_WaveFormInput()
  : m_file(NULL)
  , m_channels(2)
  , m_bitspersample(16)
  , m_samplerate(48000.0)
  , m_buffer(NULL)
  , m_totallength(0)
  , m_currentpos(0.0)
  , m_hwsamplerate(48000.0)
  , m_eof(false)
  , m_sndfile(NULL)
{}

THA_WaveFormInput::~THA_WaveFormInput()
{
  if (m_sndfile) sf.close(m_sndfile);
  if (m_file) delete m_file;
}

bool THA_WaveFormInput::Open(const char *filename)
{
  m_fn.Set(filename);

  m_hwsamplerate = THA_GetAudioDeviceSamplerate();

  int rmode, rbufsize, rnbufs;
  THA_GetDiskReadMode(&rmode, &rbufsize, &rnbufs);
  m_file = new WDL_FileRead(filename, rmode, rbufsize, rnbufs);

  if (!m_file->IsOpen())
  {
    return false;
  }

  m_vio.get_filelen = sf_filelength;
  m_vio.seek = sf_seek;
  m_vio.read = sf_read;
  m_vio.write = sf_write;
  m_vio.tell = sf_tell;

  m_sndfile = sf.open_virtual(&m_vio, SFM_READ, &m_sfinfo, m_file);

#ifdef _WIN32
  //WDL_WCHAR fn[2048];
  //WDL_MBtoWideStr(fn, m_fn.Get(), sizeof(fn));
  //m_sndfile = sf.wchar_open(fn, SFM_READ, &m_sfinfo);
#else
  //m_sndfile = sf.open(m_fn.Get(), SFM_READ, &m_sfinfo);
#endif

  if (m_sndfile)
  {
    m_channels = m_sfinfo.channels;
    m_samplerate = (double)m_sfinfo.samplerate;
    if (m_sfinfo.format & SF_FORMAT_PCM_S8) m_bitspersample = 8;
    else if (m_sfinfo.format & SF_FORMAT_PCM_U8) m_bitspersample = 8;
    else if (m_sfinfo.format & SF_FORMAT_PCM_16) m_bitspersample = 16;
    else if (m_sfinfo.format & SF_FORMAT_PCM_24) m_bitspersample = 24;
    else if (m_sfinfo.format & SF_FORMAT_PCM_32) m_bitspersample = 32;
    else if (m_sfinfo.format & SF_FORMAT_FLOAT) m_bitspersample = 32;
    else if (m_sfinfo.format & SF_FORMAT_DOUBLE) m_bitspersample = 64;
    else m_bitspersample = 16;
    m_totallength = (WDL_INT64)m_sfinfo.frames;

    bool interp, sinc;
    int filtercnt, sinc_size, sinc_interpsize;
    THA_GetResampleMode(&interp, &filtercnt, &sinc, &sinc_size, &sinc_interpsize);
    m_rs.SetMode(interp, filtercnt, sinc, sinc_size, sinc_interpsize);
    return true;
  }

  return false;
}

const char *THA_WaveFormInput::GetType() const
{
  if (!wdl_filename_cmp(m_fn.get_fileext(), ".wav")) return "WAV";
  else if (!wdl_filename_cmp(m_fn.get_fileext(), ".aiff")) return "AIFF";
  return "UNK";
}

const char *THA_WaveFormInput::GetFileName() const
{
  return m_fn.Get();
}

int THA_WaveFormInput::GetChannels() const
{
  return m_channels;
}

double THA_WaveFormInput::GetSampleRate() const
{
  return m_samplerate;
}

double THA_WaveFormInput::GetLength() const
{
  return m_totallength / m_samplerate;
}

int THA_WaveFormInput::GetBitsPerSample() const
{
  return m_bitspersample;
}

double THA_WaveFormInput::GetPosition() const
{
  return m_currentpos;
}

void THA_WaveFormInput::Seek(double time)
{}

int THA_WaveFormInput::GetSamples(SAM *buffer, int length)
{
  int ret = 0;

  while (m_samples.Available() == 0 || m_samples.Available() < length)
  {
    if (!m_eof)
    {
      ReadNext();
    }
    else
    {
      return 0;
    }
  }

  int copied = 0;

  if (m_samples.Available() < length)
  {
    memcpy(buffer, m_samples.Get(), sizeof(SAM) * m_samples.Available());
    copied = m_samples.Available();
    m_samples.Clear();
  }
  else
  {
    memcpy(buffer, m_samples.Get(), sizeof(SAM) * length);
    copied = length;
    m_samples.Advance(length);
    m_samples.Compact();
  }

  m_currentpos += copied / GetChannels() / m_hwsamplerate;

  return copied;
}

bool THA_WaveFormInput::IsStreaming() const
{
  return false;
}

int THA_WaveFormInput::Extended(int call, void *parm1, void *parm2, void *parm3)
{
  return -1;
}

void THA_WaveFormInput::ReadNext()
{
  m_buffer.Resize(4096);

  int read = (int)sf.readf_double(m_sndfile, m_buffer.Get(), m_buffer.GetSize() / m_channels);
  if (read)
  {
    m_buffer.Resize(read * m_channels, false);

    if (WDL_ApproximatelyEqual(m_samplerate, m_hwsamplerate))
    {
      m_samples.Add(m_buffer.Get(), m_buffer.GetSize());
    }
    else
    {
      const int nch = m_channels;
      int frames = read;

      m_rs.SetRates(m_samplerate, m_hwsamplerate);
      m_rs.SetFeedMode(true);

      for (;;)
      {
        WDL_ResampleSample *ob = NULL;
        int amt = m_rs.ResamplePrepare(frames, nch, &ob);
        if (amt > frames) amt = frames;
        if (ob)
        {
          for (int i = 0; i < frames; i++)
          {
            for (int j = 0; j < nch; j++)
            {
              *ob++ = m_buffer.Get()[i * nch + j];
            }
          }
        }
        frames -= amt;

        WDL_TypedBuf<WDL_ResampleSample> tmp;
        tmp.Resize(2048 * nch);
        amt = m_rs.ResampleOut(tmp.Get(), amt, 2048, nch);

        if (frames < 1 && amt < 1) break;

        amt *= nch;
        m_samples.Add(tmp.Get(), amt);
      }
    }
  }
  else
  {
    m_eof = true;
  }
}
