#include "include/dvr/dvr_api.h"
#include "include/dvr/dvr_buffer_queue.h"

#include <android/native_window.h>
#include <gui/BufferHubProducer.h>

#include "dvr_internal.h"
#include "dvr_buffer_queue_internal.h"

using namespace android;
using android::dvr::BufferConsumer;
using android::dvr::BufferHubBuffer;
using android::dvr::BufferProducer;
using android::dvr::ConsumerQueue;
using android::dvr::ProducerQueue;
using android::dvr::ProducerQueueConfigBuilder;
using android::dvr::UsagePolicy;

extern "C" {

DvrWriteBufferQueue::DvrWriteBufferQueue(
    const std::shared_ptr<ProducerQueue>& producer_queue)
    : producer_queue_(producer_queue),
      width_(producer_queue->default_width()),
      height_(producer_queue->default_height()),
      format_(producer_queue->default_format()) {}

int DvrWriteBufferQueue::GetNativeWindow(ANativeWindow** out_window) {
  if (native_window_ == nullptr) {
    // Lazy creation of |native_window|, as not everyone is using
    // DvrWriteBufferQueue as an external surface.
    sp<IGraphicBufferProducer> gbp = BufferHubProducer::Create(producer_queue_);
    native_window_ = new Surface(gbp, true);
  }

  *out_window = static_cast<ANativeWindow*>(native_window_.get());
  return 0;
}

int DvrWriteBufferQueue::CreateReadQueue(DvrReadBufferQueue** out_read_queue) {
  std::unique_ptr<ConsumerQueue> consumer_queue =
      producer_queue_->CreateConsumerQueue();
  if (consumer_queue == nullptr) {
    ALOGE(
        "DvrWriteBufferQueue::CreateReadQueue: Failed to create consumer queue "
        "from producer queue: queue_id=%d.", producer_queue_->id());
    return -ENOMEM;
  }

  *out_read_queue = new DvrReadBufferQueue(std::move(consumer_queue));
  return 0;
}

int DvrWriteBufferQueue::Dequeue(int timeout, DvrWriteBuffer* write_buffer,
                                 int* out_fence_fd) {
  DvrNativeBufferMetadata meta;
  DvrWriteBuffer* buffer = nullptr;
  int fence_fd = -1;
  if (const int ret = GainBuffer(timeout, &buffer, &meta, &fence_fd))
    return ret;
  if (!buffer)
    return -ENOMEM;

  write_buffers_[buffer->slot].reset(buffer);
  write_buffer->write_buffer = std::move(buffer->write_buffer);
  *out_fence_fd = fence_fd;
  return 0;
}

int DvrWriteBufferQueue::GainBuffer(int timeout,
                                    DvrWriteBuffer** out_write_buffer,
                                    DvrNativeBufferMetadata* out_meta,
                                    int* out_fence_fd) {
  size_t slot;
  pdx::LocalHandle release_fence;

  // Need to retry N+1 times, where N is total number of buffers in the queue.
  // As in the worst case, we will dequeue all N buffers and reallocate them, on
  // the {N+1}th dequeue, we are guaranteed to get a buffer with new dimension.
  size_t max_retries = 1 + producer_queue_->capacity();
  size_t retry = 0;

  for (; retry < max_retries; retry++) {
    auto buffer_status =
        producer_queue_->Dequeue(timeout, &slot, out_meta, &release_fence);
    if (!buffer_status) {
      ALOGE_IF(buffer_status.error() != ETIMEDOUT,
               "DvrWriteBufferQueue::GainBuffer: Failed to dequeue buffer: %s",
               buffer_status.GetErrorMessage().c_str());
      return -buffer_status.error();
    }

    if (write_buffers_[slot] == nullptr) {
      // Lazy initialization of a write_buffers_ slot. Note that a slot will
      // only be dynamically allocated once during the entire cycle life of a
      // queue.
      write_buffers_[slot] = std::make_unique<DvrWriteBuffer>();
      write_buffers_[slot]->slot = slot;
    }

    LOG_ALWAYS_FATAL_IF(
        write_buffers_[slot]->write_buffer,
        "DvrWriteBufferQueue::GainBuffer: Buffer slot is not empty: %zu", slot);
    write_buffers_[slot]->write_buffer = std::move(buffer_status.take());

    const auto& buffer_producer = write_buffers_[slot]->write_buffer;
    if (!buffer_producer)
      return -ENOMEM;

    if (width_ == buffer_producer->width() &&
        height_ == buffer_producer->height() &&
        format_ == buffer_producer->format()) {
      // Producer queue returns a buffer matches the current request.
      break;
    }

    // Needs reallocation. Note that if there are already multiple available
    // buffers in the queue, the next one returned from |queue_->Dequeue| may
    // still have the old buffer dimension or format. Retry up to N+1 times or
    // until we dequeued a buffer with new configuration.
    ALOGD_IF(TRACE,
             "DvrWriteBufferQueue::Dequeue: requested buffer at slot: %zu "
             "(w=%u, h=%u, fmt=%u) is different from the buffer returned "
             "(w=%u, h=%u, fmt=%u). Need re-allocation.",
             slot, width_, height_, format_, buffer_producer->width(),
             buffer_producer->height(), buffer_producer->format());

    // Currently, we are not storing |layer_count| and |usage| in queue
    // configuration. Copy those setup from the last buffer dequeued before we
    // remove it.
    uint32_t old_layer_count = buffer_producer->layer_count();
    uint64_t old_usage = buffer_producer->usage();

    // Allocate a new producer buffer with new buffer configs. Note that if
    // there are already multiple available buffers in the queue, the next one
    // returned from |queue_->Dequeue| may still have the old buffer dimension
    // or format. Retry up to BufferHubQueue::kMaxQueueCapacity times or until
    // we dequeued a buffer with new configuration.
    auto remove_status = producer_queue_->RemoveBuffer(slot);
    if (!remove_status) {
      ALOGE("DvrWriteBufferQueue::Dequeue: Failed to remove buffer: %s",
            remove_status.GetErrorMessage().c_str());
      return -remove_status.error();
    }
    // Make sure that the previously allocated buffer is dereferenced from
    // write_buffers_ array.
    write_buffers_[slot]->write_buffer = nullptr;

    auto allocate_status = producer_queue_->AllocateBuffer(
        width_, height_, old_layer_count, format_, old_usage);
    if (!allocate_status) {
      ALOGE("DvrWriteBufferQueue::Dequeue: Failed to allocate buffer: %s",
            allocate_status.GetErrorMessage().c_str());
      return -allocate_status.error();
    }
  }

  if (retry >= max_retries) {
    ALOGE(
        "DvrWriteBufferQueue::Dequeue: Failed to re-allocate buffer after "
        "resizing.");
    return -ENOMEM;
  }

  *out_write_buffer = write_buffers_[slot].release();
  *out_fence_fd = release_fence.Release();

  return 0;
}

int DvrWriteBufferQueue::PostBuffer(DvrWriteBuffer* write_buffer,
                                    const DvrNativeBufferMetadata* meta,
                                    int ready_fence_fd) {
  LOG_FATAL_IF(
      (write_buffers->slot < 0 || write_buffers->slot >= write_buffers_.size()),
      "DvrWriteBufferQueue::ReleaseBuffer: Invalid slot: %zu", slot);

  // Some basic sanity checks before we put the buffer back into a slot.
  size_t slot = static_cast<size_t>(write_buffer->slot);
  if (write_buffers_[slot] != nullptr) {
    ALOGE("DvrWriteBufferQueue::PostBuffer: Slot is not empty: %zu", slot);
    return -EINVAL;
  }
  if (write_buffer->write_buffer == nullptr) {
    ALOGE("DvrWriteBufferQueue::PostBuffer: Invalid write buffer.");
    return -EINVAL;
  }
  if (write_buffer->write_buffer->id() != producer_queue_->GetBufferId(slot)) {
    ALOGE(
        "DvrWriteBufferQueue::PostBuffer: Buffer to be posted does not "
        "belong to this buffer queue. Posting buffer: id=%d, buffer in "
        "queue: id=%d",
        write_buffer->write_buffer->id(), producer_queue_->GetBufferId(slot));
    return -EINVAL;
  }

  write_buffer->write_buffer->SetQueueIndex(next_post_index_++);
  pdx::LocalHandle fence(ready_fence_fd);
  const int ret = write_buffer->write_buffer->PostAsync(meta, fence);
  if (ret < 0) {
    ALOGE("DvrWriteBufferQueue::PostBuffer: Failed to post buffer, ret=%d",
          ret);
    return ret;
  }

  // Put the DvrWriteBuffer pointer back into its slot for reuse.
  write_buffers_[slot].reset(write_buffer);
  // It's import to reset the write buffer client now. It should stay invalid
  // until next GainBuffer on the same slot.
  write_buffers_[slot]->write_buffer = nullptr;
  return 0;
}

int DvrWriteBufferQueue::ResizeBuffer(uint32_t width, uint32_t height) {
  if (width == 0 || height == 0) {
    ALOGE(
        "DvrWriteBufferQueue::ResizeBuffer: invalid buffer dimension: w=%u, "
        "h=%u.",
        width, height);
    return -EINVAL;
  }

  width_ = width;
  height_ = height;
  return 0;
}

int dvrWriteBufferQueueCreate(uint32_t width, uint32_t height, uint32_t format,
                              uint32_t layer_count, uint64_t usage,
                              size_t capacity, size_t metadata_size,
                              DvrWriteBufferQueue** out_write_queue) {
  if (!out_write_queue)
    return -EINVAL;

  auto config_builder = ProducerQueueConfigBuilder()
                            .SetDefaultWidth(width)
                            .SetDefaultHeight(height)
                            .SetDefaultFormat(format)
                            .SetMetadataSize(metadata_size);
  std::unique_ptr<ProducerQueue> producer_queue =
      ProducerQueue::Create(config_builder.Build(), UsagePolicy{});
  if (!producer_queue) {
    ALOGE("dvrWriteBufferQueueCreate: Failed to create producer queue.");
    return -ENOMEM;
  }

  auto status = producer_queue->AllocateBuffers(width, height, layer_count,
                                                format, usage, capacity);
  if (!status.ok()) {
    ALOGE("dvrWriteBufferQueueCreate: Failed to allocate buffers.");
    return -ENOMEM;
  }

  *out_write_queue = new DvrWriteBufferQueue(std::move(producer_queue));
  return 0;
}

void dvrWriteBufferQueueDestroy(DvrWriteBufferQueue* write_queue) {
  delete write_queue;
}

ssize_t dvrWriteBufferQueueGetCapacity(DvrWriteBufferQueue* write_queue) {
  if (!write_queue)
    return -EINVAL;

  return write_queue->capacity();
}

int dvrWriteBufferQueueGetId(DvrWriteBufferQueue* write_queue) {
  if (!write_queue)
    return -EINVAL;

  return write_queue->id();
}

int dvrWriteBufferQueueGetANativeWindow(DvrWriteBufferQueue* write_queue,
                                        ANativeWindow** out_window) {
  if (!write_queue || !out_window)
    return -EINVAL;

  return write_queue->GetNativeWindow(out_window);
}

int dvrWriteBufferQueueCreateReadQueue(DvrWriteBufferQueue* write_queue,
                                       DvrReadBufferQueue** out_read_queue) {
  if (!write_queue || !out_read_queue)
    return -EINVAL;

  return write_queue->CreateReadQueue(out_read_queue);
}

int dvrWriteBufferQueueGainBuffer(DvrWriteBufferQueue* write_queue, int timeout,
                                  DvrWriteBuffer** out_write_buffer,
                                  DvrNativeBufferMetadata* out_meta,
                                  int* out_fence_fd) {
  if (!write_queue || !out_write_buffer || !out_meta || !out_fence_fd)
    return -EINVAL;

  return write_queue->GainBuffer(timeout, out_write_buffer, out_meta,
                                 out_fence_fd);
}

int dvrWriteBufferQueuePostBuffer(DvrWriteBufferQueue* write_queue,
                                  DvrWriteBuffer* write_buffer,
                                  const DvrNativeBufferMetadata* meta,
                                  int ready_fence_fd) {
  if (!write_queue || !write_buffer || !write_buffer->write_buffer || !meta)
    return -EINVAL;

  return write_queue->PostBuffer(write_buffer, meta, ready_fence_fd);
}

int dvrWriteBufferQueueResizeBuffer(DvrWriteBufferQueue* write_queue,
                                    uint32_t width, uint32_t height) {
  if (!write_queue)
    return -EINVAL;

  return write_queue->ResizeBuffer(width, height);
}

// ReadBufferQueue

DvrReadBufferQueue::DvrReadBufferQueue(
    const std::shared_ptr<ConsumerQueue>& consumer_queue)
    : consumer_queue_(consumer_queue) {}

int DvrReadBufferQueue::CreateReadQueue(DvrReadBufferQueue** out_read_queue) {
  std::unique_ptr<ConsumerQueue> consumer_queue =
      consumer_queue_->CreateConsumerQueue();
  if (consumer_queue == nullptr) {
    ALOGE(
        "DvrReadBufferQueue::CreateReadQueue: Failed to create consumer queue "
        "from producer queue: queue_id=%d.", consumer_queue_->id());
    return -ENOMEM;
  }

  *out_read_queue = new DvrReadBufferQueue(std::move(consumer_queue));
  return 0;
}

int DvrReadBufferQueue::AcquireBuffer(int timeout,
                                      DvrReadBuffer** out_read_buffer,
                                      DvrNativeBufferMetadata* out_meta,
                                      int* out_fence_fd) {
  size_t slot;
  pdx::LocalHandle acquire_fence;
  auto buffer_status =
      consumer_queue_->Dequeue(timeout, &slot, out_meta, &acquire_fence);
  if (!buffer_status) {
    ALOGE_IF(buffer_status.error() != ETIMEDOUT,
             "DvrReadBufferQueue::AcquireBuffer: Failed to dequeue buffer: %s",
             buffer_status.GetErrorMessage().c_str());
    return -buffer_status.error();
  }

  if (read_buffers_[slot] == nullptr) {
    // Lazy initialization of a read_buffers_ slot. Note that a slot will only
    // be dynamically allocated once during the entire cycle life of a queue.
    read_buffers_[slot] = std::make_unique<DvrReadBuffer>();
    read_buffers_[slot]->slot = slot;
  }

  LOG_FATAL_IF(
      read_buffers_[slot]->read_buffer,
      "DvrReadBufferQueue::AcquireBuffer: Buffer slot is not empty: %zu", slot);
  read_buffers_[slot]->read_buffer = std::move(buffer_status.take());

  *out_read_buffer = read_buffers_[slot].release();
  *out_fence_fd = acquire_fence.Release();

  return 0;
}

int DvrReadBufferQueue::ReleaseBuffer(DvrReadBuffer* read_buffer,
                                      const DvrNativeBufferMetadata* meta,
                                      int release_fence_fd) {
  LOG_FATAL_IF(
      (read_buffers->slot < 0 || read_buffers->slot >= read_buffers_size()),
      "DvrReadBufferQueue::ReleaseBuffer: Invalid slot: %zu", slot);

  // Some basic sanity checks before we put the buffer back into a slot.
  size_t slot = static_cast<size_t>(read_buffer->slot);
  if (read_buffers_[slot] != nullptr) {
    ALOGE("DvrReadBufferQueue::ReleaseBuffer: Slot is not empty: %zu", slot);
    return -EINVAL;
  }
  if (read_buffer->read_buffer == nullptr) {
    ALOGE("DvrReadBufferQueue::ReleaseBuffer: Invalid read buffer.");
    return -EINVAL;
  }
  if (read_buffer->read_buffer->id() != consumer_queue_->GetBufferId(slot)) {
    if (consumer_queue_->GetBufferId(slot) > 0) {
      ALOGE(
          "DvrReadBufferQueue::ReleaseBuffer: Buffer to be released may not "
          "belong to this queue (queue_id=%d): attempting to release buffer "
          "(buffer_id=%d) at slot %d which holds a different buffer "
          "(buffer_id=%d).",
          consumer_queue_->id(), read_buffer->read_buffer->id(),
          static_cast<int>(slot), consumer_queue_->GetBufferId(slot));
    } else {
      ALOGI(
          "DvrReadBufferQueue::ReleaseBuffer: Buffer to be released may not "
          "belong to this queue (queue_id=%d): attempting to release buffer "
          "(buffer_id=%d) at slot %d which is empty.",
          consumer_queue_->id(), read_buffer->read_buffer->id(),
          static_cast<int>(slot));
    }
  }

  pdx::LocalHandle fence(release_fence_fd);
  int ret = read_buffer->read_buffer->ReleaseAsync(meta, fence);
  if (ret < 0) {
    ALOGE("DvrReadBufferQueue::ReleaseBuffer: Failed to release buffer, ret=%d",
          ret);
    return ret;
  }

  // Put the DvrReadBuffer pointer back into its slot for reuse.
  read_buffers_[slot].reset(read_buffer);
  // It's import to reset the read buffer client now. It should stay invalid
  // until next AcquireBuffer on the same slot.
  read_buffers_[slot]->read_buffer = nullptr;
  return 0;
}

void DvrReadBufferQueue::SetBufferAvailableCallback(
    DvrReadBufferQueueBufferAvailableCallback callback, void* context) {
  if (callback == nullptr) {
    consumer_queue_->SetBufferAvailableCallback(nullptr);
  } else {
    consumer_queue_->SetBufferAvailableCallback(
        [callback, context]() { callback(context); });
  }
}

void DvrReadBufferQueue::SetBufferRemovedCallback(
    DvrReadBufferQueueBufferRemovedCallback callback, void* context) {
  if (callback == nullptr) {
    consumer_queue_->SetBufferRemovedCallback(nullptr);
  } else {
    consumer_queue_->SetBufferRemovedCallback(
        [callback, context](const std::shared_ptr<BufferHubBuffer>& buffer) {
          // When buffer is removed from the queue, the slot is already invalid.
          auto read_buffer = std::make_unique<DvrReadBuffer>();
          read_buffer->read_buffer =
              std::static_pointer_cast<BufferConsumer>(buffer);
          callback(read_buffer.release(), context);
        });
  }
}

int DvrReadBufferQueue::HandleEvents() {
  // TODO(jwcai) Probably should change HandleQueueEvents to return Status.
  consumer_queue_->HandleQueueEvents();
  return 0;
}

void dvrReadBufferQueueDestroy(DvrReadBufferQueue* read_queue) {
  delete read_queue;
}

ssize_t dvrReadBufferQueueGetCapacity(DvrReadBufferQueue* read_queue) {
  if (!read_queue)
    return -EINVAL;

  return read_queue->capacity();
}

int dvrReadBufferQueueGetId(DvrReadBufferQueue* read_queue) {
  if (!read_queue)
    return -EINVAL;

  return read_queue->id();
}

int dvrReadBufferQueueGetEventFd(DvrReadBufferQueue* read_queue) {
  if (!read_queue)
    return -EINVAL;

  return read_queue->event_fd();
}

int dvrReadBufferQueueCreateReadQueue(DvrReadBufferQueue* read_queue,
                                      DvrReadBufferQueue** out_read_queue) {
  if (!read_queue || !out_read_queue)
    return -EINVAL;

  return read_queue->CreateReadQueue(out_read_queue);
}

int dvrReadBufferQueueDequeue(DvrReadBufferQueue* read_queue, int timeout,
                              DvrReadBuffer* read_buffer, int* out_fence_fd,
                              void* out_meta, size_t meta_size_bytes) {
  if (!read_queue || !read_buffer || !out_fence_fd)
    return -EINVAL;

  if (meta_size_bytes != 0 && !out_meta)
    return -EINVAL;

  return read_queue->Dequeue(timeout, read_buffer, out_fence_fd, out_meta,
                             meta_size_bytes);
}

int dvrReadBufferQueueAcquireBuffer(DvrReadBufferQueue* read_queue, int timeout,
                                    DvrReadBuffer** out_read_buffer,
                                    DvrNativeBufferMetadata* out_meta,
                                    int* out_fence_fd) {
  if (!read_queue || !out_read_buffer || !out_meta || !out_fence_fd)
    return -EINVAL;

  return read_queue->AcquireBuffer(timeout, out_read_buffer, out_meta,
                                   out_fence_fd);
}

int dvrReadBufferQueueReleaseBuffer(DvrReadBufferQueue* read_queue,
                                    DvrReadBuffer* read_buffer,
                                    const DvrNativeBufferMetadata* meta,
                                    int release_fence_fd) {
  if (!read_queue || !read_buffer || !read_buffer->read_buffer || !meta)
    return -EINVAL;

  return read_queue->ReleaseBuffer(read_buffer, meta, release_fence_fd);
}

int dvrReadBufferQueueSetBufferAvailableCallback(
    DvrReadBufferQueue* read_queue,
    DvrReadBufferQueueBufferAvailableCallback callback, void* context) {
  if (!read_queue)
    return -EINVAL;

  read_queue->SetBufferAvailableCallback(callback, context);
  return 0;
}

int dvrReadBufferQueueSetBufferRemovedCallback(
    DvrReadBufferQueue* read_queue,
    DvrReadBufferQueueBufferRemovedCallback callback, void* context) {
  if (!read_queue)
    return -EINVAL;

  read_queue->SetBufferRemovedCallback(callback, context);
  return 0;
}

int dvrReadBufferQueueHandleEvents(DvrReadBufferQueue* read_queue) {
  if (!read_queue)
    return -EINVAL;

  return read_queue->HandleEvents();
}

}  // extern "C"
