/**
 *
 * Agora Real Time Engagement
 * Created by Wei Hu in 2021-11.
 * Copyright (c) 2022 Agora IO. All rights reserved.
 *
 */
#include "rte_runtime/binding/nodejs/src/msg/image_frame/image_frame.h"

#include <stdio.h>

#include "lib/alloc.h"
#include "lib/shared_ptr.h"
#include "lib/string.h"
#include "rte_runtime/binding/nodejs/src/common/common.h"
#include "rte_runtime/binding/nodejs/src/msg/msg.h"
#include "rte_runtime/msg/image_frame/image_frame.h"
#include "rte_runtime/rte/rte.h"

#define ZF_LOG_TAG "Node"
#include "zf_log.h"

static napi_ref js_image_frame_constructor_ref = NULL;  // NOLINT

static napi_value rte_nodejs_image_frame_register_class(
    napi_env env, napi_callback_info info) {
  assert(env && info);

  size_t argc = 1;
  napi_value args[1];  // ImageFrame
  if (!rte_nodejs_get_args(env, info, args, argc)) {
    return UNDEFINED(env);
  }

  UNUSED napi_status status =
      napi_create_reference(env, args[0], 1, &js_image_frame_constructor_ref);
  ASSERT_IF_NAPI_FAIL(
      status == napi_ok,
      "Failed to create C reference to JS image frame constructor: %d", status);

  return UNDEFINED(env);
}

static void rte_nodejs_image_frame_finalize(napi_env env, void* data,
                                            UNUSED void* hint) {
  rte_nodejs_image_frame_t* image_frame_bridge = data;
  assert(image_frame_bridge &&
         rte_nodejs_msg_check_integrity(&image_frame_bridge->msg, true));

  ZF_LOGV("RTE JS image_frame is finalized.");

  rte_nodejs_msg_finalize(env, &image_frame_bridge->msg);
  rte_nodejs_msg_release_ownership_from_js(&image_frame_bridge->msg);
}

napi_value rte_nodejs_image_frame_wrap(napi_env env,
                                       rte_sharedptr_t* image_frame) {
  rte_nodejs_image_frame_t* self = rte_malloc(sizeof(rte_nodejs_image_frame_t));
  assert(self);

  rte_nodejs_msg_init_from_c_msg(&self->msg, image_frame);
  rte_nodejs_msg_take_ownership_from_js(&self->msg);

  napi_value image_frame_js = rte_nodejs_create_new_js_object_and_wrap(
      env, js_image_frame_constructor_ref, self,
      rte_nodejs_image_frame_finalize, &self->msg.js_ref);
  return image_frame_js;
}

static napi_value rte_nodejs_image_frame_create(napi_env env,
                                                napi_callback_info info) {
  assert(env && info);

  size_t argc = 1;
  napi_value args[1];  // this, wrapper-only
  if (!rte_nodejs_get_args(env, info, args, argc)) {
    return UNDEFINED(env);
  }

  rte_sharedptr_t* c_image_frame = rte_image_frame_create();
  assert(c_image_frame);

  rte_nodejs_image_frame_t* image_frame_bridge =
      rte_malloc(sizeof(rte_nodejs_image_frame_t));
  assert(image_frame_bridge);

  rte_nodejs_msg_init_from_c_msg(&image_frame_bridge->msg, c_image_frame);
  // Decrement the reference count of c_image_frame to indicate that the JS
  // image frame takes the full ownership of this c_image_frame, in other words,
  // when the JS image frame is finalized, its C image frame would be destroyed,
  // too.
  rte_sharedptr_dec_rc(c_image_frame);

  rte_nodejs_msg_take_ownership_from_js(&image_frame_bridge->msg);

  napi_status status = napi_wrap(env, args[0], image_frame_bridge,
                                 rte_nodejs_image_frame_finalize, NULL,
                                 &image_frame_bridge->msg.js_ref);
  ASSERT_IF_NAPI_FAIL(status == napi_ok,
                      "Failed to bind JS image_frame & bridge: %d", status);

  return UNDEFINED(env);
}

static napi_value rte_nodejs_image_frame_get_width(napi_env env,
                                                   napi_callback_info info) {
  size_t argc = 1;
  napi_value args[1];  // this
  if (!rte_nodejs_get_args(env, info, args, argc)) {
    return UNDEFINED(env);
  }

  rte_nodejs_image_frame_t* image_frame_bridge = NULL;
  napi_status status = napi_unwrap(env, args[0], (void**)&image_frame_bridge);
  ASSERT_IF_NAPI_FAIL(status == napi_ok && image_frame_bridge != NULL,
                      "Failed to get image_frame bridge: %d", status);

  int width = rte_image_frame_get_width(image_frame_bridge->msg.c_msg);
  napi_value result = NULL;
  status = napi_create_int32(env, width, &result);
  if (status == napi_ok) {
    return result;
  } else {
    return UNDEFINED(env);
  }
}

static napi_value rte_nodejs_image_frame_get_height(napi_env env,
                                                    napi_callback_info info) {
  size_t argc = 1;
  napi_value args[1];  // this
  if (!rte_nodejs_get_args(env, info, args, argc)) {
    return UNDEFINED(env);
  }

  rte_nodejs_image_frame_t* image_frame_bridge = NULL;
  napi_status status = napi_unwrap(env, args[0], (void**)&image_frame_bridge);
  ASSERT_IF_NAPI_FAIL(status == napi_ok && image_frame_bridge != NULL,
                      "Failed to get image_frame bridge: %d", status);

  int height = rte_image_frame_get_height(image_frame_bridge->msg.c_msg);
  napi_value result = NULL;
  status = napi_create_int32(env, height, &result);
  if (status == napi_ok) {
    return result;
  } else {
    return UNDEFINED(env);
  }
}

static napi_value rte_nodejs_image_frame_set_data_of_index(
    napi_env env, napi_callback_info info) {
  size_t argc = 3;
  napi_value args[3];  // this, index, byte
  if (!rte_nodejs_get_args(env, info, args, argc)) {
    return UNDEFINED(env);
  }

  rte_nodejs_image_frame_t* image_frame_bridge = NULL;
  napi_status status = napi_unwrap(env, args[0], (void**)&image_frame_bridge);
  ASSERT_IF_NAPI_FAIL(status == napi_ok && image_frame_bridge != NULL,
                      "Failed to get image_frame bridge: %d", status);

  int index = 0;
  status = napi_get_value_int32(env, args[1], &index);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok,
                                "Failed to get image_frame index: %d", status);

  int value = 0;
  status = napi_get_value_int32(env, args[2], &value);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok,
                                "Failed to get image_frame value: %d", status);

  size_t size = rte_image_frame_get_buf_size(image_frame_bridge->msg.c_msg);
  if (index > size || index < 0) {
    rte_string_t err_str;
    rte_string_init_with_value(&err_str, "set index out of buf range: %d",
                               index);

    status = napi_throw_error(env, "EINVAL", rte_string_c_str(&err_str));
    ASSERT_IF_NAPI_FAIL(status == napi_ok, "Failed to throw JS exception: %d",
                        status);

    rte_string_deinit(&err_str);

    return UNDEFINED(env);
  }

  uint8_t* data = rte_image_frame_get_buf(image_frame_bridge->msg.c_msg);
  // TODO(Lyuge): should we check if byte out of range?
  data[size] = (uint8_t)value;

  return UNDEFINED(env);
}

static napi_value rte_nodejs_image_frame_get_data_of_index(
    napi_env env, napi_callback_info info) {
  size_t argc = 2;
  napi_value args[2];  // this, index
  if (!rte_nodejs_get_args(env, info, args, argc)) {
    return UNDEFINED(env);
  }

  rte_nodejs_image_frame_t* image_frame_bridge = NULL;
  napi_status status = napi_unwrap(env, args[0], (void**)&image_frame_bridge);
  ASSERT_IF_NAPI_FAIL(status == napi_ok && image_frame_bridge != NULL,
                      "Failed to get image_frame bridge: %d", status);

  int index = 0;
  status = napi_get_value_int32(env, args[1], &index);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok,
                                "Failed to get image_frame index: %d", status);

  size_t size = rte_image_frame_get_buf_size(image_frame_bridge->msg.c_msg);
  if (index > size || index < 0) {
    rte_string_t err_str;
    rte_string_init_with_value(&err_str, "set index out of buf range: %d",
                               index);

    status = napi_throw_error(env, "EINVAL", rte_string_c_str(&err_str));
    ASSERT_IF_NAPI_FAIL(status == napi_ok, "Failed to throw JS exception: %d",
                        status);

    rte_string_deinit(&err_str);

    return UNDEFINED(env);
  }

  uint8_t* data = rte_image_frame_get_buf(image_frame_bridge->msg.c_msg);
  napi_value result = NULL;
  status = napi_create_int32(env, data[index], &result);
  if (status == napi_ok) {
    return result;
  } else {
    return UNDEFINED(env);
  }
}

napi_value rte_nodejs_image_frame_module_init(napi_env env,
                                              napi_value exports) {
  assert(env && exports);

  EXPORT_FUNC(env, exports, rte_nodejs_image_frame_register_class);
  EXPORT_FUNC(env, exports, rte_nodejs_image_frame_create);
  EXPORT_FUNC(env, exports, rte_nodejs_image_frame_get_width);
  EXPORT_FUNC(env, exports, rte_nodejs_image_frame_get_height);
  EXPORT_FUNC(env, exports, rte_nodejs_image_frame_set_data_of_index);
  EXPORT_FUNC(env, exports, rte_nodejs_image_frame_get_data_of_index);

  return exports;
}
