/*
 * Copyright (C) 2025 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 "selection_panel.h"

#include <tuple>
#include <thread>
#include <chrono>

#include "display_manager.h"
#include "hisysevent_adapter.h"
#include "scene_board_judgement.h"
#include "selection_log.h"
#include "selectionmethod_trace.h"
#include "selection_system_ability_utils.h"

namespace OHOS {
namespace SelectionFwk {
using WMError = OHOS::Rosen::WMError;
using WindowState = OHOS::Rosen::WindowState;
using namespace Rosen;
using WindowGravity = OHOS::Rosen::WindowGravity;
using WindowState = OHOS::Rosen::WindowState;
std::atomic<uint32_t> SelectionPanel::sequenceId_ { 0 };
constexpr int32_t MAXWAITTIMES = 3;
constexpr int32_t WAITTIME = 100000;

static std::map<PanelType, std::string> windowTypeToName = {
    {PanelType::MAIN_PANEL, "SelectionMainFwkWindow"},
    {PanelType::MENU_PANEL, "SelectionMenuFwkWindow"}
};

SelectionPanel::~SelectionPanel() = default;

int32_t SelectionPanel::CreatePanel(
    const std::shared_ptr<AbilityRuntime::Context> &context, const PanelInfo &panelInfo)
{
    SELECTION_HILOGI("SelectionPanel CreatePanel start.");
    panelType_ = panelInfo.panelType;
    x_ = panelInfo.x;
    y_ = panelInfo.y;
    width_ = panelInfo.width;
    height_ = panelInfo.height;
    SELECTION_HILOGI("start , panelType:%{public}d.", static_cast<int32_t>(panelType_));

    sptr<Rosen::WindowOption> baseOp = new Rosen::WindowOption();
    baseOp->SetWindowType(Rosen::WindowType::WINDOW_TYPE_SELECTION);
    baseOp->SetWindowMode(Rosen::WindowMode::WINDOW_MODE_FLOATING);
    OHOS::Rosen::Rect windowRect = {x_, y_, width_, height_};
    baseOp->SetWindowRect(windowRect);
    WMError wmError = WMError::WM_OK;
    auto windowName = windowTypeToName[panelInfo.panelType];
    sptr<Rosen::Window> window = Rosen::Window::Create(windowName, baseOp, context, wmError);
    SELECTION_HILOGI("Window creation returns %{public}d", wmError);
    if (!window) {
        HisyseventAdapter::GetInstance()->ReportShowPanelFailed(windowName, static_cast<int32_t>(wmError),
            static_cast<int32_t>(SelectFailedReason::CREATE_PANEL_FAILED));
        SELECTION_HILOGE("Faied to create window, errCode: %{public}d", wmError);
        return ErrorCode::ERROR_SELECTION_SERVICE;
    }
    window_ = window;
    SELECTION_HILOGI("Window::Create Success");
    return 0;
}

std::string SelectionPanel::GeneratePanelName()
{
    uint32_t sequenceId = GenerateSequenceId();
    std::string windowName = panelType_ == PanelType::MENU_PANEL ? "menuPanel" + std::to_string(sequenceId) :
                                                           "mainPanel" + std::to_string(sequenceId);
    SELECTION_HILOGD("SelectionPanel, windowName: %{public}s.", windowName.c_str());
    return windowName;
}

int32_t SelectionPanel::SetPanelProperties()
{
    auto window = GetWindow();
    if (window == nullptr) {
        SELECTION_HILOGE("window is nullptr!");
        return ErrorCode::ERROR_SELECTION_SERVICE;
    }
    WindowGravity gravity = WindowGravity::WINDOW_GRAVITY_FLOAT;
    if (!isScbEnable_) {
        WMError wmError = window->SetWindowGravity(gravity, invalidGravityPercent);
        if (wmError != WMError::WM_OK) {
            SELECTION_HILOGE("failed to set window gravity, wmError is %{public}d, start destroy window!", wmError);
            return ErrorCode::ERROR_SELECTION_SERVICE;
        }
        return ErrorCode::NO_ERROR;
    }
    keyboardLayoutParams_.gravity_ = gravity;
    auto ret = window->AdjustKeyboardLayout(keyboardLayoutParams_);
    if (ret != WMError::WM_OK) {
        SELECTION_HILOGE("SetWindowGravity failed, wmError is %{public}d, start destroy window!", ret);
        return ErrorCode::ERROR_SELECTION_SERVICE;
    }
    return ErrorCode::NO_ERROR;
}

int32_t SelectionPanel::DestroyPanel()
{
    SELECTION_HILOGI("SelectionPanel DestroyPanel start");
    auto window = GetWindow();
    if (window == nullptr) {
        SELECTION_HILOGE("window_ is nullptr!");
        return ErrorCode::ERROR_SELECTION_SERVICE;
    }
    auto ret = DoHidePanel(window);
    if (ret != ErrorCode::NO_ERROR) {
        SELECTION_HILOGE("SelectionPanel, hide panel failed, ret: %{public}d!", ret);
    }

    auto result = window->Destroy();
    if (result != WMError::WM_OK) {
        SELECTION_HILOGE("destroy failed. ret: %{public}d", result);
        return ErrorCode::ERROR_SELECTION_SERVICE;
    }
    SELECTION_HILOGI("destroy ret: %{public}d", result);
    PanelStatusChange(SelectionWindowStatus::DESTROYED);
    return ErrorCode::NO_ERROR;
}

uint32_t SelectionPanel::GenerateSequenceId()
{
    uint32_t seqId = sequenceId_.fetch_add(1, std::memory_order_seq_cst);
    return seqId % std::numeric_limits<uint32_t>::max();
}

int32_t SelectionPanel::SetUiContent(const std::string &contentInfo, napi_env env)
{
    SELECTION_HILOGI("SelectionPanel SetUiContent start");
    auto window = GetWindow();
    if (window == nullptr) {
        SELECTION_HILOGE("window_ is nullptr, can not SetUiContent!");
        return ErrorCode::ERROR_PANEL_DESTROYED;
    }
    if (IsDestroyed(window)) {
        SELECTION_HILOGE("window is destroyed!");
        return ErrorCode::ERROR_PANEL_DESTROYED;
    }
    if (!IsSelectionSystemAbilityExistent()) {
        return ErrorCode::ERROR_SELECTION_SERVICE;
    }

    WMError ret = window->NapiSetUIContent(contentInfo, env, nullptr);
    WMError wmError = window->SetTransparent(true);
    if (isWaitSetUiContent_.load()) {
        isWaitSetUiContent_.store(false);
    }
    SELECTION_HILOGI("SetTransparent ret: %{public}u.", wmError);
    SELECTION_HILOGI("NapiSetUIContent ret: %{public}d.", ret);
    return ret == WMError::WM_ERROR_INVALID_PARAM ? ErrorCode::ERROR_PARAMETER_CHECK_FAILED : ErrorCode::NO_ERROR;
}

int32_t SelectionPanel::SetUiContent(const std::string &contentInfo, ani_env* env)
{
    SELECTION_HILOGI("SelectionPanel SetUiContent start");
    if (env == nullptr) {
        SELECTION_HILOGE("env is nullptr, can not SetUiContent!");
        return ErrorCode::ERROR_PANEL_DESTROYED;
    }
    auto window = GetWindow();
    if (window == nullptr) {
        SELECTION_HILOGE("window_ is nullptr, can not SetUiContent!");
        return ErrorCode::ERROR_PANEL_DESTROYED;
    }
    if (IsDestroyed(window)) {
        SELECTION_HILOGE("window is destroyed!");
        return ErrorCode::ERROR_PANEL_DESTROYED;
    }
    if (!IsSelectionSystemAbilityExistent()) {
        SELECTION_HILOGE("Selection SystemAbility is not existent");
        return ErrorCode::ERROR_SELECTION_SERVICE;
    }

    WMError ret = window->AniSetUIContent(contentInfo, env, nullptr);
    WMError wmError = window->SetTransparent(true);
    if (isWaitSetUiContent_.load()) {
        isWaitSetUiContent_.store(false);
    }
    SELECTION_HILOGI("SetTransparent ret: %{public}u.", wmError);
    SELECTION_HILOGI("AniSetUIContent ret: %{public}d.", ret);
    return ret == WMError::WM_ERROR_INVALID_PARAM ? ErrorCode::ERROR_PARAMETER_CHECK_FAILED : ErrorCode::NO_ERROR;
}

PanelType SelectionPanel::GetPanelType()
{
    return panelType_;
}

int32_t SelectionPanel::ShowPanel()
{
    SELECTION_HILOGI("SelectionPanel ShowPanel start.");
    auto window = GetWindow();
    if (window == nullptr) {
        SELECTION_HILOGE("window_ is nullptr!");
        return ErrorCode::ERROR_PANEL_DESTROYED;
    }
    if (IsDestroyed(window)) {
        SELECTION_HILOGE("window is destroyed!");
        return ErrorCode::ERROR_PANEL_DESTROYED;
    }
    if (!IsSelectionSystemAbilityExistent()) {
        return ErrorCode::ERROR_SELECTION_SERVICE;
    }

    int32_t iCounter = 0;
    while (isWaitSetUiContent_.load() && iCounter < MAXWAITTIMES) {
        usleep(WAITTIME);
        iCounter++;
        SELECTION_HILOGI("SelectionPanel show pannel waitTime %{public}d.", iCounter);
    }

    if (isWaitSetUiContent_.load()) {
        SELECTION_HILOGE("isWaitSetUiContent_ is true!");
        return ErrorCode::ERROR_SELECTION_SERVICE;
    }

    if (IsShowing(window)) {
        SELECTION_HILOGI("panel already shown.");
        return ErrorCode::NO_ERROR;
    }
    auto ret = WMError::WM_OK;
    {
        SelectionMethodSyncTrace tracer("SelectionPanel_ShowPanel");
        ret = window->Show(0, false, false);
    }
    if (ret != WMError::WM_OK) {
        SELECTION_HILOGE("ShowPanel error, err = %{public}d", ret);
        return ErrorCode::ERROR_SELECTION_SERVICE;
    }
    SELECTION_HILOGI("Selection panel shown successfully.");
    return ErrorCode::NO_ERROR;
}

bool SelectionPanel::IsShowing(const sptr<OHOS::Rosen::Window>& window)
{
    if (window == nullptr) {
        SELECTION_HILOGE("window_ is nullptr!");
        return false;
    }
    auto windowState = window->GetWindowState();
    if (windowState == WindowState::STATE_SHOWN) {
        return true;
    }
    SELECTION_HILOGD("windowState: %{public}d.", static_cast<int>(windowState));
    return false;
}

bool SelectionPanel::IsPanelShowing()
{
    return IsShowing(GetWindow());
}

void SelectionPanel::PanelStatusChange(const SelectionWindowStatus &status)
{
    if (panelStatusListener_ == nullptr) {
        SELECTION_HILOGE("panelStatusListener_ is nullptr.");
        return;
    }

    auto itr = panelStatusMap_.find(status);
    if (itr == panelStatusMap_.end()) {
        SELECTION_HILOGE("wrong status.");
        return;
    }

    uint32_t windowId = GetWindowId();
    if (status == SelectionWindowStatus::HIDDEN && hiddenRegistered_) {
        panelStatusListener_->OnPanelStatus(windowId, itr->second);
    }
    if (status == SelectionWindowStatus::DESTROYED && destroyedRegistered_) {
        panelStatusListener_->OnPanelStatus(windowId, itr->second);
    }
}

int32_t SelectionPanel::HidePanel()
{
    SELECTION_HILOGI("SelectionPanel HidePanel start");
    auto window = GetWindow();
    if (window == nullptr) {
        SELECTION_HILOGE("window_ is nullptr!");
        return ErrorCode::ERROR_PANEL_DESTROYED;
    }
    return DoHidePanel(window);
}

int32_t SelectionPanel::DoHidePanel(const sptr<OHOS::Rosen::Window>& window)
{
    SELECTION_HILOGI("SelectionPanel DoHidePanel start");
    if (IsDestroyed(window)) {
        SELECTION_HILOGE("window is destroyed!");
        return ErrorCode::ERROR_PANEL_DESTROYED;
    }
    if (IsHidden(window)) {
        SELECTION_HILOGI("panel already hidden.");
        return ErrorCode::NO_ERROR;
    }
    if (!IsSelectionSystemAbilityExistent()) {
        return ErrorCode::ERROR_SELECTION_SERVICE;
    }

    auto ret = WMError::WM_OK;
    {
        SelectionMethodSyncTrace tracer("SelectionPanel_HidePanel");
        ret = window->Hide();
    }
    if (ret != WMError::WM_OK) {
        SELECTION_HILOGE("HidePanel error, err: %{public}d!", ret);
        return ErrorCode::ERROR_SELECTION_SERVICE;
    }
    SELECTION_HILOGI("success, panelType:%{public}d.", static_cast<int32_t>(panelType_));
    PanelStatusChange(SelectionWindowStatus::HIDDEN);
    return ErrorCode::NO_ERROR;
}

bool SelectionPanel::IsHidden(const sptr<OHOS::Rosen::Window>& window)
{
    if (window == nullptr) {
        SELECTION_HILOGE("window_ is nullptr!");
        return true;
    }
    auto windowState = window->GetWindowState();
    if (windowState == WindowState::STATE_HIDDEN) {
        return true;
    }
    SELECTION_HILOGD("windowState: %{public}d.", static_cast<int>(windowState));
    return false;
}

bool SelectionPanel::IsDestroyed(const sptr<OHOS::Rosen::Window>& window)
{
    return window && window->GetWindowState() == WindowState::STATE_DESTROYED;
}

int32_t SelectionPanel::StartMoving()
{
    SELECTION_HILOGI("SelectionPanel StartMoving start");
    auto window = GetWindow();
    if (window == nullptr) {
        SELECTION_HILOGE("window_ is nullptr!");
        return ErrorCode::ERROR_PANEL_DESTROYED;
    }
    if (IsDestroyed(window)) {
        SELECTION_HILOGE("window is destroyed!");
        return ErrorCode::ERROR_PANEL_DESTROYED;
    }
    if (!IsSelectionSystemAbilityExistent()) {
        return ErrorCode::ERROR_SELECTION_SERVICE;
    }

    auto ret = window->StartMoveWindow();
    if (ret == WmErrorCode::WM_ERROR_DEVICE_NOT_SUPPORT) {
        SELECTION_HILOGE("window manager service not support error ret = %{public}d.", ret);
        return ErrorCode::ERROR_SELECTION_SERVICE;
    }
    if (ret != WmErrorCode::WM_OK) {
        SELECTION_HILOGE("window manager service error ret = %{public}d.", ret);
        return ErrorCode::ERROR_SELECTION_SERVICE;
    }
    SELECTION_HILOGI("StartMoving  success!");
    return ErrorCode::NO_ERROR;
}

int32_t SelectionPanel::MoveTo(int32_t x, int32_t y)
{
    SELECTION_HILOGI("SelectionPanel MoveTo start");
    auto window = GetWindow();
    if (window == nullptr) {
        SELECTION_HILOGE("window_ is nullptr!");
        return ErrorCode::ERROR_PANEL_DESTROYED;
    }
    if (IsDestroyed(window)) {
        SELECTION_HILOGE("window is destroyed!");
        return ErrorCode::ERROR_PANEL_DESTROYED;
    }
    if (!IsSelectionSystemAbilityExistent()) {
        return ErrorCode::ERROR_SELECTION_SERVICE;
    }
#ifdef SCENE_BOARD_ENABLE
    WMError ret = window->MoveWindowToGlobalDisplay(x, y);
#else
    WMError ret = window->MoveTo(x, y);
#endif
    SELECTION_HILOGI("Moveto finish, ret = %{public}d", ret);
    return ret == WMError::WM_ERROR_INVALID_PARAM ? ErrorCode::ERROR_PARAMETER_CHECK_FAILED : ErrorCode::NO_ERROR;
}

bool SelectionPanel::SetPanelStatusListener(std::shared_ptr<PanelStatusListener> statusListener,
    const std::string &type)
{
    if (!MarkListener(type, true)) {
        return false;
    }
    SELECTION_HILOGD("type: %{public}s.", type.c_str());
    if (panelStatusListener_ == nullptr) {
        auto isExist = [&type](const auto& pair) { return pair.second == type; };
        if (std::find_if(panelStatusMap_.begin(), panelStatusMap_.end(), isExist) != panelStatusMap_.end()) {
            SELECTION_HILOGD("panelStatusListener_ is nullptr, need to be set");
            panelStatusListener_ = std::move(statusListener);
        }
    }
    return true;
}

bool SelectionPanel::MarkListener(const std::string &type, bool isRegister)
{
    if (panelStatusMap_.find(SelectionWindowStatus::DESTROYED) != panelStatusMap_.end() &&
        type == panelStatusMap_.at(SelectionWindowStatus::DESTROYED)) {
        destroyedRegistered_ = isRegister;
    } else if (panelStatusMap_.find(SelectionWindowStatus::HIDDEN) != panelStatusMap_.end() &&
        type == panelStatusMap_.at(SelectionWindowStatus::HIDDEN)) {
        hiddenRegistered_ = isRegister;
    } else {
        SELECTION_HILOGE("type error!");
        return false;
    }
    return true;
}

void SelectionPanel::ClearPanelListener(const std::string &type)
{
    if (!MarkListener(type, false)) {
        return;
    }
    SELECTION_HILOGD("type: %{public}s.", type.c_str());
    if (panelStatusListener_ == nullptr) {
        SELECTION_HILOGD("panelStatusListener_ not set, don't need to remove.");
        return;
    }
    if (IsPanelListenerClearable()) {
        return;
    }
    panelStatusListener_ = nullptr;
}

bool SelectionPanel::IsPanelListenerClearable()
{
    return (destroyedRegistered_ || hiddenRegistered_);
}

uint32_t SelectionPanel::GetWindowId()
{
    auto window = GetWindow();
    if (window == nullptr) {
        SELECTION_HILOGE("window_ is nullptr!");
        return ErrorCode::ERROR_PANEL_DESTROYED;
    }
    return window->GetWindowId();
}

bool SelectionPanel::IsSelectionSystemAbilityExistent()
{
    return SelectionSystemAbilityUtils::IsSelectionSystemAbilityExistent();
}
} // namespace SelectionFwk
} // namespace OHOS