/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <hardware/gralloc.h>
#include <hardware/hardware.h>
#include <log/log.h>
#include <stdlib.h>

#include "guest/libs/platform_support/api_level_fixes.h"

#include "guest/hals/gralloc/gralloc_vsoc_priv.h"
#include "guest/vsoc/lib/gralloc_region_view.h"

using vsoc::gralloc::GrallocRegionView;

namespace {

static const int kSwiftShaderPadding = 4;

inline void formatToYcbcr(
    int format, int width, int height, void* base_v, android_ycbcr* ycbcr) {
  uintptr_t it = reinterpret_cast<uintptr_t>(base_v);
  // Clear reserved fields;
  memset(ycbcr, 0, sizeof(*ycbcr));
  switch (format) {
    case HAL_PIXEL_FORMAT_YV12:
    case HAL_PIXEL_FORMAT_YCbCr_420_888:
      ycbcr->ystride = align(width, 16);
      ycbcr->cstride = align(ycbcr->ystride / 2, 16);
      ycbcr->chroma_step = 1;
      ycbcr->y = reinterpret_cast<void*>(it);
      it += ycbcr->ystride * height;
      ycbcr->cr = reinterpret_cast<void*>(it);
      it += ycbcr->cstride * height / 2;
      ycbcr->cb = reinterpret_cast<void*>(it);
      break;
    default:
      ALOGE("%s: can't deal with format=0x%x", __FUNCTION__, format);
  }
}

inline int formatToBytesPerPixel(int format) {
  switch (format) {
#if VSOC_PLATFORM_SDK_AFTER(N_MR1)
    case HAL_PIXEL_FORMAT_RGBA_FP16:
      return 8;
#endif
    case HAL_PIXEL_FORMAT_RGBA_8888:
    case HAL_PIXEL_FORMAT_RGBX_8888:
    case HAL_PIXEL_FORMAT_BGRA_8888:
    // The camera 3.0 implementation assumes that IMPLEMENTATION_DEFINED
    // means HAL_PIXEL_FORMAT_RGBA_8888
    case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
      return 4;
    case HAL_PIXEL_FORMAT_RGB_888:
      return 3;
    case HAL_PIXEL_FORMAT_RGB_565:
    case HAL_PIXEL_FORMAT_YV12:
    case HAL_PIXEL_FORMAT_YCbCr_420_888:
      return 2;
    case HAL_PIXEL_FORMAT_BLOB:
      return 1;
    default:
      ALOGE("%s: unknown format=%d", __FUNCTION__, format);
      return 8;
  }
}

inline int formatToBytesPerFrame(int format, int w, int h) {
  int bytes_per_pixel = formatToBytesPerPixel(format);
  int w16, h16;
  int y_size, c_size;

  switch (format) {
    // BLOB is used to allocate buffers for JPEG formatted data. Bytes per pixel
    // is 1, the desired buffer size is in w, and h should be 1. We refrain from
    // adding additional padding, although the caller is likely to round
    // up to a page size.
    case HAL_PIXEL_FORMAT_BLOB:
      return bytes_per_pixel * w * h;
    case HAL_PIXEL_FORMAT_YV12:
    case HAL_PIXEL_FORMAT_YCbCr_420_888:
      android_ycbcr strides;
      formatToYcbcr(format, w, h, NULL, &strides);
      y_size = strides.ystride * h;
      c_size = strides.cstride * h / 2;
      return (y_size + 2 * c_size + kSwiftShaderPadding);
    /*case HAL_PIXEL_FORMAT_RGBA_8888:
    case HAL_PIXEL_FORMAT_RGBX_8888:
    case HAL_PIXEL_FORMAT_BGRA_8888:
    case HAL_PIXEL_FORMAT_RGB_888:
    case HAL_PIXEL_FORMAT_RGB_565:*/
    default:
      w16 = align(w, 16);
      h16 = align(h, 16);
      return bytes_per_pixel * w16 * h16 + kSwiftShaderPadding;
  }
}

}

/******************************************************************************/

void dump(struct alloc_device_t */*dev*/, char */*buff*/, int /*buff_len*/) {}

/******************************************************************************/

int lock(struct gralloc_module_t const* /*module*/,
         buffer_handle_t handle,
         int /*usage*/,
         int /*l*/,
         int /*t*/,
         int /*w*/,
         int /*h*/,
         void** vaddr) {
  if (!vaddr || vsoc_buffer_handle_t::validate(handle)) {
    return -EINVAL;
  }
  // TODO(jemoreira): Check allocation usage flags against requested usage.
  const vsoc_buffer_handle_t* hnd =
      reinterpret_cast<const vsoc_buffer_handle_t*>(handle);
  void* mapped = reference_buffer(hnd);
  if (mapped == NULL) {
    ALOGE("Unable to reference buffer, %s", __FUNCTION__);
    return -1;
  }
  *vaddr = mapped;
  return 0;
}

int unlock(struct gralloc_module_t const* /*module*/, buffer_handle_t handle) {
  if (vsoc_buffer_handle_t::validate(handle)) {
    return -EINVAL;
  }
  return unreference_buffer(
      reinterpret_cast<const vsoc_buffer_handle_t*>(handle));
}

int lock_ycbcr(struct gralloc_module_t const* module,
               buffer_handle_t handle,
               int usage,
               int l,
               int t,
               int w,
               int h,
               struct android_ycbcr* ycbcr) {
  void* mapped;
  int retval = lock(module, handle, usage, l, t, w, h, &mapped);
  if (retval) {
    return retval;
  }
  const vsoc_buffer_handle_t* hnd =
      reinterpret_cast<const vsoc_buffer_handle_t*>(handle);
  formatToYcbcr(hnd->format, w, h, mapped, ycbcr);
  return 0;
}

/******************************************************************************/

static int gralloc_alloc(alloc_device_t* /*dev*/,
                         int w,
                         int h,
                         int format,
                         int /*usage*/,
                         buffer_handle_t* pHandle,
                         int* pStrideInPixels) {
  int fd = -1;

  int bytes_per_pixel = formatToBytesPerPixel(format);
  int bytes_per_line;
  int stride_in_pixels;
  int size = 0;
  uint32_t offset = 0;
  // SwiftShader can't handle RGB_888, so fail fast and hard if we try to create
  // a gralloc buffer in this format.
  ALOG_ASSERT(format != HAL_PIXEL_FORMAT_RGB_888);
  if (format == HAL_PIXEL_FORMAT_YV12) {
    bytes_per_line = align(bytes_per_pixel * w, 16);
  } else {
    bytes_per_line = align(bytes_per_pixel * w, 8);
  }
  size = align(size + formatToBytesPerFrame(format, w, h), PAGE_SIZE);
  size += PAGE_SIZE;
  fd = GrallocRegionView::GetInstance()->AllocateBuffer(size, &offset);
  if (fd < 0) {
    ALOGE("Unable to allocate buffer (%s)", strerror(-fd));
    return fd;
  }

  stride_in_pixels = bytes_per_line / bytes_per_pixel;
  vsoc_buffer_handle_t* hnd = new vsoc_buffer_handle_t(fd,
                                                       offset,
                                                       size,
                                                       format,
                                                       w, h,
                                                       stride_in_pixels);
  void* addr =
      reference_buffer(reinterpret_cast<const vsoc_buffer_handle_t*>(hnd));
  if (!addr) {
    ALOGE("Unable to reference buffer, %s", __FUNCTION__);
    return -EIO;
  }

  *pHandle = hnd;
  *pStrideInPixels = stride_in_pixels;

  return 0;
}

static int gralloc_free(alloc_device_t* /*dev*/, buffer_handle_t handle) {
  // No need to do anything else, the buffer will be atomatically deallocated
  // when the handle is closed.
  return unreference_buffer(
      reinterpret_cast<const vsoc_buffer_handle_t*>(handle));
}

static int register_buffer(struct gralloc_module_t const* /*module*/,
                          buffer_handle_t handle) {
  if (vsoc_buffer_handle_t::validate(handle)) {
    return -EINVAL;
  }
  void* addr =
      reference_buffer(reinterpret_cast<const vsoc_buffer_handle_t*>(handle));
  if (!addr) {
    ALOGE("Unable to reference buffer, %s", __FUNCTION__);
    return -EIO;
  }
  return 0;
}

int unregister_buffer(struct gralloc_module_t const* /*module*/,
                     buffer_handle_t handle) {
  if (vsoc_buffer_handle_t::validate(handle)) {
    return -EINVAL;
  }
  return unreference_buffer(
      reinterpret_cast<const vsoc_buffer_handle_t*>(handle));
}

/******************************************************************************/

static int gralloc_device_close(struct hw_device_t *dev) {
  vsoc_alloc_device_t* pdev = reinterpret_cast<vsoc_alloc_device_t*>(dev);
  if (pdev) {
    free(pdev);
  }
  return 0;
}

static int gralloc_device_open(
    const hw_module_t* module, const char* name, hw_device_t** device) {
    int status = -EINVAL;
  if (!strcmp(name, GRALLOC_HARDWARE_GPU0)) {
    vsoc_alloc_device_t *dev;
    dev = (vsoc_alloc_device_t*) malloc(sizeof(*dev));
    LOG_FATAL_IF(!dev, "%s: malloc returned NULL.", __FUNCTION__);

    /* initialize our state here */
    memset(dev, 0, sizeof(*dev));

    /* initialize the procs */
    dev->device.common.tag = HARDWARE_DEVICE_TAG;
    dev->device.common.version = 0; // TODO(jemoreira): Bump to 0_2 when stable
    dev->device.common.module = const_cast<hw_module_t*>(module);
    dev->device.common.close = gralloc_device_close;

    dev->device.alloc   = gralloc_alloc;
    dev->device.free    = gralloc_free;

    if (!GrallocRegionView::GetInstance()) {
      LOG_FATAL("Unable to instantiate the gralloc region");
      free(dev);
      return -EIO;
    }

    *device = &dev->device.common;
    status = 0;
  }
  // TODO(jemoreira): Consider opening other type of devices (framebuffer)
  return status;
}

/******************************************************************************/

static struct hw_module_methods_t gralloc_module_methods = {
  .open = gralloc_device_open
};

struct vsoc_gralloc_module_t HAL_MODULE_INFO_SYM = {
  .base = {
    .common = {
      .tag = HARDWARE_MODULE_TAG,
      .version_major = GRALLOC_MODULE_API_VERSION_0_2,
      .version_minor = 0,
      .id = GRALLOC_HARDWARE_MODULE_ID,
      .name = "VSoC X86 Graphics Memory Allocator Module",
      .author = "The Android Open Source Project",
      .methods = &gralloc_module_methods,
      .dso = NULL,
      .reserved = {0},
    },
    .registerBuffer = register_buffer,
    .unregisterBuffer = unregister_buffer,
    .lock = lock,
    .unlock = unlock,
    .lock_ycbcr = lock_ycbcr,
    .perform = NULL,
  },
};