// Copyright (c) 2013 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 "content/browser/renderer_host/media/video_capture_buffer_pool.h"

#include "base/bind.h"
#include "base/callback.h"
#include "base/logging.h"
#include "media/base/video_frame.h"
#include "media/base/video_util.h"

namespace content {

VideoCaptureBufferPool::VideoCaptureBufferPool(size_t size, int count)
    : size_(size),
      count_(count) {
}

VideoCaptureBufferPool::~VideoCaptureBufferPool() {
}

bool VideoCaptureBufferPool::Allocate() {
  base::AutoLock lock(lock_);
  DCHECK(!IsAllocated());
  buffers_.resize(count_);
  for (int buffer_id = 0; buffer_id < count(); ++buffer_id) {
    Buffer* buffer = new Buffer();
    buffers_[buffer_id] = buffer;
    if (!buffer->shared_memory.CreateAndMapAnonymous(GetMemorySize()))
      return false;
  }
  return true;
}

base::SharedMemoryHandle VideoCaptureBufferPool::ShareToProcess(
    int buffer_id,
    base::ProcessHandle process_handle) {
  base::AutoLock lock(lock_);
  DCHECK(IsAllocated());
  DCHECK(buffer_id >= 0);
  DCHECK(buffer_id < count_);
  Buffer* buffer = buffers_[buffer_id];
  base::SharedMemoryHandle remote_handle;
  buffer->shared_memory.ShareToProcess(process_handle, &remote_handle);
  return remote_handle;
}

base::SharedMemoryHandle VideoCaptureBufferPool::GetHandle(int buffer_id) {
  base::AutoLock lock(lock_);
  DCHECK(IsAllocated());
  DCHECK(buffer_id >= 0);
  DCHECK(buffer_id < count_);
  return buffers_[buffer_id]->shared_memory.handle();
}

void* VideoCaptureBufferPool::GetMemory(int buffer_id) {
  base::AutoLock lock(lock_);
  DCHECK(IsAllocated());
  DCHECK(buffer_id >= 0);
  DCHECK(buffer_id < count_);
  return buffers_[buffer_id]->shared_memory.memory();
}

int VideoCaptureBufferPool::ReserveForProducer() {
  base::AutoLock lock(lock_);
  return ReserveForProducerInternal();
}

void VideoCaptureBufferPool::RelinquishProducerReservation(int buffer_id) {
  base::AutoLock lock(lock_);
  DCHECK(buffer_id >= 0);
  DCHECK(buffer_id < count());
  Buffer* buffer = buffers_[buffer_id];
  DCHECK(buffer->held_by_producer);
  buffer->held_by_producer = false;
}

void VideoCaptureBufferPool::HoldForConsumers(
    int buffer_id,
    int num_clients) {
  base::AutoLock lock(lock_);
  DCHECK(buffer_id >= 0);
  DCHECK(buffer_id < count());
  DCHECK(IsAllocated());
  Buffer* buffer = buffers_[buffer_id];
  DCHECK(buffer->held_by_producer);
  DCHECK(!buffer->consumer_hold_count);

  buffer->consumer_hold_count = num_clients;
  // Note: |held_by_producer| will stay true until
  // RelinquishProducerReservation() (usually called by destructor of the object
  // wrapping this buffer, e.g. a media::VideoFrame
}

void VideoCaptureBufferPool::RelinquishConsumerHold(int buffer_id,
                                                    int num_clients) {
  base::AutoLock lock(lock_);
  DCHECK(buffer_id >= 0);
  DCHECK(buffer_id < count());
  DCHECK_GT(num_clients, 0);
  DCHECK(IsAllocated());
  Buffer* buffer = buffers_[buffer_id];
  DCHECK_GE(buffer->consumer_hold_count, num_clients);

  buffer->consumer_hold_count -= num_clients;
}

// State query functions.
size_t VideoCaptureBufferPool::GetMemorySize() const {
  // No need to take |lock_| currently.
  return size_;
}

int VideoCaptureBufferPool::RecognizeReservedBuffer(
    base::SharedMemoryHandle maybe_belongs_to_pool) {
  base::AutoLock lock(lock_);
  for (int buffer_id = 0; buffer_id < count(); ++buffer_id) {
    Buffer* buffer = buffers_[buffer_id];
    if (buffer->shared_memory.handle() == maybe_belongs_to_pool) {
      DCHECK(buffer->held_by_producer);
      return buffer_id;
    }
  }
  return -1;  // Buffer is not from our pool.
}

scoped_refptr<media::VideoFrame> VideoCaptureBufferPool::ReserveI420VideoFrame(
    const gfx::Size& size,
    int rotation) {
  if (GetMemorySize() !=
      media::VideoFrame::AllocationSize(media::VideoFrame::I420, size)) {
    DCHECK_EQ(GetMemorySize(),
              media::VideoFrame::AllocationSize(media::VideoFrame::I420, size));
    return NULL;
  }

  base::AutoLock lock(lock_);

  int buffer_id = ReserveForProducerInternal();
  if (buffer_id < 0)
    return NULL;

  base::Closure disposal_handler = base::Bind(
      &VideoCaptureBufferPool::RelinquishProducerReservation,
      this,
      buffer_id);

  Buffer* buffer = buffers_[buffer_id];
  // Wrap the buffer in a VideoFrame container.
  scoped_refptr<media::VideoFrame> frame =
      media::VideoFrame::WrapExternalSharedMemory(
          media::VideoFrame::I420,
          size,
          gfx::Rect(size),
          size,
          static_cast<uint8*>(buffer->shared_memory.memory()),
          GetMemorySize(),
          buffer->shared_memory.handle(),
          base::TimeDelta(),
          disposal_handler);

  if (buffer->rotation != rotation) {
    // TODO(nick): Generalize the |rotation| mechanism.
    media::FillYUV(frame.get(), 0, 128, 128);
    buffer->rotation = rotation;
  }

  return frame;
}

bool VideoCaptureBufferPool::IsAnyBufferHeldForConsumers() {
  base::AutoLock lock(lock_);
  for (int buffer_id = 0; buffer_id < count(); ++buffer_id) {
    Buffer* buffer = buffers_[buffer_id];
    if (buffer->consumer_hold_count > 0)
      return true;
  }
  return false;
}

VideoCaptureBufferPool::Buffer::Buffer()
    : rotation(0),
      held_by_producer(false),
      consumer_hold_count(0) {}

int VideoCaptureBufferPool::ReserveForProducerInternal() {
  lock_.AssertAcquired();
  DCHECK(IsAllocated());

  int buffer_id = -1;
  for (int candidate_id = 0; candidate_id < count(); ++candidate_id) {
    Buffer* candidate = buffers_[candidate_id];
    if (!candidate->consumer_hold_count && !candidate->held_by_producer) {
      buffer_id = candidate_id;
      break;
    }
  }
  if (buffer_id == -1)
    return -1;

  Buffer* buffer = buffers_[buffer_id];
  CHECK_GE(buffer->shared_memory.requested_size(), size_);
  buffer->held_by_producer = true;
  return buffer_id;
}

bool VideoCaptureBufferPool::IsAllocated() const {
  lock_.AssertAcquired();
  return !buffers_.empty();
}

}  // namespace content

