/* 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_video.h"

IOTJS_DEFINE_NATIVE_HANDLE_INFO_THIS_MODULE(video);

struct iotjs_video_platform_data_s {
  video_h handle;
};

jerry_value_t iotjs_video_set_platform_config(iotjs_video_t* video,
                                             const jerry_value_t jconfig) {
  return jerry_create_undefined();
}

void iotjs_video_create_platform_data(iotjs_video_t* video) {
  video->platform_data = IOTJS_ALLOC(iotjs_video_platform_data_t);
}

void iotjs_video_destroy_platform_data(
    iotjs_video_platform_data_t* platform_data) {
  IOTJS_RELEASE(platform_data);
}

IOTJS_DEFINE_PERIPH_CREATE_FUNCTION(video);

static uint32_t get_pixel_bytes(video_pixel_format_t format) {
	switch(format) {
	case VIDEO_PIXELFORMAT_YCBCR422:
	case VIDEO_PIXELFORMAT_RGB565:
		return 2;
	case VIDEO_PIXELFORMAT_RGB888:
		return 4;
	default:
		return 0;
	}
}

bool iotjs_video_open(iotjs_video_t* video) {

  video_h handle = video_open(video->video_source);
  if( !handle ) {
    return false;
  }
  video->platform_data->handle = handle;

  jerry_value_t jvideo = video->jobject;
  iotjs_jval_set_property_number(jvideo, "width", video->video_source.width);
  iotjs_jval_set_property_number(jvideo, "height", video->video_source.height);
  iotjs_jval_set_property_number(jvideo, "pixelBytes", video->video_source.pixel_bytes);

  return true;
}

bool iotjs_video_start(iotjs_video_t* video) {
  return video_start(video->platform_data->handle, video->video_source.frame_buffer);
}

bool iotjs_video_stop(iotjs_video_t* video) {
  return video_stop(video->platform_data->handle);
}

bool iotjs_video_read_frame(iotjs_video_t* video) {
  return true;
}

bool iotjs_video_close(iotjs_video_t* video) {
  bool ret = video_close(video->platform_data->handle);
  video->platform_data->handle = 0;
  return ret;
}

static void iotjs_video_destroy(iotjs_video_t* video) {
  iotjs_video_destroy_platform_data(video->platform_data);
  IOTJS_RELEASE(video);
}

static void video_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_video_t* video = (iotjs_video_t*)req_wrap->data;

  switch (req_wrap->op) {
    case kVideoOpOpen:
      req_wrap->result = iotjs_video_open(video);
      break;
    case kVideoOpStart:
      req_wrap->result = iotjs_video_start(video);
      break;
    case kVideoOpStop:
      req_wrap->result = iotjs_video_stop(video);
      break;
    case kVideoOpReadFrame:
      req_wrap->result = iotjs_video_read_frame(video);
      break;
    case kVideoOpClose:
      req_wrap->result = iotjs_video_close(video);
      break;
    default:
      IOTJS_ASSERT(!"Invalid Operation");
  }
}

static jerry_value_t video_set_configuration(iotjs_video_t* video,
                                            jerry_value_t jconfig) {
  video_source_t* video_source = &video->video_source;
  jerry_value_t jwidth = iotjs_jval_get_property(jconfig, "width");
  if (jerry_value_is_number(jwidth)) {
    video_source->width = iotjs_jval_as_number(jwidth);
  } else {
    jerry_release_value(jwidth);
    return JS_CREATE_ERROR(TYPE, "Invalid config.width");
  }
  jerry_release_value(jwidth);

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

  jerry_value_t jformat = iotjs_jval_get_property(jconfig, "format");
  if (jerry_value_is_number(jformat)) {
    video_source->pixel_format = (video_pixel_format_t)iotjs_jval_as_number(jformat);
  } else {
    video_source->pixel_format = VIDEO_PIXELFORMAT_MAX;
  }
  jerry_release_value(jformat);
  if(video_source->pixel_format >= VIDEO_PIXELFORMAT_MAX) {
    return JS_CREATE_ERROR(TYPE, "Invalid config.format");
  }
  video_source->pixel_bytes = get_pixel_bytes(video_source->pixel_format);

  switch (video_source->device) {
    case DEVICETYPE_CMOSCAMERA:
      {
        jerry_value_t jtype = iotjs_jval_get_property(jconfig, "type");
        if (jerry_value_is_number(jtype)) {
          video_source->camera = (camera_type_t)iotjs_jval_as_number(jtype);
        } else {
          video_source->camera = CAMERATYPE_OV7725;
        }
        jerry_release_value(jtype);
        if(video_source->camera >= CAMERATYPE_MAX) {
          return JS_CREATE_ERROR(TYPE, "Invalid config.type");
        }
      }
      break;
    case DEVICETYPE_COMPOSITESIGNAL:
      {
        jerry_value_t jchannel = iotjs_jval_get_property(jconfig, "channel");
        if (jerry_value_is_number(jchannel)) {
          video_source->channel = iotjs_jval_as_number(jchannel);
        } else {
          video_source->channel = CHANNEL_MAX;
        }
        jerry_release_value(jchannel);
        if(video_source->channel >= CHANNEL_MAX) {
          return JS_CREATE_ERROR(TYPE, "Invalid config.channel");
        }

        jerry_value_t jsignalFormat = iotjs_jval_get_property(jconfig, "signalFormat");
        if (jerry_value_is_number(jsignalFormat)) {
          video_source->signal_format = (signal_format_t)iotjs_jval_as_number(jsignalFormat);
        } else {
          video_source->signal_format = SIGNALFORMAT_NTSC;
        }
        jerry_release_value(jsignalFormat);
        if(video_source->signal_format >= SIGNALFORMAT_MAX) {
          return JS_CREATE_ERROR(TYPE, "Invalid config.signalFormat");
        }
      }
      break;
    default:
      IOTJS_ASSERT(!"Invalid Device Type");
      return JS_CREATE_ERROR(TYPE, "Invalid Device Type");
  }

  return jerry_create_undefined();
}

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

  const jerry_value_t jbuffer = JS_GET_ARG(0, object);
  iotjs_aligned_buffer_wrap_t* buffer_wrap = iotjs_aligned_buffer_wrap_from_jbuffer(jbuffer);
  video->video_source.frame_buffer = iotjs_aligned_buffer_wrap_native_buffer_ptr(buffer_wrap);
  if(((buffer_wrap->alignment % 32) != 0) || (((int)video->video_source.frame_buffer % 32) != 0)) {
    return JS_CREATE_ERROR(TYPE, "Invalid buffer alignment");
  }

  return jerry_create_undefined();
}


JS_FUNCTION(VideoCons) {
  DJS_CHECK_THIS();
  DJS_CHECK_ARGS(2, object, number);
  DJS_CHECK_ARG_IF_EXIST(2, function);
  // Create Video object
  jerry_value_t jvideo = JS_GET_THIS();
  iotjs_video_t* video = video_create(jvideo);

  jerry_value_t jconfig = JS_GET_ARG(0, object);

  uint32_t device_type = JS_GET_ARG(1, number);
  if(device_type >= DEVICETYPE_MAX) {
      IOTJS_ASSERT(!"Invalid Device Type");
  }
  video->video_source.device = (device_type_t)device_type;

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

  res = video_set_configuration(video, jconfig);
  if (jerry_value_is_error(res)) {
    return res;
  }

  jerry_value_t jcallback = JS_GET_ARG_IF_EXIST(2, function);

  // If the callback doesn't exist, it is completed synchronously.
  // Otherwise, it will be executed asynchronously.
  if (!jerry_value_is_null(jcallback)) {
    iotjs_ext_periph_call_async(video, jcallback, kVideoOpOpen, video_worker);
  } else if (!iotjs_video_open(video)) {
    return JS_CREATE_ERROR(COMMON, iotjs_ext_periph_error_str(kVideoOpOpen));
  }

  return jerry_create_undefined();
}


JS_FUNCTION(Start) {
  JS_DECLARE_THIS_PTR(video, video);
  DJS_CHECK_ARGS(1, object);

  jerry_value_t jerr = video_get_start_param(video, jargv, jargc);
  if(jerry_value_is_error(jerr)) {
    return jerr;
  }

  iotjs_ext_periph_call_async(video, JS_GET_ARG_IF_EXIST(1, function), kVideoOpStart,
                          video_worker);
  return jerry_create_undefined();
}

JS_FUNCTION(StartSync) {
  JS_DECLARE_THIS_PTR(video, video);
  DJS_CHECK_ARGS(1, object);

  jerry_value_t jerr = video_get_start_param(video, jargv, jargc);
  if(jerry_value_is_error(jerr)) {
    return jerr;
  }

  if(!iotjs_video_start(video)) {
    return JS_CREATE_ERROR(COMMON, iotjs_ext_periph_error_str(kVideoOpStart));
  }
  return jerry_create_undefined();
}

JS_FUNCTION(Stop) {
  JS_DECLARE_THIS_PTR(video, video);
  iotjs_ext_periph_call_async(video, JS_GET_ARG_IF_EXIST(0, function), kVideoOpStop,
                          video_worker);
  return jerry_create_undefined();
}

JS_FUNCTION(StopSync) {
  JS_DECLARE_THIS_PTR(video, video);
  if(!iotjs_video_stop(video)) {
    return JS_CREATE_ERROR(COMMON, iotjs_ext_periph_error_str(kVideoOpStop));
  }
  return jerry_create_undefined();
}

JS_FUNCTION(ReadFrame) {
  JS_DECLARE_THIS_PTR(video, video);
  iotjs_ext_periph_call_async(video, JS_GET_ARG_IF_EXIST(1, function), kVideoOpReadFrame,
                          video_worker);
  return jerry_create_undefined();
}

JS_FUNCTION(ReadFrameSync) {
  JS_DECLARE_THIS_PTR(video, video);
  iotjs_video_read_frame(video);
  return jerry_create_undefined();
}

JS_FUNCTION(Close) {
  JS_DECLARE_OBJECT_PTR(0, video, video);
  iotjs_ext_periph_call_async(video, JS_GET_ARG_IF_EXIST(1, function), kVideoOpClose,
                          video_worker);
  return jerry_create_undefined();
}

JS_FUNCTION(CloseSync) {
  JS_DECLARE_OBJECT_PTR(0, video, video);
  if(!iotjs_video_close(video)) {
    return JS_CREATE_ERROR(COMMON, iotjs_ext_periph_error_str(kVideoOpClose));
  }
  return jerry_create_undefined();
}

#endif	// #if defined(USE_POSIX_BRIDGE)


jerry_value_t InitVideo() {

#if defined(USE_POSIX_BRIDGE)
  jerry_value_t jvideo_cons = jerry_create_external_function(VideoCons);
  iotjs_jval_set_method(jvideo_cons, "closeImpl", Close);
  iotjs_jval_set_method(jvideo_cons, "closeSyncImpl", CloseSync);

  jerry_value_t prototype = jerry_create_object();
  iotjs_jval_set_method(prototype, "start", Start);
  iotjs_jval_set_method(prototype, "startSync", StartSync);
  iotjs_jval_set_method(prototype, "stop", Stop);
  iotjs_jval_set_method(prototype, "stopSync", StopSync);
//  iotjs_jval_set_method(prototype, "readFrame", ReadFrame);
//  iotjs_jval_set_method(prototype, "readFrameSync", ReadFrameSync);

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

  // Device Type
  jerry_value_t jdevice = jerry_create_object();
  iotjs_jval_set_property_number(jdevice, "CMOSCAMERA", DEVICETYPE_CMOSCAMERA);
  iotjs_jval_set_property_number(jdevice, "COMPOSITESIGNAL", DEVICETYPE_COMPOSITESIGNAL);
  iotjs_jval_set_property_number(jdevice, "MAX", DEVICETYPE_MAX);
  iotjs_jval_set_property_jval(jvideo_cons, "DEVICETYPE", jdevice);
  jerry_release_value(jdevice);

  // Pixel Format
  jerry_value_t jpixel = jerry_create_object();
  iotjs_jval_set_property_number(jpixel, "YCBCR422", VIDEO_PIXELFORMAT_YCBCR422);
  iotjs_jval_set_property_number(jpixel, "RGB565", VIDEO_PIXELFORMAT_RGB565);
  iotjs_jval_set_property_number(jpixel, "RGB888", VIDEO_PIXELFORMAT_RGB888);
  iotjs_jval_set_property_number(jpixel, "MAX", VIDEO_PIXELFORMAT_MAX);
  iotjs_jval_set_property_jval(jvideo_cons, "PIXELFORMAT", jpixel);
  jerry_release_value(jpixel);

  // Camera Type
  jerry_value_t jcamera = jerry_create_object();
  iotjs_jval_set_property_number(jcamera, "OV7725", CAMERATYPE_OV7725);
  iotjs_jval_set_property_number(jcamera, "MAX", CAMERATYPE_MAX);
  iotjs_jval_set_property_jval(jvideo_cons, "CAMERATYPE", jcamera);
  jerry_release_value(jcamera);

  // Channel Number
  jerry_value_t jchannel = jerry_create_object();
  iotjs_jval_set_property_number(jchannel, "CHANNEL_0", CHANNEL_0);
  iotjs_jval_set_property_number(jchannel, "CHANNEL_1", CHANNEL_1);
  iotjs_jval_set_property_number(jchannel, "MAX", CHANNEL_MAX);
  iotjs_jval_set_property_jval(jvideo_cons, "CHANNEL", jchannel);
  jerry_release_value(jchannel);

  // Signal Format
  jerry_value_t jsignal = jerry_create_object();
  iotjs_jval_set_property_number(jsignal, "NTSC", SIGNALFORMAT_NTSC);
  iotjs_jval_set_property_number(jsignal, "PAL", SIGNALFORMAT_PAL);
  iotjs_jval_set_property_number(jsignal, "MAX", SIGNALFORMAT_MAX);
  iotjs_jval_set_property_jval(jvideo_cons, "SIGNALFORMAT", jsignal);
  jerry_release_value(jsignal);

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

  return jvideo_cons;
}
