/**
 *
 * 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/pcm_frame/pcm_frame.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/pcm_frame/pcm_frame.h"
#include "rte_runtime/rte/rte.h"

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

static napi_ref js_pcm_frame_constructor_ref = NULL;  // NOLINT

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

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

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

  return UNDEFINED(env);
}

static void rte_nodejs_pcm_frame_finalize(napi_env env, void* data,
                                          UNUSED void* hint) {
  rte_nodejs_pcm_frame_t* pcm_frame_bridge = data;
  assert(pcm_frame_bridge &&
         rte_nodejs_msg_check_integrity(&pcm_frame_bridge->msg, true));

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

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

napi_value rte_nodejs_pcm_frame_wrap(napi_env env, rte_sharedptr_t* pcm_frame) {
  assert(pcm_frame);

  rte_nodejs_pcm_frame_t* self = rte_malloc(sizeof(rte_nodejs_pcm_frame_t));
  assert(self);

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

  napi_value pcm_frame_js = rte_nodejs_create_new_js_object_and_wrap(
      env, js_pcm_frame_constructor_ref, self, rte_nodejs_pcm_frame_finalize,
      &self->msg.js_ref);
  return pcm_frame_js;
}

static napi_value rte_nodejs_pcm_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_pcm_frame = rte_pcm_frame_create();
  assert(c_pcm_frame);

  rte_nodejs_pcm_frame_t* pcm_frame_bridge =
      rte_malloc(sizeof(rte_nodejs_pcm_frame_t));
  assert(pcm_frame_bridge);

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

  rte_nodejs_msg_take_ownership_from_js(&pcm_frame_bridge->msg);

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

  return UNDEFINED(env);
}

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

  EXPORT_FUNC(env, exports, rte_nodejs_pcm_frame_register_class);
  EXPORT_FUNC(env, exports, rte_nodejs_pcm_frame_create);

  return exports;
}
