// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#include "qdeclarativepinchgenerator_p.h"

#include <QtTest/QtTest>
#include <QtGui/QGuiApplication>
#include <QtGui/qpa/qwindowsysteminterface.h>
#include <QtGui/QStyleHints>

QT_BEGIN_NAMESPACE

QDeclarativePinchGenerator::QDeclarativePinchGenerator():
    target_(0),
    state_(Invalid),
    window_(0),
    activeSwipe_(0),
    replayTimer_(-1),
    replayBookmark_(-1),
    masterSwipe_(-1),
    replaySpeedFactor_(1.0),
    enabled_(true)
{
    setAcceptedMouseButtons(Qt::LeftButton | Qt::MiddleButton | Qt::RightButton);
    swipeTimer_.invalidate();
    device_ = new QPointingDevice("test", -1, QInputDevice::DeviceType::TouchScreen,
                                  QPointingDevice::PointerType::Finger,
                                  QInputDevice::Capability::Position, 1, 0);
    QWindowSystemInterface::registerInputDevice(device_);
}

QDeclarativePinchGenerator::~QDeclarativePinchGenerator()
{
    clear();
}

void QDeclarativePinchGenerator::componentComplete()
{
    QQuickItem::componentComplete();
}

void QDeclarativePinchGenerator::mousePressEvent(QMouseEvent *event)
{
    if (state_ != Idle || !enabled_) {
        event->ignore();
        return;
    }
    Q_ASSERT(!activeSwipe_);
    Q_ASSERT(!swipeTimer_.isValid());
    // Start recording a pinch gesture.
    activeSwipe_ = new Swipe;
    activeSwipe_->touchPoints << event->pos();
    activeSwipe_->durations << 0;
    swipeTimer_.start();
    setState(Recording);
}

void QDeclarativePinchGenerator::mouseMoveEvent(QMouseEvent *event)
{
    if (state_ != Recording || !enabled_) {
        event->ignore();
        return;
    }
    Q_ASSERT(activeSwipe_);
    Q_ASSERT(swipeTimer_.isValid());

    activeSwipe_->touchPoints << event->pos();
    activeSwipe_->durations << swipeTimer_.elapsed();
    swipeTimer_.start();
}

void QDeclarativePinchGenerator::mouseReleaseEvent(QMouseEvent *event)
{
    if (state_ != Recording || !enabled_) {
        event->ignore();
        return;
    }
    Q_ASSERT(activeSwipe_);
    Q_ASSERT(swipeTimer_.isValid());
    activeSwipe_->touchPoints << event->pos();
    activeSwipe_->durations << swipeTimer_.elapsed();

    if (swipes_.count() == SWIPES_REQUIRED)
        delete swipes_.takeFirst();
    swipes_ << activeSwipe_;
    activeSwipe_ = 0;
    swipeTimer_.invalidate();
    if (window_ && target_) setState(Idle); else setState(Invalid);
}

void QDeclarativePinchGenerator::mouseDoubleClickEvent(QMouseEvent *event)
{
    Q_UNUSED(event);
    if (!enabled_) {
        event->ignore();
        return;
    }
    stop();
    clear();
    if (window_ && target_) setState(Idle); else setState(Invalid);
}

void QDeclarativePinchGenerator::keyPressEvent(QKeyEvent *e)
{
    if (!enabled_) {
        e->ignore();
    }

    if (e->key() == Qt::Key_C) {
        clear();
    } else if (e->key() == Qt::Key_R) {
        replay();
    } else if (e->key() == Qt::Key_S) {
        stop();
    } else if (e->key() == Qt::Key_Plus) {
        setReplaySpeedFactor(replaySpeedFactor() + 0.1);
    } else if (e->key() == Qt::Key_Minus) {
        setReplaySpeedFactor(replaySpeedFactor() - 0.1);
    } else {
        qDebug() << metaObject()->className() << "Unsupported key event.";
    }
}

bool QDeclarativePinchGenerator::enabled() const
{
    return enabled_;
}


void QDeclarativePinchGenerator::setEnabled(bool enabled)
{
    if (enabled == enabled_)
        return;
    enabled_ = enabled;
    if (!enabled_) {
        stop();
        clear();
    }
    emit enabledChanged();
}


qreal QDeclarativePinchGenerator::replaySpeedFactor() const
{
    return replaySpeedFactor_;
}

void QDeclarativePinchGenerator::setReplaySpeedFactor(qreal factor)
{
    if (factor == replaySpeedFactor_ || factor < 0.001)
        return;
    replaySpeedFactor_ = factor;
    emit replaySpeedFactorChanged();
}


QString QDeclarativePinchGenerator::state() const
{
    switch (state_) {
    case Invalid:
        return "Invalid";
    case Idle:
        return "Idle";
        break;
    case Recording:
        return "Recording";
        break;
    case Replaying:
        return "Replaying";
        break;
    default:
        Q_ASSERT(false);
    }
    return "How emberassing";
}

void QDeclarativePinchGenerator::setState(GeneratorState state)
{
    if (state == state_)
        return;
    state_ = state;
    emit stateChanged();
}

void QDeclarativePinchGenerator::itemChange(ItemChange change, const ItemChangeData & data)
{
    if (change == ItemSceneChange) {
        window_ = data.window;
        if (target_)
            setState(Idle);
    }
}

void QDeclarativePinchGenerator::timerEvent(QTimerEvent *event)
{
    Q_ASSERT(replayTimer_ == event->timerId());
    Q_UNUSED(event);
    Q_ASSERT(state_ == Replaying);

    int slaveSwipe = masterSwipe_ ^ 1;

    int masterCount = swipes_.at(masterSwipe_)->touchPoints.count();
    int slaveCount = swipes_.at(slaveSwipe)->touchPoints.count();

    if (replayBookmark_ == 0) {
        QTest::touchEvent(window_, device_)
                .press(0, swipes_.at(masterSwipe_)->touchPoints.at(replayBookmark_))
                .press(1, swipes_.at(slaveSwipe)->touchPoints.at(replayBookmark_));
    } else if (replayBookmark_ == (slaveCount - 1)) {
        if (masterCount != slaveCount) {
            QTest::touchEvent(window_, device_)
                    .move(0, swipes_.at(masterSwipe_)->touchPoints.at(replayBookmark_))
                    .release(1, swipes_.at(slaveSwipe)->touchPoints.at(replayBookmark_));
        } else {
            QTest::touchEvent(window_, device_)
                    .release(0, swipes_.at(masterSwipe_)->touchPoints.at(replayBookmark_))
                    .release(1, swipes_.at(slaveSwipe)->touchPoints.at(replayBookmark_));
        }
    } else if (replayBookmark_ == (masterCount - 1)) {
            QTest::touchEvent(window_, device_)
                    .release(0, swipes_.at(masterSwipe_)->touchPoints.at(replayBookmark_));
    }
    else {
        QTest::touchEvent(window_, device_)
                .move(0, swipes_.at(masterSwipe_)->touchPoints.at(replayBookmark_))
                .move(1, swipes_.at(slaveSwipe)->touchPoints.at(replayBookmark_));
    }

    replayBookmark_++;
    if (replayBookmark_ >= swipes_.at(masterSwipe_)->touchPoints.count())
        stop();
    else {
        killTimer(replayTimer_);
        replayTimer_ = startTimer((swipes_.at(masterSwipe_)->durations.at(replayBookmark_) + 5) / replaySpeedFactor_ );
    }
}

QQuickItem* QDeclarativePinchGenerator::target() const
{
    return target_;
}

void QDeclarativePinchGenerator::setTarget(QQuickItem* target)
{
    if (target == target_)
        return;
    target_ = target;
    stop();
    clear();
    if (window_)
        setState(Idle);
    else
        setState(Invalid);
    emit targetChanged();
}

void QDeclarativePinchGenerator::pinch(QPoint point1From,
                                       QPoint point1To,
                                       QPoint point2From,
                                       QPoint point2To,
                                       int interval1,
                                       int interval2,
                                       int samples1,
                                       int samples2)
{
    Q_ASSERT(interval1 > 10);
    Q_ASSERT(interval2 > 10);
    Q_ASSERT(samples1 >= 2); // we need press and release events at minimum
    Q_ASSERT(samples2 >= 2);

    clear();

    Swipe* swipe1 = new Swipe;
    Swipe* swipe2 = new Swipe;
    for (int i = 0; i < samples1; ++i) {
        swipe1->touchPoints << point1From + (point1To - point1From) / samples1 * i;
        swipe1->durations << interval1;
    }
    for (int i = 0; i < samples2; ++i) {
        swipe2->touchPoints << point2From + (point2To - point2From) / samples2 * i;
        swipe2->durations << interval2;
    }
    swipes_ << swipe1 << swipe2;
    Q_ASSERT(swipes_.at(0));
    Q_ASSERT(swipes_.at(1));

    masterSwipe_ = (samples1 >= samples2) ? 0 : 1;

    replayTimer_ = startTimer(swipes_.at(masterSwipe_)->durations.at(0) / replaySpeedFactor_);
    replayBookmark_ = 0;
    setState(Replaying);
}

void QDeclarativePinchGenerator::pinchPress(QPoint point1From, QPoint point2From)
{
    QTest::touchEvent(window_, device_).press(0, point1From).press(1, point2From);
}

void QDeclarativePinchGenerator::pinchMoveTo(QPoint point1To, QPoint point2To)
{
    QTest::touchEvent(window_, device_).move(0, point1To).move(1, point2To);
}

void QDeclarativePinchGenerator::pinchRelease(QPoint point1To, QPoint point2To)
{
    QTest::touchEvent(window_, device_).release(0, point1To).release(1, point2To);
}

void QDeclarativePinchGenerator::replay()
{
    if (state_ != Idle) {
        qDebug() << "Wrong state, will not replay pinch, state: " << state_;
        return;
    }
    if (swipes_.count() < SWIPES_REQUIRED) {
        qDebug() << "Too few swipes, cannot replay, amount: " << swipes_.count();
        return;
    }
    if ((swipes_.at(0)->touchPoints.count() < 2) || (swipes_.at(1)->touchPoints.count() < 2)) {
        qDebug() << "Too few touchpoints, won't replay, amount: " <<
                    swipes_.at(0)->touchPoints.count() << (swipes_.at(1)->touchPoints.count() < 2);
        return;
    }

    masterSwipe_ = (swipes_.at(0)->touchPoints.count() >= swipes_.at(1)->touchPoints.count()) ? 0 : 1;

    replayTimer_ = startTimer(swipes_.at(masterSwipe_)->touchPoints.count() / replaySpeedFactor_);
    replayBookmark_ = 0;
    setState(Replaying);
}

void QDeclarativePinchGenerator::clear()
{
    stop();
    delete activeSwipe_;
    activeSwipe_ = 0;
    if (!swipes_.isEmpty()) {
        qDeleteAll(swipes_);
        swipes_.clear();
    }
}

void QDeclarativePinchGenerator::stop()
{
    if (state_ != Replaying)
        return;
    // stop replay
    Q_ASSERT(replayTimer_ != -1);
    killTimer(replayTimer_);
    replayTimer_ = -1;
    setState(Idle);
}

int QDeclarativePinchGenerator::startDragDistance()
{
    return qApp->styleHints()->startDragDistance();
}

QT_END_NAMESPACE
