/*
 * Copyright (C) 2022-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 "accessibility_touchEvent_injector.h"
#include "hilog_wrapper.h"
#include "ext_utils.h"
#include "accessibility_input_interceptor.h"

namespace OHOS {
namespace Accessibility {
namespace {
    constexpr int32_t MS_TO_US = 1000;
    constexpr int32_t MOVE_GESTURE_MIN_PATH_COUNT = 2;
} // namespace

TouchInjectHandler::TouchInjectHandler(const std::shared_ptr<AppExecFwk::EventRunner> &runner,
    TouchEventInjector &server) : AppExecFwk::EventHandler(runner), server_(server)
{
}

void TouchInjectHandler::ProcessEvent(const AppExecFwk::InnerEvent::Pointer &event)
{
    std::shared_ptr<SendEventArgs> parameters = nullptr;
    if (!event) {
        HILOG_ERROR("event is nullptr");
        return;
    }
    switch (event->GetInnerEventId()) {
        case TouchEventInjector::SEND_TOUCH_EVENT_MSG:
            parameters = event->GetSharedObject<SendEventArgs>();
            if (parameters == nullptr) {
                HILOG_ERROR("parameters is nullptr");
                return;
            }
            if (!parameters->event_) {
                HILOG_WARN("pointer event is nullptr");
                return;
            }
            server_.SendPointerEvent(*parameters->event_);
            break;
        default:
            break;
    }
}

TouchEventInjector::TouchEventInjector()
{
    runner_ = AccessibilityInputInterceptor::GetInstance()->GetInputManagerRunner();
    if (!runner_) {
        HILOG_ERROR("get runner failed");
        return;
    }
    handler_ = std::make_shared<TouchInjectHandler>(runner_, *this);
    if (!handler_) {
        HILOG_ERROR("create event handler failed");
        return;
    }
}

bool TouchEventInjector::OnPointerEvent(MMI::PointerEvent &event)
{
    HILOG_DEBUG();

    EventTransmission::OnPointerEvent(event);
    return false;
}

void TouchEventInjector::DestroyEvents()
{
    HILOG_DEBUG();
    CancelInjectedEvents();
    isDestroyEvent_ = true;
    EventTransmission::DestroyEvents();
}

void TouchEventInjector::SendPointerEvent(MMI::PointerEvent &event)
{
    HILOG_DEBUG();
    if (GetNext() != nullptr) {
        EventTransmission::OnPointerEvent(event);
        if (event.GetPointerAction() == MMI::PointerEvent::POINTER_ACTION_DOWN) {
            isGestureUnderway_ = true;
        }
        if (event.GetPointerAction() == MMI::PointerEvent::POINTER_ACTION_UP) {
            isGestureUnderway_ = false;
        }
    }
}

void TouchEventInjector::CancelGesture()
{
    HILOG_DEBUG();
    std::shared_ptr<MMI::PointerEvent> event;
    MMI::PointerEvent::PointerItem pointer = {};
    int64_t time = GetSystemTime();
    pointer.SetDownTime(time);
    pointer.SetPointerId(0);
    if (GetNext() != nullptr && isGestureUnderway_) {
        event = obtainTouchEvent(MMI::PointerEvent::POINTER_ACTION_CANCEL, pointer, time);
        if (event == nullptr) {
            HILOG_ERROR("event is nullptr");
            return;
        }

        SendPointerEvent(*event);
        isGestureUnderway_ = false;
    }
}

void TouchEventInjector::CancelInjectedEvents()
{
    HILOG_DEBUG();
    if (handler_ == nullptr) {
        HILOG_ERROR("handler_ is nullptr");
        return;
    }
    if (handler_->HasInnerEvent(SEND_TOUCH_EVENT_MSG)) {
        handler_->RemoveEvent(SEND_TOUCH_EVENT_MSG);
        CancelGesture();
    }
}

std::shared_ptr<MMI::PointerEvent> TouchEventInjector::obtainTouchEvent(int32_t action,
    MMI::PointerEvent::PointerItem point, int64_t actionTime)
{
    HILOG_DEBUG();
    std::shared_ptr<MMI::PointerEvent> pointerEvent = MMI::PointerEvent::Create();
    if (pointerEvent == nullptr) {
        HILOG_ERROR("pointerEvent is nullptr");
        return nullptr;
    }

    pointerEvent->SetPointerId(point.GetPointerId());
    pointerEvent->SetTargetDisplayId(0);
    pointerEvent->SetPointerAction(action);
    pointerEvent->SetActionTime(actionTime);
    pointerEvent->SetActionStartTime(point.GetDownTime());
    pointerEvent->AddPointerItem(point);
    pointerEvent->SetSourceType(MMI::PointerEvent::SOURCE_TYPE_TOUCHSCREEN);
    return pointerEvent;
}

int64_t TouchEventInjector::GetSystemTime()
{
    HILOG_DEBUG();

    int64_t microsecond = ExtUtils::GetSystemTime() * 1000;
    return microsecond;
}

void TouchEventInjector::InjectEvents(const std::shared_ptr<AccessibilityGestureInjectPath>& gesturePath)
{
    HILOG_DEBUG();

    int64_t curTime = GetSystemTime();
    if (isDestroyEvent_ || !GetNext()) {
        HILOG_WARN("Inject gesture fail");
        return;
    }
    CancelInjectedEvents();
    CancelGesture();

    ParseTouchEventsFromGesturePath(curTime, gesturePath);

    if (injectedEvents_.empty()) {
        HILOG_WARN("No injected events");
        return;
    }
    for (size_t i = 0; i < injectedEvents_.size(); i++) {
        std::shared_ptr<SendEventArgs> parameters = std::make_shared<SendEventArgs>();
        parameters->event_ = injectedEvents_[i];
        if (injectedEvents_[i]) {
            int64_t timeout = (injectedEvents_[i]->GetActionTime() - curTime) / MS_TO_US;
            if (timeout < 0) {
                HILOG_WARN("timeout is error.%{public}" PRId64 "", timeout);
            } else {
                handler_->SendEvent(SEND_TOUCH_EVENT_MSG, parameters, timeout);
            }
        }
    }
    injectedEvents_.clear();
}

void TouchEventInjector::ParseTapsEvents(int64_t startTime,
    const std::shared_ptr<AccessibilityGestureInjectPath>& gesturePath)
{
    HILOG_DEBUG();

    if (!gesturePath) {
        HILOG_ERROR("gesturePath is null.");
        return;
    }
    const std::vector<AccessibilityGesturePosition> &positions = gesturePath->GetPositions();
    size_t positionSize = positions.size();
    if (!positionSize) {
        HILOG_WARN("PositionSize is zero.");
        return;
    }
    int64_t durationTime = gesturePath->GetDurationTime();
    if (durationTime < 0) {
        HILOG_WARN("DurationTime is wrong.");
        return;
    }
    int64_t perDurationTime = static_cast<int64_t>(static_cast<uint64_t>(durationTime) / positionSize);
    int64_t downTime = startTime;
    for (size_t i = 0; i < positionSize; i++) {
        std::shared_ptr<MMI::PointerEvent> event;
        MMI::PointerEvent::PointerItem pointer = {};
        pointer.SetPointerId(0);
        // Append down event
        int32_t px = static_cast<int32_t>(positions[i].positionX_);
        int32_t py = static_cast<int32_t>(positions[i].positionY_);
        pointer.SetDisplayX(px);
        pointer.SetRawDisplayX(px);
        pointer.SetDisplayY(py);
        pointer.SetRawDisplayY(py);
        pointer.SetDownTime(downTime);
        event = obtainTouchEvent(MMI::PointerEvent::POINTER_ACTION_DOWN, pointer, downTime);
        if (event == nullptr) {
            HILOG_ERROR("event is nullptr");
            return;
        }

        HILOG_DEBUG("append down event");
        injectedEvents_.push_back(event);

        // Append up event
        int64_t upTime = downTime + perDurationTime * MS_TO_US;
        event = obtainTouchEvent(MMI::PointerEvent::POINTER_ACTION_UP, pointer, upTime);
        if (event == nullptr) {
            HILOG_ERROR("event is nullptr");
            return;
        }

        HILOG_DEBUG("append up event");
        injectedEvents_.push_back(event);
        downTime = upTime + DOUBLE_TAP_MIN_TIME;
    }
}

void TouchEventInjector::ParseMovesEvents(int64_t startTime,
    const std::shared_ptr<AccessibilityGestureInjectPath>& gesturePath)
{
    HILOG_DEBUG();

    if (!gesturePath) {
        HILOG_ERROR("gesturePath is null.");
        return;
    }
    std::vector<AccessibilityGesturePosition> positions = gesturePath->GetPositions();
    size_t positionSize = positions.size();
    if (positionSize < MOVE_GESTURE_MIN_PATH_COUNT) {
        HILOG_WARN("PositionSize is wrong.");
        return;
    }
    int64_t durationTime = gesturePath->GetDurationTime();
    if (durationTime < 0) {
        HILOG_WARN("DurationTime is wrong.");
        return;
    }
    int64_t perDurationTime = static_cast<int64_t>(static_cast<uint64_t>(durationTime) / (positionSize - 1));
    int64_t downTime = startTime;
    int64_t nowTime = startTime;
    for (size_t i = 0; i < positionSize; i++) {
        std::shared_ptr<MMI::PointerEvent> event;
        MMI::PointerEvent::PointerItem pointer = {};
        int32_t px = static_cast<int32_t>(positions[i].positionX_);
        int32_t py = static_cast<int32_t>(positions[i].positionY_);
        pointer.SetPointerId(0);
        pointer.SetDisplayX(px);
        pointer.SetRawDisplayX(px);
        pointer.SetDisplayY(py);
        pointer.SetRawDisplayY(py);
        pointer.SetDownTime(downTime);
        if (i == 0) { // Append down event
            HILOG_DEBUG("append down event");
            event = obtainTouchEvent(MMI::PointerEvent::POINTER_ACTION_DOWN, pointer, downTime);
            if (event == nullptr) {
                HILOG_ERROR("event is nullptr");
                return;
            }

            injectedEvents_.push_back(event);
        } else if (i < (positionSize - 1)) { // Append move event
            HILOG_DEBUG("append move event");
            nowTime += perDurationTime * MS_TO_US;
            event = obtainTouchEvent(MMI::PointerEvent::POINTER_ACTION_MOVE, pointer, nowTime);
            if (event == nullptr) {
                HILOG_ERROR("event is nullptr");
                return;
            }

            injectedEvents_.push_back(event);
        } else { // Append up event
            HILOG_DEBUG("append move event");
            nowTime += perDurationTime * MS_TO_US;
            event = obtainTouchEvent(MMI::PointerEvent::POINTER_ACTION_MOVE, pointer, nowTime);
            if (event == nullptr) {
                HILOG_ERROR("event is nullptr");
                return;
            }

            injectedEvents_.push_back(event);
            
            HILOG_DEBUG("append up event");
            event = obtainTouchEvent(MMI::PointerEvent::POINTER_ACTION_UP, pointer, nowTime);
            if (event == nullptr) {
                HILOG_ERROR("event is nullptr");
                return;
            }
            
            injectedEvents_.push_back(event);
        }
    }
}

void TouchEventInjector::ParseTouchEventsFromGesturePath(int64_t startTime,
    const std::shared_ptr<AccessibilityGestureInjectPath>& gesturePath)
{
    HILOG_DEBUG();
    if (!gesturePath) {
        HILOG_ERROR("gesturePath is null.");
        return;
    }
    const std::vector<AccessibilityGesturePosition> &positions = gesturePath->GetPositions();
    if (positions.size() == 0) {
        HILOG_ERROR("position size is 0.");
        return;
    }
    if ((positions.size() == 1) ||
        ((positions[0].positionX_ == positions[1].positionX_) &&
        (positions[0].positionY_ == positions[1].positionY_))) {
        ParseTapsEvents(startTime, gesturePath);
    } else {
        ParseMovesEvents(startTime, gesturePath);
    }
}
} // namespace Accessibility
} // namespace OHOS