/*
 * 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 "js_inputmethod_extension.h"

#include "ability_handler.h"
#include "ability_info.h"
#include "configuration_utils.h"
#include "display_info.h"
#include "global.h"
#include "input_method_ability.h"
#include "inputmethod_extension_ability_service_impl.h"
#include "inputmethod_trace.h"
#include "js_extension_context.h"
#include "js_inputmethod_extension_context.h"
#include "js_runtime.h"
#include "js_runtime_utils.h"
#include "napi/native_api.h"
#include "napi/native_node_api.h"
#include "napi_common_util.h"
#include "napi_common_want.h"
#include "napi_remote_object.h"
#include "parameters.h"
#include "tasks/task_ams.h"
#include "tasks/task_imsa.h"
#include "task_manager.h"

namespace OHOS {
namespace AbilityRuntime {
namespace {
constexpr size_t ARGC_ONE = 1;
constexpr size_t ARGC_TWO = 2;
} // namespace
JsInputMethodExtension *JsInputMethodExtension::jsInputMethodExtension = nullptr;
using namespace OHOS::AppExecFwk;
using namespace OHOS::MiscServices;

napi_value AttachInputMethodExtensionContext(napi_env env, void *value, void *)
{
    IMSA_HILOGI("AttachInputMethodExtensionContext start.");
    if (value == nullptr) {
        IMSA_HILOGW("parameter is invalid.");
        return nullptr;
    }
    auto ptr = reinterpret_cast<std::weak_ptr<InputMethodExtensionContext> *>(value)->lock();
    if (ptr == nullptr) {
        IMSA_HILOGW("context is invalid.");
        return nullptr;
    }
    napi_value object = CreateJsInputMethodExtensionContext(env, ptr);
    auto systemModule = JsRuntime::LoadSystemModuleByEngine(env, "InputMethodExtensionContext", &object, 1);
    if (systemModule == nullptr) {
        IMSA_HILOGE("failed to load system module by engine!");
        return nullptr;
    }
    auto contextObj = systemModule ->GetNapiValue();
    napi_coerce_to_native_binding_object(env, contextObj, DetachCallbackFunc, AttachInputMethodExtensionContext, value,
        nullptr);
    auto workContext = new (std::nothrow) std::weak_ptr<InputMethodExtensionContext>(ptr);
    if (workContext == nullptr) {
        IMSA_HILOGE("workContext is nullptr!");
        return nullptr;
    }
    napi_status status = napi_wrap(
        env, contextObj, workContext,
        [](napi_env, void *data, void *) {
            IMSA_HILOGI("finalizer for weak_ptr input method extension context is called.");
            delete static_cast<std::weak_ptr<InputMethodExtensionContext> *>(data);
        },
        nullptr, nullptr);
    if (status != napi_ok) {
        IMSA_HILOGE("InputMethodExtensionContext wrap failed: %{public}d!", status);
        delete workContext;
        return nullptr;
    }
    return object;
}

JsInputMethodExtension *JsInputMethodExtension::Create(const std::unique_ptr<Runtime> &runtime)
{
    IMSA_HILOGI("JsInputMethodExtension Create.");
    if (runtime == nullptr) {
        IMSA_HILOGE("runtime is nullptr.");
        return nullptr;
    }
    jsInputMethodExtension = new JsInputMethodExtension(static_cast<JsRuntime &>(*runtime));
    return jsInputMethodExtension;
}

JsInputMethodExtension::JsInputMethodExtension(JsRuntime &jsRuntime) : jsRuntime_(jsRuntime)
{
}

JsInputMethodExtension::~JsInputMethodExtension()
{
    jsRuntime_.FreeNativeReference(std::move(jsObj_));
}

void JsInputMethodExtension::Init(const std::shared_ptr<AbilityLocalRecord> &record,
    const std::shared_ptr<OHOSApplication> &application, std::shared_ptr<AbilityHandler> &handler,
    const sptr<IRemoteObject> &token)
{
    IMSA_HILOGI("JsInputMethodExtension Init.");
    InputMethodExtension::Init(record, application, handler, token);
    std::string srcPath;
    GetSrcPath(srcPath);
    if (srcPath.empty()) {
        IMSA_HILOGE("failed to get srcPath!");
        return;
    }

    if (abilityInfo_ == nullptr) {
        IMSA_HILOGE("abilityInfo_ is nullptr!");
        return;
    }
    std::string moduleName(Extension::abilityInfo_->moduleName);
    moduleName.append("::").append(abilityInfo_->name);
    IMSA_HILOGI("JsInputMethodExtension, module: %{public}s, srcPath:%{public}s.", moduleName.c_str(), srcPath.c_str());
    HandleScope handleScope(jsRuntime_);
    napi_env env = jsRuntime_.GetNapiEnv();
    jsObj_ = jsRuntime_.LoadModule(moduleName, srcPath, abilityInfo_->hapPath,
        abilityInfo_->compileMode == CompileMode::ES_MODULE);
    if (jsObj_ == nullptr) {
        IMSA_HILOGE("failed to get jsObj_!");
        return;
    }
    IMSA_HILOGI("JsInputMethodExtension::Init GetNapiValue.");
    napi_value obj = jsObj_->GetNapiValue();
    if (obj == nullptr) {
        IMSA_HILOGE("failed to get JsInputMethodExtension object!");
        return;
    }
    BindContext(env, obj);
    handler_ = handler;
    ListenWindowManager();
    IMSA_HILOGI("JsInputMethodExtension end.");
}

void JsInputMethodExtension::ListenWindowManager()
{
    IMSA_HILOGD("register window manager service listener.");
    displayListener_ = sptr<InputMethodDisplayAttributeListener>::MakeSptr(GetContext());
    if (displayListener_ == nullptr) {
        IMSA_HILOGE("failed to create display listener!");
        return;
    }
    std::vector<std::string> attributes = {"rotation", "width", "height"};
    Rosen::DisplayManager::GetInstance().RegisterDisplayAttributeListener(attributes, displayListener_);
}

void JsInputMethodExtension::BindContext(napi_env env, napi_value obj)
{
    IMSA_HILOGI("JsInputMethodExtension::BindContext");
    auto context = GetContext();
    if (context == nullptr) {
        IMSA_HILOGE("failed to get context!");
        return;
    }
    IMSA_HILOGD("JsInputMethodExtension::Init CreateJsInputMethodExtensionContext.");
    napi_value contextObj = CreateJsInputMethodExtensionContext(env, context);
    auto shellContextRef = jsRuntime_.LoadSystemModule("InputMethodExtensionContext", &contextObj, ARGC_ONE);
    if (shellContextRef == nullptr) {
        IMSA_HILOGE("shellContextRef is nullptr!");
        return;
    }
    contextObj = shellContextRef->GetNapiValue();
    if (contextObj == nullptr) {
        IMSA_HILOGE("failed to get input method extension native object!");
        return;
    }
    auto workContext = new (std::nothrow) std::weak_ptr<InputMethodExtensionContext>(context);
    if (workContext == nullptr) {
        IMSA_HILOGE("workContext is nullptr!");
        return;
    }
    napi_coerce_to_native_binding_object(env, contextObj, DetachCallbackFunc, AttachInputMethodExtensionContext,
        workContext, nullptr);
    IMSA_HILOGD("JsInputMethodExtension::Init Bind.");
    context->Bind(jsRuntime_, shellContextRef.release());
    IMSA_HILOGD("JsInputMethodExtension::SetProperty.");
    napi_set_named_property(env, obj, "context", contextObj);
    napi_status status = napi_wrap(
        env, contextObj, workContext,
        [](napi_env, void *data, void *) {
            IMSA_HILOGI("Finalizer for weak_ptr input method extension context is called.");
            delete static_cast<std::weak_ptr<InputMethodExtensionContext> *>(data);
        },
        nullptr, nullptr);
    if (status != napi_ok) {
        IMSA_HILOGE("InputMethodExtensionContext wrap failed: %{public}d", status);
        delete workContext;
    }
}

void JsInputMethodExtension::OnStart(const AAFwk::Want &want)
{
    auto task = std::make_shared<TaskAmsInit>();
    TaskManager::GetInstance().PostTask(task);
    InputMethodAbility::GetInstance().InitConnect();
    StartAsync("OnStart", static_cast<int32_t>(TraceTaskId::ONSTART_EXTENSION));
    StartAsync("Extension::OnStart", static_cast<int32_t>(TraceTaskId::ONSTART_MIDDLE_EXTENSION));
    Extension::OnStart(want);
    FinishAsync("Extension::OnStart", static_cast<int32_t>(TraceTaskId::ONSTART_MIDDLE_EXTENSION));
    IMSA_HILOGI("JsInputMethodExtension OnStart begin.");
    HandleScope handleScope(jsRuntime_);
    napi_env env = jsRuntime_.GetNapiEnv();
    napi_value napiWant = OHOS::AppExecFwk::WrapWant(env, want);
    napi_value argv[] = { napiWant };
    StartAsync("onCreate", static_cast<int32_t>(TraceTaskId::ONCREATE_EXTENSION));
    CallObjectMethod("onCreate", argv, ARGC_ONE);
    FinishAsync("onCreate", static_cast<int32_t>(TraceTaskId::ONCREATE_EXTENSION));
    TaskManager::GetInstance().PostTask(std::make_shared<TaskImsaSetCoreAndAgent>());
    IMSA_HILOGI("ime bind imf");
    FinishAsync("OnStart", static_cast<int32_t>(TraceTaskId::ONSTART_EXTENSION));
    TaskManager::GetInstance().Complete(task->GetSeqId());
}

void JsInputMethodExtension::OnStop()
{
    InputMethodExtension::OnStop();
    IMSA_HILOGI("JsInputMethodExtension OnStop start.");
    CallObjectMethod("onDestroy");
    auto context = GetContext();
    if (context == nullptr) {
        IMSA_HILOGE("context is nullptr.");
        return;
    }
    bool ret = ConnectionManager::GetInstance().DisconnectCaller(context->GetToken());
    if (ret) {
        IMSA_HILOGI("the input method extension connection is not disconnected.");
    }
    IMSA_HILOGI("JsInputMethodExtension %{public}s end.", __func__);
}

sptr<IRemoteObject> JsInputMethodExtension::OnConnect(const AAFwk::Want &want)
{
    IMSA_HILOGI("JsInputMethodExtension OnConnect start.");
    Extension::OnConnect(want);
    auto remoteObj = new (std::nothrow) InputMethodExtensionAbilityServiceImpl();
    if (remoteObj == nullptr) {
        IMSA_HILOGE("failed to create InputMethodExtensionAbilityServiceImpl!");
        return nullptr;
    }
    return remoteObj;
}

void JsInputMethodExtension::OnDisconnect(const AAFwk::Want &want)
{
    IMSA_HILOGI("JsInputMethodExtension OnDisconnect start.");
    Extension::OnDisconnect(want);
    IMSA_HILOGI("%{public}s start.", __func__);
    HandleScope handleScope(jsRuntime_);
    napi_env env = jsRuntime_.GetNapiEnv();
    napi_value napiWant = OHOS::AppExecFwk::WrapWant(env, want);
    napi_value argv[] = { napiWant };
    if (jsObj_ == nullptr) {
        IMSA_HILOGE("not found InputMethodExtension.js!");
        return;
    }

    napi_value obj = jsObj_->GetNapiValue();
    if (obj == nullptr) {
        IMSA_HILOGE("failed to get InputMethodExtension object!");
        return;
    }

    napi_value method = nullptr;
    napi_get_named_property(env, obj, "onDisconnect", &method);
    if (method == nullptr) {
        IMSA_HILOGE("failed to get onDisconnect from InputMethodExtension object!");
        return;
    }
    napi_value remoteNapi = nullptr;
    napi_call_function(env, obj, method, ARGC_ONE, argv, &remoteNapi);
    IMSA_HILOGI("%{public}s end.", __func__);
}

void JsInputMethodExtension::OnCommand(const AAFwk::Want &want, bool restart, int startId)
{
    IMSA_HILOGI("JsInputMethodExtension OnCommand start.");
    Extension::OnCommand(want, restart, startId);
    IMSA_HILOGI("%{public}s start restart=%{public}s,startId=%{public}d.", __func__, restart ? "true" : "false",
        startId);
    HandleScope handleScope(jsRuntime_);
    napi_env env = jsRuntime_.GetNapiEnv();
    napi_value napiWant = OHOS::AppExecFwk::WrapWant(env, want);
    napi_value napiStartId = nullptr;
    napi_create_int32(env, startId, &napiStartId);
    napi_value argv[] = { napiWant, napiStartId };
    CallObjectMethod("onRequest", argv, ARGC_TWO);
    IMSA_HILOGI("%{public}s end.", __func__);
}

napi_value JsInputMethodExtension::CallObjectMethod(const char *name, const napi_value *argv, size_t argc)
{
    IMSA_HILOGI("JsInputMethodExtension::CallObjectMethod(%{public}s), start.", name);

    if (jsObj_ == nullptr) {
        IMSA_HILOGW("not found InputMethodExtension.js.");
        return nullptr;
    }

    HandleScope handleScope(jsRuntime_);
    napi_env env = jsRuntime_.GetNapiEnv();
    napi_value obj = jsObj_->GetNapiValue();
    if (obj == nullptr) {
        IMSA_HILOGE("failed to get InputMethodExtension object!");
        return nullptr;
    }

    napi_value method = nullptr;
    napi_get_named_property(env, obj, name, &method);
    if (method == nullptr) {
        IMSA_HILOGE("failed to get '%{public}s' from InputMethodExtension object!", name);
        return nullptr;
    }
    IMSA_HILOGI("JsInputMethodExtension::CallFunction(%{public}s), success.", name);
    napi_value remoteNapi = nullptr;
    napi_status status = napi_call_function(env, obj, method, argc, argv, &remoteNapi);
    if (status != napi_ok) {
        return nullptr;
    }
    return remoteNapi;
}

void JsInputMethodExtension::GetSrcPath(std::string &srcPath)
{
    IMSA_HILOGD("JsInputMethodExtension GetSrcPath start.");
    if (abilityInfo_ == nullptr) {
        IMSA_HILOGE("abilityInfo_ is nullptr!");
        return;
    }
    if (!Extension::abilityInfo_->isModuleJson) {
        /* temporary compatibility api8 + config.json */
        srcPath.append(Extension::abilityInfo_->package);
        srcPath.append("/assets/js/");
        if (!Extension::abilityInfo_->srcPath.empty()) {
            srcPath.append(Extension::abilityInfo_->srcPath);
        }
        srcPath.append("/").append(Extension::abilityInfo_->name).append(".abc");
        return;
    }

    if (!Extension::abilityInfo_->srcEntrance.empty()) {
        srcPath.append(Extension::abilityInfo_->moduleName + "/");
        srcPath.append(Extension::abilityInfo_->srcEntrance);
        srcPath.erase(srcPath.rfind('.'));
        srcPath.append(".abc");
    }
}
} // namespace AbilityRuntime
} // namespace OHOS