#include "euterpe_gen/ffmpeg_input.h"

#include <string.h>
#include <ctype.h>

#include "WDL/fpcmp.h"

RSE_FFmpegInput::RSE_FFmpegInput()
  : m_protocol(NULL)
  , m_glue(NULL)
  , m_context(NULL)
  , m_resampler(NULL)
  , m_hw_samplerate(0)
  , m_eof(false)
  , m_position(0)
  , m_samplerate(0)
  , m_bits_per_sample(0)
  , m_timestamp(0)
{}

RSE_FFmpegInput::~RSE_FFmpegInput()
{
  delete m_protocol;
  delete m_glue;
  delete m_context;
  swr_free(&m_resampler);
}

bool RSE_FFmpegInput::Open(const char *filename)
{
  if (m_protocol)
  {
    delete m_protocol;
    m_protocol = NULL;
  }

  if (m_glue)
  {
    delete m_glue;
    m_glue = NULL;
  }

  if (m_context)
  {
    delete m_context;
    m_context = NULL;
  }

  if (m_resampler)
  {
    swr_free(&m_resampler);
    m_resampler = NULL;
  }

  m_hw_samplerate = GetHardwareSampleRate();

  m_protocol = new RSE_FFmpegFileProtocol(filename);
  m_glue = new RSE_FFmpegGlue(m_protocol);

  AVFormatContext *format_context = m_glue->FormatContext();

  // Disable ID3v1 tag reading to avoid costly seeks to end of file
  // for data we don't use. FFmpeg will only read ID3v1 tag if no
  // other metadata is available, so add a metadata entry to ensure
  // some is always present.
  //av_dict_set(&format_context->metadata, "skip_id3v1_tags", "", 0);
    //AV_DICT_DONT_STRDUP_KEY|AV_DICT_DONT_STRDUP_VAL);

  // Ensure FFmpeg doesn't give up to early while looking for stream
  // params; this does not increase the amount of data downloaded.
  // The default value is 5 AV_TIME_BASE units (1 second each), which
  // prevents some oddly muxed streams from being detected properly;
  // this value was chosen arbitrarily.
  format_context->max_analyze_duration = 60 * AV_TIME_BASE;

  // Open the AVFormatContext using our glue layer.
  if (!m_glue->OpenContext())
  {
    wdl_log("FFmpeg cannot open glue context\n");
    return false;
  }

  if (!FindCodec())
  {
    wdl_log("FFmpeg cannot find codec\n");
    return false;
  }

  m_context = new ScopedContext(&m_codec_info);

  if (avcodec_open2(m_context->ptr, m_codec_info.codec, NULL) < 0)
  {
    wdl_log("FFmpeg cannot open codec\n");
    return false;
  }

#if 1
  m_resampler = swr_alloc_set_opts(NULL,
    // output
    AV_CH_LAYOUT_STEREO, //m_context->ptr->channel_layout,
#if (EUTERPE_SAMPLE_PRECISION == 8)
    AV_SAMPLE_FMT_DBL,
#else
    AV_SAMPLE_FMT_FLT,
#endif
    m_context->ptr->sample_rate, //(int)m_hw_samplerate, //m_context->ptr->sample_rate,
    // input
    m_context->ptr->channel_layout,
    m_context->ptr->sample_fmt,
    m_context->ptr->sample_rate,
    0, NULL);
#endif
#if 0
  m_resampler = swr_alloc();

  av_opt_set_channel_layout(m_resampler, "in_channel_layout",
    m_context->ptr->channel_layout, 0);
  av_opt_set_channel_layout(m_resampler, "out_channel_layout",
    m_context->ptr->channel_layout, 0);
  av_opt_set_int(m_resampler, "in_sample_rate", m_context->ptr->sample_rate, 0);
  av_opt_set_int(m_resampler, "out_sample_rate", m_context->ptr->sample_rate, 0);
  av_opt_set_sample_fmt(m_resampler, "in_sample_fmt", m_context->ptr->sample_fmt, 0);
#if (EUTERPE_SAMPLE_PRECISION == 8)
  av_opt_set_sample_fmt(m_resampler, "out_sample_fmt", AV_SAMPLE_FMT_DBL, 0);
#else
  av_opt_set_sample_fmt(m_resampler, "out_sample_fmt", AV_SAMPLE_FMT_FLT, 0);
#endif
#endif

  swr_init(m_resampler);

  m_samplerate = (double)m_codec_info.stream->codecpar->sample_rate;

  int sinc = GetResamplerSinc();
  m_rs.SetMode(true, 1, true, sinc, sinc / 2);

  return true;
}

bool RSE_FFmpegInput::FindCodec()
{
  AVFormatContext *format_context = m_glue->FormatContext();

  // Retrieve stream information.
  if (avformat_find_stream_info(format_context, NULL) < 0)
  {
    wdl_log("FFmpeg cannot retrieve stream information\n");
    return false;
  }

  for (unsigned int i = 0; i < format_context->nb_streams; i++)
  {
    AVStream *stream = format_context->streams[i];

    if (stream && stream->codecpar && stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
    {
      AVCodec *codec = avcodec_find_decoder(stream->codecpar->codec_id);

      if (codec)
      {
        m_codec_info.stream_index = i;
        m_codec_info.stream = stream;
        m_codec_info.codec = codec;

        return true;
      }
    }
  }

  wdl_log("FFmpeg could not find the codec\n");
  return false;
}

bool RSE_FFmpegInput::ConvertFormat(int ff_fmt, int *fmt, bool *planar)
{
  switch (ff_fmt)
  {
  case AV_SAMPLE_FMT_U8:
    *fmt = AV_SAMPLE_FMT_U8;
    *planar = false;
    break;

  case AV_SAMPLE_FMT_S16:
    *fmt = AV_SAMPLE_FMT_S16;
    *planar = false;
    break;

  case AV_SAMPLE_FMT_S32:
    *fmt = AV_SAMPLE_FMT_S32;
    *planar = false;
    break;

  case AV_SAMPLE_FMT_FLT:
    *fmt = AV_SAMPLE_FMT_FLT;
    *planar = false;
    break;

  case AV_SAMPLE_FMT_DBL:
    *fmt = AV_SAMPLE_FMT_DBL;
    *planar = false;
    break;

  case AV_SAMPLE_FMT_U8P:
    *fmt = AV_SAMPLE_FMT_U8P;
    *planar = true;
    break;

  case AV_SAMPLE_FMT_S16P:
    *fmt = AV_SAMPLE_FMT_S16P;
    *planar = true;
    break;

  case AV_SAMPLE_FMT_S32P:
    *fmt = AV_SAMPLE_FMT_S32P;
    *planar = true;
    break;

  case AV_SAMPLE_FMT_FLTP:
    *fmt = AV_SAMPLE_FMT_FLTP;
    *planar = true;
    break;

  case AV_SAMPLE_FMT_DBLP:
    *fmt = AV_SAMPLE_FMT_DBLP;
    *planar = true;
    break;

  default:
    wdl_log("unsupported audio format %d\n", ff_fmt);
    return false;
  }

  return true;
}

int RSE_FFmpegInput::SizeOfFormat(int fmt)
{
  switch (fmt)
  {
  case AV_SAMPLE_FMT_U8:
  case AV_SAMPLE_FMT_U8P:
    return 1;

  case AV_SAMPLE_FMT_S16:
  case AV_SAMPLE_FMT_S16P:
    return 2;

  case AV_SAMPLE_FMT_S32:
  case AV_SAMPLE_FMT_S32P:
    return 4;

  case AV_SAMPLE_FMT_FLT:
  case AV_SAMPLE_FMT_FLTP:
    return 4;

  case AV_SAMPLE_FMT_DBL:
  case AV_SAMPLE_FMT_DBLP:
    return 8;

  default:
    return 0;
  }
}

void RSE_FFmpegInput::PrintFormat(int fmt, bool planar)
{
  switch (fmt)
  {
  case AV_SAMPLE_FMT_U8:
    printf("AV_SAMPLE_FMT_U8\n");
    break;

  case AV_SAMPLE_FMT_S16:
    printf("AV_SAMPLE_FMT_S16\n");
    break;

  case AV_SAMPLE_FMT_S32:
    printf("AV_SAMPLE_FMT_S32\n");
    break;

  case AV_SAMPLE_FMT_FLT:
    printf("AV_SAMPLE_FMT_FLT\n");
    break;

  case AV_SAMPLE_FMT_DBL:
    printf("AV_SAMPLE_FMT_DBL\n");
    break;

  case AV_SAMPLE_FMT_U8P:
    printf("AV_SAMPLE_FMT_U8P\n");
    break;

  case AV_SAMPLE_FMT_S16P:
    printf("AV_SAMPLE_FMT_S16P\n");
    break;

  case AV_SAMPLE_FMT_S32P:
    printf("AV_SAMPLE_FMT_S32P\n");
    break;

  case AV_SAMPLE_FMT_FLTP:
    printf("AV_SAMPLE_FMT_FLTP\n");
    break;

  case AV_SAMPLE_FMT_DBLP:
    printf("AV_SAMPLE_FMT_DBLP\n");
    break;

  default:
    printf("unsupported audio format");
  }
}

const char *RSE_FFmpegInput::GetType()
{
  return m_type.Get();
}

const char *RSE_FFmpegInput::GetFileName()
{
  return m_filename.Get();
}

int RSE_FFmpegInput::GetChannels()
{
  //return m_codec_info.stream->codecpar->channels;
  return 2;
}

double RSE_FFmpegInput::GetSampleRate()
{
  return m_samplerate;
}

double RSE_FFmpegInput::GetLength()
{
  AVFormatContext *ctx = m_glue->FormatContext();
  return (double)ctx->duration / AV_TIME_BASE;
}

int RSE_FFmpegInput::GetBitsPerSample()
{
  return SizeOfFormat(m_codec_info.stream->codecpar->format) * 8;
}

double RSE_FFmpegInput::GetPosition()
{
  return m_position;
}

void RSE_FFmpegInput::Seek(double time)
{
  AVFormatContext *format_context = m_glue->FormatContext();

  int ret = 0;
  m_eof = false;

  // flush buffers
  m_buffer_queue.Clear();

  int64_t seek_point = (int64_t)(time * AV_TIME_BASE);

  ret = av_seek_frame(format_context, -1,
    seek_point, AVSEEK_FLAG_BACKWARD);
    //seek_point, AVSEEK_FLAG_FRAME);

  if (ret < 0)
  {
    char error[1024];
    av_strerror(ret, error, sizeof(error));

    wdl_log("FFmpeg seek error: %s\n", error);
  }

  avcodec_flush_buffers(m_context->ptr);

  bool done = false;

  do
  {
    ScopedPacket packet;

again:
    ret = av_read_frame(format_context, &packet);

    if (!packet.data && !packet.size && ret >= 0) goto again;

    if (ret < 0)
    {
      if (ret == (int)AVERROR_EOF)
      {
        m_eof = true;
      }
      else
      {
        continue;
      }
    }
    else
    {
      if (packet.stream_index != m_codec_info.stream_index)
      {
        continue;
      }
    }

    AVPacket tmp;

    if (m_eof)
    {
      break;
      tmp = AVPacket();
      av_init_packet(&tmp);
    }
    else
    {
      tmp = packet;
    }

    ret = avcodec_send_packet(m_context->ptr, &tmp);

    if (ret < 0)
    {
      char error[1024];
      av_strerror(ret, error, sizeof(error));

      wdl_log("FFmpeg error submitting packet to the decoder: %s\n", error);
    }

    while (ret >= 0)
    {
      ScopedFrame frame;

      ret = avcodec_receive_frame(m_context->ptr, frame.ptr);

      if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
      {
        break;
      }

      if (ret < 0)
      {
        break; // Read next packet, continue past errors.
      }

      // Convert PTS to seconds
      double sec = (double)frame->pkt_dts *
        m_codec_info.stream->time_base.num /
        m_codec_info.stream->time_base.den;

      if (WDL_EssentiallyEqual(sec, time) ||
        WDL_DefinitelyGreaterThan(sec, time))
      {
        done = true;
        break;
      }
    }
  } while (!done);

  m_position = time;
}

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

  while (m_buffer_queue.Available() < length && !m_eof)
    ReadNext();

  if (m_buffer_queue.Available() > length)
  {
    memcpy(buffer, m_buffer_queue.Get(), length * sizeof(SAM));
    ret = length;
    m_buffer_queue.Advance(length);
    m_buffer_queue.Compact();
  }
  else
  {
    memcpy(buffer, m_buffer_queue.Get(), m_buffer_queue.Available() * sizeof(SAM));
    ret = m_buffer_queue.Available();
    m_buffer_queue.Clear();
  }

  m_position += ret / m_hw_samplerate / GetChannels();

  return ret;
}

bool RSE_FFmpegInput::IsStreaming()
{
  return false;
}

void RSE_FFmpegInput::ReadNext()
{
  AVFormatContext *format_context = m_glue->FormatContext();

  int fmt;
  bool planar;

  if (!ConvertFormat(m_context->ptr->sample_fmt, &fmt, &planar))
  {
    wdl_log("FFmpeg cannot convert format\n");
  }

  while (!m_eof)
  //while (!m_eof && m_buffer_queue.Available() == 0)
  {
    ScopedPacket packet;

again:
    int ret = av_read_frame(format_context, &packet);

    if (!packet.data && !packet.size && ret >= 0) goto again;

    if (ret < 0)
    {
      if (ret == (int)AVERROR_EOF)
      {
        m_eof = true;
      }
      else
      {
        continue;
      }
    }
    else
    {
      if (packet.stream_index != m_codec_info.stream_index)
      {
        continue;
      }
    }

    AVPacket tmp;

    if (m_eof)
    {
      break;
      tmp = AVPacket();
      av_init_packet(&tmp);
    }
    else
    {
      tmp = packet;
    }

    ret = avcodec_send_packet(m_context->ptr, &tmp);

    if (ret < 0)
    {
      char error[1024];
      av_strerror(ret, error, sizeof(error));

      wdl_log("FFmpeg error submitting packet to the decoder: %s\n", error);
    }

    while (ret >= 0)
    {
      ScopedFrame frame;
      ScopedFrame resampled_frame;

      ret = avcodec_receive_frame(m_context->ptr, frame.ptr);

      if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
      {
        break;
      }

      if (ret < 0)
      {
        break; // Read next packet, continue past errors.
      }

      //tmp.size -= ret;
      //tmp.data += ret;

      resampled_frame->channel_layout = AV_CH_LAYOUT_STEREO; //frame->channel_layout;
      resampled_frame->sample_rate = frame->sample_rate; //(int)m_hw_samplerate; //frame->sample_rate;
#if (EUTERPE_SAMPLE_PRECISION == 8)
      resampled_frame->format = AV_SAMPLE_FMT_DBL;
#else
      resampled_frame->format = AV_SAMPLE_FMT_FLT;
#endif

      if ((ret = swr_convert_frame(m_resampler, resampled_frame.ptr, frame.ptr)) < 0)
      {
        //break; // Read next packet, continue past errors.
        continue;
      }

      WDL_ASSERT(sizeof(SAM) == SizeOfFormat(resampled_frame->format));

      int size_bytes = SizeOfFormat(resampled_frame->format) *
                       resampled_frame->channels * resampled_frame->nb_samples;

      int size = resampled_frame->channels * resampled_frame->nb_samples;
      SAM *data = (SAM *)resampled_frame->data[0];

      int datasize = av_samples_get_buffer_size(NULL, resampled_frame->channels,
        resampled_frame->nb_samples, (AVSampleFormat)resampled_frame->format, 1);

      //m_buffer_queue.Add(data, size);
#if 1
      if (WDL_ApproximatelyEqual(m_samplerate, m_hw_samplerate))
      {
        m_buffer_queue.Add(data, size);
      }
      else
      {
        const int nch = GetChannels();
        int frames = size / nch;

        //SAM **samples = (SAM **)malloc(nch * sizeof(SAM *));

        //for (int i = 0; i < nch; i++)
        //{
        //  samples[i] = (SAM *)malloc(frames * sizeof(SAM));

        //  for (int j = 0; j < frames; j++)
        //  {
        //    samples[i][j] = data[j * nch + i];
        //  }
        //}

        m_rs.SetRates(m_samplerate, m_hw_samplerate);
        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++ = data[i * nch + j];
              }
            }
          }
          frames -= amt;

          WDL_ResampleSample tmp[2048];
          amt = m_rs.ResampleOut(tmp, amt, 2048 / nch, nch);

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

          amt *= nch;
          m_buffer_queue.Add(tmp, amt);
        }

        //for (int i = 0; i < nch; i++)
        //{
        //  free (samples[i]);
        //}

        //free (samples);
      }
#endif
#if 0
      int64_t xxx = swr_get_delay(m_resampler, (int64_t)m_hw_samplerate);

      if (xxx > 0)
      {
        ScopedFrame sframe;
        sframe->channel_layout = AV_CH_LAYOUT_STEREO;
        sframe->sample_rate = (int)m_hw_samplerate;
#if (EUTERPE_SAMPLE_PRECISION == 8)
        sframe->format = AV_SAMPLE_FMT_DBL;
#else
        sframe->format = AV_SAMPLE_FMT_FLT;
#endif

        swr_convert_frame(m_resampler, sframe.ptr, NULL);
        int newsize = sframe->channels * sframe->nb_samples;
        SAM *newdata = (SAM *)sframe->data[0];

        if (newsize)
        {
          m_buffer_queue.Add(newdata, newsize);
        }
      }
#endif

    }
    break;
  }
}

int RSE_FFmpegInput::Extended(int call, void *parm1, void *parm2, void *parm3)
{
  return 0;
}
