/*
 * Copyright (c) 2022 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 "webview_javascript_execute_callback.h"

#include "business_error.h"
#include "napi_parse_utils.h"
#include "nweb_napi_scope.h"
#include "nweb_log.h"
#include "web_errors.h"
#include "nweb_message_ext.h"

namespace OHOS::NWeb {
using namespace NWebError;
const std::string JS_EXCUTE_MSG_ENUM_NAME = "JsMessageType";
const std::string JS_EXT_MSG_CLASS_NAME = "JsMessageExt";
thread_local napi_ref g_jsMsgExtClassRef;
// static
void WebviewJavaScriptExecuteCallback::InitJSExcute(napi_env env, napi_value exports)
{
    napi_value jsMsgTypeEnum = nullptr;
    napi_property_descriptor jsMsgTypeProperties[] = {
        DECLARE_NAPI_STATIC_PROPERTY("NOT_SUPPORT", NapiParseUtils::ToInt32Value(env,
            static_cast<int32_t>(JsMessageType::NOTSUPPORT))),
        DECLARE_NAPI_STATIC_PROPERTY("STRING", NapiParseUtils::ToInt32Value(env,
            static_cast<int32_t>(JsMessageType::STRING))),
        DECLARE_NAPI_STATIC_PROPERTY("NUMBER", NapiParseUtils::ToInt32Value(env,
            static_cast<int32_t>(JsMessageType::NUMBER))),
        DECLARE_NAPI_STATIC_PROPERTY("BOOLEAN", NapiParseUtils::ToInt32Value(env,
            static_cast<int32_t>(JsMessageType::BOOLEAN))),
        DECLARE_NAPI_STATIC_PROPERTY("ARRAY_BUFFER", NapiParseUtils::ToInt32Value(env,
            static_cast<int32_t>(JsMessageType::ARRAYBUFFER))),
        DECLARE_NAPI_STATIC_PROPERTY("ARRAY", NapiParseUtils::ToInt32Value(env,
            static_cast<int32_t>(JsMessageType::ARRAY)))
    };
    napi_define_class(env, JS_EXCUTE_MSG_ENUM_NAME.c_str(), JS_EXCUTE_MSG_ENUM_NAME.length(),
        NapiParseUtils::CreateEnumConstructor, nullptr, sizeof(jsMsgTypeProperties) /
        sizeof(jsMsgTypeProperties[0]), jsMsgTypeProperties, &jsMsgTypeEnum);
    napi_set_named_property(env, exports, JS_EXCUTE_MSG_ENUM_NAME.c_str(), jsMsgTypeEnum);

    napi_value jsMsgExtClass = nullptr;
    napi_property_descriptor jsMsgExtClsProperties[] = {
        DECLARE_NAPI_FUNCTION("getType", NapiJsMessageExt::GetType),
        DECLARE_NAPI_FUNCTION("getString", NapiJsMessageExt::GetString),
        DECLARE_NAPI_FUNCTION("getNumber", NapiJsMessageExt::GetNumber),
        DECLARE_NAPI_FUNCTION("getBoolean", NapiJsMessageExt::GetBoolean),
        DECLARE_NAPI_FUNCTION("getArrayBuffer", NapiJsMessageExt::GetArrayBuffer),
        DECLARE_NAPI_FUNCTION("getArray", NapiJsMessageExt::GetArray),
        DECLARE_NAPI_FUNCTION("getErrorDescription", NapiJsMessageExt::GetErrorDescription)
    };
    napi_define_class(env, JS_EXT_MSG_CLASS_NAME.c_str(), JS_EXT_MSG_CLASS_NAME.length(),
        NapiJsMessageExt::JsConstructor, nullptr, sizeof(jsMsgExtClsProperties) / sizeof(jsMsgExtClsProperties[0]),
        jsMsgExtClsProperties, &jsMsgExtClass);
    napi_create_reference(env, jsMsgExtClass, 1, &g_jsMsgExtClassRef);
    napi_set_named_property(env, exports, JS_EXT_MSG_CLASS_NAME.c_str(), jsMsgExtClass);
}

void WebviewJavaScriptExecuteCallback::OnReceiveValue(std::shared_ptr<NWebMessage> result)
{
    WVLOG_D("WebviewJavaScriptExecuteCallback::OnReceiveValue start");
    uv_loop_s *loop = nullptr;
    uv_work_t *work = nullptr;

    napi_get_uv_event_loop(env_, &loop);
    if (loop == nullptr) {
        return;
    }
    work = new (std::nothrow) uv_work_t;
    if (work == nullptr) {
        return;
    }

    JavaScriptExecuteParam *param = new (std::nothrow) JavaScriptExecuteParam();
    if (param == nullptr) {
        delete work;
        return;
    }
    param->env_ = env_;
    param->callbackRef_ = callbackRef_;
    param->deferred_ = deferred_;
    param->result_ = result;
    param->extention_ = extention_;

    work->data = reinterpret_cast<void*>(param);

    int ret = uv_queue_work_with_qos_internal(loop, work, [](uv_work_t *work) {}, UvAfterWorkCb, uv_qos_user_initiated,
                                              "WebviewJavaScript_napi");
    if (ret != 0) {
        if (param != nullptr) {
            delete param;
            param = nullptr;
        }
        if (work != nullptr) {
            delete work;
            work = nullptr;
        }
    }
}

void WebviewJavaScriptExecuteCallback::OnReceiveValueV2(std::shared_ptr<NWebHapValue> result)
{
    WVLOG_D("WebviewJavaScriptExecuteCallback::OnReceiveValue2 start");
    std::shared_ptr<NWebMessage> message = ConvertNwebHap2NwebMessage(result);
    OnReceiveValue(message);
}

void WebviewJavaScriptExecuteCallback::UvAfterWorkCb(uv_work_t* work, int status)
{
    WVLOG_D("WebviewJavaScriptExecuteCallback::UvAfterWorkCb");
    (void)status;
    if (!work) {
        return;
    }
    JavaScriptExecuteParam *param = reinterpret_cast<JavaScriptExecuteParam*>(work->data);
    if (!param) {
        delete work;
        work = nullptr;
        return;
    }
    NApiScope scope(param->env_);
    if (!scope.IsVaild()) {
        return;
    }

    if (param->callbackRef_) {
        UvAfterWorkCbAsync(param->env_, param->callbackRef_, param->result_, param->extention_);
    } else if (param->deferred_) {
        UvAfterWorkCbPromise(param->env_, param->deferred_, param->result_, param->extention_);
    }

    delete param;
    param = nullptr;
    delete work;
    work = nullptr;
}

void WebviewJavaScriptExecuteCallback::UvAfterWorkCbAsync(napi_env env, napi_ref callbackRef,
    std::shared_ptr<NWebMessage> result, bool extention)
{
    WVLOG_D("WebviewJavaScriptExecuteCallback::UvAfterWorkCbAsync");
    napi_value setResult[INTEGER_TWO] = {0};
    if (result->GetType() == NWebValue::Type::STRING && result->GetString().empty()) {
        setResult[INTEGER_ZERO] = BusinessError::CreateError(env, NWebError::INVALID_RESOURCE,
            NWebError::FormatString(ParamCheckErrorMsgTemplate::JS_RETURN_EMPTY_ARRAYBUFFER));
        napi_get_null(env, &setResult[INTEGER_ONE]);
    } else {
        napi_get_undefined(env, &setResult[INTEGER_ZERO]);
        if (!extention) {
            OHOS::NWeb::NapiParseUtils::ConvertNWebToNapiValue(env, result, setResult[INTEGER_ONE]);
        } else {
            napi_value jsMsgExt = nullptr;
            NAPI_CALL_RETURN_VOID(env, napi_get_reference_value(env, g_jsMsgExtClassRef, &jsMsgExt));
            NAPI_CALL_RETURN_VOID(env, napi_new_instance(env, jsMsgExt, 0, NULL, &setResult[INTEGER_ONE]));

            WebJsMessageExt *webJsMessageExt = new (std::nothrow) WebJsMessageExt(result);
            if (webJsMessageExt == nullptr) {
                WVLOG_E("new WebJsMessageExt failed.");
                return;
            }

            napi_status status = napi_wrap(env, setResult[INTEGER_ONE], webJsMessageExt,
                [](napi_env env, void *data, void *hint) {
                    WebJsMessageExt *webJsMessageExt = static_cast<WebJsMessageExt *>(data);
                    delete webJsMessageExt;
                    webJsMessageExt = nullptr;
                },
                nullptr, nullptr);
            if (status != napi_status::napi_ok) {
                WVLOG_E("napi_wrap failed");
                delete webJsMessageExt;
                webJsMessageExt = nullptr;
                return;
            }
        }
    }
    napi_value args[INTEGER_TWO] = {setResult[INTEGER_ZERO], setResult[INTEGER_ONE]};
    napi_value callback = nullptr;
    napi_value callbackResult = nullptr;

    napi_get_reference_value(env, callbackRef, &callback);
    napi_call_function(env, nullptr, callback, INTEGER_TWO, args, &callbackResult);
    napi_delete_reference(env, callbackRef);
}

void WebviewJavaScriptExecuteCallback::UvAfterWorkCbPromise(napi_env env, napi_deferred deferred,
    std::shared_ptr<NWebMessage> result, bool extention)
{
    WVLOG_D("WebviewJavaScriptExecuteCallback::UvAfterWorkCbPromise");
    napi_value setResult[INTEGER_TWO] = {0};
    setResult[INTEGER_ZERO] = NWebError::BusinessError::CreateError(env, NWebError::INVALID_RESOURCE,
        NWebError::FormatString(ParamCheckErrorMsgTemplate::JS_RETURN_EMPTY_ARRAYBUFFER));
    if (!extention) {
        OHOS::NWeb::NapiParseUtils::ConvertNWebToNapiValue(env, result, setResult[INTEGER_ONE]);
    } else {
        napi_value jsMsgExt = nullptr;
        napi_status status = napi_get_reference_value(env, g_jsMsgExtClassRef, &jsMsgExt);
        if (status != napi_status::napi_ok) {
            WVLOG_E("napi_get_reference_value failed.");
            return;
        }
        status = napi_new_instance(env, jsMsgExt, 0, NULL, &setResult[INTEGER_ONE]);
        if (status != napi_status::napi_ok) {
            WVLOG_E("napi_new_instance failed.");
            return;
        }
        WebJsMessageExt *webJsMessageExt = new (std::nothrow) WebJsMessageExt(result);
        if (webJsMessageExt == nullptr) {
            WVLOG_E("new WebJsMessageExt failed.");
            return;
        }

        status = napi_wrap(env, setResult[INTEGER_ONE], webJsMessageExt,
            [](napi_env env, void *data, void *hint) {
                WebJsMessageExt *webJsMessageExt = static_cast<WebJsMessageExt *>(data);
                delete webJsMessageExt;
                webJsMessageExt = nullptr;
            },
            nullptr, nullptr);
        if (status != napi_status::napi_ok) {
            WVLOG_E("napi_wrap failed.");
            return;
        }
    }

    napi_value args[INTEGER_TWO] = {setResult[INTEGER_ZERO], setResult[INTEGER_ONE]};
    if (result->GetType() == NWebValue::Type::STRING && result->GetString().empty()) {
        napi_reject_deferred(env, deferred, args[INTEGER_ZERO]);
    } else {
        napi_resolve_deferred(env, deferred, args[INTEGER_ONE]);
    }
}

napi_value NapiJsMessageExt::JsConstructor(napi_env env, napi_callback_info info)
{
    napi_value thisVar = nullptr;
    napi_get_cb_info(env, info, nullptr, nullptr, &thisVar, nullptr);
    return thisVar;
}

int32_t WebJsMessageExt::ConvertToJsType(NWebValue::Type type)
{
    JsMessageType jsMessageType = JsMessageType::NOTSUPPORT;
    switch (type) {
        case NWebValue::Type::STRING:
            jsMessageType = JsMessageType::STRING;
            break;
        case NWebValue::Type::INTEGER:
        case NWebValue::Type::DOUBLE:
            jsMessageType = JsMessageType::NUMBER;
            break;
        case NWebValue::Type::BOOLEAN:
            jsMessageType = JsMessageType::BOOLEAN;
            break;
        case NWebValue::Type::BINARY:
            jsMessageType = JsMessageType::ARRAYBUFFER;
            break;
        case NWebValue::Type::STRINGARRAY:
        case NWebValue::Type::BOOLEANARRAY:
        case NWebValue::Type::DOUBLEARRAY:
        case NWebValue::Type::INT64ARRAY:
            jsMessageType = JsMessageType::ARRAY;
            break;
        default:
            jsMessageType = JsMessageType::NOTSUPPORT;
            break;
    }
    return static_cast<int32_t>(jsMessageType);
}

int32_t WebJsMessageExt::GetType()
{
    if (value_) {
        return ConvertToJsType(value_->GetType());
    }
    return static_cast<int32_t>(JsMessageType::NOTSUPPORT);
}

std::string WebJsMessageExt::GetString()
{
    if (value_) {
        return value_->GetString();
    }
    return "";
}

double WebJsMessageExt::GetNumber()
{
    if (value_) {
        return value_->GetDouble();
    }
    return 0;
}

bool WebJsMessageExt::GetBoolean()
{
    if (value_) {
        return value_->GetBoolean();
    }
    return false;
}

napi_value NapiJsMessageExt::GetType(napi_env env, napi_callback_info info)
{
    WVLOG_D("GetType webJsMessageExt start");
    napi_value thisVar = nullptr;
    napi_value result = nullptr;
    size_t argc = INTEGER_ONE;
    napi_value argv[INTEGER_ONE] = { 0 };

    WebJsMessageExt *webJsMessageExt = nullptr;
    napi_status status = napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr);
    if (status != napi_status::napi_ok) {
        WVLOG_E("napi_get_cb_info failed.");
        return result;
    }
    status = napi_unwrap(env, thisVar, (void **)&webJsMessageExt);
    if (status != napi_status::napi_ok) {
        WVLOG_E("napi_unwrap failed.");
        return result;
    }
    if (webJsMessageExt == nullptr) {
        WVLOG_E("unwrap webJsMessageExt failed.");
        return result;
    }

    int32_t type = webJsMessageExt->GetType();
    status = napi_create_int32(env, type, &result);
    if (status != napi_status::napi_ok) {
        WVLOG_E("napi_create_int32 failed.");
        return result;
    }
    return result;
}

napi_value NapiJsMessageExt::GetString(napi_env env, napi_callback_info info)
{
    WVLOG_D("GetString webJsMessageExt");
    napi_value thisVar = nullptr;
    napi_value result = nullptr;
    size_t argc = INTEGER_ONE;
    napi_value argv[INTEGER_ONE] = { 0 };

    WebJsMessageExt *webJsMessageExt = nullptr;
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
    NAPI_CALL(env, napi_unwrap(env, thisVar, (void **)&webJsMessageExt));
    if (webJsMessageExt == nullptr) {
        WVLOG_E("unwrap webJsMessageExt failed.");
        return result;
    }

    if (webJsMessageExt->GetType() != static_cast<int32_t>(JsMessageType::STRING)) {
        BusinessError::ThrowErrorByErrcode(env, TYPE_NOT_MATCH_WITCH_VALUE);
        return nullptr;
    }

    NapiParseUtils::ConvertNWebToNapiValue(env, webJsMessageExt->GetJsMsgResult(), result);
    return result;
}

napi_value NapiJsMessageExt::GetNumber(napi_env env, napi_callback_info info)
{
    WVLOG_D("GetNumber webJsMessageExt");
    napi_value thisVar = nullptr;
    napi_value result = nullptr;
    size_t argc = INTEGER_ONE;
    napi_value argv[INTEGER_ONE] = { 0 };

    WebJsMessageExt *webJsMessageExt = nullptr;
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
    NAPI_CALL(env, napi_unwrap(env, thisVar, (void **)&webJsMessageExt));
    if (webJsMessageExt == nullptr) {
        WVLOG_E("unwrap webJsMessageExt failed.");
        return result;
    }

    if (webJsMessageExt->GetType() != static_cast<int32_t>(JsMessageType::NUMBER)) {
        BusinessError::ThrowErrorByErrcode(env, TYPE_NOT_MATCH_WITCH_VALUE);
        WVLOG_E("GetNumber webJsMessageExt failed not match");
        return nullptr;
    }

    NapiParseUtils::ConvertNWebToNapiValue(env, webJsMessageExt->GetJsMsgResult(), result);
    return result;
}

napi_value NapiJsMessageExt::GetBoolean(napi_env env, napi_callback_info info)
{
    napi_value thisVar = nullptr;
    napi_value result = nullptr;
    size_t argc = INTEGER_ONE;
    napi_value argv[INTEGER_ONE] = { 0 };

    WebJsMessageExt *webJsMessageExt = nullptr;
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
    NAPI_CALL(env, napi_unwrap(env, thisVar, (void **)&webJsMessageExt));
    if (webJsMessageExt == nullptr) {
        WVLOG_E("unwrap webJsMessageExt failed.");
        return result;
    }

    if (webJsMessageExt->GetType() != static_cast<int32_t>(JsMessageType::BOOLEAN)) {
        BusinessError::ThrowErrorByErrcode(env, TYPE_NOT_MATCH_WITCH_VALUE);
        return nullptr;
    }

    NapiParseUtils::ConvertNWebToNapiValue(env, webJsMessageExt->GetJsMsgResult(), result);
    return result;
}

napi_value NapiJsMessageExt::GetArrayBuffer(napi_env env, napi_callback_info info)
{
    napi_value thisVar = nullptr;
    napi_value result = nullptr;
    size_t argc = INTEGER_ONE;
    napi_value argv[INTEGER_ONE] = { 0 };

    WebJsMessageExt *webJsMessageExt = nullptr;
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
    NAPI_CALL(env, napi_unwrap(env, thisVar, (void **)&webJsMessageExt));
    if (webJsMessageExt == nullptr) {
        WVLOG_E("unwrap webJsMessageExt failed.");
        return result;
    }

    if (webJsMessageExt->GetType() != static_cast<int32_t>(JsMessageType::ARRAYBUFFER)) {
        BusinessError::ThrowErrorByErrcode(env, TYPE_NOT_MATCH_WITCH_VALUE);
        return nullptr;
    }
    NapiParseUtils::ConvertNWebToNapiValue(env, webJsMessageExt->GetJsMsgResult(), result);
    return result;
}

napi_value NapiJsMessageExt::GetArray(napi_env env, napi_callback_info info)
{
    napi_value thisVar = nullptr;
    napi_value result = nullptr;
    size_t argc = INTEGER_ONE;
    napi_value argv[INTEGER_ONE] = { 0 };

    WebJsMessageExt *webJsMessageExt = nullptr;
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
    NAPI_CALL(env, napi_unwrap(env, thisVar, (void **)&webJsMessageExt));
    if (webJsMessageExt == nullptr) {
        WVLOG_E("unwrap webJsMessageExt failed.");
        return result;
    }

    if (webJsMessageExt->GetType() != static_cast<int32_t>(JsMessageType::ARRAY)) {
        BusinessError::ThrowErrorByErrcode(env, TYPE_NOT_MATCH_WITCH_VALUE);
        return nullptr;
    }

    NapiParseUtils::ConvertNWebToNapiValue(env, webJsMessageExt->GetJsMsgResult(), result);
    return result;
}

napi_value NapiJsMessageExt::GetErrorDescription(napi_env env, napi_callback_info info)
{
    napi_value thisVar = nullptr;
    napi_value result = nullptr;
    size_t argc = INTEGER_ONE;
    napi_value argv[INTEGER_ONE] = { 0 };

    WebJsMessageExt *webJsMessageExt = nullptr;
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
    NAPI_CALL(env, napi_unwrap(env, thisVar, (void **)&webJsMessageExt));
    if (webJsMessageExt == nullptr) {
        WVLOG_E("unwrap webJsMessageExt failed.");
        return result;
    }

    std::string msgStr = webJsMessageExt->GetJsMsgResult()->GetErrorDescription();
    if (!msgStr.empty()) {
        napi_create_string_utf8(env, msgStr.c_str(), msgStr.length(), &result);
    } else {
        napi_get_null(env, &result);
    }
    return result;
}

} // namespace OHOS::NWeb