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

#include "hisysevent_adapter.h"
#include "nweb_log.h"
#include "ohos_adapter_helper.h"
#include "cJSON.h"

namespace OHOS::NWeb {
constexpr char INPUT_METHOD[] = "INPUT_METHOD";
constexpr char ATTACH_CODE[] = "ATTACH_CODE";
constexpr char IS_SHOW_KEY_BOARD[] = "IS_SHOW_KEY_BOARD";
constexpr int32_t IMF_LISTENER_NULL_POINT = 1;
constexpr int32_t IMF_TEXT_CONFIG_NULL_POINT = 2;
const std::string AUTO_FILL_CANCEL_PRIVATE_COMMAND = "autofill.cancel";

IMFTextListenerAdapterImpl::IMFTextListenerAdapterImpl(const std::shared_ptr<IMFTextListenerAdapter>& listener)
    : listener_(listener) {};

IMFTextListenerAdapterImpl::~IMFTextListenerAdapterImpl() = default;

void IMFTextListenerAdapterImpl::InsertText(const std::u16string& text)
{
    if (listener_) {
        listener_->InsertText(text);
    }
}

void IMFTextListenerAdapterImpl::DeleteForward(int32_t length)
{
    if (listener_) {
        listener_->DeleteForward(length);
    }
}

void IMFTextListenerAdapterImpl::DeleteBackward(int32_t length)
{
    if (listener_) {
        listener_->DeleteBackward(length);
    }
}

void IMFTextListenerAdapterImpl::SendKeyEventFromInputMethod(const MiscServices::KeyEvent& event)
{
    (void)event;
    if (listener_) {
        listener_->SendKeyEventFromInputMethod();
    }
}

void IMFTextListenerAdapterImpl::SendKeyboardStatus(const MiscServices::KeyboardStatus& keyboardStatus)
{
    if (listener_) {
        auto status = IMFAdapterKeyboardStatus::NONE;
        if (keyboardStatus == MiscServices::KeyboardStatus::SHOW) {
            status = IMFAdapterKeyboardStatus::SHOW;
        } else if (keyboardStatus == MiscServices::KeyboardStatus::HIDE) {
            status = IMFAdapterKeyboardStatus::HIDE;
        }
        listener_->SendKeyboardStatus(status);
    }
}

void IMFTextListenerAdapterImpl::SendFunctionKey(const MiscServices::FunctionKey& functionKey)
{
    if (listener_) {
        std::shared_ptr<IMFAdapterFunctionKeyAdapterImpl> adapterFunction =
            std::make_shared<IMFAdapterFunctionKeyAdapterImpl>();
        switch (functionKey.GetEnterKeyType()) {
            case MiscServices::EnterKeyType::UNSPECIFIED:
                adapterFunction->SetEnterKeyType(IMFAdapterEnterKeyType::UNSPECIFIED);
                break;
            case MiscServices::EnterKeyType::NONE:
                adapterFunction->SetEnterKeyType(IMFAdapterEnterKeyType::NONE);
                break;
            case MiscServices::EnterKeyType::GO:
                adapterFunction->SetEnterKeyType(IMFAdapterEnterKeyType::GO);
                break;
            case MiscServices::EnterKeyType::SEARCH:
                adapterFunction->SetEnterKeyType(IMFAdapterEnterKeyType::SEARCH);
                break;
            case MiscServices::EnterKeyType::SEND:
                adapterFunction->SetEnterKeyType(IMFAdapterEnterKeyType::SEND);
                break;
            case MiscServices::EnterKeyType::NEXT:
                adapterFunction->SetEnterKeyType(IMFAdapterEnterKeyType::NEXT);
                break;
            case MiscServices::EnterKeyType::DONE:
                adapterFunction->SetEnterKeyType(IMFAdapterEnterKeyType::DONE);
                break;
            case MiscServices::EnterKeyType::PREVIOUS:
                adapterFunction->SetEnterKeyType(IMFAdapterEnterKeyType::PREVIOUS);
                break;
            case MiscServices::EnterKeyType::NEW_LINE:
                adapterFunction->SetEnterKeyType(IMFAdapterEnterKeyType::NEW_LINE);
                break;
            default:
                WVLOG_E("unknown functionKey");
                break;
        }
        listener_->SendFunctionKey(adapterFunction);
    }
}

void IMFTextListenerAdapterImpl::SetKeyboardStatus(bool status)
{
    if (listener_) {
        listener_->SetKeyboardStatus(status);
    }
}

void IMFTextListenerAdapterImpl::MoveCursor(const MiscServices::Direction direction)
{
    if (listener_) {
        IMFAdapterDirection adapterDirection;
        switch (direction) {
            case MiscServices::Direction::UP: {
                adapterDirection = IMFAdapterDirection::UP;
                break;
            }
            case MiscServices::Direction::DOWN: {
                adapterDirection = IMFAdapterDirection::DOWN;
                break;
            }
            case MiscServices::Direction::LEFT: {
                adapterDirection = IMFAdapterDirection::LEFT;
                break;
            }
            case MiscServices::Direction::RIGHT: {
                adapterDirection = IMFAdapterDirection::RIGHT;
                break;
            }
            default: {
                adapterDirection = IMFAdapterDirection::NONE;
            }
        }
        listener_->MoveCursor(adapterDirection);
    }
}

void IMFTextListenerAdapterImpl::HandleSetSelection(int32_t start, int32_t end)
{
    if (listener_) {
        listener_->HandleSetSelection(start, end);
    }
}

void IMFTextListenerAdapterImpl::HandleExtendAction(int32_t action)
{
    if (listener_) {
        listener_->HandleExtendAction(action);
    }
}

void IMFTextListenerAdapterImpl::HandleSelect(int32_t keyCode, int32_t cursorMoveSkip)
{
    if (listener_) {
        listener_->HandleSelect(keyCode, cursorMoveSkip);
    }
}

int32_t IMFTextListenerAdapterImpl::GetTextIndexAtCursor()
{
    if (listener_) {
        return listener_->GetTextIndexAtCursor();
    }
    return -1;
}

std::u16string IMFTextListenerAdapterImpl::GetLeftTextOfCursor(int32_t number)
{
    if (listener_) {
        return listener_->GetLeftTextOfCursor(number);
    }
    return u"";
}

std::u16string IMFTextListenerAdapterImpl::GetRightTextOfCursor(int32_t number)
{
    if (listener_) {
        return listener_->GetRightTextOfCursor(number);
    }
    return u"";
}

int32_t IMFTextListenerAdapterImpl::SetPreviewText(const std::u16string& text, const MiscServices::Range& range)
{
    if (listener_) {
        return listener_->SetPreviewText(text, range.start, range.end);
    }
    return -1;
}

void IMFTextListenerAdapterImpl::FinishTextPreview()
{
    if (listener_) {
        listener_->FinishTextPreview();
    }
}

int32_t IMFTextListenerAdapterImpl::ReceivePrivateCommand(
    const std::unordered_map<std::string, MiscServices::PrivateDataValue>& privateCommand)
{
    WVLOG_I("ReceivePrivateCommand");
    auto item = privateCommand.find(PREVIEW_TEXT_STYLE_KEY);
    if (item != privateCommand.end()) {
        bool isNeedUnderline = false;
        MiscServices::PrivateDataValue data = item->second;
        std::string previewStyle = std::get<std::string>(data);
        if (previewStyle == PREVIEW_TEXT_STYLE_UNDERLINE) {
            isNeedUnderline = true;
        }

        if (listener_) {
            listener_->SetNeedUnderLine(isNeedUnderline);
        }
    }

    item = privateCommand.find(AUTO_FILL_PARAMS_USERNAME);
    if (item != privateCommand.end()) {
        if (listener_) {
            std::string content = std::get<std::string>(item->second);
            listener_->AutoFillWithIMFEvent(true, false, false, content);
        }
    }

    item = privateCommand.find(AUTO_FILL_PARAMS_OTHERACCOUNT);
    if (item != privateCommand.end()) {
        if (listener_) {
            std::string content = std::string("");
            listener_->AutoFillWithIMFEvent(false, true, false, content);
        }
    }

    return 0;
}

bool IMFAdapterImpl::Attach(std::shared_ptr<IMFTextListenerAdapter> listener, bool isShowKeyboard)
{
    if (!listener) {
        WVLOG_E("the listener is nullptr");
        return false;
    }
    if (!textListener_) {
        textListener_ = new (std::nothrow) IMFTextListenerAdapterImpl(listener);
        if (!textListener_) {
            WVLOG_E("new textListener failed");
            return false;
        }
    }
    auto controller = MiscServices::InputMethodController::GetInstance();
    if (!controller) {
        WVLOG_E("MiscServices::InputMethodController::GetInstance failed");
        return false;
    }
    int32_t ret = controller->Attach(textListener_, isShowKeyboard);
    if (ret != 0) {
        WVLOG_E("inputmethod attach failed, errcode=%{public}d", ret);
        return false;
    }
    return true;
}

void ReportImfErrorEvent(int32_t ret, bool isShowKeyboard)
{
    std::string isShowKeyboardStr = isShowKeyboard ? "true" : "false";
    OhosAdapterHelper::GetInstance().GetHiSysEventAdapterInstance().Write(INPUT_METHOD,
        HiSysEventAdapter::EventType::FAULT,
        { ATTACH_CODE, std::to_string(ret), IS_SHOW_KEY_BOARD, isShowKeyboardStr });
}

bool IMFAdapterImpl::Attach(std::shared_ptr<IMFTextListenerAdapter> listener, bool isShowKeyboard,
    const std::shared_ptr<IMFTextConfigAdapter> config, bool isResetListener)
{
    if (!AttachParamsCheck(listener, isShowKeyboard, config, isResetListener)) {
        return false;
    }
    
    MiscServices::InputAttribute inputAttribute = { .inputPattern = config->GetInputAttribute()->GetInputPattern(),
        .enterKeyType = config->GetInputAttribute()->GetEnterKeyType(),
        .immersiveMode = config->GetInputAttribute()->GetKeyboardImmersiveMode(),
        .isTextPreviewSupported = true };

    MiscServices::CursorInfo imfInfo = { .left = config->GetCursorInfo()->GetLeft(),
        .top = config->GetCursorInfo()->GetTop(),
        .width = config->GetCursorInfo()->GetWidth(),
        .height = config->GetCursorInfo()->GetHeight() };

    MiscServices::TextConfig textConfig = { .inputAttribute = inputAttribute,
        .cursorInfo = imfInfo,
        .windowId = config->GetWindowId(),
        .positionY = config->GetPositionY(),
        .height = config->GetHeight() };
    WVLOG_I(
        "web inputmethod attach, isShowKeyboard=%{public}d, textConfig=%{public}s",
        isShowKeyboard,
        textConfig.ToString().c_str());
    auto controller = MiscServices::InputMethodController::GetInstance();
    if (!controller) {
        WVLOG_E("MiscServices::InputMethodController::GetInstance failed");
        return false;
    }
    int32_t ret = controller->Attach(textListener_, isShowKeyboard, textConfig);
    if (ret != 0) {
        WVLOG_E("inputmethod attach failed, errcode=%{public}d", ret);
        ReportImfErrorEvent(ret, isShowKeyboard);
        return false;
    }
    return true;
}

bool IMFAdapterImpl::AttachWithRequestKeyboardReason(std::shared_ptr<IMFTextListenerAdapter> listener,
    bool isShowKeyboard, const std::shared_ptr<IMFTextConfigAdapter> config, bool isResetListener,
    int32_t requestKeyboardReason)
{
    if (!AttachParamsCheck(listener, isShowKeyboard, config, isResetListener)) {
        return false;
    }

    MiscServices::InputAttribute inputAttribute = { .inputPattern = config->GetInputAttribute()->GetInputPattern(),
        .enterKeyType = config->GetInputAttribute()->GetEnterKeyType(),
        .immersiveMode = config->GetInputAttribute()->GetKeyboardImmersiveMode(),
        .isTextPreviewSupported = true };

    MiscServices::CursorInfo imfInfo = { .left = config->GetCursorInfo()->GetLeft(),
        .top = config->GetCursorInfo()->GetTop(),
        .width = config->GetCursorInfo()->GetWidth(),
        .height = config->GetCursorInfo()->GetHeight() };

    MiscServices::TextConfig textConfig = { .inputAttribute = inputAttribute,
        .cursorInfo = imfInfo,
        .windowId = config->GetWindowId(),
        .positionY = config->GetPositionY(),
        .height = config->GetHeight() };
    OHOS::MiscServices::AttachOptions attachOptions;
    attachOptions.isShowKeyboard = isShowKeyboard;
    attachOptions.requestKeyboardReason = static_cast<OHOS::MiscServices::RequestKeyboardReason>(requestKeyboardReason);
    WVLOG_I(
        "web inputmethod attach, isShowKeyboard=%{public}d, requestKeyboardReason=%{public}d",
        isShowKeyboard,
        requestKeyboardReason);
    auto controller = MiscServices::InputMethodController::GetInstance();
    if (!controller) {
        WVLOG_E("MiscServices::InputMethodController::GetInstance failed");
        return false;
    }
    int32_t ret = controller->Attach(textListener_, attachOptions, textConfig);
    if (ret != 0) {
        WVLOG_E("inputmethod attach failed, errcode=%{public}d", ret);
        ReportImfErrorEvent(ret, isShowKeyboard);
        return false;
    }
    return true;
}

void IMFAdapterImpl::ShowCurrentInput(const IMFAdapterTextInputType& inputType)
{
    MiscServices::Configuration config;
    if (inputType == IMFAdapterTextInputType::NUMBER) {
        config.SetTextInputType(MiscServices::TextInputType::NUMBER);
    } else {
        config.SetTextInputType(MiscServices::TextInputType::TEXT);
    }
    auto controller = MiscServices::InputMethodController::GetInstance();
    if (!controller) {
        WVLOG_E("MiscServices::InputMethodController::GetInstance failed");
        return;
    }
    controller->OnConfigurationChange(config);
    controller->ShowCurrentInput();
}

void IMFAdapterImpl::HideTextInput()
{
    auto controller = MiscServices::InputMethodController::GetInstance();
    if (!controller) {
        WVLOG_E("MiscServices::InputMethodController::GetInstance failed");
        return;
    }
    controller->HideTextInput();
}

void IMFAdapterImpl::Close()
{
    auto controller = MiscServices::InputMethodController::GetInstance();
    if (!controller) {
        WVLOG_E("MiscServices::InputMethodController::GetInstance failed");
        return;
    }
    controller->Close();
}

void IMFAdapterImpl::OnCursorUpdate(const std::shared_ptr<IMFCursorInfoAdapter> cursorInfo)
{
    if (!cursorInfo) {
        WVLOG_E("inputmethod OnCursorUpdate cursorInfo is null");
        return;
    }

    MiscServices::CursorInfo imfInfo = { .left = cursorInfo->GetLeft(),
        .top = cursorInfo->GetTop(),
        .width = cursorInfo->GetWidth(),
        .height = cursorInfo->GetHeight() };
    WVLOG_D("imfInfo left = %{public}f, top = %{public}f, width = %{public}f, height = %{public}f", imfInfo.left,
        imfInfo.top, imfInfo.width, imfInfo.height);
    auto controller = MiscServices::InputMethodController::GetInstance();
    if (!controller) {
        WVLOG_E("MiscServices::InputMethodController::GetInstance failed");
        return;
    }
    controller->OnCursorUpdate(imfInfo);
}

void IMFAdapterImpl::OnSelectionChange(std::u16string text, int start, int end)
{
    auto controller = MiscServices::InputMethodController::GetInstance();
    if (!controller) {
        WVLOG_E("MiscServices::InputMethodController::GetInstance failed");
        return;
    }
    controller->OnSelectionChange(text, start, end);
}

bool IMFAdapterImpl::SendPrivateCommand(const std::string& commandKey, const std::string& commandValue)
{
    if (commandKey == AUTO_FILL_CANCEL_PRIVATE_COMMAND) {
        std::unordered_map<std::string, MiscServices::PrivateDataValue> privateCommand;
        ParseFillContentJsonValue(commandValue, privateCommand);
        auto controller = MiscServices::InputMethodController::GetInstance();
        if (!controller) {
            WVLOG_E("MiscServices::InputMethodController::GetInstance failed");
            return false;
        }
        int32_t ret = controller->SendPrivateCommand(privateCommand);
        if (ret != 0) {
            WVLOG_E("inputmethod SendPrivateCommand failed, errcode=%{public}d", ret);
            return false;
        }
        WVLOG_I("inputmethod  SendPrivateCommand success");
        return true;
    }
    return false;
}

bool IMFAdapterImpl::ParseFillContentJsonValue(const std::string& commandValue,
    std::unordered_map<std::string, std::variant<std::string, bool, int32_t>>& map)
{
    cJSON* sourceJson = cJSON_Parse(commandValue.c_str());
    if (sourceJson == nullptr || cJSON_IsNull(sourceJson)) {
        cJSON_Delete(sourceJson);
        return false;
    }
    if (cJSON_HasObjectItem(sourceJson, "userName")) {
        cJSON* userName = cJSON_GetObjectItem(sourceJson, "userName");
        if (userName != nullptr && cJSON_IsString(userName) && userName->valuestring != nullptr) {
            map.insert(std::make_pair("userName", userName->valuestring));
        }
    }
    if (cJSON_HasObjectItem(sourceJson, "hasAccount")) {
        cJSON* hasAccount = cJSON_GetObjectItem(sourceJson, "hasAccount");
        if (hasAccount != nullptr && cJSON_IsString(hasAccount) && hasAccount->valuestring != nullptr) {
            map.insert(std::make_pair("hasAccount", hasAccount->valuestring));
        }
    }
    cJSON_Delete(sourceJson);
    return true;
}

bool IMFAdapterImpl::AttachParamsCheck(std::shared_ptr<IMFTextListenerAdapter> listener, bool isShowKeyboard,
    const std::shared_ptr<IMFTextConfigAdapter> config, bool isResetListener)
{
    if (!listener) {
        WVLOG_E("the listener is nullptr");
        ReportImfErrorEvent(IMF_LISTENER_NULL_POINT, isShowKeyboard);
        return false;
    }
    if (!config || !(config->GetInputAttribute()) || !(config->GetCursorInfo())) {
        WVLOG_E("the config is nullptr");
        ReportImfErrorEvent(IMF_TEXT_CONFIG_NULL_POINT, isShowKeyboard);
        return false;
    }

    if ((textListener_ != nullptr) && isResetListener) {
        textListener_ = nullptr;
        WVLOG_I("attach node is changed, need reset listener");
    }

    if (!textListener_) {
        textListener_ = new (std::nothrow) IMFTextListenerAdapterImpl(listener);
        if (!textListener_) {
            WVLOG_E("new textListener failed");
            ReportImfErrorEvent(IMF_LISTENER_NULL_POINT, isShowKeyboard);
            return false;
        }
    }
    return true;
}

void IMFTextListenerAdapterImpl::NotifyPanelStatusInfo(const MiscServices::PanelStatusInfo& info)
{
    WVLOG_I("IMFTextListenerAdapterImpl::NotifyPanelStatusInfo, visible:%{public}d", info.visible);
    MiscServices::Trigger triggerFrom = info.trigger;
    if (listener_) {
        listener_->WebSetImeShow(info.visible);
        if (triggerFrom == MiscServices::Trigger::IME_APP && !info.visible) {
            WVLOG_I("IMFTextListenerAdapterImpl::NotifyPanelStatusInfo, info.IME_APP");
            listener_->KeyboardUpperRightCornerHide();
        }
    }
}
} // namespace OHOS::NWeb