/*
 * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "js_runtime_utils.h"

#include "hilog_tag_wrapper.h"
#include "js_runtime.h"
#include "napi/native_api.h"

namespace OHOS {
namespace AbilityRuntime {
namespace {
std::unique_ptr<NapiAsyncTask> CreateAsyncTaskWithLastParam(napi_env env, napi_value lastParam,
    std::unique_ptr<NapiAsyncTask::ExecuteCallback>&& execute,
    std::unique_ptr<NapiAsyncTask::CompleteCallback>&& complete,
    napi_value* result)
{
    napi_valuetype type = napi_undefined;
    napi_typeof(env, lastParam, &type);
    if (lastParam == nullptr || type != napi_function) {
        napi_deferred nativeDeferred = nullptr;
        napi_create_promise(env, &nativeDeferred, result);
        return std::make_unique<NapiAsyncTask>(nativeDeferred, std::move(execute), std::move(complete));
    } else {
        napi_get_undefined(env, result);
        napi_ref callbackRef = nullptr;
        napi_create_reference(env, lastParam, 1, &callbackRef);
        return std::make_unique<NapiAsyncTask>(callbackRef, std::move(execute), std::move(complete));
    }
}
} // namespace

// Help Functions
napi_value CreateJsError(napi_env env, int32_t errCode, const std::string& message)
{
    napi_value result = nullptr;
    napi_create_error(env, CreateJsValue(env, errCode), CreateJsValue(env, message), &result);
    return result;
}

void BindNativeFunction(napi_env env, napi_value object, const char* name,
    const char* moduleName, napi_callback func)
{
    std::string fullName(moduleName);
    fullName += ".";
    fullName += name;
    napi_value result = nullptr;
    napi_create_function(env, fullName.c_str(), fullName.length(), func, nullptr, &result);
    napi_set_named_property(env, object, name, result);
}

void BindNativeProperty(napi_env env, napi_value object, const char* name, napi_callback getter)
{
    napi_property_descriptor properties[1];
    properties[0].utf8name = name;
    properties[0].name = nullptr;
    properties[0].method = nullptr;
    properties[0].getter = getter;
    properties[0].setter = nullptr;
    properties[0].value = nullptr;
    properties[0].attributes = napi_default;
    properties[0].data = nullptr;
    napi_define_properties(env, object, 1, properties);
}

void* GetNativePointerFromCallbackInfo(napi_env env, napi_callback_info info, const char* name)
{
    size_t argcAsync = ARGC_MAX_COUNT;
    napi_value args[ARGC_MAX_COUNT] = {nullptr};
    napi_value thisVar = nullptr;
    NAPI_CALL_NO_THROW(napi_get_cb_info(env, info, &argcAsync, args, &thisVar, nullptr), nullptr);
    if (name != nullptr) {
        napi_get_named_property(env, thisVar, name, &thisVar);
    }
    void* result = nullptr;
    NAPI_CALL_NO_THROW(napi_unwrap(env, thisVar, &result), nullptr);
    return result;
}

void* GetCbInfoFromCallbackInfo(napi_env env, napi_callback_info info, size_t* argc, napi_value* argv)
{
    napi_value thisVar = nullptr;
    NAPI_CALL_NO_THROW(napi_get_cb_info(env, info, argc, argv, &thisVar, nullptr), nullptr);
    void* result = nullptr;
    NAPI_CALL_NO_THROW(napi_unwrap(env, thisVar, &result), nullptr);
    return result;
}

void* GetNapiCallbackInfoAndThis(napi_env env, napi_callback_info info, NapiCallbackInfo& napiInfo, const char* name)
{
    NAPI_CALL_NO_THROW(napi_get_cb_info(
        env, info, &napiInfo.argc, napiInfo.argv, &napiInfo.thisVar, nullptr), nullptr);
    napi_value value = napiInfo.thisVar;
    if (name != nullptr) {
        napi_get_named_property(env, value, name, &value);
    }
    void* result = nullptr;
    NAPI_CALL_NO_THROW(napi_unwrap(env, value, &result), nullptr);
    return result;
}

void SetNamedNativePointer(napi_env env, napi_value object, const char* name, void* ptr, napi_finalize func)
{
    napi_value objValue = nullptr;
    napi_create_object(env, &objValue);
    napi_wrap(env, objValue, ptr, func, nullptr, nullptr);
    napi_set_named_property(env, object, name, objValue);
}

void* GetNamedNativePointer(napi_env env, napi_value object, const char* name)
{
    napi_value proValue = nullptr;
    napi_get_named_property(env, object, name, &proValue);
    void* result = nullptr;
    napi_unwrap(env, proValue, &result);
    return result;
}

bool CheckTypeForNapiValue(napi_env env, napi_value param, napi_valuetype expectType)
{
    napi_valuetype valueType = napi_undefined;
    if (napi_typeof(env, param, &valueType) != napi_ok) {
        return false;
    }
    return valueType == expectType;
}

// Handle Scope
HandleScope::HandleScope(JsRuntime& jsRuntime)
{
    env_ = (napi_env)jsRuntime.GetNativeEnginePointer();
    napi_open_handle_scope(env_, &scope_);
}

HandleScope::HandleScope(napi_env env)
{
    env_ = env;
    napi_open_handle_scope(env_, &scope_);
}

HandleScope::~HandleScope()
{
    napi_close_handle_scope(env_, scope_);
}

// Handle Escape
HandleEscape::HandleEscape(JsRuntime& jsRuntime)
{
    env_ = (napi_env)jsRuntime.GetNativeEnginePointer();
    napi_open_escapable_handle_scope(env_, &scope_);
}

HandleEscape::HandleEscape(napi_env env)
{
    env_ = env;
    napi_open_escapable_handle_scope(env_, &scope_);
}

HandleEscape::~HandleEscape()
{
    napi_close_escapable_handle_scope(env_, scope_);
}

napi_value HandleEscape::Escape(napi_value value)
{
    napi_value result = nullptr;
    napi_escape_handle(env_, scope_, value, &result);
    return result;
}

// Async Task
NapiAsyncTask::NapiAsyncTask(napi_deferred deferred, std::unique_ptr<NapiAsyncTask::ExecuteCallback>&& execute,
    std::unique_ptr<NapiAsyncTask::CompleteCallback>&& complete)
    : deferred_(deferred), execute_(std::move(execute)), complete_(std::move(complete))
{}

NapiAsyncTask::NapiAsyncTask(napi_ref callbackRef, std::unique_ptr<NapiAsyncTask::ExecuteCallback>&& execute,
    std::unique_ptr<NapiAsyncTask::CompleteCallback>&& complete)
    : callbackRef_(callbackRef), execute_(std::move(execute)), complete_(std::move(complete))
{}

NapiAsyncTask::~NapiAsyncTask()
{
    if (work_ && env_) {
        napi_delete_async_work(env_, work_);
        work_ = nullptr;
    }
}

void NapiAsyncTask::Schedule(const std::string &name, napi_env env, std::unique_ptr<NapiAsyncTask>&& task)
{
    if (task && task->Start(name, env)) {
        task.release();
    }
}

void NapiAsyncTask::ScheduleHighQos(const std::string &name, napi_env env, std::unique_ptr<NapiAsyncTask>&& task)
{
    if (task && task->StartHighQos(name, env)) {
        task.release();
    }
}

void NapiAsyncTask::ScheduleLowQos(const std::string &name, napi_env env, std::unique_ptr<NapiAsyncTask>&& task)
{
    if (task && task->StartLowQos(name, env)) {
        task.release();
    }
}

void NapiAsyncTask::ScheduleWithDefaultQos(const std::string &name, napi_env env, std::unique_ptr<NapiAsyncTask>&& task)
{
    if (task && task->StartWithDefaultQos(name, env)) {
        task.release();
    }
}

bool NapiAsyncTask::StartWithDefaultQos(const std::string &name, napi_env env)
{
    if (work_) {
        napi_delete_async_work(env, work_);
        work_ = nullptr;
    }
    if (env == nullptr) {
        return false;
    }
    NativeEngine* engine = reinterpret_cast<NativeEngine*>(env);
    work_ = reinterpret_cast<napi_async_work>(engine->CreateAsyncWork(name,
        reinterpret_cast<NativeAsyncExecuteCallback>(Execute),
        reinterpret_cast<NativeAsyncCompleteCallback>(Complete), this));
    napi_queue_async_work_with_qos(env, work_, napi_qos_default);
    return true;
}

void NapiAsyncTask::Resolve(napi_env env, napi_value value)
{
    TAG_LOGD(AAFwkTag::JSRUNTIME, "called");
    if (deferred_) {
        napi_resolve_deferred(env, deferred_, value);
        deferred_ = nullptr;
    }
    if (callbackRef_) {
        napi_value argv[] = {
            CreateJsError(env, 0),
            value,
        };
        napi_value func = nullptr;
        napi_get_reference_value(env, callbackRef_, &func);
        napi_call_function(env, CreateJsUndefined(env), func, ArraySize(argv), argv, nullptr);
        napi_delete_reference(env, callbackRef_);
        callbackRef_ = nullptr;
    }
}

void NapiAsyncTask::ResolveWithNoError(napi_env env, napi_value value)
{
    TAG_LOGD(AAFwkTag::JSRUNTIME, "called");
    if (deferred_) {
        napi_resolve_deferred(env, deferred_, value);
        deferred_ = nullptr;
    }
    if (callbackRef_) {
        napi_value argv[] = {
            CreateJsNull(env),
            value,
        };
        napi_value func = nullptr;
        napi_get_reference_value(env, callbackRef_, &func);
        napi_call_function(env, CreateJsUndefined(env), func, ArraySize(argv), argv, nullptr);
        napi_delete_reference(env, callbackRef_);
        callbackRef_ = nullptr;
    }
}

void NapiAsyncTask::Reject(napi_env env, napi_value error)
{
    if (deferred_) {
        napi_reject_deferred(env, deferred_, error);
        deferred_ = nullptr;
    }
    if (callbackRef_) {
        napi_value argv[] = {
            error,
            CreateJsUndefined(env),
        };
        napi_value func = nullptr;
        napi_get_reference_value(env, callbackRef_, &func);
        napi_call_function(env, CreateJsUndefined(env), func, ArraySize(argv), argv, nullptr);
        napi_delete_reference(env, callbackRef_);
        callbackRef_ = nullptr;
    }
}

void NapiAsyncTask::ResolveWithCustomize(napi_env env, napi_value error, napi_value value)
{
    TAG_LOGD(AAFwkTag::JSRUNTIME, "called");
    if (deferred_) {
        napi_resolve_deferred(env, deferred_, value);
        deferred_ = nullptr;
    }
    if (callbackRef_) {
        napi_value argv[] = {
            error,
            value,
        };
        napi_value func = nullptr;
        napi_get_reference_value(env, callbackRef_, &func);
        napi_call_function(env, CreateJsUndefined(env), func, ArraySize(argv), argv, nullptr);
        napi_delete_reference(env, callbackRef_);
        callbackRef_ = nullptr;
    }
}

void NapiAsyncTask::RejectWithCustomize(napi_env env, napi_value error, napi_value value)
{
    TAG_LOGD(AAFwkTag::JSRUNTIME, "called");
    if (deferred_) {
        napi_reject_deferred(env, deferred_, error);
        deferred_ = nullptr;
    }
    if (callbackRef_) {
        napi_value argv[] = {
            error,
            value,
        };
        napi_value func = nullptr;
        napi_get_reference_value(env, callbackRef_, &func);
        napi_call_function(env, CreateJsUndefined(env), func, ArraySize(argv), argv, nullptr);
        napi_delete_reference(env, callbackRef_);
        callbackRef_ = nullptr;
    }
}

void NapiAsyncTask::Execute(napi_env env, void* data)
{
    if (data == nullptr) {
        return;
    }
    auto me = static_cast<NapiAsyncTask*>(data);
    if (me->execute_ && *(me->execute_)) {
        (*me->execute_)();
    }
}

void NapiAsyncTask::Complete(napi_env env, napi_status status, void* data)
{
    TAG_LOGD(AAFwkTag::JSRUNTIME, "called");
    if (data == nullptr) {
        return;
    }
    std::unique_ptr<NapiAsyncTask> me(static_cast<NapiAsyncTask*>(data));
    if (me->complete_ && *(me->complete_)) {
        HandleScope handleScope(env);
        (*me->complete_)(env, *me, static_cast<int32_t>(status));
    }
}

bool NapiAsyncTask::Start(const std::string &name, napi_env env)
{
    if (work_) {
        napi_delete_async_work(env, work_);
        work_ = nullptr;
    }
    if (env == nullptr) {
        return false;
    }
    env_ = env;
    NativeEngine* engine = reinterpret_cast<NativeEngine*>(env);
    work_ = reinterpret_cast<napi_async_work>(engine->CreateAsyncWork(name,
        reinterpret_cast<NativeAsyncExecuteCallback>(Execute),
        reinterpret_cast<NativeAsyncCompleteCallback>(Complete), this));
    napi_queue_async_work(env, work_);
    return true;
}

bool NapiAsyncTask::StartHighQos(const std::string &name, napi_env env)
{
    if (work_) {
        napi_delete_async_work(env, work_);
        work_ = nullptr;
    }
    if (env == nullptr) {
        return false;
    }
    NativeEngine* engine = reinterpret_cast<NativeEngine*>(env);
    work_ = reinterpret_cast<napi_async_work>(engine->CreateAsyncWork(name,
        reinterpret_cast<NativeAsyncExecuteCallback>(Execute),
        reinterpret_cast<NativeAsyncCompleteCallback>(Complete), this));
    napi_queue_async_work_with_qos(env, work_, napi_qos_user_initiated);
    return true;
}

bool NapiAsyncTask::StartLowQos(const std::string &name, napi_env env)
{
    if (work_) {
        napi_delete_async_work(env, work_);
        work_ = nullptr;
    }
    if (env == nullptr) {
        return false;
    }
    NativeEngine* engine = reinterpret_cast<NativeEngine*>(env);
    work_ = reinterpret_cast<napi_async_work>(engine->CreateAsyncWork(name,
        reinterpret_cast<NativeAsyncExecuteCallback>(Execute),
        reinterpret_cast<NativeAsyncCompleteCallback>(Complete), this));
    napi_queue_async_work_with_qos(env, work_, napi_qos_utility);
    return true;
}

std::unique_ptr<NapiAsyncTask> CreateAsyncTaskWithLastParam(napi_env env, napi_value lastParam,
    NapiAsyncTask::ExecuteCallback&& execute, NapiAsyncTask::CompleteCallback&& complete, napi_value* result)
{
    return CreateAsyncTaskWithLastParam(env, lastParam,
        std::make_unique<NapiAsyncTask::ExecuteCallback>(std::move(execute)),
        std::make_unique<NapiAsyncTask::CompleteCallback>(std::move(complete)), result);
}

std::unique_ptr<NapiAsyncTask> CreateAsyncTaskWithLastParam(napi_env env, napi_value lastParam,
    NapiAsyncTask::ExecuteCallback&& execute, nullptr_t, napi_value* result)
{
    return CreateAsyncTaskWithLastParam(
        env, lastParam, std::make_unique<NapiAsyncTask::ExecuteCallback>(std::move(execute)), nullptr, result);
}

std::unique_ptr<NapiAsyncTask> CreateAsyncTaskWithLastParam(napi_env env, napi_value lastParam,
    nullptr_t, NapiAsyncTask::CompleteCallback&& complete, napi_value* result)
{
    return CreateAsyncTaskWithLastParam(
        env, lastParam, nullptr, std::make_unique<NapiAsyncTask::CompleteCallback>(std::move(complete)), result);
}

std::unique_ptr<NapiAsyncTask> CreateAsyncTaskWithLastParam(napi_env env, napi_value lastParam,
    nullptr_t, nullptr_t, napi_value* result)
{
    return CreateAsyncTaskWithLastParam(env, lastParam, std::unique_ptr<NapiAsyncTask::ExecuteCallback>(),
        std::unique_ptr<NapiAsyncTask::CompleteCallback>(), result);
}

std::unique_ptr<NapiAsyncTask> CreateEmptyAsyncTask(napi_env env, napi_value lastParam, napi_value* result)
{
    napi_valuetype type = napi_undefined;
    napi_typeof(env, lastParam, &type);
    if (lastParam == nullptr || type != napi_function) {
        napi_deferred nativeDeferred = nullptr;
        napi_create_promise(env, &nativeDeferred, result);
        return std::make_unique<NapiAsyncTask>(nativeDeferred, std::unique_ptr<NapiAsyncTask::ExecuteCallback>(),
            std::unique_ptr<NapiAsyncTask::CompleteCallback>());
    } else {
        napi_get_undefined(env, result);
        napi_ref callbackRef = nullptr;
        napi_create_reference(env, lastParam, 1, &callbackRef);
        return std::make_unique<NapiAsyncTask>(callbackRef, std::unique_ptr<NapiAsyncTask::ExecuteCallback>(),
            std::unique_ptr<NapiAsyncTask::CompleteCallback>());
    }
}
}  // namespace AbilityRuntime
}  // namespace OHOS