#include <js_native_api.h>
#include "../common.h"

static napi_value TestCreateFunctionParameters(napi_env env,
                                               napi_callback_info info) {
  napi_status status;
  napi_value result, return_value;

  NAPI_CALL(env, napi_create_object(env, &return_value));

  status = napi_create_function(NULL,
                                "TrackedFunction",
                                NAPI_AUTO_LENGTH,
                                TestCreateFunctionParameters,
                                NULL,
                                &result);

  add_returned_status(env,
                      "envIsNull",
                      return_value,
                      "Invalid argument",
                      napi_invalid_arg,
                      status);

  napi_create_function(env,
                       NULL,
                       NAPI_AUTO_LENGTH,
                       TestCreateFunctionParameters,
                       NULL,
                       &result);

  add_last_status(env, "nameIsNull", return_value);

  napi_create_function(env,
                       "TrackedFunction",
                       NAPI_AUTO_LENGTH,
                       NULL,
                       NULL,
                       &result);

  add_last_status(env, "cbIsNull", return_value);

  napi_create_function(env,
                       "TrackedFunction",
                       NAPI_AUTO_LENGTH,
                       TestCreateFunctionParameters,
                       NULL,
                       NULL);

  add_last_status(env, "resultIsNull", return_value);

  return return_value;
}

static napi_value TestCallFunction(napi_env env, napi_callback_info info) {
  size_t argc = 10;
  napi_value args[10];
  NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));

  NAPI_ASSERT(env, argc > 0, "Wrong number of arguments");

  napi_valuetype valuetype0;
  NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0));

  NAPI_ASSERT(env, valuetype0 == napi_function,
      "Wrong type of arguments. Expects a function as first argument.");

  napi_value* argv = args + 1;
  argc = argc - 1;

  napi_value global;
  NAPI_CALL(env, napi_get_global(env, &global));

  napi_value result;
  NAPI_CALL(env, napi_call_function(env, global, args[0], argc, argv, &result));

  return result;
}

static napi_value TestFunctionName(napi_env env, napi_callback_info info) {
  return NULL;
}

static void finalize_function(napi_env env, void* data, void* hint) {
  napi_ref ref = data;

  // Retrieve the JavaScript undefined value.
  napi_value undefined;
  NAPI_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined));

  // Retrieve the JavaScript function we must call.
  napi_value js_function;
  NAPI_CALL_RETURN_VOID(env, napi_get_reference_value(env, ref, &js_function));

  // Call the JavaScript function to indicate that the generated JavaScript
  // function is about to be gc-ed.
  NAPI_CALL_RETURN_VOID(env, napi_call_function(env,
                                                undefined,
                                                js_function,
                                                0,
                                                NULL,
                                                NULL));

  // Destroy the persistent reference to the function we just called so as to
  // properly clean up.
  NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, ref));
}

static napi_value MakeTrackedFunction(napi_env env, napi_callback_info info) {
  size_t argc = 1;
  napi_value js_finalize_cb;
  napi_valuetype arg_type;

  // Retrieve and validate from the arguments the function we will use to
  // indicate to JavaScript that the function we are about to create is about to
  // be gc-ed.
  NAPI_CALL(env, napi_get_cb_info(env,
                                  info,
                                  &argc,
                                  &js_finalize_cb,
                                  NULL,
                                  NULL));
  NAPI_ASSERT(env, argc == 1, "Wrong number of arguments");
  NAPI_CALL(env, napi_typeof(env, js_finalize_cb, &arg_type));
  NAPI_ASSERT(env, arg_type == napi_function, "Argument must be a function");

  // Dynamically create a function.
  napi_value result;
  NAPI_CALL(env, napi_create_function(env,
                                      "TrackedFunction",
                                      NAPI_AUTO_LENGTH,
                                      TestFunctionName,
                                      NULL,
                                      &result));

  // Create a strong reference to the function we will call when the tracked
  // function is about to be gc-ed.
  napi_ref js_finalize_cb_ref;
  NAPI_CALL(env, napi_create_reference(env,
                                       js_finalize_cb,
                                       1,
                                       &js_finalize_cb_ref));

  // Attach a finalizer to the dynamically created function and pass it the
  // strong reference we created in the previous step.
  NAPI_CALL(env, napi_wrap(env,
                           result,
                           js_finalize_cb_ref,
                           finalize_function,
                           NULL,
                           NULL));

  return result;
}

static napi_value TestBadReturnExceptionPending(napi_env env, napi_callback_info info) {
  napi_throw_error(env, "throwing exception", "throwing exception");

  // addons should only ever return a valid napi_value even if an
  // exception occurs, but we have seen that the C++ wrapper
  // with exceptions enabled sometimes returns an invalid value
  // when an exception is thrown. Test that we ignore the return
  // value then an exeption is pending. We use 0xFFFFFFFF as a value
  // that should never be a valid napi_value and node seems to
  // crash if it is not ignored indicating that it is indeed invalid.
  return (napi_value)(0xFFFFFFFFF);
}

EXTERN_C_START
napi_value Init(napi_env env, napi_value exports) {
  napi_value fn1;
  NAPI_CALL(env, napi_create_function(
      env, NULL, NAPI_AUTO_LENGTH, TestCallFunction, NULL, &fn1));

  napi_value fn2;
  NAPI_CALL(env, napi_create_function(
      env, "Name", NAPI_AUTO_LENGTH, TestFunctionName, NULL, &fn2));

  napi_value fn3;
  NAPI_CALL(env, napi_create_function(
      env, "Name_extra", 5, TestFunctionName, NULL, &fn3));

  napi_value fn4;
  NAPI_CALL(env, napi_create_function(env,
                                      "MakeTrackedFunction",
                                      NAPI_AUTO_LENGTH,
                                      MakeTrackedFunction,
                                      NULL,
                                      &fn4));

  napi_value fn5;
  NAPI_CALL(env, napi_create_function(env,
                                      "TestCreateFunctionParameters",
                                      NAPI_AUTO_LENGTH,
                                      TestCreateFunctionParameters,
                                      NULL,
                                      &fn5));

  napi_value fn6;
  NAPI_CALL(env,
      napi_create_function(
          env, "TestBadReturnExceptionPending", NAPI_AUTO_LENGTH,
          TestBadReturnExceptionPending, NULL, &fn6));

  NAPI_CALL(env, napi_set_named_property(env, exports, "TestCall", fn1));
  NAPI_CALL(env, napi_set_named_property(env, exports, "TestName", fn2));
  NAPI_CALL(env, napi_set_named_property(env, exports, "TestNameShort", fn3));
  NAPI_CALL(env, napi_set_named_property(env,
                                         exports,
                                         "MakeTrackedFunction",
                                         fn4));

  NAPI_CALL(env, napi_set_named_property(env,
                                         exports,
                                         "TestCreateFunctionParameters",
                                         fn5));

  NAPI_CALL(env,
      napi_set_named_property(
          env, exports, "TestBadReturnExceptionPending", fn6));

  return exports;
}
EXTERN_C_END