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

#include <android/native_window.h>
#include <gui/Surface.h>
#include <private/dvr/buffer_hub_queue_client.h>
#include <private/dvr/buffer_hub_queue_producer.h>

#include "dvr_internal.h"

#define CHECK_PARAM(param)                                               \
  LOG_ALWAYS_FATAL_IF(param == nullptr, "%s: " #param "cannot be NULL.", \
                      __FUNCTION__)

using namespace android;

namespace android {
namespace dvr {

DvrWriteBufferQueue* CreateDvrWriteBufferQueueFromProducerQueue(
    const std::shared_ptr<dvr::ProducerQueue>& producer_queue) {
  return new DvrWriteBufferQueue{std::move(producer_queue)};
}

DvrReadBufferQueue* CreateDvrReadBufferQueueFromConsumerQueue(
    const std::shared_ptr<dvr::ConsumerQueue>& consumer_queue) {
  return new DvrReadBufferQueue{std::move(consumer_queue)};
}

dvr::ProducerQueue* GetProducerQueueFromDvrWriteBufferQueue(
    DvrWriteBufferQueue* write_queue) {
  return write_queue->producer_queue.get();
}

}  // namespace dvr
}  // namespace android

extern "C" {

void dvrWriteBufferQueueDestroy(DvrWriteBufferQueue* write_queue) {
  if (write_queue != nullptr && write_queue->native_window != nullptr)
    ANativeWindow_release(write_queue->native_window);

  delete write_queue;
}

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

  return write_queue->producer_queue->capacity();
}

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

  return write_queue->producer_queue->id();
}

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

  if (write_queue->producer_queue->metadata_size() !=
      sizeof(DvrNativeBufferMetadata)) {
    ALOGE(
        "The size of buffer metadata (%zu) of the write queue does not match "
        "of size of DvrNativeBufferMetadata (%zu).",
        write_queue->producer_queue->metadata_size(),
        sizeof(DvrNativeBufferMetadata));
    return -EINVAL;
  }

  // Lazy creation of |native_window|.
  if (write_queue->native_window == nullptr) {
    std::shared_ptr<dvr::BufferHubQueueCore> core =
        dvr::BufferHubQueueCore::Create(write_queue->producer_queue);
    if (core == nullptr) {
      ALOGE(
          "dvrWriteBufferQueueGetExternalSurface: Failed to create native "
          "window.");
      return -ENOMEM;
    }

    sp<IGraphicBufferProducer> gbp = new dvr::BufferHubQueueProducer(core);
    sp<Surface> surface = new Surface(gbp, true);
    write_queue->native_window = static_cast<ANativeWindow*>(surface.get());
    ANativeWindow_acquire(write_queue->native_window);
  }

  *out_window = write_queue->native_window;
  return 0;
}

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

  auto read_queue = std::make_unique<DvrReadBufferQueue>();
  read_queue->consumer_queue =
      write_queue->producer_queue->CreateConsumerQueue();
  if (read_queue->consumer_queue == nullptr) {
    ALOGE(
        "dvrWriteBufferQueueCreateReadQueue: Failed to create consumer queue "
        "from DvrWriteBufferQueue[%p].",
        write_queue);
    return -ENOMEM;
  }

  *out_read_queue = read_queue.release();
  return 0;
}

int dvrWriteBufferQueueDequeue(DvrWriteBufferQueue* write_queue, int timeout,
                               DvrWriteBuffer* write_buffer,
                               int* out_fence_fd) {
  if (!write_queue || !write_queue->producer_queue || !write_buffer ||
      !out_fence_fd) {
    return -EINVAL;
  }

  size_t slot;
  pdx::LocalHandle release_fence;
  auto buffer_status =
      write_queue->producer_queue->Dequeue(timeout, &slot, &release_fence);
  if (!buffer_status) {
    ALOGE_IF(buffer_status.error() != ETIMEDOUT,
             "dvrWriteBufferQueueDequeue: Failed to dequeue buffer: %s",
             buffer_status.GetErrorMessage().c_str());
    return -buffer_status.error();
  }

  write_buffer->write_buffer = buffer_status.take();
  *out_fence_fd = release_fence.Release();
  return 0;
}

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

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

  return read_queue->consumer_queue->capacity();
}

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

  return read_queue->consumer_queue->id();
}

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

  auto new_read_queue = std::make_unique<DvrReadBufferQueue>();
  new_read_queue->consumer_queue =
      read_queue->consumer_queue->CreateConsumerQueue();
  if (new_read_queue->consumer_queue == nullptr) {
    ALOGE(
        "dvrReadBufferQueueCreateReadQueue: Failed to create consumer queue "
        "from DvrReadBufferQueue[%p].",
        read_queue);
    return -ENOMEM;
  }

  *out_read_queue = new_read_queue.release();
  return 0;
}

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_queue->consumer_queue || !read_buffer ||
      !out_fence_fd || !out_meta) {
    return -EINVAL;
  }

  if (meta_size_bytes != read_queue->consumer_queue->metadata_size()) {
    ALOGE(
        "dvrReadBufferQueueDequeue: Invalid metadata size, expected (%zu), "
        "but actual (%zu).",
        read_queue->consumer_queue->metadata_size(), meta_size_bytes);
    return -EINVAL;
  }

  size_t slot;
  pdx::LocalHandle acquire_fence;
  auto buffer_status = read_queue->consumer_queue->Dequeue(
      timeout, &slot, out_meta, meta_size_bytes, &acquire_fence);
  if (!buffer_status) {
    ALOGE_IF(buffer_status.error() != ETIMEDOUT,
             "dvrReadBufferQueueDequeue: Failed to dequeue buffer: %s",
             buffer_status.GetErrorMessage().c_str());
    return -buffer_status.error();
  }

  read_buffer->read_buffer = buffer_status.take();
  *out_fence_fd = acquire_fence.Release();
  return 0;
}

}  // extern "C"
