void KisZoomAndPanTest::testZoomOnBorderZoomLevels()
{
    ZoomAndPanTester t;
    initializeViewport(t, false, false, false);

    QPoint widgetPoint(100,100);

    // test min zoom level
    t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, KoZoomMode::minimumZoom());
    QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 0.5, true));
    QVERIFY(checkZoomWithAction(t, KoZoomMode::minimumZoom() * 0.5, true));

    // test max zoom level
    t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, KoZoomMode::maximumZoom());
    QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 2.0, true));
    QVERIFY(checkZoomWithAction(t, KoZoomMode::maximumZoom() * 2.0, true));
}
void KisZoomAndPanTest::testZoomOnBorderZoomLevels()
{
    ZoomAndPanTester t;
    initializeViewport(t, false, false, false);

    QPoint widgetPoint(100,100);

    warnKrita << "WARNING: testZoomOnBorderZoomLevels() is disabled due to some changes in KoZoomMode::minimum/maximumZoom()";
    return;

    // test min zoom level
    t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, KoZoomMode::minimumZoom());
    QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 0.5, true));
    QVERIFY(checkZoomWithAction(t, KoZoomMode::minimumZoom() * 0.5, true));

    // test max zoom level
    t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, KoZoomMode::maximumZoom());
    QVERIFY(checkZoomWithWheel(t, QPoint(100,100), 2.0, true));
    QVERIFY(checkZoomWithAction(t, KoZoomMode::maximumZoom() * 2.0, true));
}
void KisZoomAndPanTest::testImageCropped()
{
    ZoomAndPanTester t;
    QApplication::processEvents();
    initializeViewport(t, false, false, false);
    QApplication::processEvents();
    QVERIFY(checkPan(t, QPoint(-150,-150)));
    QApplication::processEvents();

    QPointF oldStillPoint =
        t.coordinatesConverter()->imageToWidget(QPointF(150,150));

    t.image()->cropImage(QRect(100,100,100,100));
    t.image()->waitForDone();
    QApplication::processEvents();

    QPointF newStillPoint =
        t.coordinatesConverter()->imageToWidget(QPointF(50,50));

    QVERIFY(compareWithRounding(oldStillPoint, newStillPoint, 1.0));
}
bool KisZoomAndPanTest::checkPan(ZoomAndPanTester &t, QPoint shift)
{
    QPoint oldOffset = t.coordinatesConverter()->documentOffset();
    QPointF oldPrefCenter = t.canvasController()->preferredCenter();

    t.canvasController()->pan(shift);

    QPoint newOffset  = t.coordinatesConverter()->documentOffset();
    QPointF newPrefCenter = t.canvasController()->preferredCenter();
    QPointF newTopLeft = t.coordinatesConverter()->imageRectInWidgetPixels().topLeft();

    QPoint expectedOffset  = oldOffset + shift;
    QPointF expectedPrefCenter = oldPrefCenter + shift;


    // no tolerance accepted for pan
    bool offsetAsExpected = newOffset == expectedOffset;

    // rounding can happen due to the scroll bars being the main
    // source of the offset
    bool preferredCenterAsExpected =
        compareWithRounding(expectedPrefCenter, newPrefCenter, 1.0);

    bool topLeftAsExpected = newTopLeft.toPoint() == -newOffset;

    if (!offsetAsExpected ||
        !preferredCenterAsExpected ||
        !topLeftAsExpected) {

        qDebug() << "***** PAN *****************";

        if(!offsetAsExpected) {
            qDebug() << " ### Offset invariant broken";
        }

        if(!preferredCenterAsExpected) {
            qDebug() << " ### Preferred center invariant broken";
        }

        if(!topLeftAsExpected) {
            qDebug() << " ### TopLeft invariant broken";
        }

        qDebug() << ppVar(expectedOffset);
        qDebug() << ppVar(expectedPrefCenter);
        qDebug() << ppVar(oldOffset) << ppVar(newOffset);
        qDebug() << ppVar(oldPrefCenter) << ppVar(newPrefCenter);
        qDebug() << ppVar(newTopLeft);
        qDebug() << "***************************";
    }

    return offsetAsExpected && preferredCenterAsExpected && topLeftAsExpected;
}
bool KisZoomAndPanTest::checkZoomWithWheel(ZoomAndPanTester &t, const QPoint &widgetPoint, qreal zoomCoeff, bool limitedZoom)
{
    QPoint oldOffset = t.coordinatesConverter()->documentOffset();
    QPointF oldPrefCenter = t.canvasController()->preferredCenter();
    qreal oldZoom = t.zoomController()->zoomAction()->effectiveZoom();
    QSize oldDocumentSize = t.canvasController()->documentSize();

    t.canvasController()->zoomRelativeToPoint(widgetPoint, zoomCoeff);

    QPointF newTopLeft = t.coordinatesConverter()->imageRectInWidgetPixels().topLeft();

    return checkInvariants(oldOffset + widgetPoint,
                           oldOffset,
                           oldPrefCenter,
                           oldZoom,
                           t.coordinatesConverter()->documentOffset(),
                           t.canvasController()->preferredCenter(),
                           limitedZoom ? oldZoom : zoomCoeff * oldZoom,
                           newTopLeft,
                           oldDocumentSize);
}
bool KisZoomAndPanTest::checkZoomWithAction(ZoomAndPanTester &t, qreal newZoom, bool limitedZoom)
{
    QPoint oldOffset = t.coordinatesConverter()->documentOffset();
    QPointF oldPrefCenter = t.canvasController()->preferredCenter();
    qreal oldZoom = t.zoomController()->zoomAction()->effectiveZoom();
    QSize oldDocumentSize = t.canvasController()->documentSize();

    t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, newZoom);

    QPointF newTopLeft = t.coordinatesConverter()->imageRectInWidgetPixels().topLeft();

    return checkInvariants(oldPrefCenter,
                           oldOffset,
                           oldPrefCenter,
                           oldZoom,
                           t.coordinatesConverter()->documentOffset(),
                           t.canvasController()->preferredCenter(),
                           limitedZoom ? oldZoom : newZoom,
                           newTopLeft,
                           oldDocumentSize);
}
bool verifyOffset(ZoomAndPanTester &t, const QPoint &offset) {

    if (t.coordinatesConverter()->documentOffset() != offset) {
            qDebug() << "########################";
            qDebug() << "Expected Offset:" << offset;
            qDebug() << "Actual values:";
            qDebug() << "Offset:" << t.coordinatesConverter()->documentOffset();
            qDebug() << "wsize:"  << t.canvasWidget()->size();
            qDebug() << "vport:"  << t.canvasController()->viewportSize();
            qDebug() << "pref:"  << t.canvasController()->preferredCenter();
            qDebug() << "########################";
    }

    return t.coordinatesConverter()->documentOffset() == offset;
}
void KisZoomAndPanTest::testImageRescaled_0_5()
{
    ZoomAndPanTester t;
    QApplication::processEvents();
    initializeViewport(t, false, false, false);
    QApplication::processEvents();
    QVERIFY(checkPan(t, QPoint(200,200)));
    QApplication::processEvents();

    QPointF oldStillPoint =
        t.coordinatesConverter()->imageRectInWidgetPixels().center();

    KisFilterStrategy *strategy = new KisBilinearFilterStrategy();
    t.image()->scaleImage(QSize(320, 220), t.image()->xRes(), t.image()->yRes(), strategy);
    t.image()->waitForDone();
    QApplication::processEvents();
    delete strategy;

    QPointF newStillPoint =
        t.coordinatesConverter()->imageRectInWidgetPixels().center();

    QVERIFY(compareWithRounding(oldStillPoint, newStillPoint, 1.0));
}
void KisZoomAndPanTest::testRotation(qreal vastScrolling, qreal zoom)
{
    KisConfig cfg;
    cfg.setVastScrolling(vastScrolling);

    ZoomAndPanTester t;

    QCOMPARE(t.image()->size(), QSize(640,441));
    QCOMPARE(t.image()->xRes(), 1.0);
    QCOMPARE(t.image()->yRes(), 1.0);

    QPointF preferredCenter = zoom * t.image()->bounds().center();

    t.canvasController()->resize(QSize(500,500));
    t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, zoom);
    t.canvasController()->setPreferredCenter(preferredCenter.toPoint());

    QCOMPARE(t.canvasWidget()->size(), QSize(483,483));
    QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize());

    QPointF realCenterPoint = t.coordinatesConverter()->widgetToImage(t.coordinatesConverter()->widgetCenterPoint());
    QPointF expectedCenterPoint = QPointF(t.image()->bounds().center());

    if(!compareWithRounding(realCenterPoint, expectedCenterPoint, 2/zoom)) {
        qDebug() << "Failed to set initial center point";
        qDebug() << ppVar(expectedCenterPoint) << ppVar(realCenterPoint);
        QFAIL("FAIL: Failed to set initial center point");
    }

    QVERIFY(checkRotation(t, 30));
    QVERIFY(checkRotation(t, 20));
    QVERIFY(checkRotation(t, 10));
    QVERIFY(checkRotation(t, 5));
    QVERIFY(checkRotation(t, 5));
    QVERIFY(checkRotation(t, 5));

    if(vastScrolling < 0.5 && zoom < 1) {
        qWarning() << "Disabling a few tests for vast scrolling ="
                   << vastScrolling << ". See comment for more";
        /**
         * We have to disable a couple of tests here for the case when
         * vastScrolling value is 0.2. The problem is that the centering
         * correction applied  to the offset in
         * KisCanvasController::rotateCanvas pollutes the preferredCenter
         * value, because KoCnvasControllerWidget has no access to this
         * correction and cannot calculate the real value of the center of
         * the image. To fix this bug the calculation of correction
         * (aka "origin") should be moved to the KoCanvasControllerWidget
         * itself which would cause quite huge changes (including the change
         * of the external interface of it). Namely, we would have to
         * *calculate* offset from the value of the scroll bars, but not
         * use their values directly:
         *
         * offset = scrollBarValue - origin
         *
         * So now we just disable these unittests and allow a couple
         * of "jumping" bugs appear in vastScrolling < 0.5 modes, which
         * is, actually, not the default case.
         */

    } else {
        QVERIFY(checkRotation(t, 5));
        QVERIFY(checkRotation(t, 5));
        QVERIFY(checkRotation(t, 5));
    }
}
bool KisZoomAndPanTest::checkRotation(ZoomAndPanTester &t, qreal angle)
{
    // save old values
    QPoint oldOffset = t.coordinatesConverter()->documentOffset();
    QPointF oldCenteringCorrection = t.coordinatesConverter()->centeringCorrection();
    QPointF oldPreferredCenter = t.canvasController()->preferredCenter();
    QPointF oldRealCenterPoint = t.coordinatesConverter()->widgetToImage(t.coordinatesConverter()->widgetCenterPoint());
    QSize oldDocumentSize = t.canvasController()->documentSize();

    qreal baseAngle = t.coordinatesConverter()->rotationAngle();
    t.canvasController()->rotateCanvas(angle);

    // save result values
    QPoint newOffset = t.coordinatesConverter()->documentOffset();
    QPointF newCenteringCorrection = t.coordinatesConverter()->centeringCorrection();
    QPointF newPreferredCenter = t.canvasController()->preferredCenter();
    QPointF newRealCenterPoint = t.coordinatesConverter()->widgetToImage(t.coordinatesConverter()->widgetCenterPoint());
    QSize newDocumentSize = t.canvasController()->documentSize();


    // calculate theoretical preferred center
    QTransform rot;
    rot.rotate(angle);

    QSizeF dSize = t.coordinatesConverter()->imageSizeInFlakePixels();
    QPointF dPoint(dSize.width(), dSize.height());

    QPointF expectedPreferredCenter =
        (oldPreferredCenter - dPoint * correctionMatrix(baseAngle)) * rot +
         dPoint * correctionMatrix(baseAngle + angle);

    // calculate theoretical offset based on the real preferred center
    QPointF wPoint(t.canvasWidget()->size().width(), t.canvasWidget()->size().height());
    QPointF expectedOldOffset = oldPreferredCenter - 0.5 * wPoint;
    QPointF expectedNewOffset = newPreferredCenter - 0.5 * wPoint;

    bool preferredCenterAsExpected =
        compareWithRounding(expectedPreferredCenter, newPreferredCenter, 2);
    bool oldOffsetAsExpected =
        compareWithRounding(expectedOldOffset + oldCenteringCorrection, QPointF(oldOffset), 2);
    bool newOffsetAsExpected =
        compareWithRounding(expectedNewOffset + newCenteringCorrection, QPointF(newOffset), 3);

    qreal zoom = t.zoomController()->zoomAction()->effectiveZoom();
    bool realCenterPointAsExpected =
        compareWithRounding(oldRealCenterPoint, newRealCenterPoint, 2/zoom);


    if (!oldOffsetAsExpected ||
        !newOffsetAsExpected ||
        !preferredCenterAsExpected ||
        !realCenterPointAsExpected) {

        qDebug() << "***** ROTATE **************";

        if(!oldOffsetAsExpected) {
            qDebug() << " ### Old offset invariant broken";
        }

        if(!newOffsetAsExpected) {
            qDebug() << " ### New offset invariant broken";
        }

        if(!preferredCenterAsExpected) {
            qDebug() << " ### Preferred center invariant broken";
        }

        if(!realCenterPointAsExpected) {
            qDebug() << " ### *Real* center invariant broken";
        }

        qDebug() << ppVar(expectedOldOffset);
        qDebug() << ppVar(expectedNewOffset);
        qDebug() << ppVar(expectedPreferredCenter);
        qDebug() << ppVar(oldOffset) << ppVar(newOffset);
        qDebug() << ppVar(oldCenteringCorrection) << ppVar(newCenteringCorrection);
        qDebug() << ppVar(oldPreferredCenter) << ppVar(newPreferredCenter);
        qDebug() << ppVar(oldRealCenterPoint) << ppVar(newRealCenterPoint);
        qDebug() << ppVar(oldDocumentSize) << ppVar(newDocumentSize);
        qDebug() << ppVar(baseAngle) << "deg";
        qDebug() << ppVar(angle) << "deg";
        qDebug() << "***************************";
    }

    return preferredCenterAsExpected && oldOffsetAsExpected && newOffsetAsExpected && realCenterPointAsExpected;
}
void KisZoomAndPanTest::initializeViewport(ZoomAndPanTester &t, bool fullscreenMode, bool rotate, bool mirror)
{
    QCOMPARE(t.image()->size(), QSize(640,441));
    QCOMPARE(t.image()->xRes(), 1.0);
    QCOMPARE(t.image()->yRes(), 1.0);

    t.canvasController()->resize(QSize(500,500));
    t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, 1.0);
    t.canvasController()->setPreferredCenter(QPoint(320,220));

    QCOMPARE(t.canvasWidget()->size(), QSize(483,483));
    QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize());
    QVERIFY(verifyOffset(t, QPoint(79,-21)));

    if (fullscreenMode) {
        QCOMPARE(t.canvasController()->preferredCenter(), QPointF(320,220));

        QAction *action = t.view()->actionCollection()->action("view_show_just_the_canvas");
        action->setChecked(true);

        QVERIFY(verifyOffset(t, QPoint(79,-21)));
        QCOMPARE(t.canvasController()->preferredCenter(), QPointF(329,220));


        t.canvasController()->resize(QSize(483,483));
        QCOMPARE(t.canvasWidget()->size(), QSize(483,483));
        QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize());
        QVERIFY(verifyOffset(t, QPoint(79,-21)));


        /**
         * FIXME: here is a small flaw in KoCanvasControllerWidget
         * We cannot set the center point explicitly, because it'll be rounded
         * up by recenterPreferred function, so real center point will be
         * different. Make the preferredCenter() return real center of the
         * image instead of the set value
         */
        QCOMPARE(t.canvasController()->preferredCenter(), QPointF(320.5,220));
    }

    if (rotate) {
        t.canvasController()->rotateCanvas(90);
        QVERIFY(verifyOffset(t, QPoint(-21,79)));
        QVERIFY(compareWithRounding(QPointF(220,320), t.canvasController()->preferredCenter(), 2));
        QCOMPARE(t.coordinatesConverter()->imageRectInWidgetPixels().topLeft().toPoint(), -t.coordinatesConverter()->documentOffset());
    }

    if (mirror) {
        t.canvasController()->mirrorCanvas(true);
        QVERIFY(verifyOffset(t, QPoint(78, -21)));
        QVERIFY(compareWithRounding(QPointF(320,220), t.canvasController()->preferredCenter(), 2));
        QCOMPARE(t.coordinatesConverter()->imageRectInWidgetPixels().topLeft().toPoint(), -t.coordinatesConverter()->documentOffset());
    }
}
void KisZoomAndPanTest::testZoom100ChangingWidgetSize()
{
    ZoomAndPanTester t;

    QCOMPARE(t.image()->size(), QSize(640,441));
    QCOMPARE(t.image()->xRes(), 1.0);
    QCOMPARE(t.image()->yRes(), 1.0);

    t.canvasController()->resize(QSize(1000,1000));
    t.zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, 1.0);
    t.canvasController()->setPreferredCenter(QPoint(320,220));

    QCOMPARE(t.canvasWidget()->size(), QSize(983,983));
    QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize());
    QVERIFY(verifyOffset(t, QPoint(-171,-271)));

    t.canvasController()->resize(QSize(700,700));

    QCOMPARE(t.canvasWidget()->size(), QSize(683,683));
    QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize());
    QVERIFY(verifyOffset(t, QPoint(-171,-271)));

    t.canvasController()->setPreferredCenter(QPoint(320,220));

    QVERIFY(verifyOffset(t, QPoint(-21,-121)));

    t.canvasController()->resize(QSize(400,400));

    QCOMPARE(t.canvasWidget()->size(), QSize(383,383));
    QCOMPARE(t.canvasWidget()->size(), t.canvasController()->viewportSize());
    QVERIFY(verifyOffset(t, QPoint(-21,-121)));

    t.canvasController()->setPreferredCenter(QPoint(320,220));

    QVERIFY(verifyOffset(t, QPoint(129,29)));

    t.canvasController()->pan(QPoint(100,100));

    QVERIFY(verifyOffset(t, QPoint(229,129)));
}