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

#include <vector>

#include "display.h"
#include "vsync_station.h"
#include "window_helper.h"
#include "window_inner_manager.h"
#include "window_manager_hilog.h"
#include "window_manager_service.h"
#include "window_node.h"
#include "window_node_container.h"
#include "window_property.h"
#include "wm_common.h"
#include "wm_math.h"
#include "xcollie/watchdog.h"

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

void DragController::UpdateDragInfo(uint32_t windowId)
{
    PointInfo point;
    if (!GetHitPoint(windowId, point)) {
        return;
    }
    sptr<WindowNode> dragNode = windowRoot_->GetWindowNode(windowId);
    if (dragNode == nullptr) {
        return;
    }
    sptr<WindowNode> hitWindowNode = GetHitWindow(dragNode->GetDisplayId(), point);
    if (hitWindowNode == nullptr) {
        WLOGFE("Get point failed %{public}d %{public}d", point.x, point.y);
        return;
    }
    auto token = hitWindowNode->GetWindowToken();
    if (token) {
        if (hitWindowNode->GetWindowId() == hitWindowId_) {
            token->UpdateWindowDragInfo(point, DragEvent::DRAG_EVENT_MOVE);
            return;
        }
        token->UpdateWindowDragInfo(point, DragEvent::DRAG_EVENT_IN);
    }
    sptr<WindowNode> oldHitWindow = windowRoot_->GetWindowNode(hitWindowId_);
    if (oldHitWindow != nullptr && oldHitWindow->GetWindowToken()) {
        oldHitWindow->GetWindowToken()->UpdateWindowDragInfo(point, DragEvent::DRAG_EVENT_OUT);
    }
    hitWindowId_ = hitWindowNode->GetWindowId();
}

void DragController::StartDrag(uint32_t windowId)
{
    PointInfo point;
    if (!GetHitPoint(windowId, point)) {
        WLOGFE("Get hit point failed");
        return;
    }
    sptr<WindowNode> dragNode = windowRoot_->GetWindowNode(windowId);
    if (dragNode == nullptr) {
        return;
    }
    sptr<WindowNode> hitWindow = GetHitWindow(dragNode->GetDisplayId(), point);
    if (hitWindow == nullptr) {
        WLOGFE("Get point failed %{public}d %{public}d", point.x, point.y);
        return;
    }
    if (hitWindow->GetWindowToken()) {
        hitWindow->GetWindowToken()->UpdateWindowDragInfo(point, DragEvent::DRAG_EVENT_IN);
    }
    hitWindowId_ = windowId;
    WLOGI("start Drag");
}

void DragController::FinishDrag(uint32_t windowId)
{
    sptr<WindowNode> node = windowRoot_->GetWindowNode(windowId);
    if (node == nullptr) {
        WLOGFE("get node failed");
        return;
    }
    if (node->GetWindowType() != WindowType::WINDOW_TYPE_DRAGGING_EFFECT) {
        return;
    }

    sptr<WindowNode> hitWindow = windowRoot_->GetWindowNode(hitWindowId_);
    if (hitWindow != nullptr) {
        auto property = node->GetWindowProperty();
        PointInfo point = {property->GetWindowRect().posX_ + property->GetHitOffset().x,
            property->GetWindowRect().posY_ + property->GetHitOffset().y};
        if (hitWindow->GetWindowToken()) {
            hitWindow->GetWindowToken()->UpdateWindowDragInfo(point, DragEvent::DRAG_EVENT_END);
        }
    }
    WLOGI("end drag");
}

sptr<WindowNode> DragController::GetHitWindow(DisplayId id, PointInfo point)
{
    // Need get display by point
    if (id == DISPLAY_ID_INVALID) {
        WLOGFE("Get invalid display");
        return nullptr;
    }
    sptr<WindowNodeContainer> container = windowRoot_->GetOrCreateWindowNodeContainer(id);
    if (container == nullptr) {
        WLOGFE("get container failed %{public}" PRIu64"", id);
        return nullptr;
    }

    std::vector<sptr<WindowNode>> windowNodes;
    container->TraverseContainer(windowNodes);
    for (auto windowNode : windowNodes) {
        if (windowNode->GetWindowType() >= WindowType::WINDOW_TYPE_PANEL) {
            continue;
        }
        if (WindowHelper::IsPointInTargetRect(point.x, point.y, windowNode->GetWindowRect())) {
            return windowNode;
        }
    }
    return nullptr;
}

bool DragController::GetHitPoint(uint32_t windowId, PointInfo& point)
{
    sptr<WindowNode> windowNode = windowRoot_->GetWindowNode(windowId);
    if (windowNode == nullptr || windowNode->GetWindowType() != WindowType::WINDOW_TYPE_DRAGGING_EFFECT) {
        WLOGFE("Get hit point failed");
        return false;
    }
    sptr<WindowProperty> property = windowNode->GetWindowProperty();
    point.x = property->GetWindowRect().posX_ + property->GetHitOffset().x;
    point.y = property->GetWindowRect().posY_ + property->GetHitOffset().y;
    return true;
}

void DragInputEventListener::OnInputEvent(std::shared_ptr<MMI::KeyEvent> keyEvent) const
{
    if (keyEvent == nullptr) {
        WLOGFE("KeyEvent is nullptr");
        return;
    }
    uint32_t windowId = static_cast<uint32_t>(keyEvent->GetAgentWindowId());
    WLOGFD("[WMS] Receive keyEvent, windowId: %{public}u", windowId);
    keyEvent->MarkProcessed();
}

void DragInputEventListener::OnInputEvent(std::shared_ptr<MMI::AxisEvent> axisEvent) const
{
    if (axisEvent == nullptr) {
        WLOGFE("AxisEvent is nullptr");
        return;
    };
    WLOGFD("[WMS] Receive axisEvent, windowId: %{public}u", axisEvent->GetAgentWindowId());
    axisEvent->MarkProcessed();
}

void DragInputEventListener::OnInputEvent(std::shared_ptr<MMI::PointerEvent> pointerEvent) const
{
    if (pointerEvent == nullptr) {
        WLOGFE("PointerEvent is nullptr");
        return;
    }
    uint32_t windowId = static_cast<uint32_t>(pointerEvent->GetAgentWindowId());
    WLOGFD("[WMS] Receive pointerEvent, windowId: %{public}u", windowId);

    WindowInnerManager::GetInstance().ConsumePointerEvent(pointerEvent);
}

void MoveDragController::SetInputEventConsumer()
{
    if (!inputListener_ || !inputEventHandler_) {
        WLOGFE("InputListener or inputEventHandler is nullptr");
        return;
    }
    MMI::InputManager::GetInstance()->SetWindowInputEventConsumer(inputListener_, inputEventHandler_);
}

bool MoveDragController::Init()
{
    // create handler for input event
    inputEventHandler_ = std::make_shared<AppExecFwk::EventHandler>(
        AppExecFwk::EventRunner::Create(INNER_WM_INPUT_THREAD_NAME));
    if (inputEventHandler_ == nullptr) {
        return false;
    }
    int ret = HiviewDFX::Watchdog::GetInstance().AddThread(INNER_WM_INPUT_THREAD_NAME, inputEventHandler_);
    if (ret != 0) {
        WLOGFE("Add watchdog thread failed");
    }
    inputListener_ = std::make_shared<DragInputEventListener>(DragInputEventListener());
    SetInputEventConsumer();
    VsyncStation::GetInstance().SetIsMainHandlerAvailable(false);
    VsyncStation::GetInstance().SetVsyncEventHandler(inputEventHandler_);
    return true;
}

void MoveDragController::Stop()
{
    if (inputEventHandler_ != nullptr) {
        inputEventHandler_.reset();
    }
}

void MoveDragController::HandleReadyToMoveOrDrag(uint32_t windowId, sptr<WindowProperty>& windowProperty,
    sptr<MoveDragProperty>& moveDragProperty)
{
    SetActiveWindowId(windowId);
    SetWindowProperty(windowProperty);
    SetDragProperty(moveDragProperty);
}

void MoveDragController::HandleEndUpMovingOrDragging(uint32_t windowId)
{
    if (activeWindowId_ != windowId) {
        WLOGFE("end up moving or dragging failed, windowId: %{public}u", windowId);
        return;
    }
    ResetMoveOrDragState();
}

void MoveDragController::HandleWindowRemovedOrDestroyed(uint32_t windowId)
{
    if (GetMoveDragProperty() == nullptr) {
        return;
    }
    if (!(GetMoveDragProperty()->startMoveFlag_ || GetMoveDragProperty()->startDragFlag_)) {
        return;
    }
    VsyncStation::GetInstance().RemoveCallback();
    ResetMoveOrDragState();
}

void MoveDragController::ConvertPointerPosToDisplayGroupPos(DisplayId displayId, int32_t& posX, int32_t& posY)
{
    auto displayRect = DisplayGroupInfo::GetInstance().GetDisplayRect(displayId);
    posX += displayRect.posX_;
    posY += displayRect.posY_;
}

void MoveDragController::HandleDisplayLimitRectChange(const std::map<DisplayId, Rect>& limitRectMap)
{
    limitRectMap_.clear();
    for (auto& elem : limitRectMap) {
        limitRectMap_.insert(elem);
    }
}

void MoveDragController::ConsumePointerEvent(const std::shared_ptr<MMI::PointerEvent>& pointerEvent)
{
    if (pointerEvent == nullptr) {
        WLOGFE("pointerEvent is nullptr or is handling pointer event");
        return;
    }
    if (pointerEvent->GetPointerAction() == MMI::PointerEvent::POINTER_ACTION_MOVE) {
        moveEvent_ = pointerEvent;
        VsyncStation::GetInstance().RequestVsync(vsyncCallback_);
    } else {
        WLOGFD("[WMS] Dispatch non-move event, action: %{public}d", pointerEvent->GetPointerAction());
        HandlePointerEvent(pointerEvent);
        pointerEvent->MarkProcessed();
    }
}

void MoveDragController::OnReceiveVsync(int64_t timeStamp)
{
    if (moveEvent_ == nullptr) {
        WLOGFE("moveEvent is nullptr");
        return;
    }
    WLOGFD("[OnReceiveVsync] receive move event, action: %{public}d", moveEvent_->GetPointerAction());
    HandlePointerEvent(moveEvent_);
    moveEvent_->MarkProcessed();
}

Rect MoveDragController::GetHotZoneRect()
{
    auto startPointPosX = moveDragProperty_->startPointPosX_;
    auto startPointPosY = moveDragProperty_->startPointPosY_;
    ConvertPointerPosToDisplayGroupPos(moveDragProperty_->targetDisplayId_, startPointPosX, startPointPosY);

    Rect hotZoneRect;
    const auto& startRectExceptCorner =  moveDragProperty_->startRectExceptCorner_;
    const auto& startRectExceptFrame =  moveDragProperty_->startRectExceptFrame_;
    if ((startPointPosX > startRectExceptCorner.posX_ &&
        (startPointPosX < startRectExceptCorner.posX_ +
         static_cast<int32_t>(startRectExceptCorner.width_))) &&
        (startPointPosY > startRectExceptCorner.posY_ &&
        (startPointPosY < startRectExceptCorner.posY_ +
        static_cast<int32_t>(startRectExceptCorner.height_)))) {
        hotZoneRect = startRectExceptFrame; // drag type: left/right/top/bottom
    } else {
        hotZoneRect = startRectExceptCorner; // drag type: left_top/right_top/left_bottom/right_bottom
    }
    return hotZoneRect;
}

bool MoveDragController::CheckWindowRect(DisplayId displayId, float vpr, const Rect& rect)
{
    uint32_t titleBarHeight = static_cast<uint32_t>(WINDOW_TITLE_BAR_HEIGHT * vpr);
    auto iter = limitRectMap_.find(displayId);
    Rect limitRect;
    if (iter != limitRectMap_.end()) {
        limitRect = iter->second;
    }
    if (WindowHelper::IsEmptyRect(limitRect) || MathHelper::NearZero(vpr)) {
        return true; // If limitRect is empty, we can't use limitRect to check window rect
    }

    if ((rect.posX_ > static_cast<int32_t>(limitRect.posX_ + limitRect.width_ - titleBarHeight)) ||
        (rect.posX_ + static_cast<int32_t>(rect.width_) <
         static_cast<int32_t>(limitRect.posX_ + titleBarHeight)) ||
        (rect.posY_ < limitRect.posY_) ||
        (rect.posY_ > static_cast<int32_t>(limitRect.posY_ + limitRect.height_ - titleBarHeight))) {
        WLOGFD("[WMS] Invalid window rect, id: %{public}u, rect: [%{public}d, %{public}d, %{public}d, %{public}d]",
            windowProperty_->GetWindowId(), rect.posX_, rect.posY_, rect.width_, rect.height_);
        return false;
    }
    return true;
}

void MoveDragController::CalculateNewWindowRect(Rect& newRect, DisplayId displayId, int32_t posX, int32_t posY)
{
    auto startPointPosX = moveDragProperty_->startPointPosX_;
    auto startPointPosY = moveDragProperty_->startPointPosY_;
    ConvertPointerPosToDisplayGroupPos(moveDragProperty_->targetDisplayId_, startPointPosX, startPointPosY);
    const auto& startPointRect = moveDragProperty_->startPointRect_;
    Rect hotZoneRect = GetHotZoneRect();
    int32_t diffX = posX - startPointPosX;
    int32_t diffY = posY - startPointPosY;

    float vpr = DisplayGroupInfo::GetInstance().GetDisplayVirtualPixelRatio(displayId);
    if (MathHelper::NearZero(vpr)) {
        return;
    }
    uint32_t minWidth = static_cast<uint32_t>(MIN_FLOATING_WIDTH * vpr);
    uint32_t minHeight = static_cast<uint32_t>(MIN_FLOATING_HEIGHT * vpr);
    if (startPointPosX <= hotZoneRect.posX_) {
        if (diffX > static_cast<int32_t>(startPointRect.width_ - minWidth)) {
            diffX = static_cast<int32_t>(startPointRect.width_ - minWidth);
        }
        newRect.posX_ += diffX;
        newRect.width_ = static_cast<uint32_t>(static_cast<int32_t>(newRect.width_) - diffX);
    } else if (startPointPosX >= hotZoneRect.posX_ + static_cast<int32_t>(hotZoneRect.width_)) {
        if (diffX < 0 && (-diffX > static_cast<int32_t>(startPointRect.width_ - minWidth))) {
            diffX = -(static_cast<int32_t>(startPointRect.width_ - minWidth));
        }
        newRect.width_ = static_cast<uint32_t>(static_cast<int32_t>(newRect.width_) + diffX);
    }
    if (startPointPosY <= hotZoneRect.posY_) {
        if (diffY > static_cast<int32_t>(startPointRect.height_ - minHeight)) {
            diffY = static_cast<int32_t>(startPointRect.height_ - minHeight);
        }
        newRect.posY_ += diffY;
        newRect.height_ = static_cast<uint32_t>(static_cast<int32_t>(newRect.height_) - diffY);
    } else if (startPointPosY >= hotZoneRect.posY_ + static_cast<int32_t>(hotZoneRect.height_)) {
        if (diffY < 0 && (-diffY > static_cast<int32_t>(startPointRect.height_ - minHeight))) {
            diffY = -(static_cast<int32_t>(startPointRect.height_ - minHeight));
        }
        newRect.height_ = static_cast<uint32_t>(static_cast<int32_t>(newRect.height_) + diffY);
    }
}

void MoveDragController::HandleDragEvent(DisplayId displayId, int32_t posX, int32_t posY,
                                         int32_t pointId, int32_t sourceType)
{
    if (moveDragProperty_ == nullptr || !moveDragProperty_->startDragFlag_ ||
        (pointId != moveDragProperty_->startPointerId_) || (sourceType != moveDragProperty_->sourceType_)) {
        return;
    }

    Rect newRect = moveDragProperty_->startPointRect_;
    CalculateNewWindowRect(newRect, displayId, posX, posY);

    if (!CheckWindowRect(displayId, DisplayGroupInfo::GetInstance().GetDisplayVirtualPixelRatio(displayId), newRect)) {
        return;
    }

    WLOGFD("[WMS] HandleDragEvent, id: %{public}u, newRect: [%{public}d, %{public}d, %{public}d, %{public}d]",
        windowProperty_->GetWindowId(), newRect.posX_, newRect.posY_, newRect.width_, newRect.height_);
    windowProperty_->SetRequestRect(newRect);
    windowProperty_->SetWindowSizeChangeReason(WindowSizeChangeReason::DRAG);
    windowProperty_->SetDragType(moveDragProperty_->dragType_);
    WindowManagerService::GetInstance().UpdateProperty(windowProperty_, PropertyChangeAction::ACTION_UPDATE_RECT, true);
}

void MoveDragController::HandleMoveEvent(DisplayId displayId, int32_t posX, int32_t posY,
                                         int32_t pointId, int32_t sourceType)
{
    if (moveDragProperty_ == nullptr) {
        return;
    }
    if (!moveDragProperty_->startMoveFlag_ ||
        (pointId != moveDragProperty_->startPointerId_) ||
        (sourceType != moveDragProperty_->sourceType_)) {
        return;
    }
    auto startPointPosX = moveDragProperty_->startPointPosX_;
    auto startPointPosY = moveDragProperty_->startPointPosY_;
    ConvertPointerPosToDisplayGroupPos(moveDragProperty_->targetDisplayId_, startPointPosX, startPointPosY);
    int32_t targetX = moveDragProperty_->startPointRect_.posX_ + (posX - startPointPosX);
    int32_t targetY = moveDragProperty_->startPointRect_.posY_ + (posY - startPointPosY);

    const Rect& oriRect = moveDragProperty_->startPointRect_;
    Rect newRect = { targetX, targetY, oriRect.width_, oriRect.height_ };
    if (limitRectMap_.find(displayId) != limitRectMap_.end()) {
        newRect.posY_ = std::max(newRect.posY_, limitRectMap_[displayId].posY_);
    }
    WLOGFD("[WMS] HandleMoveEvent, id: %{public}u, newRect: [%{public}d, %{public}d, %{public}d, %{public}d]",
        windowProperty_->GetWindowId(), newRect.posX_, newRect.posY_, newRect.width_, newRect.height_);
    windowProperty_->SetRequestRect(newRect);
    windowProperty_->SetWindowSizeChangeReason(WindowSizeChangeReason::MOVE);
    WindowManagerService::GetInstance().UpdateProperty(windowProperty_, PropertyChangeAction::ACTION_UPDATE_RECT, true);
}

void MoveDragController::HandlePointerEvent(const std::shared_ptr<MMI::PointerEvent>& pointerEvent)
{
    if (windowProperty_) {
        windowProperty_->UpdatePointerEvent(pointerEvent);
    }
    MMI::PointerEvent::PointerItem pointerItem;
    int32_t pointId = pointerEvent->GetPointerId();
    int32_t sourceType = pointerEvent->GetSourceType();
    if (!pointerEvent->GetPointerItem(pointId, pointerItem) ||
        (sourceType == MMI::PointerEvent::SOURCE_TYPE_MOUSE &&
        pointerEvent->GetButtonId() != MMI::PointerEvent::MOUSE_BUTTON_LEFT)) {
        WLOGFW("invalid pointerEvent");
        return;
    }

    int32_t pointPosX = pointerItem.GetDisplayX();
    int32_t pointPosY = pointerItem.GetDisplayY();
    int32_t action = pointerEvent->GetPointerAction();
    int32_t targetDisplayId = pointerEvent->GetTargetDisplayId();
    ConvertPointerPosToDisplayGroupPos(targetDisplayId, pointPosX, pointPosY);
    switch (action) {
        case MMI::PointerEvent::POINTER_ACTION_DOWN:
        case MMI::PointerEvent::POINTER_ACTION_BUTTON_DOWN: {
            if (pointId == moveDragProperty_->startPointerId_ && sourceType == moveDragProperty_->sourceType_) {
                moveDragProperty_->startMoveFlag_ = false;
                moveDragProperty_->startDragFlag_ = false;
            }
            WLOGFD("[Server Point Down]: windowId: %{public}u, pointId: %{public}d, sourceType: %{public}d, "
                   "hasPointStarted: %{public}d, startMove: %{public}d, startDrag: %{public}d, targetDisplayId: "
                   "%{public}d, pointPos: [%{public}d, %{public}d]", activeWindowId_, pointId, sourceType,
                   moveDragProperty_->pointEventStarted_, moveDragProperty_->startMoveFlag_,
                   moveDragProperty_->startDragFlag_, targetDisplayId, pointPosX, pointPosY);
            break;
        }
        // ready to move or drag
        case MMI::PointerEvent::POINTER_ACTION_MOVE: {
            HandleMoveEvent(targetDisplayId, pointPosX, pointPosY, pointId, sourceType);
            HandleDragEvent(targetDisplayId, pointPosX, pointPosY, pointId, sourceType);
            break;
        }
        // End move or drag
        case MMI::PointerEvent::POINTER_ACTION_UP:
        case MMI::PointerEvent::POINTER_ACTION_BUTTON_UP:
        case MMI::PointerEvent::POINTER_ACTION_CANCEL: {
            WindowManagerService::GetInstance().NotifyWindowClientPointUp(activeWindowId_, pointerEvent);
            WLOGFD("[Server Point Up/Cancel]: windowId: %{public}u, action: %{public}d, sourceType: %{public}d",
                activeWindowId_, action, sourceType);
            break;
        }
        default:
            break;
    }
}

void MoveDragController::SetDragProperty(const sptr<MoveDragProperty>& moveDragProperty)
{
    moveDragProperty_->CopyFrom(moveDragProperty);
}

void MoveDragController::SetWindowProperty(const sptr<WindowProperty>& windowProperty)
{
    windowProperty_->CopyFrom(windowProperty);
}

const sptr<MoveDragProperty>& MoveDragController::GetMoveDragProperty() const
{
    return moveDragProperty_;
}

const sptr<WindowProperty>& MoveDragController::GetWindowProperty() const
{
    return windowProperty_;
}

void MoveDragController::ResetMoveOrDragState()
{
    activeWindowId_ = INVALID_WINDOW_ID;
    auto moveDragProperty = new MoveDragProperty();
    SetDragProperty(moveDragProperty);
}

void MoveDragController::SetActiveWindowId(uint32_t activeWindowId)
{
    activeWindowId_ = activeWindowId;
}

uint32_t MoveDragController::GetActiveWindowId() const
{
    return activeWindowId_;
}
}
}