/* Copyright 2017-present Renesas Electronics Corporation and other contributors
 *
 * 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 "iotjs_def.h"

#if defined(USE_POSIX_BRIDGE)
#include "peripheral_io.h"
#include "iotjs_module_aligned_buffer.h"
#include "iotjs_module_jpeg.h"
#include <malloc.h>

IOTJS_DEFINE_NATIVE_HANDLE_INFO_THIS_MODULE(jpeg);

struct iotjs_jpeg_platform_data_s {
  int dummy;
};

jerry_value_t iotjs_jpeg_set_platform_config(iotjs_jpeg_t* jpeg,
                                             const jerry_value_t jconfig) {
  return jerry_create_undefined();
}

void iotjs_jpeg_create_platform_data(iotjs_jpeg_t* jpeg) {
  jpeg->platform_data = IOTJS_ALLOC(iotjs_jpeg_platform_data_t);
}

void iotjs_jpeg_destroy_platform_data(
    iotjs_jpeg_platform_data_t* platform_data) {
  IOTJS_RELEASE(platform_data);
}

IOTJS_DEFINE_PERIPH_CREATE_FUNCTION(jpeg);

static uint32_t get_pixel_bytes(jpeg_pixel_format_t format) {
	switch(format) {
	case JPEG_PIXELFORMAT_YCBCR422:
	case JPEG_PIXELFORMAT_RGB565:
		return 2;
	case JPEG_PIXELFORMAT_ARGB8888:
		return 4;
	default:
		return 0;
	}
}

bool iotjs_jpeg_encode(iotjs_jpeg_t* jpeg) {
  uint32_t width = (jpeg->encode_data.width + 31)&~31;
  uint32_t height = (jpeg->encode_data.height + 15)&~15;
  uint32_t encode_len = width * height * get_pixel_bytes(jpeg->encode_data.pixel_format);
  jpeg->encode_data.dst.buf = memalign(32, encode_len);
  if(jpeg->encode_data.dst.buf == NULL) {
    return false;
  }
  jpeg->encode_data.src.len = encode_len;
  jpeg->encode_data.dst.len = encode_len;

  int ret = jpeg_encode(jpeg->encode_data);
  if(ret < 0) {
    IOTJS_RELEASE(jpeg->encode_data.dst.buf);
    return false;
  }

  jpeg->encode_data.dst.len = ret;

  return true;
}

bool iotjs_jpeg_decode(iotjs_jpeg_t* jpeg) {
  uint32_t width = (jpeg->decode_data.width + 31)&~31;
  uint32_t height = (jpeg->decode_data.height + 15)&~15;
  uint32_t decode_len = width * height * get_pixel_bytes(jpeg->decode_data.pixel_format);
  jpeg->decode_data.dst.buf = memalign(32, decode_len);
  if(jpeg->decode_data.dst.buf == NULL) {
    return false;
  }
  memset(jpeg->decode_data.dst.buf, 0, decode_len);
  jpeg->decode_data.dst.len = decode_len;

  bool ret = jpeg_decode(jpeg->decode_data);
  if(!ret) {
    IOTJS_RELEASE(jpeg->decode_data.dst.buf);
    return false;
  }

  return true;
}

static void iotjs_jpeg_destroy(iotjs_jpeg_t* jpeg) {
  iotjs_jpeg_destroy_platform_data(jpeg->platform_data);
  IOTJS_RELEASE(jpeg);
}

static void jpeg_worker(uv_work_t* work_req) {
  iotjs_periph_reqwrap_t* req_wrap =
      (iotjs_periph_reqwrap_t*)(iotjs_reqwrap_from_request(
          (uv_req_t*)work_req));
  iotjs_jpeg_t* jpeg = (iotjs_jpeg_t*)req_wrap->data;

  switch (req_wrap->op) {
    case kJpegOpEncode:
      req_wrap->result = iotjs_jpeg_encode(jpeg);
      break;
    case kJpegOpDecode:
      req_wrap->result = iotjs_jpeg_decode(jpeg);
      break;
    default:
      IOTJS_ASSERT(!"Invalid Operation");
  }
}

static jerry_value_t jpeg_set_configuration(iotjs_jpeg_t* jpeg,
                                            jerry_value_t jconfig) {
  return jerry_create_undefined();
}


static jerry_value_t jpeg_get_encode_param(iotjs_jpeg_t* jpeg,
                                            const jerry_value_t jargv[],
                                            const jerry_length_t jargc) {
  if(jargc < 1) {
    return JS_CREATE_ERROR(TYPE, "Invalid Arguments");
  }

  jerry_value_t jimage = JS_GET_ARG(0, object);
  jerry_value_t jwidth = iotjs_jval_get_property(jimage, "width");
  if (jerry_value_is_number(jwidth)) {
    jpeg->encode_data.width = iotjs_jval_as_number(jwidth);
  } else {
    jerry_release_value(jwidth);
    return JS_CREATE_ERROR(
        TYPE, "Invalid image.width");
  }
  jerry_release_value(jwidth);

  jerry_value_t jheight = iotjs_jval_get_property(jimage, "height");
  if (jerry_value_is_number(jheight)) {
    jpeg->encode_data.height = iotjs_jval_as_number(jheight);
  } else {
    jerry_release_value(jheight);
    return JS_CREATE_ERROR(
        TYPE, "Invalid image.height");
  }
  jerry_release_value(jheight);

  jerry_value_t jformat = iotjs_jval_get_property(jimage, "format");
  if (jerry_value_is_number(jformat)) {
    jpeg->encode_data.pixel_format = iotjs_jval_as_number(jformat);
  } else {
    jpeg->encode_data.pixel_format = JPEG_PIXELFORMAT_MAX;
  }
  jerry_release_value(jformat);
  if(jpeg->encode_data.pixel_format != JPEG_PIXELFORMAT_YCBCR422) {
    return JS_CREATE_ERROR(TYPE, "Invalid image.format");
  }

  jerry_value_t jbitmap = iotjs_jval_get_property(jimage, "bitmap");
  iotjs_aligned_buffer_wrap_t* buffer_wrap = iotjs_aligned_buffer_wrap_from_jbuffer(jbitmap);
  jpeg->encode_data.src.buf = (uint8_t*)iotjs_aligned_buffer_wrap_native_buffer_ptr(buffer_wrap);
  jerry_release_value(jbitmap);
  if(((buffer_wrap->alignment % 32) != 0) || (((int)jpeg->encode_data.src.buf % 32) != 0)) {
    return JS_CREATE_ERROR(TYPE, "Invalid image.bitmap alignment");
  }

  return jerry_create_undefined();
}


static jerry_value_t jpeg_get_decode_param(iotjs_jpeg_t* jpeg,
                                            const jerry_value_t jargv[],
                                            const jerry_length_t jargc) {
  if(jargc < 2) {
    return JS_CREATE_ERROR(TYPE, "Invalid Arguments");
  }

  const jerry_value_t jjpeg_data = JS_GET_ARG(0, object);
  iotjs_aligned_buffer_wrap_t* buffer_wrap = iotjs_aligned_buffer_wrap_from_jbuffer(jjpeg_data);
  jpeg->decode_data.src.buf = iotjs_aligned_buffer_wrap_native_buffer_ptr(buffer_wrap);
  jpeg->decode_data.src.len = buffer_wrap->length;
  if(((buffer_wrap->alignment % 32) != 0) || (((int)jpeg->decode_data.src.buf % 32) != 0)) {
    return JS_CREATE_ERROR(TYPE, "Invalid jpegData alignment");
  }

  const jerry_value_t jconfig = JS_GET_ARG(1, object);

  jerry_value_t jwidth = iotjs_jval_get_property(jconfig, "width");
  if (jerry_value_is_number(jwidth)) {
    jpeg->decode_data.width = iotjs_jval_as_number(jwidth);
  } else {
    jerry_release_value(jwidth);
    return JS_CREATE_ERROR(
        TYPE, "Invalid options.width");
  }
  jerry_release_value(jwidth);

  jerry_value_t jheight = iotjs_jval_get_property(jconfig, "height");
  if (jerry_value_is_number(jheight)) {
    jpeg->decode_data.height = iotjs_jval_as_number(jheight);
  } else {
    jerry_release_value(jheight);
    return JS_CREATE_ERROR(
        TYPE, "Invalid options.height");
  }
  jerry_release_value(jheight);

  jerry_value_t jformat = iotjs_jval_get_property(jconfig, "format");
  if (jerry_value_is_number(jformat)) {
    jpeg->decode_data.pixel_format = iotjs_jval_as_number(jformat);
  } else {
    jpeg->decode_data.pixel_format = JPEG_PIXELFORMAT_MAX;
  }
  jerry_release_value(jformat);
  if(jpeg->decode_data.pixel_format >= JPEG_PIXELFORMAT_MAX) {
    return JS_CREATE_ERROR(TYPE, "Invalid options.format");
  }

  if(jpeg->decode_data.pixel_format == JPEG_PIXELFORMAT_ARGB8888) {
    jerry_value_t jalpha = iotjs_jval_get_property(jconfig, "alpha");
    if (jerry_value_is_undefined(jalpha)) {
      jpeg->decode_data.alpha = 0xFF;
    } else {
      if (jerry_value_is_number(jalpha)) {
        jpeg->decode_data.alpha = iotjs_jval_as_number(jalpha);
      } else {
        jerry_release_value(jalpha);
        return JS_CREATE_ERROR(TYPE, "Invalid options.alpha");
      }
    }
    jerry_release_value(jalpha);
  } else {
    jpeg->decode_data.alpha = 0;
  }

  return jerry_create_undefined();
}

JS_FUNCTION(JpegCons) {
  DJS_CHECK_THIS();

  // Create Jpeg object
  jerry_value_t jjpeg = JS_GET_THIS();
  iotjs_jpeg_t* jpeg = jpeg_create(jjpeg);

  jerry_value_t jconfig = JS_GET_ARG(0, object);

  // set configuration
  jerry_value_t res = iotjs_jpeg_set_platform_config(jpeg, jconfig);
  if (jerry_value_is_error(res)) {
    return res;
  }

  res = jpeg_set_configuration(jpeg, jconfig);
  if (jerry_value_is_error(res)) {
    return res;
  }

  return jerry_create_undefined();
}


JS_FUNCTION(Encode) {
  JS_DECLARE_THIS_PTR(jpeg, jpeg);
  DJS_CHECK_ARGS(1, object);

  jerry_value_t jerr = jpeg_get_encode_param(jpeg, jargv, jargc);
  if(jerry_value_is_error(jerr)) {
    return jerr;
  }
  iotjs_ext_periph_call_async(jpeg, JS_GET_ARG_IF_EXIST(1, function), kJpegOpEncode,
                          jpeg_worker);
  return jerry_create_undefined();
}


JS_FUNCTION(EncodeSync) {
  JS_DECLARE_THIS_PTR(jpeg, jpeg);
  DJS_CHECK_ARGS(1, object);

  jerry_value_t jerr = jpeg_get_encode_param(jpeg, jargv, jargc);
  if(jerry_value_is_error(jerr)) {
    return jerr;
  }

  if(!iotjs_jpeg_encode(jpeg)) {
    return JS_CREATE_ERROR(COMMON, "Encode error, cannot encode Jpeg");
  }

  jerry_value_t jbuf = iotjs_aligned_buffer_wrap_create_buffer((size_t)jpeg->encode_data.dst.len, 32);
  if (jerry_value_is_error(jbuf)) {
    IOTJS_RELEASE(jpeg->encode_data.dst.buf);
    return jbuf;
  }

  iotjs_aligned_buffer_wrap_t* buf_wrap = iotjs_aligned_buffer_wrap_from_jbuffer(jbuf);
  iotjs_aligned_buffer_wrap_copy(buf_wrap, jpeg->encode_data.dst.buf, jpeg->encode_data.dst.len);
  IOTJS_RELEASE(jpeg->encode_data.dst.buf);

  return jbuf;
}


JS_FUNCTION(Decode) {
  JS_DECLARE_THIS_PTR(jpeg, jpeg);
  DJS_CHECK_ARGS(2, object, object);

  jerry_value_t res = jpeg_get_decode_param(jpeg, jargv, jargc);
  if (jerry_value_is_error(res)) {
    return res;
  }

  iotjs_ext_periph_call_async(jpeg, JS_GET_ARG_IF_EXIST(2, function), kJpegOpDecode,
                          jpeg_worker);
  return jerry_create_undefined();
}


JS_FUNCTION(DecodeSync) {
  JS_DECLARE_THIS_PTR(jpeg, jpeg);
  DJS_CHECK_ARGS(2, object, object);

  jerry_value_t res = jpeg_get_decode_param(jpeg, jargv, jargc);
  if (jerry_value_is_error(res)) {
    return res;
  }

  if(!iotjs_jpeg_decode(jpeg)) {
    return JS_CREATE_ERROR(COMMON, "Decode error, cannot decode Jpeg");
  }

  jerry_value_t jbuf = iotjs_aligned_buffer_wrap_create_buffer((size_t)jpeg->decode_data.dst.len, 32);
  if (jerry_value_is_error(jbuf)) {
    IOTJS_RELEASE(jpeg->decode_data.dst.buf);
    return jbuf;
  }

  iotjs_aligned_buffer_wrap_t* buf_wrap = iotjs_aligned_buffer_wrap_from_jbuffer(jbuf);
  iotjs_aligned_buffer_wrap_copy(buf_wrap, jpeg->decode_data.dst.buf, jpeg->decode_data.dst.len);
  IOTJS_RELEASE(jpeg->decode_data.dst.buf);

  return jbuf;
}

#endif	// #if defined(USE_POSIX_BRIDGE)


jerry_value_t InitJpeg() {

#if defined(USE_POSIX_BRIDGE)
  jerry_value_t jjpeg_cons = jerry_create_external_function(JpegCons);

  jerry_value_t prototype = jerry_create_object();
  iotjs_jval_set_method(prototype, "encode", Encode);
  iotjs_jval_set_method(prototype, "encodeSync", EncodeSync);
  iotjs_jval_set_method(prototype, "decode", Decode);
  iotjs_jval_set_method(prototype, "decodeSync", DecodeSync);

  // JPEG Pixel Format
  jerry_value_t jformat = jerry_create_object();
  iotjs_jval_set_property_number(jformat, "YCBCR422", JPEG_PIXELFORMAT_YCBCR422);
  iotjs_jval_set_property_number(jformat, "ARGB8888", JPEG_PIXELFORMAT_ARGB8888);
  iotjs_jval_set_property_number(jformat, "RGB565", JPEG_PIXELFORMAT_RGB565);
  iotjs_jval_set_property_number(jformat, "MAX", JPEG_PIXELFORMAT_MAX);
  iotjs_jval_set_property_jval(prototype, "PIXELFORMAT", jformat);
  jerry_release_value(jformat);

  iotjs_jval_set_property_jval(jjpeg_cons, IOTJS_MAGIC_STRING_PROTOTYPE,
                               prototype);
  jerry_release_value(prototype);

#else	// #if defined(USE_POSIX_BRIDGE)
  jerry_value_t jjpeg_cons = jerry_create_object();
#endif	// #if defined(USE_POSIX_BRIDGE)

  return jjpeg_cons;
}
