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

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

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

THA_Mp3Input::~THA_Mp3Input()
{
  if (m_index) m_index->release_index(m_index);
  if (m_file) delete m_file;
  if (m_stream) JNL::close_socketlib();
}

bool THA_Mp3Input::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 (!strncmp(url.Get(), "http", 4))
  {
    url.Set(m_fn.Get());
    url.remove_filepart();

    JNL::open_socketlib();

    m_get.addheader("icy-metadata:1");
    m_get.connect(url.Get());

    m_stream = true;
    int tr = 500;

    while (1)
    {
      int status = m_get.run();

      if (status < 0)
      {
        wdl_log("HTTPGet error: %s\n", m_get.geterrorstr());
        m_eof = true;
        return false;
      }

      if (m_get.get_status() > 0)
      {
        wdl_log("reply: %s (code: %d)\n", m_get.getreply(), m_get.getreplycode());

        if (m_get.get_status() == 2)
        {
          wdl_log("headers:\n");
          char *p = (char *)m_get.getallheaders();
          while (p && *p)
          {
            wdl_log("%s\n", p);

            WDL_String tmp(p);
            for (int i = 0; i < tmp.GetLength(); i++)
            {
              tmp.Get()[i] = tolower_safe(tmp.Get()[i]);
            }

            if (!strncmp(tmp.Get(), "icy-metaint:", 12))
            {
              m_metaint = atoi(tmp.Get() + 12);
            }

            p += strlen(p) + 1;
          }

          while (!m_decoder.SyncState())
          {
            if (m_decoder.queue_bytes_in.Available() < 4096)
            {
              char buf[4096];
              int l = sizeof(buf);
              l = m_get.get_bytes(buf, l);
              if (!l) break;
              m_readmetaint += 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();
            m_syncframeinfo = m_decoder.m_lastframe;
            return true;
          }
          else
          {
            if (tr-- < 0)
            {
              m_eof = 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;
    }

    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, true);

    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;
  THA_GetResampleMode(&interp, &filtercnt, &sinc, &sinc_size, &sinc_interpsize);
  m_rs.SetMode(interp, filtercnt, sinc, sinc_size, sinc_interpsize);

  return true;
}

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

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

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

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

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

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

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

void THA_Mp3Input::Seek(double time)
{}

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

  if (m_stream)
  {
    while (m_samples.Available() < length && !m_eof)
    {
      int status = m_get.run();

      if (status < 0)
      {
        wdl_log("HTTPGet error: %s\n", m_get.geterrorstr());
        m_eof = true;
      }

      if (m_get.get_status() > 0)
      {
        wdl_log("reply: %s (code: %d)\n", m_get.getreply(), m_get.getreplycode());

        if (m_get.get_status() == 2)
        {
          wdl_log("headers:\n");
          char *p = (char *)m_get.getallheaders();
          while (p && *p)
          {
            wdl_log("%s\n", p);
            p += strlen(p) + 1;
          }

          int hasHadRdError=0;
          while (m_decoder.queue_samples_out.Available() < length * (int)sizeof(double))
          {
            if (m_decoder.queue_bytes_in.Available() < 4096)
            {
              char buf[4096];
              int l = sizeof(buf);
              l = m_get.get_bytes(buf, l);
              if (l < 1) hasHadRdError = 1;
              else
              {
                if (m_metasz)
                {
                  m_fn.AppendRaw(buf, m_metasz);

                  int s = 0, e = 0;
                  for (int i = 0; i < m_fn.GetLength(); i++)
                  {
                    if (m_fn.Get()[i] == '\'') { s = i; break; }
                  }

                  m_fn.DeleteSub(0, s + 1);

                  for (int i = 0; i < m_fn.GetLength() - 1; i++)
                  {
                    if (m_fn.Get()[i] == '\'' && m_fn.Get()[i + 1] == ';')
                    {
                      e = i; break;
                    }
                  }

                  if (e > s)
                  {
                    m_fn.DeleteSub(e, m_fn.GetLength() - e);
                  }
                  else m_fn.Set("");
                }

                m_readmetaint += l - m_metasz;

                if (m_metaint == 0) m_readmetaint = 0;

                if (m_readmetaint <= m_metaint)
                {
                  m_decoder.queue_bytes_in.Add(buf + m_metasz, l - m_metasz);
                  m_metasz = 0;
                }
                else
                {
                  int oh = m_readmetaint - m_metaint;
                  m_readmetaint = 0;

                  WDL_ASSERT(oh <= l);
                  m_decoder.queue_bytes_in.Add(buf, l - oh);

                  m_metasz = (int)buf[l - oh];

                  char *meta = &buf[l - oh + 1];
                  m_metasz *= 16;

                  if (l - oh + 1 + m_metasz <= l)
                  {
                    if (m_metasz > 0)
                    {
                      m_fn.SetRaw(meta, m_metasz);
                      int s = 0, e = 0;
                      for (int i = 0; i < m_fn.GetLength(); i++)
                      {
                        if (m_fn.Get()[i] == '\'') { s = i; break; }
                      }

                      m_fn.DeleteSub(0, s + 1);

                      for (int i = 0; i < m_fn.GetLength() - 1; i++)
                      {
                        if (m_fn.Get()[i] == '\'' && m_fn.Get()[i + 1] == ';')
                        {
                          e = i; break;
                        }
                      }

                      if (e > s)
                      {
                        m_fn.DeleteSub(e, m_fn.GetLength() - e);
                      }
                      else m_fn.Set("");
                    }
                    int rem = l - (l - oh + 1 + m_metasz);
                    m_decoder.queue_bytes_in.Add(&buf[l - oh + 1 + m_metasz], rem);
                    m_readmetaint = rem;
                    m_metasz = 0;
                  }
                  else
                  {
                    int rem = l - (l - oh + 1);
                    if (m_metasz > 0) m_fn.SetRaw(meta, rem);
                    m_readmetaint = 0;
                    m_metasz -= rem;
                  }
                }
              }
            }

            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 (m_decoder.GetNumChannels() == 1)
            {
              double *inptr=(double *)m_decoder.queue_samples_out.Get();
              m_rbuf.Resize(samples_read);
              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;
                }
              }
              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)
                {
                  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();
          }
        }
      }

      if (status == 1) // Connection closed
      {
        wdl_log("HTTPGet done!\n");
        m_eof = true;
      }
    }
  }
  else
  {
    while (m_samples.Available() < length && !m_eof)
    {
      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);
            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 (m_decoder.GetNumChannels() == 1)
        {
          double *inptr=(double *)m_decoder.queue_samples_out.Get();
          m_rbuf.Resize(samples_read);
          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;
            }
          }
          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)
            {
              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 THA_Mp3Input::IsStreaming() const
{
  return m_stream;
}

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