/*
 * Copyright (c) 2023-2026 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 "cooperate_free.h"
#include "cooperate_hisysevent.h"

#include "devicestatus_define.h"
#include "utility.h"

#undef LOG_TAG
#define LOG_TAG "CooperateFree"

namespace OHOS {
namespace Msdp {
namespace DeviceStatus {
namespace Cooperate {

CooperateFree::CooperateFree(IStateMachine &parent, IContext *env)
    : ICooperateState(parent), env_(env)
{
    initial_ = std::make_shared<Initial>(*this);
    Initial::BuildChains(initial_, *this);
    current_ = initial_;
}

CooperateFree::~CooperateFree()
{
    Initial::RemoveChains(initial_);
}

void CooperateFree::OnEvent(Context &context, const CooperateEvent &event)
{
    current_->OnEvent(context, event);
}

void CooperateFree::OnEnterState(Context &context)
{
    CALL_INFO_TRACE;
}

void CooperateFree::OnLeaveState(Context &context)
{
    CALL_INFO_TRACE;
    UpdateCooperateFlagEvent event {
        .mask = COOPERATE_FLAG_HIDE_CURSOR,
    };
    context.UpdateCooperateFlag(event);
}

void CooperateFree::SetPointerVisible(Context &context)
{
    CHKPV(env_);
    bool hasLocalPointerDevice =  env_->GetDeviceManager().HasLocalPointerDevice() ||
        env_->GetInput().HasLocalPointerDevice() ||
        env_->GetDeviceManager().HasPencilAirMouse();
    bool visible = !context.NeedHideCursor() && hasLocalPointerDevice;
    FI_HILOGI("Set pointer visible:%{public}s, HasLocalPointerDevice:%{public}s",
        visible ? "true" : "false", hasLocalPointerDevice ? "true" : "false");
    env_->GetInput().SetPointerVisibility(visible, PRIORITY);
}

void CooperateFree::UnchainConnections(Context &context, const StopCooperateEvent &event) const
{
    CALL_DEBUG_ENTER;
    if (event.isUnchained) {
        FI_HILOGI("Unchain all connections");
        context.dsoftbus_.CloseAllSessions();
        context.eventMgr_.OnUnchain(event);
    }
}

void CooperateFree::SimulateShowPointerEvent(Context &context)
{
    CALL_INFO_TRACE;
    CHKPV(env_);
    auto pointerEvent = OHOS::MMI::PointerEvent::Create();
    OHOS::MMI::PointerEvent::PointerItem item;
    item.SetPointerId(0);
    item.SetRawDx(0);
    item.SetRawDy(0);
    CHKPV(pointerEvent);
    pointerEvent->SetPointerAction(OHOS::MMI::PointerEvent::POINTER_ACTION_MOVE);
    pointerEvent->AddFlag(OHOS::MMI::InputEvent::EVENT_FLAG_RAW_POINTER_MOVEMENT);
    pointerEvent->SetPointerId(0);
    pointerEvent->SetSourceType(OHOS::MMI::PointerEvent::SOURCE_TYPE_MOUSE);
    pointerEvent->AddPointerItem(item);
    auto pointerEventFromBuilder = context.inputEventBuilder_.GetPointerEvent();
    CHKPV(pointerEventFromBuilder);
    if (auto pressedButtons = pointerEventFromBuilder->GetPressedButtons(); !pressedButtons.empty()) {
        FI_HILOGI("Has pressed buttons");
        for (auto buttonId : pressedButtons) {
            pointerEvent->SetButtonPressed(buttonId);
        }
    }
    env_->GetInput().SimulateInputEvent(pointerEvent);
}

CooperateFree::Initial::Initial(CooperateFree &parent)
    : ICooperateStep(parent, nullptr), parent_(parent)
{
    AddHandler(CooperateEventType::START, [this](Context &context, const CooperateEvent &event) {
        this->OnStart(context, event);
    });
    AddHandler(CooperateEventType::WITH_OPTIONS_START, [this](Context &context, const CooperateEvent &event) {
        this->OnStartWithOptions(context, event);
    });
    AddHandler(CooperateEventType::STOP, [this](Context &context, const CooperateEvent &event) {
        this->OnStop(context, event);
    });
    AddHandler(CooperateEventType::DISABLE, [this](Context &context, const CooperateEvent &event) {
        this->OnDisable(context, event);
    });
    AddHandler(CooperateEventType::APP_CLOSED, [this](Context &context, const CooperateEvent &event) {
        this->OnAppClosed(context, event);
    });
    AddHandler(CooperateEventType::DSOFTBUS_START_COOPERATE, [this](Context &context, const CooperateEvent &event) {
        this->OnRemoteStart(context, event);
    });
    AddHandler(CooperateEventType::INPUT_POINTER_EVENT, [this](Context &context, const CooperateEvent &event) {
        this->OnPointerEvent(context, event);
    });
    AddHandler(CooperateEventType::UPDATE_COOPERATE_FLAG,
        [this](Context &context, const CooperateEvent &event) {
            this->OnUpdateCooperateFlag(context, event);
    });
    AddHandler(CooperateEventType::DSOFTBUS_COOPERATE_WITH_OPTIONS,
        [this](Context &context, const CooperateEvent &event) {
            this->OnRemoteStartWithOptions(context, event);
    });
}

void CooperateFree::Initial::OnProgress(Context &context, const CooperateEvent &event)
{}

void CooperateFree::Initial::OnProgressWithOptions(Context &context, const CooperateEvent &event)
{}

void CooperateFree::Initial::OnReset(Context &context, const CooperateEvent &event)
{}

void CooperateFree::Initial::BuildChains(std::shared_ptr<Initial> initial, CooperateFree &parent)
{}

void CooperateFree::Initial::RemoveChains(std::shared_ptr<Initial> initial)
{}

void CooperateFree::Initial::OnStart(Context &context, const CooperateEvent &event)
{
    CALL_INFO_TRACE;
    StartCooperateEvent notice = std::get<StartCooperateEvent>(event.event);
    FI_HILOGI("[start cooperation] With \'%{public}s\'", Utility::Anonymize(notice.remoteNetworkId).c_str());
    context.StartCooperate(notice);
    context.eventMgr_.StartCooperate(notice);

    CHKPV(parent_.env_);
    if (parent_.env_->GetDragManager().GetDragState() == DragState::MOTION_DRAGGING) {
        FI_HILOGE("Not allow cooperate");
        NotAllowCooperateWhenMotionDragging result {
            .pid = notice.pid,
            .userData = notice.userData,
            .networkId = notice.remoteNetworkId,
            .success = false,
            .errCode = static_cast<int32_t>(CoordinationErrCode::NOT_AOLLOW_COOPERATE_WHEN_MOTION_DRAGGING)
        };
        context.eventMgr_.ErrorNotAollowCooperateWhenMotionDragging(result);
        return;
    }
    bool needCheckSameAccount = !(parent_.env_->GetDSoftbus().HasSessionExisted(context.Peer()));
    int32_t ret = context.dsoftbus_.OpenSession(context.Peer());
    if (ret != RET_OK) {
        CooperateFail(context, ret);
        return;
    }
    InitiatorPointerVisible(false);
    ret = context.dsoftbus_.CheckDeviceOnline(context.Peer());
    if (ret != RET_OK) {
        FI_HILOGE("CheckDeviceOnline failed, networkId:%{public}s", Utility::Anonymize(context.Peer()).c_str());
        InitiatorPointerVisible(true);
        return;
    }
    DSoftbusStartCooperate startNotice {
        .originNetworkId = context.Local(),
        .success = true,
        .cursorPos = context.NormalizedCursorPosition(),
        .pointerSpeed = context.GetPointerSpeed(),
        .touchPadSpeed = context.GetTouchPadSpeed(),
        .uid = notice.uid,
        .userId = parent_.env_->GetDDM().GetUserId(),
        .accountId = parent_.env_->GetDDM().GetAccountId(),
        .needCheckSameAccount = needCheckSameAccount
    };
    context.OnStartCooperate(startNotice.extra);
    ret = context.dsoftbus_.StartCooperate(context.Peer(), startNotice);
    if (ret != RET_OK) {
        InitiatorPointerVisible(true);
        return;
    }
    ret = context.inputEventInterceptor_.Enable(context);
    if (ret != RET_OK) {
        InitiatorPointerVisible(true);
        return;
    }
    context.eventMgr_.StartCooperateFinish(startNotice);
    FI_HILOGI("[start cooperation] Cooperation with \'%{public}s\' established",
        Utility::Anonymize(context.Peer()).c_str());
    TransiteTo(context, CooperateState::COOPERATE_STATE_OUT);
    context.OnTransitionOut();
#ifdef ENABLE_PERFORMANCE_CHECK
    std::ostringstream ss;
    ss << "start_cooperation_with_ " << Utility::Anonymize(context.Peer()).c_str();
    context.FinishTrace(ss.str());
#endif // ENABLE_PERFORMANCE_CHECK
}

void CooperateFree::Initial::InitiatorPointerVisible(bool visible)
{
    FI_HILOGI("Set pointer visible:%{public}s", visible ? "true" : "false");
    CHKPV(parent_.env_);
    parent_.env_->GetInput().SetPointerVisibility(visible);
}

void CooperateFree::Initial::CooperateFail(Context &context, int32_t ret)
{
    CALL_INFO_TRACE;
    if (ret != RET_OK) {
        FI_HILOGE("[start cooperation] Failed to connect to \'%{public}s\'",
            Utility::Anonymize(context.Peer()).c_str());
        CooperateRadarInfo radarInfo {
            .funcName = __FUNCTION__,
            .bizState = static_cast<int32_t> (BizState::STATE_END),
            .bizStage = static_cast<int32_t> (BizCooperateStage::STAGE_OPEN_DSOFTBUS_SESSION),
            .stageRes = static_cast<int32_t> (BizCooperateStageRes::RES_FAIL),
            .bizScene = static_cast<int32_t> (BizCooperateScene::SCENE_ACTIVE),
            .errCode = static_cast<int32_t> (CooperateRadarErrCode::OPEN_DSOFTBUS_SESSION_FAILED),
            .localNetId = Utility::DFXRadarAnonymize(context.Local().c_str()),
            .peerNetId = Utility::DFXRadarAnonymize(context.Peer().c_str())
        };
        CooperateRadar::ReportCooperateRadarInfo(radarInfo);
        int32_t errNum = (ret == RET_ERR ? static_cast<int32_t>(CoordinationErrCode::OPEN_SESSION_FAILED) : ret);
        DSoftbusStartCooperateFinished failNotice {
            .success = false,
            .errCode = errNum
        };
        context.eventMgr_.StartCooperateFinish(failNotice);
        return;
    }
}

void CooperateFree::Initial::OnStop(Context &context, const CooperateEvent &event)
{
    CALL_INFO_TRACE;
    StopCooperateEvent param = std::get<StopCooperateEvent>(event.event);
    context.eventMgr_.StopCooperate(param);
    param.networkId = context.Peer();
    DSoftbusStopCooperateFinished notice {
        .networkId = context.Peer(),
        .normal = true,
    };
    context.eventMgr_.StopCooperateFinish(notice);
    parent_.UnchainConnections(context, param);
}

void CooperateFree::Initial::OnStartWithOptions(Context &context, const CooperateEvent &event)
{
    CALL_INFO_TRACE;
    CHKPV(parent_.env_);
    StartWithOptionsEvent notice = std::get<StartWithOptionsEvent>(event.event);
    FI_HILOGI("[start With] With \'%{public}s\'", Utility::Anonymize(notice.remoteNetworkId).c_str());
    context.StartCooperateWithOptions(notice);
    context.eventMgr_.StartCooperateWithOptions(notice);
    if (parent_.env_->GetDragManager().GetDragState() == DragState::MOTION_DRAGGING) {
        FI_HILOGE("Not allow cooperate");
        NotAllowCooperateWhenMotionDragging result {
            .pid = notice.pid,
            .userData = notice.userData,
            .networkId = notice.remoteNetworkId,
            .success = false,
            .errCode = static_cast<int32_t>(CoordinationErrCode::NOT_AOLLOW_COOPERATE_WHEN_MOTION_DRAGGING)
        };
        context.eventMgr_.ErrorNotAollowCooperateWhenMotionDragging(result);
        return;
    }
    bool needCheckSameAccount = !(parent_.env_->GetDSoftbus().HasSessionExisted(context.Peer()));
    int32_t ret = context.dsoftbus_.OpenSession(context.Peer());
    if (ret != RET_OK) {
        FI_HILOGE("[start cooperation] Failed to connect\'%{public}s\'", Utility::Anonymize(context.Peer()).c_str());
        int32_t errNum = (ret == RET_ERR ? static_cast<int32_t>(CoordinationErrCode::OPEN_SESSION_FAILED) : ret);
        DSoftbusStartCooperateFinished failNotice {
            .success = false,
            .errCode = errNum
        };
        context.eventMgr_.StartCooperateFinish(failNotice);
        return;
    }
    DSoftbusCooperateOptions startNotice {
        .originNetworkId = context.Local(),
        .success = true,
        .cooperateOptions = {notice.displayX, notice.displayY, notice.displayId},
        .pointerSpeed = context.GetPointerSpeed(),
        .touchPadSpeed = context.GetTouchPadSpeed(),
        .userId = parent_.env_->GetDDM().GetUserId(),
        .accountId = parent_.env_->GetDDM().GetAccountId(),
        .needCheckSameAccount = needCheckSameAccount
    };
    context.OnStartCooperate(startNotice.extra);
    context.dsoftbus_.StartCooperateWithOptions(context.Peer(), startNotice);
    context.inputEventInterceptor_.Enable(context);
    context.eventMgr_.StartCooperateWithOptionsFinish(startNotice);
    FI_HILOGI("[start cooperation] Cooperation with \'%{public}s\' established",
        Utility::Anonymize(context.Peer()).c_str());
    TransiteTo(context, CooperateState::COOPERATE_STATE_OUT);
    context.OnTransitionOut();
#ifdef ENABLE_PERFORMANCE_CHECK
    std::ostringstream ss;
    ss << "start_cooperation_with_ " << Utility::Anonymize(context.Peer()).c_str();
    context.FinishTrace(ss.str());
#endif // ENABLE_PERFORMANCE_CHECK
}

void CooperateFree::Initial::OnDisable(Context &context, const CooperateEvent &event)
{
    FI_HILOGI("[disable cooperation] Stop cooperation");
    CHKPV(parent_.env_);
    auto dragState = parent_.env_->GetDragManager().GetDragState();
    if (dragState == DragState::START && parent_.env_->GetDragManager().IsCrossDragging()) {
        FI_HILOGI("reset cooperation");
        context.OnResetCooperation();
    } else if (dragState == DragState::START) {
        FI_HILOGI("drag state is start");
        return;
    }
    bool hasLocalPointerDevice =  parent_.env_->GetDeviceManager().HasLocalPointerDevice() ||
        parent_.env_->GetInput().HasLocalPointerDevice() ||
        parent_.env_->GetDeviceManager().HasPencilAirMouse();
    FI_HILOGI("HasLocalPointerDevice:%{public}s", hasLocalPointerDevice ? "true" : "false");
    parent_.env_->GetInput().SetPointerVisibility(hasLocalPointerDevice, PRIORITY);
}

void CooperateFree::Initial::OnAppClosed(Context &context, const CooperateEvent &event)
{
    FI_HILOGI("[app closed] Close all connections");
    context.inputDevMgr_.RemoveVirtualInputDevice(context.Peer());
    context.dsoftbus_.CloseAllSessions();
}

void CooperateFree::Initial::OnRemoteStart(Context &context, const CooperateEvent &event)
{
    CALL_INFO_TRACE;
    DSoftbusStartCooperate notice = std::get<DSoftbusStartCooperate>(event.event);
    context.StorePeerPointerSpeed(notice.pointerSpeed);
    context.StorePeerTouchPadSpeed(notice.touchPadSpeed);
    context.OnRemoteStartCooperate(notice.extra);
    context.eventMgr_.RemoteStart(notice);
    context.RemoteStartSuccess(notice);
    context.inputEventBuilder_.Enable(context);
    context.eventMgr_.RemoteStartFinish(notice);
    context.inputDevMgr_.AddVirtualInputDevice(context.Peer());
    FI_HILOGI("[remote start] Cooperation with \'%{public}s\' established", Utility::Anonymize(context.Peer()).c_str());
    TransiteTo(context, CooperateState::COOPERATE_STATE_IN);
    context.OnTransitionIn();
    if (!context.NeedFreezeCursor()) {
        parent_.SimulateShowPointerEvent(context);
    }
}

void CooperateFree::Initial::OnRemoteStartWithOptions(Context &context, const CooperateEvent &event)
{
    CALL_INFO_TRACE;
    DSoftbusCooperateOptions notice = std::get<DSoftbusCooperateOptions>(event.event);
    context.StorePeerPointerSpeed(notice.pointerSpeed);
    context.StorePeerTouchPadSpeed(notice.touchPadSpeed);
    context.OnRemoteStartCooperate(notice.extra);
    context.eventMgr_.RemoteStartWithOptions(notice);
    context.AdjustPointerPos(notice);
    context.OnRemoteStart(notice);
    context.inputEventBuilder_.Enable(context);
    context.eventMgr_.RemoteStartWithOptionsFinish(notice);
    context.inputDevMgr_.AddVirtualInputDevice(context.Peer());
    FI_HILOGI("[remote start with options] Cooperation with \'%{public}s\' established",
        Utility::Anonymize(context.Peer()).c_str());
    TransiteTo(context, CooperateState::COOPERATE_STATE_IN);
    context.OnTransitionIn();
    if (!context.NeedFreezeCursor()) {
        parent_.SimulateShowPointerEvent(context);
    }
}

void CooperateFree::Initial::OnPointerEvent(Context &context, const CooperateEvent &event)
{
    CALL_DEBUG_ENTER;
    if (context.NeedHideCursor() && context.IsCooperateWithCrossDrag()) {
        FI_HILOGD("Hide cursor before dragData rcvd when come back");
        return;
    }
    InputPointerEvent notice = std::get<InputPointerEvent>(event.event);
    CHKPV(parent_.env_);
    if (auto dragState = parent_.env_->GetDragManager().GetDragState(); dragState == DragState::START) {
        FI_HILOGD("Current dragState:START");
        return;
    }
    if (InputEventBuilder::IsLocalEvent(notice) && context.NeedHideCursor()) {
        UpdateCooperateFlagEvent event {
            .mask = COOPERATE_FLAG_HIDE_CURSOR,
        };
        context.UpdateCooperateFlag(event);
        parent_.SetPointerVisible(context);
    }
}

void CooperateFree::Initial::OnUpdateCooperateFlag(Context &context, const CooperateEvent &event)
{
    CALL_INFO_TRACE;
    if (std::holds_alternative<UpdateCooperateFlagEvent>(event.event)) {
        UpdateCooperateFlagEvent notice = std::get<UpdateCooperateFlagEvent>(event.event);
        context.UpdateCooperateFlag(notice);
    } else {
        FI_HILOGE("event type is not UpdateCooperateFlagEvent");
    }
}
} // namespace Cooperate
} // namespace DeviceStatus
} // namespace Msdp
} // namespace OHOS