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

#include "RSI/track.h"
#include "RSI/data_bank.h"
#include "RSI/rsi_plugin.h"

#include "WDL/fpcmp.h"

RSI_Track::RSI_Track(int data_bank_slot, const char *filename, bool repeat)
  : m_srate(44100.0)
  , m_bqms(1200)
  , m_eof(false)
  , m_fn(filename)
  , m_active(false)
  , m_shift(1.0)
  , m_tempo(1.0)
  , m_lastshift(1.0)
  , m_lasttempo(1.0)
  , m_ps(NULL)
  , m_repeat(repeat)
  , m_pause(false)
  , m_rwd(false)
  , m_fwd(false)
  , m_ss(false)
  , m_lastpause(false)
  , m_lastrev(false)
  , m_rwdfwdshift(1.0)
  , m_rwdfwdtempo(1.0)
  , m_vol(0.0)
  , m_volstep(2.0)
  , m_databankslot(data_bank_slot)
  , m_reverse(false)
  , m_startstop(0.0)
  , m_headphone(false)
{
  m_srate = g_preferences->GetAudioDeviceSamplerate();
  m_bqms = g_preferences->GetMediaBufferSize();
  m_ps = CreatePitchShift("soundtouch");
  m_volstep = g_preferences->GetVolumeStep();

  WDL_ASSERT(m_ps);

  RSI_IFileTag *ft = CreateFileTag(m_fn.Get());
  if (ft)
  {
    m_artist.Set(ft->GetArtist());
    m_title.Set(ft->GetTitle());
    delete ft;
  }
}

RSI_Track::~RSI_Track()
{
  delete m_ps;
}

void RSI_Track::Pause()
{
  m_pause = !m_pause;
  m_active = true;
  m_startstop = GetTime();
}

void RSI_Track::Rewind(bool state)
{
  if (m_rwd == state) return; else m_rwd = state;
  if (m_rwd)
  {
    if (!m_active) m_pause = true;
    m_lastpause = m_pause;
    m_lastrev = IsPlaybackReverse();
    m_rwdfwdshift = GetShift();
    m_rwdfwdtempo = GetTempo();
    if (!IsPlaybackReverse()) ToggleReversePlayback();
    m_active = true;
    m_pause = false;
    SetShift(4.0);
    SetTempo(4.0);
  }
  else
  {
    if (m_lastrev != IsPlaybackReverse()) ToggleReversePlayback();
    SetShift(m_rwdfwdshift);
    SetTempo(m_rwdfwdtempo);
    m_startstop = GetTime();
    m_pause = m_lastpause;
  }
}

void RSI_Track::FastForward(bool state)
{
  if (m_fwd == state) return; else m_fwd = state;
  if (m_fwd)
  {
    if (!m_active) m_pause = true;
    m_lastpause = m_pause;
    m_lastrev = IsPlaybackReverse();
    m_rwdfwdshift = GetShift();
    m_rwdfwdtempo = GetTempo();
    if (IsPlaybackReverse()) ToggleReversePlayback();
    m_active = true;
    m_pause = false;
    SetShift(4.0);
    SetTempo(4.0);
  }
  else
  {
    if (m_lastrev != IsPlaybackReverse()) ToggleReversePlayback();
    SetShift(m_rwdfwdshift);
    SetTempo(m_rwdfwdtempo);
    m_startstop = GetTime();
    m_pause = m_lastpause;
  }
}

void RSI_Track::Play()
{
  RSI_DataBankSlot *dbs = g_databank->GetSlot(m_databankslot);
  if (dbs)
  {
    if (m_pause) { m_pause = false; return; }
    m_active = false;
    if (IsPlaybackReverse()) ToggleReversePlayback();
    m_bq.Flush();
    m_ps->Reset();
    dbs->Seek(0.0);
    m_startstop = 0.0;
    m_active = true;
  }
}

void RSI_Track::PlayReverse()
{
  RSI_DataBankSlot *dbs = g_databank->GetSlot(m_databankslot);
  if (dbs)
  {
    if (m_pause) { if (!IsPlaybackReverse()) ToggleReversePlayback(); m_pause = false; return; }
    m_active = false;
    if (!IsPlaybackReverse()) ToggleReversePlayback();
    m_bq.Flush();
    m_ps->Reset();
    dbs->Seek(dbs->GetLength());
    m_startstop = 0.0;
    m_active = true;
  }
}

void RSI_Track::Stop()
{
  RSI_DataBankSlot *dbs = g_databank->GetSlot(m_databankslot);
  if (dbs)
  {
    m_active = false;
    m_pause = false;
    m_eof = false;
    m_bq.Flush();
    m_ps->Reset();
    dbs->Seek(0.0);
    m_startstop = 0.0;
    if (IsPlaybackReverse()) ToggleReversePlayback();
  }
}

void RSI_Track::StartStop(bool state)
{
  if (m_ss == state) return; else m_ss = state;
  if (m_ss)
  {
    RSI_DataBankSlot *dbs = g_databank->GetSlot(m_databankslot);
    if (dbs)
    {
      if (m_pause) { m_pause = false; return; }
      m_active = false;
      m_bq.Flush();
      m_ps->Reset();
      dbs->Seek(m_startstop);
      m_active = true;
    }
  }
  else
  {
    RSI_DataBankSlot *dbs = g_databank->GetSlot(m_databankslot);
    if (dbs)
    {
      m_pause = true;
      m_eof = false;
      m_bq.Flush();
      m_ps->Reset();
      dbs->Seek(m_startstop);
    }
  }
}

void RSI_Track::Seek(double time)
{
  RSI_DataBankSlot *dbs = g_databank->GetSlot(m_databankslot);
  if (dbs)
  {
    dbs->Seek(time);
    m_startstop = time;
  }
}

bool RSI_Track::IsPaused() const
{
  return m_pause;
}

bool RSI_Track::IsRewind() const
{
  return m_rwd;
}

bool RSI_Track::IsFastForward() const
{
  return m_fwd;
}

bool RSI_Track::IsActive() const
{
  return m_active;
}

bool RSI_Track::IsDrained() const
{
  return m_eof && m_bq.GetSize() == 0;
}

bool RSI_Track::WantMore() const
{
  if (!m_eof && m_active)
  {
    // duration = filesize / (srate * nch * (bps / 8))
    int duration = (int)(((double)m_bq.GetSize() / (m_srate * 2 * sizeof(SAM))) * 1000);
    if (duration < m_bqms) return true;
  }

  return false;
}

bool RSI_Track::DoBuffering(int buffer_size)
{
  RSI_DataBankSlot *dbs = g_databank->GetSlot(m_databankslot);

  if (dbs)
  {
    if (m_eof) return false;

    m_buffer.Resize(buffer_size);

    int nsam;

    if (WDL_ApproximatelyEqual(m_shift, m_lastshift) &&
      WDL_ApproximatelyEqual(m_tempo, m_lasttempo))
    {
      nsam = dbs->GetSamples(m_buffer.Get(), m_buffer.GetSize());
    }
    else
    {
      m_lastshift = m_shift;
      m_lasttempo = m_tempo;

      nsam = dbs->GetSamples(m_buffer.Get(), m_buffer.GetSize());
    }

    m_buffer.Resize(nsam, false);

    if (nsam > 0)
    {
      m_ps->set_srate(m_srate);
      m_ps->set_nch(2);
      m_ps->set_shift(m_shift);
      m_ps->set_tempo(m_tempo);

      int spl = m_buffer.GetSize() / 2;

      SAM *sam = m_ps->GetBuffer(spl);
      memcpy(sam, m_buffer.Get(), m_buffer.GetSize() * sizeof(SAM));
      m_ps->BufferDone(spl);

      int want = (int)((nsam / 2) * (1.0 / m_tempo));
      if (want)
      {
        m_buffer.Resize(want * 2);
        int rd = m_ps->GetSamples(want, m_buffer.Get());
        if (rd) m_buffer.Resize(rd * 2);
      }
      m_bq.AddBlock(&m_buffer);
      return true;
    }
    else { if (m_repeat) dbs->Seek(0.0); else m_eof = true; }
  }

  return false;
}

bool RSI_Track::DoBufferingReverse(int buffer_size)
{
  RSI_DataBankSlot *dbs = g_databank->GetSlot(m_databankslot);

  if (dbs)
  {
    if (m_eof) return false;
    if (WDL_ApproximatelyEqual(dbs->GetPosition(), 0.0))
    {
      double l = dbs->GetLength();
      if (m_repeat) dbs->Seek(l); else Stop();
    }

    m_buffer.Resize(buffer_size);

    int nsam;

    if (WDL_ApproximatelyEqual(m_shift, m_lastshift) &&
      WDL_ApproximatelyEqual(m_tempo, m_lasttempo))
    {
      nsam = dbs->GetSamplesReverse(m_buffer.Get(), m_buffer.GetSize());
    }
    else
    {
      m_lastshift = m_shift;
      m_lasttempo = m_tempo;

      nsam = dbs->GetSamplesReverse(m_buffer.Get(), m_buffer.GetSize());
    }

    m_buffer.Resize(nsam, false);

    if (nsam > 0)
    {
      m_ps->set_srate(m_srate);
      m_ps->set_nch(2);
      m_ps->set_shift(m_shift);
      m_ps->set_tempo(m_tempo);

      int spl = m_buffer.GetSize() / 2;

      SAM *sam = m_ps->GetBuffer(spl);
      memcpy(sam, m_buffer.Get(), m_buffer.GetSize() * sizeof(SAM));
      m_ps->BufferDone(spl);

      int want = (int)((nsam / 2) * (1.0 / m_tempo));
      if (want)
      {
        m_buffer.Resize(want * 2);
        int rd = m_ps->GetSamples(want, m_buffer.Get());
        if (rd) m_buffer.Resize(rd * 2);
      }
      m_bq.AddBlock(&m_buffer);
      return true;
    }
  }

  return false;
}

double RSI_Track::GetTime() const
{
  RSI_DataBankSlot *dbs = g_databank->GetSlot(m_databankslot);

  if (dbs)
  {
    //int nch = 2;
    //double bufdur = ((double)m_bq.GetSize() / (nch * m_srate * sizeof(SAM)));
    double bufdur = (double)m_bqms / 1000;
    return wdl_clamp(dbs->GetPosition() - bufdur, 0.0, dbs->GetLength());
  }

  return 0.0;
}

double RSI_Track::GetTotalTime() const
{
  RSI_DataBankSlot *dbs = g_databank->GetSlot(m_databankslot);

  if (dbs)
  {
    return dbs->GetLength();
  }

  return 0.0;
}

void RSI_Track::SetShift(double shift)
{
  m_shift = shift;
}

void RSI_Track::SetTempo(double tempo)
{
  m_tempo = tempo;
}

double RSI_Track::GetShift() const
{
  return m_shift;
}

double RSI_Track::GetTempo() const
{
  return m_tempo;
}

void RSI_Track::ToggleRepeat()
{
  m_repeat = !m_repeat;
}

bool RSI_Track::IsRepeat() const
{
  return m_repeat;
}

void RSI_Track::ToggleHeadphone()
{
  m_headphone = !m_headphone;
}

bool RSI_Track::IsHeadphone() const
{
  return m_headphone;
}

void RSI_Track::ToggleReversePlayback()
{
  m_reverse = !m_reverse;
}

bool RSI_Track::IsPlaybackReverse() const
{
  return m_reverse;
}

void RSI_Track::IncreaseVolume()
{
  m_vol = wdl_clamp(m_vol + m_volstep, -150.0, 0.0);
}

void RSI_Track::DecreaseVolume()
{
  m_vol = wdl_clamp(m_vol - m_volstep, -150.0, 0.0);
}

double RSI_Track::GetVolume() const
{
  return m_vol;
}

void RSI_Track::SetVolume(double db)
{
  m_vol = wdl_clamp(db, -150.0, 0.0);
}

const char *RSI_Track::GetArtist() const
{
  return m_artist.Get();
}

const char *RSI_Track::GetTitle() const
{
  return m_title.Get();
}
