/*
 * Copyright (c) 2021-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 "avoid_area_controller.h"
#include "display_manager_service_inner.h"
#include "window_helper.h"
#include "window_manager_hilog.h"
#include "wm_trace.h"

namespace OHOS {
namespace Rosen {
namespace {
    constexpr HiviewDFX::HiLogLabel LABEL = {LOG_CORE, HILOG_DOMAIN_WINDOW, "AvoidAreaController"};
}

const int32_t AVOID_NUM = 4;

bool AvoidAreaController::IsAvoidAreaNode(const sptr<WindowNode>& node) const
{
    if (node == nullptr) {
        WLOGFE("IsAvoidAreaNode Failed, node is nullprt");
        return false;
    }

    if (!WindowHelper::IsAvoidAreaWindow(node->GetWindowType())) {
        WLOGFE("IsAvoidAreaNode Failed, node type is not avoid type");
        return false;
    }

    return true;
}

AvoidPosType AvoidAreaController::GetAvoidPosType(const Rect& rect) const
{
    auto display = DisplayManagerServiceInner::GetInstance().GetDisplayById(displayId_);
    if (display == nullptr) {
        WLOGFE("GetAvoidPosType fail. Get display fail. displayId:%{public}" PRIu64"", displayId_);
        return AvoidPosType::AVOID_POS_UNKNOWN;
    }
    uint32_t displayWidth = static_cast<uint32_t>(display->GetWidth());
    uint32_t displayHeight = static_cast<uint32_t>(display->GetHeight());

    return WindowHelper::GetAvoidPosType(rect, displayWidth, displayHeight);
}

WMError AvoidAreaController::AvoidControl(const sptr<WindowNode>& node, AvoidControlType type)
{
    WM_FUNCTION_TRACE();
    if (!IsAvoidAreaNode(node)) {
        WLOGFE("AvoidControl check param Failed. Type: %{public}u", type);
        return WMError::WM_ERROR_INVALID_PARAM;
    }
    uint32_t windowId = node->GetWindowId();
    auto iter = avoidNodes_.find(windowId);
    // do not add a exist node(the same id)
    if (type == AvoidControlType::AVOID_NODE_ADD && iter != avoidNodes_.end()) {
        WLOGFE("WinId:%{public}d is added. AvoidControl Add Failed. Type: %{public}u", windowId, type);
        return WMError::WM_ERROR_INVALID_PARAM;
    }
    // do not update or removew a unexist node
    if (type != AvoidControlType::AVOID_NODE_ADD && iter == avoidNodes_.end()) {
        WLOGFE("WinId:%{public}d not exist. AvoidControl Update or Remove Failed. Type: %{public}u", windowId, type);
        return WMError::WM_ERROR_INVALID_PARAM;
    }

    switch (type) {
        case AvoidControlType::AVOID_NODE_ADD:
            avoidNodes_[windowId] = node;
            WLOGFI("WinId:%{public}d add. And the windowType is %{public}d", windowId, node->GetWindowType());
            break;
        case AvoidControlType::AVOID_NODE_UPDATE:
            avoidNodes_[windowId] = node;
            WLOGFI("WinId:%{public}d update. And the windowType is %{public}d", windowId, node->GetWindowType());
            break;
        case AvoidControlType::AVOID_NODE_REMOVE:
            avoidNodes_.erase(iter);
            WLOGFI("WinId:%{public}d remove. And the windowType is %{public}d", windowId, node->GetWindowType());
            break;
        default:
            WLOGFE("invalid AvoidControlType: %{public}u", type);
            return WMError::WM_ERROR_INVALID_PARAM;
    }

    // get all Area info and notify windowcontainer
    std::vector<Rect> avoidAreas = GetAvoidArea();
    DumpAvoidArea(avoidAreas);
    UseCallbackNotifyAvoidAreaChanged(avoidAreas);
    return WMError::WM_OK;
}

std::vector<Rect> AvoidAreaController::GetAvoidArea() const
{
    std::vector<Rect> avoidArea(AVOID_NUM, {0, 0, 0, 0});  // avoid area  left, top, right, bottom
    for (auto iter = avoidNodes_.begin(); iter != avoidNodes_.end(); ++iter) {
        Rect curRect = iter->second->GetLayoutRect();
        auto curPos = GetAvoidPosType(curRect);
        if (curPos == AvoidPosType::AVOID_POS_UNKNOWN) {
            WLOGFE("GetAvoidArea AVOID_POS_UNKNOWN Rect: x : %{public}d, y:  %{public}d, w: %{public}u h: %{public}u",
                static_cast<uint32_t>(curRect.posX_), static_cast<uint32_t>(curRect.posY_),
                static_cast<uint32_t>(curRect.width_), static_cast<uint32_t>(curRect.height_));
            continue;
        }
        avoidArea[static_cast<uint32_t>(curPos)] = curRect;
    }
    return avoidArea;
}

std::vector<Rect> AvoidAreaController::GetAvoidAreaByType(AvoidAreaType avoidAreaType) const
{
    if (avoidAreaType != AvoidAreaType::TYPE_SYSTEM) {
        WLOGFE("GetAvoidAreaByType. Support Type is AvoidAreaType::TYPE_SYSTEM. But current type is %{public}u",
            static_cast<uint32_t>(avoidAreaType));
        std::vector<Rect> avoidArea(AVOID_NUM, {0, 0, 0, 0});
        return avoidArea;
    }
    WLOGFI("AvoidAreaController::GetAvoidAreaByType Success");
    return GetAvoidArea();
}

void AvoidAreaController::UseCallbackNotifyAvoidAreaChanged(std::vector<Rect>& avoidArea) const
{
    if (updateAvoidAreaCallBack_) {
        updateAvoidAreaCallBack_(avoidArea);
    }
}

void AvoidAreaController::DumpAvoidArea(const std::vector<Rect>& avoidArea) const
{
    WLOGFI("----------------- AvoidArea Begin-----------------");
    WLOGFI("  No [   x    y    w    h]");
    for (uint32_t i = 0; i < avoidArea.size(); i++) {
        WLOGFI("%{public}4u [%{public}4d %{public}4d %{public}4u %{public}4u]", i,
            avoidArea[i].posX_, avoidArea[i].posY_, avoidArea[i].width_, avoidArea[i].height_);
    }
    WLOGFI("----------------- AvoidArea End-----------------");
}
}
}