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

#include "thalia_opus/opus_input.h"
#include "thalia_opus/opus_entry_point.h"

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

THA_OpusInput::THA_OpusInput()
  : 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_of(NULL)
{}

THA_OpusInput::~THA_OpusInput()
{
  if (m_of) op_free(m_of);
  if (m_file) delete m_file;
}

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

  m_hwsamplerate = THA_GetAudioDeviceSamplerate();

  WDL_String url(filename);
  url.DeleteSub(4, url.GetLength() - 4);
  for (int i = 0; i < url.GetLength(); i++)
  {
    url.Get()[i] = tolower_safe(url.Get()[i]);
  }

  if (0)//if (!strncmp(url.Get(), "http", 4))
  {
    url.Set(m_fn.Get());
    url.remove_filepart();
    int ret;
    int is_ssl = 0;
    m_of = op_open_url(url.Get(), &ret, OP_GET_SERVER_INFO(&m_info), NULL);
    if (m_of)
    {
      if (m_info.name != NULL)
      {
        wdl_log("Station name: %s\n", m_info.name);
      }
      if (m_info.description != NULL)
      {
        wdl_log("Station description: %s\n", m_info.description);
      }
      if (m_info.genre != NULL)
      {
        wdl_log("Station genre: %s\n", m_info.genre);
      }
      if (m_info.url != NULL)
      {
        wdl_log("Station homepage: %s\n", m_info.url);
      }
      if (m_info.bitrate_kbps >= 0)
      {
        wdl_log("Station bitrate: %u kbps\n", (unsigned)m_info.bitrate_kbps);
      }
      if (m_info.is_public >= 0)
      {
        wdl_log("%s\n", m_info.is_public ? "Station is public." : "Station is private.");
      }
      if (m_info.server != NULL)
      {
        wdl_log("Server software: %s\n", m_info.server);
      }
      if (m_info.content_type != NULL)
      {
        wdl_log("Content-Type: %s\n", m_info.content_type);
      }
      is_ssl = m_info.is_ssl;
      opus_server_info_clear(&m_info);
      return true;
    }
    return false;
  }
  else
  {
    int rmode, rbufsize, rnbufs;
    THA_GetDiskReadMode(&rmode, &rbufsize, &rnbufs);
    m_file = new WDL_FileRead(filename, rmode, rbufsize, rnbufs);

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

    int ret;
    OpusFileCallbacks cb = { tha_read, tha_seek, tha_tell, NULL };
    m_of = op_open_callbacks(m_file, &cb, NULL, 0, &ret);
    //m_of = op_open_file(m_fn.Get(), &ret);
    if (m_of)
    {
      if (op_seekable(m_of))
      {
        m_channels = op_channel_count(m_of, -1);
        m_samplerate = 48000.0;
        m_bitspersample = 16;
        m_totallength = op_pcm_total(m_of, -1);
        return true;
      }
    }
  }

  return false;
}

const char *THA_OpusInput::GetType() const
{
  return "OPUS";
}

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

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

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

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

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

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

void THA_OpusInput::Seek(double time)
{}

int THA_OpusInput::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_OpusInput::IsStreaming() const
{
  return false;
}

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

void THA_OpusInput::ReadNext()
{
  m_buffer.Resize(4096);
  int li;
  int read = op_read_float(m_of, m_buffer.Get(), m_buffer.GetSize() / m_channels, &li);
  if (read > 0)
  {
    m_buffer.Resize(read * m_channels, false);

    WDL_TypedBuf<SAM> tmp_samples;
    tmp_samples.Resize(read * m_channels);
    tmp_samples.SetToZero();

    for (int i = 0; i < tmp_samples.GetSize(); i++)
    {
      tmp_samples.Get()[i] = (SAM)m_buffer.Get()[i];
      tmp_samples.Get()[i] = wdl_clamp(tmp_samples.Get()[i], -1.0, 1.0);
    }

    if (WDL_ApproximatelyEqual(m_samplerate, m_hwsamplerate))
    {
      m_samples.Add(tmp_samples.Get(), tmp_samples.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++ = tmp_samples.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;
  }
}
