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

#include "js_native_api.h"
#include "lib/alloc.h"
#include "lib/atomic.h"
#include "lib/shared_ptr.h"
#include "lib/signature.h"
#include "lib/string.h"
#include "rte_runtime/binding/nodejs/src/common/common.h"
#include "rte_runtime/binding/nodejs/src/common/semaphore.h"
#include "rte_runtime/binding/nodejs/src/common/tsfn.h"
#include "rte_runtime/binding/nodejs/src/extension/extension.h"
#include "rte_runtime/binding/nodejs/src/extension_group/extension_group.h"
#include "rte_runtime/binding/nodejs/src/metadata/metadata.h"
#include "rte_runtime/binding/nodejs/src/rte/rte.h"
#include "rte_runtime/extension/extension.h"
#include "rte_runtime/extension_group/extension_group.h"
#include "rte_runtime/metadata/metadata.h"
#include "rte_runtime/rte/rte.h"
#include "rte_runtime/sanitizer/thread_check.h"

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

typedef struct on_addon_init_call_info_t {
  rte_nodejs_addon_t* addon_bridge;
  rte_t* rte;
  rte_metadata_info_t* manifest;
  rte_metadata_info_t* property;
  rte_nodejs_sem_t sem;
  bool js_result;
} on_addon_init_call_info_t;

typedef struct create_instance_call_info_t {
  napi_ref js_ref;
  const char* name;

  bool is_extension;
  union {
    rte_extension_t* c_extension;
    rte_extension_group_t* c_extension_group;
  } u;

  rte_nodejs_sem_t sem;
  bool js_result;
} create_instance_call_info_t;

typedef struct destroy_instance_call_info_t {
  rte_nodejs_addon_t* addon_bridge;
  napi_ref js_ref;

  union {
    rte_extension_t* c_extension;
    rte_extension_group_t* c_extension_group;
  } u;

  rte_nodejs_sem_t sem;
  bool js_result;
} destroy_instance_call_info_t;

static rte_atomic_t initializing_addons = 0;  // NOLINT

UNUSED static bool rte_nodejs_addon_check_integrity(rte_nodejs_addon_t* self,
                                                    bool check_thread) {
  assert(self);

  if (rte_signature_get(&self->signature) != RTE_NODEJS_ADDON_SIGNATURE) {
    return false;
  }
  if (check_thread &&
      !rte_sanitizer_thread_check_do_check(&self->thread_check)) {
    return false;
  }
  return true;
}

static void invoke_addon_on_init(napi_env env, napi_value fn,
                                 UNUSED void* context, void* data) {
  assert(env);

  on_addon_init_call_info_t* call_info = data;
  assert(call_info);
  assert(call_info->addon_bridge &&
         rte_nodejs_addon_check_integrity(call_info->addon_bridge, true));

  napi_value js_manifest = NULL;
  napi_value js_property = NULL;

  napi_value js_rte = rte_nodejs_rte_wrap(env, call_info->rte, NULL);
  if (!js_rte) {
    goto error;
  }

  assert(call_info->manifest &&
         rte_metadata_info_check_integrity(call_info->manifest));
  js_manifest = rte_nodejs_metadata_info_wrap(env, call_info->manifest);
  if (!js_manifest) {
    goto error;
  }

  assert(call_info->property &&
         rte_metadata_info_check_integrity(call_info->property));
  js_property = rte_nodejs_metadata_info_wrap(env, call_info->property);
  if (!js_property) {
    goto error;
  }

  napi_value js_addon = NULL;
  napi_status status =
      napi_get_reference_value(env, call_info->addon_bridge->js_ref, &js_addon);
  GOTO_LABEL_IF_NAPI_FAIL(error, status == napi_ok && js_addon != NULL,
                          "Failed to get JS addon: %d", status);

  napi_ref js_ref = NULL;
  status = napi_create_reference(env, js_rte, 0, &js_ref);
  ASSERT_IF_NAPI_FAIL(status == napi_ok,
                      "Failed to create C reference to JS 'rte': %d", status);

  napi_value result = NULL;
  napi_value argv[] = {js_rte, js_manifest, js_property};
  status = napi_call_function(env, js_addon, fn, 3, argv, &result);
  GOTO_LABEL_IF_NAPI_FAIL(error, status == napi_ok,
                          "Failed to call JS addon on_init(): %d", status);

  goto done;

error:
  if (js_manifest) {
    rte_nodejs_metadata_info_invalidate_js(env, js_manifest);
  }
  if (js_property) {
    rte_nodejs_metadata_info_invalidate_js(env, js_property);
  }

done:
  rte_nodejs_sem_signal(&call_info->sem);
}

static void proxy_addon_on_dynamic_init(rte_dynamic_t* addon, rte_t* rte,
                                        rte_metadata_info_t* manifest,
                                        rte_metadata_info_t* property,
                                        rte_atomic_t* user_data) {
  assert(addon && manifest && property && user_data);
  assert(rte && rte_check_integrity(rte));

  rte_nodejs_addon_t* addon_bridge = (rte_nodejs_addon_t*)*user_data;
  assert(addon_bridge &&
         // RTE_NOLINTNEXTLINE(thread-check)
         // thread-check: this function is called from a standalone addon
         // registration thread, and only when all the addons are registered,
         // the RTE world can go on, so it's thread safe.
         rte_nodejs_addon_check_integrity(addon_bridge, false));

  on_addon_init_call_info_t call_info = {
      addon_bridge, rte, manifest, property, {0}, .js_result = true,
  };

  PREPARE_TO_CALL_TSFN(call_info);

  // Call the on_init function of the addon in the Javascript world.
  bool rc = rte_nodejs_threadsafe_function_invoke(addon_bridge->js_on_init,
                                                  &call_info);

  END_TO_CALL_TSFN(call_info, "Failed to call addon on_init().");

  if (!rc || !call_info.js_result) {
    // Failed to call JS on_init(), so that we need to call on_init_done() here
    // to let RTE runtime proceed.
    rte_on_init_done(rte, manifest, property);
  }
}

static void invoke_addon_create_instance(napi_env env, napi_value fn,
                                         UNUSED void* context, void* data) {
  create_instance_call_info_t* call_info = data;
  assert(call_info);

  napi_value js_addon = NULL;
  napi_status status =
      napi_get_reference_value(env, call_info->js_ref, &js_addon);
  GOTO_LABEL_IF_NAPI_FAIL(error, status == napi_ok && js_addon != NULL,
                          "Failed to get JS addon: %d", status);

  napi_value js_name = NULL;
  status = napi_create_string_utf8(env, call_info->name,
                                   strlen(call_info->name), &js_name);
  GOTO_LABEL_IF_NAPI_FAIL(error, status == napi_ok,
                          "Failed to create JS string: %d", status);

  napi_value result = NULL;
  status = napi_call_function(env, js_addon, fn, 1, &js_name, &result);
  GOTO_LABEL_IF_NAPI_FAIL(error, status == napi_ok,
                          "Failed to call JS addon create_instance(): %d",
                          status);

  // get the extension/group object from result
  void* extension_or_group_bridge = NULL;
  status = napi_unwrap(env, result, &extension_or_group_bridge);
  GOTO_LABEL_IF_NAPI_FAIL(
      error, status == napi_ok && extension_or_group_bridge != NULL,
      "Failed to get extension_or_group bridge: %d", status);

  napi_ref extension_or_group_js_ref = NULL;
  if (call_info->is_extension) {
    rte_nodejs_extension_t* extension_bridge = extension_or_group_bridge;
    assert(extension_bridge &&
           rte_nodejs_extension_check_integrity(extension_bridge, true));

    rte_extension_t* c_extension = extension_bridge->c_extension;
    assert(c_extension && rte_extension_check_integrity(c_extension));

    // Increase the reference count of the extension bridge to indicate that the
    // addon take one ownership of the extension_bridge; and must be paired with
    // dec_rc() when the addon destroy the extension.
    rte_sharedptr_inc_rc(extension_bridge->shared_ptr);

    call_info->u.c_extension = c_extension;
    extension_or_group_js_ref = extension_bridge->js_ref;
  } else {
    rte_nodejs_extension_group_t* extension_group_bridge =
        extension_or_group_bridge;
    assert(extension_group_bridge && rte_nodejs_extension_group_check_integrity(
                                         extension_group_bridge, true));

    rte_extension_group_t* c_extension_group =
        extension_group_bridge->c_extension_group;
    assert(c_extension_group &&
           rte_extension_group_check_integrity(c_extension_group, true));

    // Increase the following reference count to indicate that the addon take a
    // ownership of the extension_group_bridge; and must be paired with dec_rc()
    // when the addon destroy the extension group.
    rte_sharedptr_inc_rc(extension_group_bridge->shared_ptr);

    call_info->u.c_extension_group = c_extension_group;
    extension_or_group_js_ref = extension_group_bridge->js_ref;
  }

  uint32_t result_count = 0;
  status = napi_reference_ref(env, extension_or_group_js_ref, &result_count);
  ASSERT_IF_NAPI_FAIL(
      status == napi_ok,
      "Failed to reference newly created JS extension/group: %d", status);

  goto done;

error:
  call_info->js_result = false;

done:
  rte_nodejs_sem_signal(&call_info->sem);
}

static void* proxy_addon_create_instance(UNUSED rte_dynamic_t* addon,
                                         const char* name,
                                         rte_atomic_t* user_data) {
  rte_nodejs_addon_t* addon_bridge = (rte_nodejs_addon_t*)*user_data;
  assert(addon_bridge &&
         // RTE_NOLINTNEXTLINE(thread-check)
         // thread-check: in order to maintain thread safety, we use semaphore
         // below to prevent JS main thread and the RTE native thread access the
         // bridge at the same time.
         rte_nodejs_addon_check_integrity(addon_bridge, false));

  ZF_LOGV("Call JS addon create_instance().");

  create_instance_call_info_t call_info = {
      addon_bridge->js_ref, name, addon_bridge->is_extension, NULL, {0},
      .js_result = true,
  };

  PREPARE_TO_CALL_TSFN(call_info);

  bool rc = rte_nodejs_threadsafe_function_invoke(
      addon_bridge->js_on_create_instance, &call_info);

  END_TO_CALL_TSFN(call_info, "Failed to call addon create_instance().");

  if (rc && call_info.js_result) {
    // The addon is used to create an instance here, and we need this addon to
    // remain alive until the created instance is destroyed by this addon. so we
    // need to increment the reference count of 'addon_bridge' to prevent it
    // from being destroyed, and decrement the reference count after the addon
    // is used to destroy the created instance.
    rte_sharedptr_inc_rc(addon_bridge->shared_ptr);
  }

  if (addon_bridge->is_extension) {
    return call_info.u.c_extension;
  } else {
    return call_info.u.c_extension_group;
  }
}

static void invoke_addon_destroy_instance(napi_env env, napi_value fn,
                                          UNUSED void* context, void* data) {
  destroy_instance_call_info_t* call_info = data;
  assert(call_info);
  assert(call_info->addon_bridge &&
         rte_nodejs_addon_check_integrity(call_info->addon_bridge, true));

  napi_value js_addon = NULL;
  napi_status status =
      napi_get_reference_value(env, call_info->js_ref, &js_addon);
  GOTO_LABEL_IF_NAPI_FAIL(done, status == napi_ok && js_addon != NULL,
                          "Failed to get JS addon: %d", status);

  napi_ref extension_or_group_js_ref = NULL;
  if (call_info->addon_bridge->is_extension) {
    rte_extension_t* c_extension = call_info->u.c_extension;
    // Here, the C extension group is accessed in the JS main thread, we need to
    // carefully consider if there is any thread-safety issue.
    assert(c_extension && rte_extension_check_integrity(c_extension));

    rte_nodejs_extension_t* extension_bridge =
        rte_extension_get_me_in_target_lang(c_extension);
    assert(extension_bridge &&
           rte_nodejs_extension_check_integrity(extension_bridge, true));

    // Because the addon take a ownership (inc_rc) of the extension when it
    // created the extension, so the addon needs to release the ownership
    // (dec_rc) when it destroy the extension.
    //
    //                           addon
    //                             |
    //                             x
    //                             |
    //                             v
    //   JS extension       extension_bridge         C extension
    //         o       ==>         o          <==         o
    //                             ^
    //                             |
    //                           group
    //
    rte_sharedptr_dec_rc(extension_bridge->shared_ptr);

    // TODO(xilin): This function should be called in the extension thread. We
    // will move this function to addon::on_destroy_instance_done in the future.
    rte_extension_destroy(c_extension);

    extension_or_group_js_ref = extension_bridge->js_ref;
  } else {
    rte_extension_group_t* c_extension_group = call_info->u.c_extension_group;
    assert(c_extension_group &&
           // RTE_NOLINTNEXTLINE(thread-check)
           // thread-check: here, the C extension group is accessed in the JS
           // main thread, we need to carefully consider if there is any
           // thread-safety issue.
           rte_extension_group_check_integrity(c_extension_group, false));

    rte_nodejs_extension_group_t* extension_group_bridge =
        rte_extension_group_get_me_in_target_lang(c_extension_group);
    assert(extension_group_bridge && rte_nodejs_extension_group_check_integrity(
                                         extension_group_bridge, true));

    // Because the addon take a ownership (inc_rc) of the extension_group when
    // it created the extension_group, so the addon needs to release the
    // ownership (dec_rc) when it destroy the extension_group.
    rte_sharedptr_dec_rc(extension_group_bridge->shared_ptr);

    // TODO(xilin): This function should be called in the extension thread. We
    // will move this function to addon::on_destroy_instance_done in the future.
    rte_extension_group_destroy(c_extension_group);

    extension_or_group_js_ref = extension_group_bridge->js_ref;
  }

  napi_value js_extension_or_extension_group = NULL;
  status = napi_get_reference_value(env, extension_or_group_js_ref,
                                    &js_extension_or_extension_group);
  GOTO_LABEL_IF_NAPI_FAIL(
      done, status == napi_ok && js_extension_or_extension_group != NULL,
      "Failed to get JS extension/group: %d", status);

  napi_value result = NULL;
  status = napi_call_function(env, js_addon, fn, 1,
                              &js_extension_or_extension_group, &result);
  GOTO_LABEL_IF_NAPI_FAIL(done, status == napi_ok,
                          "Failed to call JS addon destroyInstance: %d",
                          status);

  // Release one napi reference count, because JS part has been destroyed.
  status = napi_reference_unref(env, extension_or_group_js_ref, NULL);
  ASSERT_IF_NAPI_FAIL(status == napi_ok,
                      "Failed to unreference JS extension/group: %d", status);

done:
  rte_nodejs_sem_signal(&call_info->sem);
}

static void proxy_addon_destroy_instance(UNUSED rte_dynamic_t* addon,
                                         void* extension_or_group,
                                         rte_atomic_t* user_data) {
  rte_nodejs_addon_t* addon_bridge = (rte_nodejs_addon_t*)*user_data;
  assert(addon_bridge &&
         // RTE_NOLINTNEXTLINE(thread-check)
         // thread-check: in order to maintain thread safety, we use semaphore
         // below to prevent JS main thread and the RTE native thread access the
         // bridge at the same time.
         rte_nodejs_addon_check_integrity(addon_bridge, false));

  ZF_LOGV("Call JS addon destroy_instance().");

  destroy_instance_call_info_t call_info = {
      addon_bridge, addon_bridge->js_ref, extension_or_group,
      {0},          .js_result = true,
  };

  PREPARE_TO_CALL_TSFN(call_info);

  bool rc = rte_nodejs_threadsafe_function_invoke(
      addon_bridge->js_on_destroy_instance, &call_info);

  END_TO_CALL_TSFN(call_info, "Failed to call addon destroy_instance().");

  // Decrement the reference count because the addon is completed its job, even
  // if the destroy_instance() is failed.
  rte_sharedptr_dec_rc(addon_bridge->shared_ptr);
}

static void rte_nodejs_addon_detach_callbacks(rte_nodejs_addon_t* self) {
  assert(self);

  rte_nodejs_threadsafe_function_dec_rc(self->js_on_init);
  rte_nodejs_threadsafe_function_dec_rc(self->js_on_create_instance);
  rte_nodejs_threadsafe_function_dec_rc(self->js_on_destroy_instance);
}

static void rte_nodejs_addon_release_callbacks(napi_env env,
                                               rte_nodejs_addon_t* self) {
  assert(self);

  rte_nodejs_threadsafe_function_release(env, self->js_on_init);
  rte_nodejs_threadsafe_function_release(env, self->js_on_create_instance);
  rte_nodejs_threadsafe_function_release(env, self->js_on_destroy_instance);
}

// Delete a factory (addon).
static void rte_nodejs_addon_finalize(napi_env env, void* data,
                                      UNUSED void* hint) {
  assert(env);

  rte_nodejs_addon_t* addon_bridge = data;
  assert(addon_bridge && rte_nodejs_addon_check_integrity(addon_bridge, true));

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

  if (addon_bridge->on_init_done_is_called == false) {
    // Addon is finalized _before_ on_init_done() is called, so we have to
    // decrement 'initializing_addons' to ensure that the number of it is
    // correct.
    rte_atomic_fetch_sub(&initializing_addons, 1);
  }

  // The JS addon is finalized, so delete the reference to it.
  napi_delete_reference(env, addon_bridge->js_ref);

  // The JS addon is finalized, so decrease the reference count to the addon
  // bridge to indicate that one of the ownership of the addon bridge will not
  // be hold by the JS addon.
  rte_sharedptr_dec_rc(addon_bridge->shared_ptr);
}

static void register_factory_exec(void* data) {
  rte_nodejs_addon_t* addon_bridge = data;
  assert(addon_bridge &&
         // RTE_NOLINTNEXTLINE(thread-check)
         // thread-check: this function is called from a standalone addon
         // registration thread, and only when all the addons are registered,
         // the RTE world can go on, so it's thread safe.
         rte_nodejs_addon_check_integrity(addon_bridge, false));

  ZF_LOGV("RTE JS addon registration thread starts.");

  if (addon_bridge->is_extension) {
    rte_addon_extension_register(rte_string_c_str(&addon_bridge->addon_name),
                                 &addon_bridge->c_addon);
  } else {
    rte_addon_extension_group_register(
        rte_string_c_str(&addon_bridge->addon_name), &addon_bridge->c_addon);
  }
}

static void rte_nodejs_addon_destroy(rte_nodejs_addon_t* self) {
  assert(self && rte_nodejs_addon_check_integrity(self, true));

  ZF_LOGV("Destroy RTE nodejs addon bridge.");

  rte_nodejs_addon_detach_callbacks(self);

  rte_string_deinit(&self->addon_name);
  rte_sanitizer_thread_check_deinit(&self->thread_check);
  rte_free(self);
}

// This function would be registered into the Javascript world, and be called
// from the Javascript, too. Also wraps rte_nodejs_addon_t with js factory
// object.
static napi_value rte_nodejs_addon_register(napi_env env,
                                            napi_callback_info info) {
  assert(env && info);

  ZF_LOGV("RTE JS addon register.");

  rte_string_t addon_name;
  rte_string_init(&addon_name);

  // Get the arguments passed from the Javascript world.
  const size_t argc = 6;
  napi_value argv[6];  // name, isExtension, factory, onInit, create, destroy
  if (!rte_nodejs_get_args(env, info, argv, argc)) {
    goto done;
  }

  // Extension name could be retrieved from manifest.json, so the name
  // parameter could be omitted.
  if (is_string(env, argv[0])) {
    if (!rte_nodejs_get_str(env, argv[0], &addon_name)) {
      goto done;
    }
  }

  bool is_extension = false;
  napi_status status = napi_get_value_bool(env, argv[1], &is_extension);
  GOTO_LABEL_IF_NAPI_FAIL(done, status == napi_ok,
                          "Failed to get JS boolean: %d", status);

  rte_nodejs_addon_t* addon_bridge = rte_malloc(sizeof(rte_nodejs_addon_t));
  assert(addon_bridge);

  rte_signature_set(&addon_bridge->signature, RTE_NODEJS_ADDON_SIGNATURE);
  rte_sanitizer_thread_check_init_with_current_thread(
      &addon_bridge->thread_check);

  rte_string_copy(&addon_bridge->addon_name, &addon_name);
  addon_bridge->is_extension = is_extension;
  addon_bridge->js_ref = NULL;
  addon_bridge->c_addon.on_init = proxy_addon_on_dynamic_init;
  addon_bridge->c_addon.on_deinit = NULL,
  addon_bridge->c_addon.create_instance = proxy_addon_create_instance;
  addon_bridge->c_addon.destroy_instance = proxy_addon_destroy_instance;
  rte_atomic_store(&addon_bridge->c_addon.user_data, (int64_t)addon_bridge);

  addon_bridge->on_init_done_is_called = false;

  status = napi_wrap(env, argv[2], addon_bridge, rte_nodejs_addon_finalize,
                     NULL, &addon_bridge->js_ref);
  GOTO_LABEL_IF_NAPI_FAIL(error, status == napi_ok,
                          "Failed to bind JS addon & bridge: %d", status);

  // There are two references to this addon_bridge, one is from the JS
  // world, and the other is from the native part. The reference count from the
  // following rte_sharedptr_create() is of JS addon.
  addon_bridge->shared_ptr =
      rte_sharedptr_create(addon_bridge, rte_nodejs_addon_destroy);

  CREATE_JS_CALLBACK(addon_bridge->js_on_init, env, "addon::onInit", argv[3],
                     invoke_addon_on_init);

  CREATE_JS_CALLBACK(addon_bridge->js_on_create_instance, env,
                     "addon::onCreateInstance", argv[4],
                     invoke_addon_create_instance);

  CREATE_JS_CALLBACK(addon_bridge->js_on_destroy_instance, env,
                     "addon::onDestroyInstance", argv[5],
                     invoke_addon_destroy_instance);

  UNUSED int64_t running_count = rte_atomic_add_fetch(&initializing_addons, 1);
  assert(running_count);

  // The reference count from the following rte_sharedptr_inc_rc() is of C
  // addon.
  rte_sharedptr_inc_rc(addon_bridge->shared_ptr);

  // The registration invokes on_init, which calls a threadsafe function in a
  // "blocking" way, which must not be invoked in the main thread.
  uv_thread_t tid = 0;
  uv_thread_create(&tid, register_factory_exec, addon_bridge);

  goto done;

error:
  if (addon_bridge) {
    rte_free(addon_bridge);
  }

done:
  rte_string_deinit(&addon_name);

  return UNDEFINED(env);
}

// This function would be registered into the Javascript world, and be called
// from the Javascript, too. Also wraps rte_nodejs_addon_t with js factory
// object.
static napi_value rte_nodejs_addon_unregister(napi_env env,
                                              napi_callback_info info) {
  assert(env && info);

  ZF_LOGV("RTE JS addon unregister.");

  rte_string_t addon_name;
  rte_string_init(&addon_name);

  const size_t argc = 2;
  napi_value argv[2];  // name, factory
  if (!rte_nodejs_get_args(env, info, argv, argc)) {
    goto done;
  }

  if (!rte_nodejs_get_str(env, argv[0], &addon_name)) {
    goto done;
  }

  // Find and unregister the corresponding addon with the specified name.
  rte_sharedptr_t* addon = rte_addon_find(rte_string_c_str(&addon_name));
  assert(addon);

  rte_addon_t* raw_addon = rte_sharedptr_get_data(addon);
  assert(raw_addon);

  rte_addon_unregister(rte_string_c_str(&addon_name), raw_addon->base.factory);

  rte_sharedptr_dec_rc(addon);

  rte_nodejs_addon_t* addon_bridge = NULL;
  napi_status status = napi_unwrap(env, argv[1], (void**)&addon_bridge);
  ASSERT_IF_NAPI_FAIL(status == napi_ok && addon_bridge != NULL,
                      "Failed to get addon bridge: %d", status);
  assert(addon_bridge && rte_nodejs_addon_check_integrity(addon_bridge, true));

  rte_nodejs_addon_release_callbacks(env, addon_bridge);

  // The JS part is disappear, so decrement the reference count to reflect this
  // fact.
  rte_sharedptr_dec_rc(addon_bridge->shared_ptr);

  // In the Javascript world, the finalizer will remove the corresponding
  // Javascript addon from the Javascript addons container.

done:
  rte_string_deinit(&addon_name);

  return UNDEFINED(env);
}

// This function would be registered into the Javascript world, and be called
// from the Javascript, too. Also wraps rte_nodejs_addon_t with js factory
// object.
static napi_value rte_nodejs_addon_is_ready(napi_env env,
                                            UNUSED napi_callback_info info) {
  int64_t res = rte_atomic_load(&initializing_addons);
  napi_value result = NULL;
  napi_get_boolean(env, res == 0, &result);
  return result;
}

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

  ZF_LOGV("RTE addon on_init() done.");

  const size_t argc = 1;
  napi_value args[argc];  // klass
  if (!rte_nodejs_get_args(env, info, args, argc)) {
    assert(0 && "Should not happen");
  }

  rte_nodejs_addon_t* addon_bridge = NULL;
  napi_status status = napi_unwrap(env, args[0], (void**)&addon_bridge);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok && addon_bridge != NULL,
                                "Failed to get rte bridge: %d", status);
  assert(addon_bridge && rte_nodejs_addon_check_integrity(addon_bridge, true));

  addon_bridge->on_init_done_is_called = true;

  rte_atomic_fetch_sub(&initializing_addons, 1);

  return UNDEFINED(env);
}

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

  EXPORT_FUNC(env, exports, rte_nodejs_addon_register);
  EXPORT_FUNC(env, exports, rte_nodejs_addon_unregister);
  EXPORT_FUNC(env, exports, rte_nodejs_addon_is_ready);
  EXPORT_FUNC(env, exports, rte_nodejs_addon_on_init_done);

  return exports;
}
