// Based on:
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license.

#include "euterpe_gen/ffmpeg_glue.h"
#include "euterpe_gen/ffmpeg_common.h"
#include "WDL/mutex.h"

enum { kBufferSize = 32 * 1024 };

static int AVIOReadOperation(void *opaque, unsigned char *buffer, int buffer_size)
{
  RSE_FFmpegURLProtocol *protocol = (RSE_FFmpegURLProtocol *)opaque;
  int result = protocol->Read(buffer, buffer_size);

  if (result < 0)
  {
    result = AVERROR(EIO);
  }

  return result;
}

static int64_t AVIOSeekOperation(void *opaque, int64_t offset, int whence)
{
  RSE_FFmpegURLProtocol *protocol = (RSE_FFmpegURLProtocol *)opaque;
  int64_t new_offset = AVERROR(EIO);

  switch (whence)
  {
  case SEEK_SET:
    if (protocol->SetPosition(offset))
    {
      protocol->GetPosition(&new_offset);
    }

    break;

  case SEEK_CUR:
    int64_t pos;

    if (!protocol->GetPosition(&pos))
    {
      break;
    }

    if (protocol->SetPosition(pos + offset))
    {
      protocol->GetPosition(&new_offset);
    }

    break;

  case SEEK_END:
    int64_t size;

    if (!protocol->GetSize(&size))
    {
      break;
    }

    if (protocol->SetPosition(size + offset))
    {
      protocol->GetPosition(&new_offset);
    }

    break;

  case AVSEEK_SIZE:
    protocol->GetSize(&new_offset);
    break;

  default:
    ; // not_reached
  }

  if (new_offset < 0)
  {
    new_offset = AVERROR(EIO);
  }

  return new_offset;
}

static int LockManagerOperation(void **lock, enum AVLockOp op)
{
  switch (op)
  {
  case AV_LOCK_CREATE:
    *lock = new WDL_Mutex;
    return 0;

  case AV_LOCK_OBTAIN:
    ((WDL_Mutex *)*lock)->Enter();
    return 0;

  case AV_LOCK_RELEASE:
    ((WDL_Mutex *)*lock)->Leave();
    return 0;

  case AV_LOCK_DESTROY:
    delete (WDL_Mutex *)*lock;
    *lock = NULL;
    return 0;
  }

  return 1;
}

/*
void RSE_FFmpegGlue::InitFFmpeg()
{
    bool initialized = true;

    if (av_lockmgr_register(&lock_manager_operation) != 0)
    {
        initialized = false;
    }

    assert(initialized);

    av_register_all();
}
*/

RSE_FFmpegGlue::RSE_FFmpegGlue(RSE_FFmpegURLProtocol* protocol)
  : m_open_called(false)
  , m_format_context(NULL)
  , m_avio_context(NULL)
{
  static bool init_euterpe_ffmpeg = false;

  if (!init_euterpe_ffmpeg)
  {
    avcodec_register_all();
    av_register_all();
    init_euterpe_ffmpeg = true;
  }

  m_format_context = avformat_alloc_context();
  m_avio_context = avio_alloc_context(
    (unsigned char *)av_malloc(kBufferSize), kBufferSize, 0, protocol,
    &AVIOReadOperation, NULL, &AVIOSeekOperation);

  m_avio_context->seekable =
    protocol->IsStreaming() ? 0 : AVIO_SEEKABLE_NORMAL;

  m_avio_context->write_flag = 0;

  m_format_context->flags |= AVFMT_FLAG_CUSTOM_IO;
  m_format_context->flags |= AVFMT_FLAG_FAST_SEEK;
  m_format_context->flags |= AVFMT_FLAG_KEEP_SIDE_DATA;

  m_format_context->error_recognition |= AV_EF_EXPLODE;

  m_format_context->pb = m_avio_context;
}

RSE_FFmpegGlue::~RSE_FFmpegGlue()
{
  av_lockmgr_register(NULL);

  if (!m_format_context)
  {
    av_free(m_avio_context->buffer);
    av_free(m_avio_context);
    return;
  }

  if (!m_open_called)
  {
    avformat_free_context(m_format_context);
    av_free(m_avio_context->buffer);
    av_free(m_avio_context);
    return;
  }

  avformat_close_input(&m_format_context);
  av_free(m_avio_context->buffer);
  av_free(m_avio_context);
}

bool RSE_FFmpegGlue::OpenContext()
{
  WDL_ASSERT(!m_open_called); // OpenContext shouldn't be called twice

  m_open_called = true;

  const int ret = avformat_open_input(&m_format_context, NULL, NULL, NULL);

  if (ret == AVERROR_INVALIDDATA)
  {
    wdl_log("FFmpeg cannot identify the file\n");
    return false;
  }
  else if (ret < 0)
  {
    wdl_log("FFmpeg cannot open input\n");
    return false;
  }

  return true;
}
