// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/filters/ffmpeg_glue.h"

#include "base/logging.h"
#include "base/stringprintf.h"
#include "media/base/filters.h"
#include "media/ffmpeg/ffmpeg_common.h"

namespace media {

static FFmpegURLProtocol* ToProtocol(void* data) {
  return reinterpret_cast<FFmpegURLProtocol*>(data);
}

// FFmpeg protocol interface.
static int OpenContext(URLContext* h, const char* filename, int flags) {
  FFmpegURLProtocol* protocol;
  FFmpegGlue::GetInstance()->GetProtocol(filename, &protocol);
  if (!protocol)
    return AVERROR(EIO);

  h->priv_data = protocol;
  h->flags = URL_RDONLY;
  h->is_streamed = protocol->IsStreaming();
  return 0;
}

static int ReadContext(URLContext* h, unsigned char* buf, int size) {
  FFmpegURLProtocol* protocol = ToProtocol(h->priv_data);
  int result = protocol->Read(size, buf);
  if (result < 0)
    result = AVERROR(EIO);
  return result;
}

#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(52, 68, 0)
static int WriteContext(URLContext* h, const unsigned char* buf, int size) {
#else
static int WriteContext(URLContext* h, unsigned char* buf, int size) {
#endif
  // We don't support writing.
  return AVERROR(EIO);
}

static int64 SeekContext(URLContext* h, int64 offset, int whence) {
  FFmpegURLProtocol* protocol = ToProtocol(h->priv_data);
  int64 new_offset = AVERROR(EIO);
  switch (whence) {
    case SEEK_SET:
      if (protocol->SetPosition(offset))
        protocol->GetPosition(&new_offset);
      break;

    case SEEK_CUR:
      int64 pos;
      if (!protocol->GetPosition(&pos))
        break;
      if (protocol->SetPosition(pos + offset))
        protocol->GetPosition(&new_offset);
      break;

    case SEEK_END:
      int64 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:
      NOTREACHED();
  }
  if (new_offset < 0)
    new_offset = AVERROR(EIO);
  return new_offset;
}

static int CloseContext(URLContext* h) {
  h->priv_data = NULL;
  return 0;
}

static int LockManagerOperation(void** lock, enum AVLockOp op) {
  switch (op) {
    case AV_LOCK_CREATE:
      *lock = new base::Lock();
      if (!*lock)
        return 1;
      return 0;

    case AV_LOCK_OBTAIN:
      static_cast<base::Lock*>(*lock)->Acquire();
      return 0;

    case AV_LOCK_RELEASE:
      static_cast<base::Lock*>(*lock)->Release();
      return 0;

    case AV_LOCK_DESTROY:
      delete static_cast<base::Lock*>(*lock);
      *lock = NULL;
      return 0;
  }
  return 1;
}

// Use the HTTP protocol to avoid any file path separator issues.
static const char kProtocol[] = "http";

// Fill out our FFmpeg protocol definition.
static URLProtocol kFFmpegURLProtocol = {
  kProtocol,
  &OpenContext,
  &ReadContext,
  &WriteContext,
  &SeekContext,
  &CloseContext,
};

FFmpegGlue::FFmpegGlue() {
  // Before doing anything disable logging as it interferes with layout tests.
  av_log_set_level(AV_LOG_QUIET);

  // Register our protocol glue code with FFmpeg.
  avcodec_init();
  av_register_protocol2(&kFFmpegURLProtocol, sizeof(kFFmpegURLProtocol));
  av_lockmgr_register(&LockManagerOperation);

  // Now register the rest of FFmpeg.
  av_register_all();
}

FFmpegGlue::~FFmpegGlue() {
  av_lockmgr_register(NULL);
}

// static
FFmpegGlue* FFmpegGlue::GetInstance() {
  return Singleton<FFmpegGlue>::get();
}

std::string FFmpegGlue::AddProtocol(FFmpegURLProtocol* protocol) {
  base::AutoLock auto_lock(lock_);
  std::string key = GetProtocolKey(protocol);
  if (protocols_.find(key) == protocols_.end()) {
    protocols_[key] = protocol;
  }
  return key;
}

void FFmpegGlue::RemoveProtocol(FFmpegURLProtocol* protocol) {
  base::AutoLock auto_lock(lock_);
  for (ProtocolMap::iterator cur, iter = protocols_.begin();
       iter != protocols_.end();) {
    cur = iter;
    iter++;

    if (cur->second == protocol)
      protocols_.erase(cur);
  }
}

void FFmpegGlue::GetProtocol(const std::string& key,
                             FFmpegURLProtocol** protocol) {
  base::AutoLock auto_lock(lock_);
  ProtocolMap::iterator iter = protocols_.find(key);
  if (iter == protocols_.end()) {
    *protocol = NULL;
    return;
  }
  *protocol = iter->second;
}

std::string FFmpegGlue::GetProtocolKey(FFmpegURLProtocol* protocol) {
  // Use the FFmpegURLProtocol's memory address to generate the unique string.
  // This also has the nice property that adding the same FFmpegURLProtocol
  // reference will not generate duplicate entries.
  return base::StringPrintf("%s://%p", kProtocol, static_cast<void*>(protocol));
}

}  // namespace media
