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

#include "js_native_api_types.h"
#include "lib/alloc.h"
#include "macro/mark.h"
#include "rte_runtime/binding/nodejs/src/common/common.h"
#include "rte_runtime/binding/nodejs/src/msg/msg.h"
#include "rte_runtime/msg/data/data.h"
#include "rte_runtime/rte/rte.h"

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

static napi_ref js_data_constructor_ref = NULL;  // NOLINT

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

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

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

  return UNDEFINED(env);
}

static void rte_nodejs_data_finalize(napi_env env, void* data_,
                                     UNUSED void* hint) {
  rte_nodejs_data_t* data_bridge = data_;
  assert(data_bridge &&
         rte_nodejs_msg_check_integrity(&data_bridge->msg, true));

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

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

napi_value rte_nodejs_data_wrap(napi_env env, rte_sharedptr_t* data) {
  rte_nodejs_data_t* self = rte_malloc(sizeof(rte_nodejs_data_t));
  assert(self);

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

  napi_value data_js = rte_nodejs_create_new_js_object_and_wrap(
      env, js_data_constructor_ref, self, rte_nodejs_data_finalize,
      &self->msg.js_ref);
  return data_js;
}

static napi_value rte_nodejs_data_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_data = rte_data_create();
  assert(c_data);

  rte_nodejs_data_t* data_bridge = rte_malloc(sizeof(rte_nodejs_data_t));
  assert(data_bridge);

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

  rte_nodejs_msg_take_ownership_from_js(&data_bridge->msg);

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

  return UNDEFINED(env);
}

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

  EXPORT_FUNC(env, exports, rte_nodejs_data_register_class);
  EXPORT_FUNC(env, exports, rte_nodejs_data_create);

  return exports;
}
