#include "rsi_mp3/mp3_input.h"
#include "rsi_mp3/mp3_entry_point.h"
#include "rsi_mp3/tag.h"

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

RSI_Mp3Input::RSI_Mp3Input()
  : m_eof(false)
  , m_index(NULL)
  , m_file(NULL)
  , m_channelmode(0)
  , m_channels(2)
  , m_bitspersample(16)
  , m_samplerate(48000.0)
  , m_lengthsamples(0)
  , m_hwsamplerate(48000.0)
  , m_currentpos(0.0)
  , m_streamstartpos(0)
  , m_streamendpos(0)
  , m_readpos(0)
  , m_metadata(false, WDL_StringKeyedArray<char*>::freecharptr)
{
  memset(&m_syncframeinfo, 0, sizeof(m_syncframeinfo));
}

RSI_Mp3Input::~RSI_Mp3Input()
{
  if (m_index) m_index->release_index(m_index);
  if (m_file) delete m_file;
}

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

  m_hwsamplerate = RSI_GetAudioDeviceSamplerate();

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

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

  m_streamstartpos = 0;
  m_streamendpos = (unsigned int)m_file->GetSize();

  WDL_INT64 fstart = 0, fend = m_streamendpos;
  if (ReadMediaTags(m_file, &m_metadata, &fstart, &fend))
  {
    m_streamstartpos = (unsigned int)fstart;
    m_streamendpos = (unsigned int)fend;
  }

  m_file->SetPosition(0);

  if (!m_index) m_index = m_index->indexFromFilename(filename, m_file);

  if (m_index && m_index->GetFrameCount())
  {
    m_streamstartpos = m_index->GetStreamStart();
  }

  m_file->SetPosition(m_streamstartpos);

  m_readpos = m_streamstartpos;

  while (!m_decoder.SyncState())
  {
    if (m_decoder.queue_bytes_in.Available() < 4096)
    {
      char buf[4096];
      int l = m_streamendpos - m_readpos;
      if (l > sizeof(buf)) l = sizeof(buf);
      l = m_file->Read(buf, l);
      if (!l) break;

      m_readpos += l;
      if (l > 0) m_decoder.queue_bytes_in.Add(buf, l);
    }
    if (m_decoder.Run()) break;
  }

  m_decoder.queue_bytes_in.Compact();

  if (m_decoder.SyncState())
  {
    m_channelmode = m_decoder.GetChannelMode();
    m_samplerate = (double)m_decoder.GetSampleRate();
    m_channels = m_decoder.GetNumChannels();
    if (m_index) m_lengthsamples = (WDL_INT64)((m_index->GetFrameCount() *
      (double)m_samplerate) / m_decoder.GetFrameRate() + 0.5);
    else m_lengthsamples = 0;
    m_syncframeinfo = m_decoder.m_lastframe;
  }
  else
  {
    return false;
  }

  m_file->SetPosition((WDL_INT64)m_index->GetStreamStart());

  bool interp, sinc;
  int filtercnt, sinc_size, sinc_interpsize;
  RSI_GetResampleMode(&interp, &filtercnt, &sinc, &sinc_size, &sinc_interpsize);
  m_rs.SetMode(interp, filtercnt, sinc, sinc_size, sinc_interpsize);

  return true;
}

const char *RSI_Mp3Input::GetType() const
{
  return "MP3";
}

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

int RSI_Mp3Input::GetChannels() const
{
  return 2; //m_channels;
}

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

double RSI_Mp3Input::GetLength() const
{
  return m_lengthsamples / m_samplerate;
}

int RSI_Mp3Input::GetBitsPerSample() const
{
  return 16;
}

double RSI_Mp3Input::GetPosition() const
{
  //return ReadMetadataPrefPos(&m_metadata, m_samplerate);
  return m_currentpos;
}

void RSI_Mp3Input::Seek(double time)
{}

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

  while (m_samples.Available() < length && !m_eof)
  {
    int tr=0;
    int hasHadRdError=0;
    while (m_decoder.queue_samples_out.Available() < length * (int)sizeof(double))
    {
      if (m_decoder.queue_bytes_in.Available() < 4096)
      {
        int l = m_streamendpos - m_readpos;
        char buf[4096];
        if (l > sizeof(buf)) l = sizeof(buf);
        l = m_file->Read(buf, l);
        if (l < 1) hasHadRdError = 1;
        else
        {
          m_decoder.queue_bytes_in.Add(buf,l);
          tr+=l;
          m_readpos += l;
        }
      }

      int os = m_decoder.queue_samples_out.Available();
      if (m_decoder.Run()) break;
      int l = m_decoder.queue_samples_out.Available();

      if (l <= os && hasHadRdError) { m_eof = true; break; }
    }
    m_decoder.queue_bytes_in.Compact();

    int samples_read = 0;
    if (m_decoder.GetNumChannels())
    {
      samples_read = m_decoder.queue_samples_out.Available() / sizeof(double) / m_decoder.GetNumChannels();
    }

    if (samples_read > 0)
    {
      if (0)
      //if (m_decoder.GetNumChannels() == m_channels)
      {
        m_samples.Add((SAM *)m_decoder.queue_samples_out.Get(), samples_read * m_channels);
        m_decoder.queue_samples_out.Advance(samples_read * sizeof(double) * m_channels);
      }
      else if (m_decoder.GetNumChannels() == 1)
      {
        double *inptr=(double *)m_decoder.queue_samples_out.Get();
        m_rbuf.Resize(samples_read * 2);
        SAM *outptr = m_rbuf.Get();
        int i;
        for (i = 0; i < samples_read; i ++)
        {
          double s=*inptr++;
          int ch;
          for (ch = 0; ch < 1; ch ++)
          {
            *outptr++ = s;
            *outptr++ = s;
          }
        }
        m_decoder.queue_samples_out.Advance(samples_read * sizeof(double));
      }
      else if (m_decoder.GetNumChannels() == 2)
      {
        double *inptr=(double *)m_decoder.queue_samples_out.Get();
        m_rbuf.Resize(samples_read * 2);
        SAM *outptr = m_rbuf.Get();
        const int nch = 2;
        if (nch == 1)
        {
          for (int i = 0; i < samples_read; i++)
          {
            *outptr++ = inptr[0] * 0.5 + inptr[1] * 0.5;
            inptr+=2;
          }
        }
        else if (nch>1)
        {
          for (int i = 0; i < samples_read; i++)
          {
            *outptr++ = inptr[0];
            *outptr++ = inptr[1];
            inptr+=2;
            for (int ch = 2; ch < nch; ch ++) *outptr++ = 0.0;
          }
        }
        m_decoder.queue_samples_out.Advance(samples_read * sizeof(double) * 2);
      }

      if (WDL_ApproximatelyEqual(m_samplerate, m_hwsamplerate))
      {
        m_samples.Add(m_rbuf.Get(), m_rbuf.GetSize());
      }
      else
      {
        const int nch = GetChannels();
        int frames = samples_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) //cd->splcvt(ob, samples, nch, nch, 1, frames);
          {
            for (int i = 0; i < frames; i++)
            {
              for (int j = 0; j < nch; j++)
              {
                *ob++ = m_rbuf.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);
        }
      }
      m_decoder.queue_samples_out.Compact();
    }
  }

  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();
  }

  if (m_eof && m_samples.GetSize() == 0)
  {
    m_currentpos = GetLength();
  }
  else
  {
    m_currentpos += copied / GetChannels() / m_hwsamplerate;
  }

  return copied;
}

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

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