/*
 * Copyright (C) 2016 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 <limits.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>

#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>

#include <cutils/ashmem.h>
#include <cutils/log.h>
#include <cutils/atomic.h>
#include <utils/String8.h>

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

#include <guest/libs/platform_support/api_level_fixes.h>

#include "common/libs/auto_resources/auto_resources.h"
#include "common/vsoc/lib/screen_region_view.h"
#include "gralloc_vsoc_priv.h"
#include "region_registry.h"

using vsoc::screen::ScreenRegionView;

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

static inline size_t roundUpToPageSize(size_t x) {
  return (x + (PAGE_SIZE-1)) & ~(PAGE_SIZE-1);
}

static int gralloc_alloc_buffer(
    alloc_device_t* /*dev*/, int format, int w, int h,
    buffer_handle_t* pHandle, int* pStrideInPixels) {
  int err = 0;
  int fd = -1;
  static int sequence = 0;

  int bytes_per_pixel = formatToBytesPerPixel(format);
  int bytes_per_line;
  int stride_in_pixels;
  int size = 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 = ScreenRegionView::align(bytes_per_pixel * w, 16);
  } else {
    bytes_per_line = ScreenRegionView::align(bytes_per_pixel * w);
  }
  size = roundUpToPageSize(size + formatToBytesPerFrame(format, w, h));
  size += PAGE_SIZE;
  fd = ashmem_create_region(
      android::String8::format(
          "gralloc-%d.%d", getpid(), sequence++).string(),
      size);
  if (fd < 0) {
    ALOGE("couldn't create ashmem (%s)", strerror(-errno));
    err = -errno;
  }

  if (err == 0) {
    stride_in_pixels = bytes_per_line / bytes_per_pixel;
    private_handle_t* hnd =
        new private_handle_t(fd, size, format, w, h, stride_in_pixels, 0);
    void* base = reference_region(__FUNCTION__, hnd);
    if (base) {
      *pHandle = hnd;
      *pStrideInPixels = stride_in_pixels;
    } else {
      err = -EIO;
    }
  }

  ALOGE_IF(err, "gralloc failed err=%s", strerror(-err));

  return err;
}

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

static int gralloc_alloc(alloc_device_t* dev, int w, int h, int format,
                         int /*usage*/, buffer_handle_t* pHandle,
                         int* pStrideInPixels) {
  if (!pHandle || !pStrideInPixels)
    return -EINVAL;

  int err = gralloc_alloc_buffer(dev, format, w, h, pHandle, pStrideInPixels);

  if (err < 0) {
    return err;
  }
  return 0;
}

static int gralloc_free(alloc_device_t* /*dev*/, buffer_handle_t handle) {
  if (private_handle_t::validate(handle) < 0) {
    return -EINVAL;
  }

  private_handle_t const* hnd = reinterpret_cast<private_handle_t const*>(
    handle);
  int retval = unreference_region(__FUNCTION__, hnd);

  close(hnd->fd);
  delete hnd;
  return retval;
}

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

static int gralloc_close(struct hw_device_t *dev) {
  priv_alloc_device_t* ctx = reinterpret_cast<priv_alloc_device_t*>(dev);
  if (ctx) {
    /* TODO: keep a list of all buffer_handle_t created, and free them
     * all here.
     */
    free(ctx);
  }
  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)) {
    priv_alloc_device_t *dev;
    dev = (priv_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;
    dev->device.common.module = const_cast<hw_module_t*>(module);
    dev->device.common.close = gralloc_close;

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

    *device = &dev->device.common;
    status = 0;
  } else {
    status = fb_device_open(module, name, device);
  }
  return status;
}

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

static struct hw_module_methods_t gralloc_module_methods = {
  VSOC_STATIC_INITIALIZER(open) gralloc_device_open
};

struct private_module_t HAL_MODULE_INFO_SYM = {
  VSOC_STATIC_INITIALIZER(base) {
    VSOC_STATIC_INITIALIZER(common) {
      VSOC_STATIC_INITIALIZER(tag) HARDWARE_MODULE_TAG,
#ifdef GRALLOC_MODULE_API_VERSION_0_2
      VSOC_STATIC_INITIALIZER(version_major) GRALLOC_MODULE_API_VERSION_0_2,
#else
      VSOC_STATIC_INITIALIZER(version_major) 1,
#endif
      VSOC_STATIC_INITIALIZER(version_minor) 0,
      VSOC_STATIC_INITIALIZER(id) GRALLOC_HARDWARE_MODULE_ID,
      VSOC_STATIC_INITIALIZER(name) "VSOC X86 Graphics Memory Allocator Module",
      VSOC_STATIC_INITIALIZER(author) "The Android Open Source Project",
      VSOC_STATIC_INITIALIZER(methods) &gralloc_module_methods,
      VSOC_STATIC_INITIALIZER(dso) NULL,
      VSOC_STATIC_INITIALIZER(reserved) {0},
    },
    VSOC_STATIC_INITIALIZER(registerBuffer) gralloc_register_buffer,
    VSOC_STATIC_INITIALIZER(unregisterBuffer) gralloc_unregister_buffer,
    VSOC_STATIC_INITIALIZER(lock) gralloc_lock,
    VSOC_STATIC_INITIALIZER(unlock) gralloc_unlock,
#ifdef GRALLOC_MODULE_API_VERSION_0_2
    VSOC_STATIC_INITIALIZER(perform) NULL,
    VSOC_STATIC_INITIALIZER(lock_ycbcr) gralloc_lock_ycbcr,
#endif
  },
};