/* 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.
 */
#if defined(USE_POSIX_BRIDGE)
#include "iotjs_def.h"

#include "peripheral_io.h"
#include "iotjs_ext_module_periph_common.h"
#include "modules/iotjs_module_buffer.h"
#include "iotjs_module_video.h"
#include "iotjs_module_display.h"
#include "iotjs_module_jpeg.h"
#include "iotjs_module_aligned_buffer.h"

const char* iotjs_ext_periph_error_str(uint8_t op) {
  switch (op) {
#if ENABLE_MODULE_VIDEO
    case kVideoOpOpen:
      return "Open error, cannot open Video";
    case kVideoOpStart:
      return "Start error, cannot start Video";
    case kVideoOpStop:
      return "Stop error, cannot stop Video";
    case kVideoOpReadFrame:
      return "ReadFrame error, cannot readFrame Video";
    case kVideoOpClose:
      return "Close error, cannot close Video";
#endif /* ENABLE_MODULE_VIDEO */
#if ENABLE_MODULE_DISPLAY
    case kDisplayOpOpen:
      return "Open error, cannot open Display";
    case kDisplayOpStart:
      return "Start error, cannot start Display";
    case kDisplayOpStop:
      return "Stop error, cannot stop Display";
    case kDisplayOpUpdate:
      return "Update error, cannot update Display";
    case kDisplayOpClose:
      return "Close error, cannot close Display";
#endif /* ENABLE_MODULE_VIDEO */
#if ENABLE_MODULE_JPEG
    case kJpegOpEncode:
      return "Encode error, cannot encode Jpeg";
    case kJpegOpDecode:
      return "Decode error, cannot decode Jpeg";
#endif /* ENABLE_MODULE_JPEG */
#if ENABLE_MODULE_GRAPHICS
    case kGraphicsOpDrawImage:
      return "Draw error, cannot draw Image";
    case kGraphicsOpDrawLine:
      return "Draw error, cannot draw Line";
    case kGraphicsOpDrawRect:
      return "Draw error, cannot draw Rect";
    case kGraphicsOpDrawArc:
      return "Draw error, cannot draw Arc";
    case kGraphicsOpDrawCircle:
      return "Draw error, cannot draw Circle";
    case kGraphicsOpDrawEllipse:
      return "Draw error, cannot draw Ellipse";
    case kGraphicsOpDrawPolygon:
      return "Draw error, cannot draw Polygon";
    case kGraphicsOpDrawText:
      return "Draw error, cannot draw Text";
#endif /* ENABLE_MODULE_GRAPHICS */
    default:
      return "Unknown error";
  }
}

static void after_worker(uv_work_t* work_req, int status) {
  iotjs_periph_reqwrap_t* reqwrap =
      (iotjs_periph_reqwrap_t*)iotjs_reqwrap_from_request((uv_req_t*)work_req);

  size_t jargc = 0;
  jerry_value_t jargs[2] = { 0 };

  if (status) {
    jargs[jargc++] = iotjs_jval_create_error_without_error_flag("System error");
  } else {
    if (!reqwrap->result) {
      jargs[jargc++] = iotjs_jval_create_error_without_error_flag(
          iotjs_ext_periph_error_str(reqwrap->op));
    } else {
      jargs[jargc++] = jerry_create_null();
      switch (reqwrap->op) {
        case kVideoOpStart:
        case kVideoOpStop:
        case kVideoOpReadFrame:
        case kVideoOpClose:
        case kDisplayOpStart:
        case kDisplayOpStop:
        case kDisplayOpUpdate:
        case kDisplayOpClose:
        case kGraphicsOpDrawImage:
        case kGraphicsOpDrawLine:
        case kGraphicsOpDrawRect:
        case kGraphicsOpDrawArc:
        case kGraphicsOpDrawCircle:
        case kGraphicsOpDrawEllipse:
        case kGraphicsOpDrawPolygon:
        case kGraphicsOpDrawText: {
          break;
        }
        case kVideoOpOpen: {
#if ENABLE_MODULE_VIDEO
//          iotjs_video_t* video = (iotjs_video_t*)reqwrap->data;
//          jargs[jargc++] = video->jobject;
#endif /* ENABLE_MODULE_VIDEO */
          break;
        }
        case kDisplayOpOpen: {
#if ENABLE_MODULE_DISPLAY
//          iotjs_display_t* display = (iotjs_display_t*)reqwrap->data;
//          jargs[jargc++] = display->jobject;
#endif /* ENABLE_MODULE_DISPLAY */
          break;
        }
        case kJpegOpEncode: {
#if ENABLE_MODULE_JPEG
          iotjs_jpeg_t* jpeg = (iotjs_jpeg_t*)reqwrap->data;
          jerry_value_t jbuf = iotjs_aligned_buffer_wrap_create_buffer((size_t)jpeg->encode_data.dst.len, 32);
          if (jerry_value_is_error(jbuf)) {
            jerry_release_value(jargs[--jargc]);
          } else {
            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);
          }
          jargs[jargc++] = jbuf;
          IOTJS_RELEASE(jpeg->encode_data.dst.buf);
#endif /* ENABLE_MODULE_JPEG */
          break;
        }
        case kJpegOpDecode: {
#if ENABLE_MODULE_JPEG
          iotjs_jpeg_t* jpeg = (iotjs_jpeg_t*)reqwrap->data;
          jerry_value_t jbuf = iotjs_aligned_buffer_wrap_create_buffer((size_t)jpeg->decode_data.dst.len, 32);
          if (jerry_value_is_error(jbuf)) {
            jerry_release_value(jargs[--jargc]);
          } else {
            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);
          }
          jargs[jargc++] = jbuf;
          IOTJS_RELEASE(jpeg->decode_data.dst.buf);
#endif /* ENABLE_MODULE_JPEG */
          break;
        }
        default: {
          IOTJS_ASSERT(!"Unreachable");
          break;
        }
      }
    }
  }

  jerry_value_t jcallback = iotjs_reqwrap_jcallback(&reqwrap->reqwrap);
  if (jerry_value_is_function(jcallback)) {
    iotjs_invoke_callback(jcallback, jerry_create_undefined(), jargs, jargc);
  }

  for (size_t i = 0; i < jargc; i++) {
    jerry_release_value(jargs[i]);
  }

  iotjs_reqwrap_destroy(&reqwrap->reqwrap);
  IOTJS_RELEASE(reqwrap);
}

static iotjs_periph_reqwrap_t* reqwrap_create(const jerry_value_t jcallback,
                                              void* data, uint8_t op) {
  iotjs_periph_reqwrap_t* reqwrap = IOTJS_ALLOC(iotjs_periph_reqwrap_t);
  iotjs_reqwrap_initialize((iotjs_reqwrap_t*)reqwrap, jcallback,
                           (uv_req_t*)&reqwrap->req);
  reqwrap->op = op;
  reqwrap->data = data;
  return (iotjs_periph_reqwrap_t*)reqwrap;
}

void iotjs_ext_periph_call_async(void* data, jerry_value_t jcallback, uint8_t op,
                             uv_work_cb worker) {
  uv_loop_t* loop = iotjs_environment_loop(iotjs_environment_get());
  iotjs_periph_reqwrap_t* req_wrap = reqwrap_create(jcallback, data, op);
  uv_queue_work(loop, &req_wrap->req, worker, after_worker);
}
#endif	// #if defined(USE_POSIX_BRIDGE)
