#include "euterpe_gen/ffmpeg_pic.h"

RSE_FFmpegPic::RSE_FFmpegPic()
  : m_protocol(NULL)
  , m_glue(NULL)
  , m_context(NULL)
  , m_eof(false)
{}

RSE_FFmpegPic::~RSE_FFmpegPic()
{
  delete m_protocol;
  delete m_glue;
  delete m_context;
}

bool RSE_FFmpegPic::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_VIDEO)
    {
      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_FFmpegPic::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_pic.GetSize()) m_pic.Resize(0, true);

  m_fn.Set(filename);
  m_protocol  = new RSE_FFmpegFileProtocol(m_fn.Get());
  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;
  }

  ReadPicture();

  return true;
}

void RSE_FFmpegPic::ReadPicture()
{
  AVFormatContext *format_context = m_glue->FormatContext();

  while (!m_eof)
  {
    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;
    }

    if (tmp.size > 0)
    {
      m_pic.Resize(tmp.size);
      memcpy(m_pic.Get(), tmp.data, tmp.size);
    }

    m_eof = true;
    // TODO: remove the while loop and m_eof.
  }
}

int RSE_FFmpegPic::GetPicSize() const
{
  return m_pic.GetSize();
}

void *RSE_FFmpegPic::GetPic() const
{
  return m_pic.Get();
}
