/*
 *  SPDX-FileCopyrightText: 2016 Dmitry Kazakov <dimula73@gmail.com>
 *
 *  SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "kis_algebra_2d_test.h"

#include <QtMath>
#include <QRandomGenerator>
#include <simpletest.h>

#include "kis_algebra_2d.h"
#include "kis_debug.h"

Q_DECLARE_METATYPE(KisAlgebra2D::VectorPath::VectorPathPoint);
Q_DECLARE_METATYPE(QPainterPath);


namespace KisAlgebra2D {

}

void KisAlgebra2DTest::testHalfPlane()
{
    {
        QPointF a(10,10);
        QPointF b(5,5);

        KisAlgebra2D::RightHalfPlane p(a, b);

        QVERIFY(p.value(QPointF(7, 5)) > 0);
        QVERIFY(p.value(QPointF(3, 5)) < 0);
        QVERIFY(p.value(QPointF(3, 3)) == 0);
    }

    {
        QPointF a(10,10);
        QPointF b(15,10);

        KisAlgebra2D::RightHalfPlane p(a, b);
        QCOMPARE(p.value(QPointF(3, 5)), -5.0);
        QCOMPARE(p.value(QPointF(500, 15)), 5.0);
        QCOMPARE(p.value(QPointF(1000, 10)), 0.0);

        QCOMPARE(p.valueSq(QPointF(3, 5)), -25.0);
        QCOMPARE(p.valueSq(QPointF(500, 15)), 25.0);
        QCOMPARE(p.valueSq(QPointF(1000, 10)), 0.0);

        QCOMPARE(p.pos(QPointF(3, 5)), -1);
        QCOMPARE(p.pos(QPointF(500, 15)), 1);
        QCOMPARE(p.pos(QPointF(1000, 10)), 0);
    }
}

void KisAlgebra2DTest::testOuterCircle()
{
    QPointF a(10,10);
    KisAlgebra2D::OuterCircle p(a, 5);

    QVERIFY(p.value(QPointF(3, 5)) > 0);
    QVERIFY(p.value(QPointF(7, 7)) < 0);
    QVERIFY(p.value(QPointF(10, 5)) == 0);

    QCOMPARE(p.value(QPointF(10, 12)), -3.0);
    QCOMPARE(p.value(QPointF(10, 15)), 0.0);
    QCOMPARE(p.value(QPointF(10, 17)), 2.0);
}

void KisAlgebra2DTest::testQuadraticEquation()
{
    int result = 0;
    qreal x1 = 0;
    qreal x2 = 0;

    result = KisAlgebra2D::quadraticEquation(3, -11, 6, &x1, &x2);

    QCOMPARE(result, 2);
    QCOMPARE(x2, 2.0 / 3.0);
    QCOMPARE(x1, 3.0);

    result = KisAlgebra2D::quadraticEquation(9, -12, 4, &x1, &x2);

    QCOMPARE(result, 1);
    QCOMPARE(x1, 2.0 / 3.0);

    result = KisAlgebra2D::quadraticEquation(9, -1, 4, &x1, &x2);
    QCOMPARE(result, 0);
}

void KisAlgebra2DTest::testIntersections()
{
    QVector<QPointF> points;

    points = KisAlgebra2D::intersectTwoCircles(QPointF(10,10), 5.0,
                                               QPointF(20,10), 5.0);

    QCOMPARE(points.size(), 1);
    QCOMPARE(points[0], QPointF(15, 10));

    points = KisAlgebra2D::intersectTwoCircles(QPointF(10,10), 5.0,
                                               QPointF(18,10), 5.0);

    QCOMPARE(points.size(), 2);
    QCOMPARE(points[0], QPointF(14, 13));
    QCOMPARE(points[1], QPointF(14, 7));

    points = KisAlgebra2D::intersectTwoCircles(QPointF(10,10), 5.0,
                                               QPointF(10,20), 5.0);

    QCOMPARE(points.size(), 1);
    QCOMPARE(points[0], QPointF(10, 15));

    points = KisAlgebra2D::intersectTwoCircles(QPointF(10,10), 5.0,
                                               QPointF(10,18), 5.0);

    QCOMPARE(points.size(), 2);
    QCOMPARE(points[0], QPointF(7, 14));
    QCOMPARE(points[1], QPointF(13, 14));

    points = KisAlgebra2D::intersectTwoCircles(QPointF(10,10), 5.0,
                                               QPointF(17,17), 5.0);

    QCOMPARE(points.size(), 2);
    QCOMPARE(points[0], QPointF(13, 14));
    QCOMPARE(points[1], QPointF(14, 13));

    points = KisAlgebra2D::intersectTwoCircles(QPointF(10,10), 5.0,
                                               QPointF(10,100), 5.0);

    QCOMPARE(points.size(), 0);
}

void KisAlgebra2DTest::testWeirdIntersections()
{
    QVector<QPointF> points;

    QPointF c1 = QPointF(5369.14,3537.98);
    QPointF c2 = QPointF(5370.24,3536.71);
    qreal r1 = 8.5;
    qreal r2 = 10;

    points = KisAlgebra2D::intersectTwoCircles(c1, r1, c2, r2);

    QCOMPARE(points.size(), 2);
    //QCOMPARE(points[0], QPointF(15, 10));
}

void KisAlgebra2DTest::testMatrixDecomposition1()
{
    QTransform Sh;
    Sh.shear(0.2, 0);
    QTransform R;
    R.rotate(30);
    const QTransform t0 =
        QTransform::fromScale(0.5, -0.6) *
        R * Sh *
        QTransform::fromTranslate(100, 200);

    KisAlgebra2D::DecomposedMatrix matrix(t0);

    QCOMPARE(matrix.isValid(), true);
    QVERIFY(KisAlgebra2D::fuzzyMatrixCompare(matrix.transform(), t0, 1e-4));
}

void KisAlgebra2DTest::testMatrixDecomposition2()
{
    QPolygonF poly;
    poly << QPointF(-0.3,-0.3);
    poly << QPointF(1,0);
    poly << QPointF(0.8,0.8);
    poly << QPointF(0,1);

    QTransform t0;
    bool valid = QTransform::squareToQuad(poly, t0);
    QVERIFY(valid);

    KisAlgebra2D::DecomposedMatrix matrix(t0);

    QCOMPARE(matrix.isValid(), true);
    QVERIFY(KisAlgebra2D::fuzzyMatrixCompare(matrix.transform(), t0, 1e-4));
}

void KisAlgebra2DTest::testMatrixDecomposition3()
{
    //QTransform t0 = QTransform(177.097, 28.4105, -0.344587, -231.26, -33.6893, -0.338797, 95.3185, -54.9594, 0.658308);
    QTransform t0 = QTransform(266.715, 65.9483, 0.153259, 331.445, 81.615, 0.922963, 500.68, 363.222, 1.53811);
    ENTER_FUNCTION() << ppVar(t0.m33()) << ppVar(t0.determinant());
    KisAlgebra2D::DecomposedMatrix matrix(t0);

    QCOMPARE(matrix.isValid(), true);
    QVERIFY(KisAlgebra2D::fuzzyMatrixCompare(matrix.transform(), t0, 1e-4));
    ENTER_FUNCTION() << matrix.transform();

    // test 4




}



void KisAlgebra2DTest::testDivisionWithFloor()
{
    QBENCHMARK {

    for (int a = -1000; a < 1000; a++) {
        for (int b = -1000; b < 1000; b++) {
            if (b == 0) continue;

            int refValue = qFloor(qreal(a) / b);
            int value = KisAlgebra2D::divideFloor(a, b);

            if (refValue != value) {
                qDebug() << ppVar(a) << ppVar(b);

                QCOMPARE(value, refValue);
            }
        }
    }
    }
}

#include <QPainter>
#include <QPainterPath>

void KisAlgebra2DTest::testDrawEllipse()
{
    QImage image(QSize(300,300), QImage::Format_ARGB32);
    image.fill(255);

    QPainter gc(&image);

    QTransform rot;
    rot.rotate(-30);

    QTransform shear;
    shear.shear(0.5, 0.3);

    const QTransform transform =
        rot * QTransform::fromTranslate(10, 30) * shear * QTransform::fromTranslate(150, 150);

    const qreal a = 100;
    const qreal b = 50;

    gc.setTransform(transform);
    gc.setPen(Qt::black);
    gc.drawEllipse(QPointF(0,0), a, b);

    gc.setPen(Qt::blue);
    gc.drawEllipse(QPointF(a, 0), 3, 3);

    gc.setPen(Qt::red);
    gc.drawEllipse(QPointF(0, b), 3, 3);

    QPointF newAxes;
    QTransform newTransform;

    std::tie(newAxes, newTransform) = KisAlgebra2D::transformEllipse(QPointF(a, b), transform);

    gc.setOpacity(50);
    gc.resetTransform();
    gc.setTransform(newTransform);
    gc.setPen(QPen(Qt::blue, 2));
    gc.drawEllipse(QPointF(0,0), newAxes.x(), newAxes.y());

    gc.setPen(QPen(Qt::green, 2));
    gc.drawEllipse(QPointF(newAxes.x(), 0), 5, 5);

    gc.setPen(Qt::yellow);
    gc.drawEllipse(QPointF(0, newAxes.y()), 5, 5);

    image.save("ellipse_result.png");
}

void KisAlgebra2DTest::testNullRectProcessing()
{
    QPainterPath line;
    line.moveTo(10,10);
    line.lineTo(110,10);

    const QRectF lineRect(line.boundingRect());
    qDebug() << ppVar(lineRect) << ppVar(lineRect.isValid()) << ppVar(lineRect.isNull());



    // test relative operations
    const QPointF relPoint = KisAlgebra2D::absoluteToRelative(QPointF(30, 10), lineRect);
    QCOMPARE(relPoint, QPointF(0.2, 0.0));

    const QPointF absPoint = KisAlgebra2D::relativeToAbsolute(relPoint, lineRect);
    QCOMPARE(absPoint, QPointF(30.0, 10.0));

    const QTransform relTransform = KisAlgebra2D::mapToRect(lineRect);
    QCOMPARE(relTransform.map(relPoint), absPoint);

    // test relative isotropic operations
    QCOMPARE(KisAlgebra2D::absoluteToRelative(
                 KisAlgebra2D::relativeToAbsolute(0.2, lineRect),
                 lineRect),
             0.2);

    /// test transformations

    // translate
    QCOMPARE(QTransform::fromTranslate(10, 20).mapRect(lineRect), QRect(20, 30, 100, 0));

    // scale
    QCOMPARE(QTransform::fromScale(2.0, 2.0).mapRect(lineRect), QRect(20, 20, 200, 0));

    // rotate
    QTransform rot;
    rot.rotate(90);
    QCOMPARE(rot.mapRect(lineRect), QRectF(-10, 10, 0, 100));

    // shear-x
    QTransform shearX;
    shearX.shear(2.0, 0.0);
    QCOMPARE(shearX.mapRect(lineRect), QRectF(30, 10, 100, 0));

    // shear-y
    QTransform shearY;
    shearY.shear(0.0, 2.0);
    QCOMPARE(shearY.mapRect(lineRect), QRectF(10, 30, 100, 200));

    /// binary operations

    QCOMPARE(QRectF() | lineRect, lineRect);
    QCOMPARE(QRectF(10, 10, 40, 40) | lineRect, QRectF(10, 10, 100, 40));

    QCOMPARE(QRectF(20, 20, 40, 40) & lineRect, QRectF());

    QEXPECT_FAIL("", "Qt's rectangle intersection operator doesn't work with line-rects", Continue);
    QCOMPARE(QRectF(10, 10, 40, 40) & lineRect, QRectF(10, 10, 40, 0));

    /// QPolygon's bounding rect

    QCOMPARE(QPolygonF(lineRect).boundingRect(), lineRect);
}

void KisAlgebra2DTest::testLineIntersections()
{
    using KisAlgebra2D::intersectLines;
    double epsilon = 1e-06;

    {
        boost::optional<QPointF> p =
                intersectLines(QLineF(QPointF(50,50), QPointF(100,50)),
                               QLineF(QPointF(75,0), QPointF(75,1)));
        QVERIFY(p);
        QCOMPARE(*p, QPointF(75, 50));
    }

    {
        boost::optional<QPointF> p =
                intersectLines(QLineF(QPointF(50,50), QPointF(100,50)),
                               QLineF(QPointF(75,0), QPointF(76,1)));
        QVERIFY(!p);
    }

    {
        boost::optional<QPointF> p =
                intersectLines(QLineF(QPointF(50,50), QPointF(100,50)),
                               QLineF(QPointF(50,51), QPointF(100,51)));
        QVERIFY(!p);
    }

    {
        boost::optional<QPointF> p =
                intersectLines(QLineF(QPointF(51,50), QPointF(51,100)),
                               QLineF(QPointF(50,50), QPointF(50,100)));
        QVERIFY(!p);
    }

    {
        boost::optional<QPointF> p =
                intersectLines(QLineF(QPointF(50,50), QPointF(51,51)),
                               QLineF(QPointF(51,50), QPointF(52,51)));
        QVERIFY(!p);
    }

    {
        // first bounded, then unbounded
        boost::optional<QPointF> p =
                intersectLines(QLineF(QPointF(4,5),QPointF(-1,5)),
                               QLineF(QPointF(-1,0),QPointF(-1,3)));
        QVERIFY(p);
        qCritical() << "What I've got is" << *p << qAbs(p.value().x() + 1) << qAbs(p.value().y() - 5);
        QVERIFY(qAbs(p.value().x() + 1) < epsilon);
        QVERIFY(qAbs(p.value().y() - 5) < epsilon);
    }
}

void testLineRectIntersection(QRect rect, QLineF line, QLineF expected, bool extendFirst, bool extendSecond, bool intersectsExpected)
{
    QLineF result = line;
    bool intersects = KisAlgebra2D::intersectLineRect(result, rect, extendFirst, extendSecond);

    float epsilon = 1e-4; // good enough for most usecases // I don't use qFuzzyCompare because it has too small epsilon

    auto fuzzyCompare = [epsilon] (float a, float b) -> bool {
            return (qAbs(a - b) < epsilon); };
    auto fuzzyComparePoint = [&] (QPointF a, QPointF b) -> bool {
            return fuzzyCompare(a.x(), b.x()) && fuzzyCompare(a.y(), b.y()); };
    auto fuzzyCompareLine = [&] (QLineF a, QLineF b) -> bool {
            return fuzzyComparePoint(a.p1(), b.p1()) && fuzzyComparePoint(a.p2(), b.p2()); };

    bool success = fuzzyCompareLine(result, expected) && intersectsExpected == intersects;

    if (!success) {
        qCritical() << "FAILURE";
        qCritical() << "intersects: " << intersects;
        qCritical() << "intersects expected: " << intersectsExpected;
        qCritical() << "Extend: " << extendFirst << extendSecond;
        qCritical();

        qCritical() << "original: " << line;
        qCritical() << "rectangle was: " << rect;
        qCritical() << "expected: " << expected;
        qCritical() << "result: " << result;
    }

    QVERIFY(success);

}


void KisAlgebra2DTest::testLineRectIntersectionsManual()
{
    int x1 = 100, x2 = 400; // for x values
    int y1 = 130, y2 = 430; // for y values


    QRect rect1(x1, y1, x2 - x1, y2 - y1);
    QLineF line1original(x1, y1, x2, y2);
    QLineF line1expected = line1original;

    testLineRectIntersection(rect1, line1original, line1expected, false, false, true);


    QRect rect2(x1, y1, x2, y2);
    QLineF line2original(x1, y1, x2, y2);
    QLineF line2expected = line2original;

    testLineRectIntersection(rect2, line2original, line2expected, false, false, true);

    QRect rect3 = QRect(0,0, 2616, 1748);
    QLineF line3original = QLineF(QPointF(1110.74,398.93), QPointF(0,394.068));
    QLineF line3expected = line3original;

    testLineRectIntersection(rect3, line3original, line3expected, false, false, true);

    QRect rect4 = rect1;
    QLineF line4 = QLineF(x2, y1, x1, y2);
    testLineRectIntersection(rect4, line4, line4, false, false, true);


    QRect rect5 = rect1;
    QLineF line5 = QLineF(x2, y2, x1, y1);
    testLineRectIntersection(rect5, line5, line5, false, false, true);

    QRect rect6 = rect1;
    QLineF line6 = QLineF(x2, y1, x2, y2);
    testLineRectIntersection(rect6, line6, line6, false, false, true);

    QRect rect7 = rect3;
    QLineF line7 = QLineF(QPointF(338.69,421.014),QPointF(983.456,421.014));
    testLineRectIntersection(rect7, line7, line7, false, false, true);

    QRect rect8 = rect3;
    QLineF line8 = QLineF(QPointF(-3545.25,618.966),QPointF(102.104,618.966));
    QLineF line8expected = QLineF(QPointF(0,618.966),QPointF(102.104,618.966));
    testLineRectIntersection(rect8, line8, line8expected, false, false, true);


    // vertical
    //   inside the line
    testLineRectIntersection(QRect(0, 0, 20, 20), QLineF(0, 5, 0, 15), QLineF(0, 5, 0, 15), false, false, true);
    //   exactly the line
    testLineRectIntersection(QRect(0, 0, 20, 20), QLineF(0, 0, 0, 20), QLineF(0, 0, 0, 20), false, false, true);
    //   extending the line
    testLineRectIntersection(QRect(0, 0, 20, 20), QLineF(0, -5, 0, 25), QLineF(0, 0, 0, 20), false, false, true);

    // diagonal
    //   exactly
    testLineRectIntersection(QRect(0, 0, 20, 20), QLineF(0, 0, 20, 20), QLineF(0, 0, 20, 20), false, false, true);
    //   inside
    testLineRectIntersection(QRect(0, 0, 20, 20), QLineF(5, 5, 15, 15), QLineF(5, 5, 15, 15), false, false, true);
    //   extending
    testLineRectIntersection(QRect(0, 0, 20, 20), QLineF(-5, -5, 30, 30), QLineF(0, 0, 20, 20), false, false, true);


    testLineRectIntersection(QRect(0, 0, 20, 20), QLineF(-5, -100, -5, 100), QLineF(-5, -100, -5, 100), false, false, false);

    // start: (-1, -2)
    // end: (4, 5);
    // width, height: 5, 7
    QRect testRect = QRect(-1, -2, 5, 7);

    // first item: line, second item: result after cropping
    QList<QLineF> linesListIntersecting = QList<QLineF>({
                                                            // horizontal
                                                            QLineF(-1, -2, 4, -2), QLineF(-1, -2, 4, -2), // on the edge, exact
                                                            QLineF(-1, 0, 4, 0), QLineF(-1, 0, 4, 0), // below the edge, exact
                                                            QLineF(-10, -2, 10, -2), QLineF(-1, -2, 4, -2), // on the edge, longer
                                                            QLineF(-10, 0, 10, 0), QLineF(-1, 0, 4, 0), // below the edge, longer
                                                            QLineF(0, -2, 3, -2), QLineF(0, -2, 3, -2), // on the edge, shorter
                                                            QLineF(0, 0, 3, -2), QLineF(0, 0, 3, -2), // below the edge, shorter

                                                            // vertical
                                                            QLineF(-1, -2, -1, 5), QLineF(-1, -2, -1, 5), // on the edge, exact
                                                            QLineF(0, -2, 0, 5), QLineF(0, -2, 0, 5), // below the edge, exact
                                                            QLineF(-1, -10, -1, 10), QLineF(-1, -2, -1, 5), // on the edge, longer
                                                            QLineF(0, -10, 0, 10), QLineF(0, -2, 0, 5), // below the edge, longer
                                                            QLineF(-1, 0, -1, 3), QLineF(-1, 0, -1, 3), // on the edge, shorter
                                                            QLineF(0, 0, 0, 3), QLineF(0, 0, 0, 3), // below the edge, shorter


                                                            // skewed
                                                            QLineF(-6, 0, 9, 3), QLineF(-1, 1, 4, 2), // very horizontal-like
                                                            QLineF(-1, 1, 4, 2), QLineF(-1, 1, 4, 2), // very horizontal-like, exact
                                                            QLineF(-6, 0, 4, 2), QLineF(-1, 1, 4, 2), // very horizontal-like, half-exact

                                                            QLineF(-6, -9, 9, 12), QLineF(-1, -2, 4, 5), // perfectly diagonal
                                                            QLineF(-1, -2, 4, 5), QLineF(-1, -2, 4, 5), // perfectly diagonal, exact
                                                            QLineF(-1, -2, 9, 12), QLineF(-1, -2, 4, 5), // perfectly diagonal, half-exact

                                                            QLineF(9, 12, -6, -9), QLineF(4, 5, -1, -2), // perfectly diagonal, p1 and p2 reversed

                                                            QLineF(1, 3, 3, 4), QLineF(1, 3, 3, 4), // skewed, fully inside
                                                            QLineF(2, 2, 6, 4), QLineF(2, 2, 4, 3), // skewed, partially inside


                                                        });

    for (int i = 0; i < linesListIntersecting.size()/2; i++)
    {
        testLineRectIntersection(testRect, linesListIntersecting[2*i], linesListIntersecting[2*i + 1], false, false, true);
    }

    // now with extend = true

    // first item: line, second item: result after cropping
    QList<QLineF> linesListIntersectingExtend = QList<QLineF>({
                                                            // horizontal
                                                            QLineF(-1, -2, 4, -2), QLineF(-1, -2, 4, -2), // on the edge, exact
                                                            QLineF(-1, 0, 4, 0), QLineF(-1, 0, 4, 0), // below the edge, exact
                                                            QLineF(-10, -2, 10, -2), QLineF(-1, -2, 4, -2), // on the edge, longer
                                                            QLineF(-10, 0, 10, 0), QLineF(-1, 0, 4, 0), // below the edge, longer
                                                            QLineF(0, -2, 3, -2), QLineF(-1, -2, 4, -2), // on the edge, shorter
                                                            QLineF(0, 0, 3, 0), QLineF(-1, 0, 4, 0), // below the edge, shorter

                                                            // vertical
                                                            QLineF(-1, -2, -1, 5), QLineF(-1, -2, -1, 5), // on the edge, exact
                                                            QLineF(0, -2, 0, 5), QLineF(0, -2, 0, 5), // below the edge, exact
                                                            QLineF(-1, -10, -1, 10), QLineF(-1, -2, -1, 5), // on the edge, longer
                                                            QLineF(0, -10, 0, 10), QLineF(0, -2, 0, 5), // below the edge, longer
                                                            QLineF(-1, 0, -1, 3), QLineF(-1, -2, -1, 5), // on the edge, shorter
                                                            QLineF(0, 0, 0, 3), QLineF(0, -2, 0, 5), // below the edge, shorter


                                                            // skewed
                                                            QLineF(-6, 0, 9, 3), QLineF(-1, 1, 4, 2), // very horizontal-like
                                                            QLineF(-1, 1, 4, 2), QLineF(-1, 1, 4, 2), // very horizontal-like, exact
                                                            QLineF(-6, 0, 4, 2), QLineF(-1, 1, 4, 2), // very horizontal-like, half-exact

                                                            QLineF(-6, -9, 9, 12), QLineF(-1, -2, 4, 5), // perfectly diagonal
                                                            QLineF(-1, -2, 4, 5), QLineF(-1, -2, 4, 5), // perfectly diagonal, exact
                                                            QLineF(-1, -2, 9, 12), QLineF(-1, -2, 4, 5), // perfectly diagonal, half-exact

                                                            QLineF(9, 12, -6, -9), QLineF(4, 5, -1, -2), // perfectly diagonal, p1 and p2 reversed

                                                            QLineF(1, 3, 3, 4), QLineF(-1, 2, 4, 4.5), // skewed, fully inside
                                                            QLineF(2, 2, 6, 4), QLineF(-1, 0.5, 4, 3), // skewed, partially inside

                                                        });

    for (int i = 0; i < linesListIntersectingExtend.size()/2; i++)
    {
        testLineRectIntersection(testRect, linesListIntersectingExtend[2*i], linesListIntersectingExtend[2*i + 1], true, true, true);
    }

    QList<QLineF> linesListIntersectingExtendOneSide = QList<QLineF>({
                                                            // horizontal

                                                            QLineF(-1, -2, 4, -2), QLineF(-1, -2, 4, -2), // on the edge, exact
                                                            QLineF(-1, 0, 4, 0), QLineF(-1, 0, 4, 0), // below the edge, exact
                                                            QLineF(-10, -2, 10, -2), QLineF(-1, -2, 4, -2), // on the edge, longer
                                                            QLineF(-10, 0, 10, 0), QLineF(-1, 0, 4, 0), // below the edge, longer
                                                            QLineF(0, -2, 3, -2), QLineF(-1, -2, 3, -2), // on the edge, shorter
                                                            QLineF(0, 0, 3, 0), QLineF(-1, 0, 3, 0), // below the edge, shorter

                                                            // vertical
                                                            QLineF(-1, -2, -1, 5), QLineF(-1, -2, -1, 5), // on the edge, exact
                                                            QLineF(0, -2, 0, 5), QLineF(0, -2, 0, 5), // below the edge, exact
                                                            QLineF(-1, -10, -1, 10), QLineF(-1, -2, -1, 5), // on the edge, longer
                                                            QLineF(0, -10, 0, 10), QLineF(0, -2, 0, 5), // below the edge, longer
                                                            QLineF(-1, 0, -1, 3), QLineF(-1, -2, -1, 3), // on the edge, shorter
                                                            QLineF(0, 0, 0, 3), QLineF(0, -2, 0, 3), // below the edge, shorter


                                                            // skewed
                                                            QLineF(-6, 0, 9, 3), QLineF(-1, 1, 4, 2), // very horizontal-like
                                                            QLineF(-1, 1, 4, 2), QLineF(-1, 1, 4, 2), // very horizontal-like, exact
                                                            QLineF(-6, 0, 4, 2), QLineF(-1, 1, 4, 2), // very horizontal-like, half-exact

                                                            QLineF(-6, -9, 9, 12), QLineF(-1, -2, 4, 5), // perfectly diagonal
                                                            QLineF(-1, -2, 4, 5), QLineF(-1, -2, 4, 5), // perfectly diagonal, exact
                                                            QLineF(-1, -2, 9, 12), QLineF(-1, -2, 4, 5), // perfectly diagonal, half-exact

                                                            QLineF(9, 12, -6, -9), QLineF(4, 5, -1, -2), // perfectly diagonal, p1 and p2 reversed

                                                            QLineF(1, 3, 3, 4), QLineF(-1, 2, 3, 4), // skewed, fully inside
                                                            QLineF(2, 2, 6, 4), QLineF(-1, 0.5, 4, 3), // skewed, partially inside


                                                        });

    for (int i = 0; i < linesListIntersectingExtend.size()/2; i++)
    {
        // first from one side
        testLineRectIntersection(testRect, linesListIntersectingExtendOneSide[2*i], linesListIntersectingExtendOneSide[2*i + 1], true, false, true);

        // and then reversed
        QLineF l = QLineF(linesListIntersectingExtendOneSide[2*i].p2(), linesListIntersectingExtendOneSide[2*i].p1());
        QLineF lexp = QLineF(linesListIntersectingExtendOneSide[2*i + 1].p2(), linesListIntersectingExtendOneSide[2*i + 1].p1());
        testLineRectIntersection(testRect, l, lexp, false, true, true);
    }


}

void KisAlgebra2DTest::testLineRectIntersectionsRandom()
{
    // start: (-1, -2)
    // end: (4, 5);
    // width, height: 5, 7
    QRect testRect = QRect(-1, -2, 5, 7);

    int numberOfTests = 100;
    QRandomGenerator random(1000);

    for (int i = 0; i < numberOfTests; i++) {

        QList<QPointF> points;
        for (int j = 0; j < 2; j++)
        {
            // find two random points on the edges of the rectangle
            int direction = random.generate()%2;
            int boundary = random.generate()%2;
            points.append(QPointF());
            if (direction == 0) {
                points[j].setX(testRect.x() + random.generateDouble()*testRect.width());
                points[j].setY(boundary == 0 ? testRect.y() : (testRect.y() + testRect.height()));
            } else {
                points[j].setY(testRect.y() + random.generateDouble()*testRect.height());
                points[j].setX(boundary == 0 ? testRect.x() : (testRect.x() + testRect.width()));
            }
        }

        bool onEdge = false;
        if (points[0].x() == points[1].x() || points[0].y() == points[1].y()) {
            onEdge = true;
            // 25% chance that it happens
        }

        // line equation: x = x1 + t*(x2 - x1)
        float t1 = random.generateDouble()*3 - 1; // range: [-2, 3] - which means 2x higher chance it would end up outside of the rectangle than inside
        float t2 = random.generateDouble()*3 - 1; // range: [-2, 3] - which means 2x higher chance it would end up outside of the rectangle than inside

        if (t1 > t2) {
            float tmp = t1;
            t1 = t2;
            t2 = tmp;
        }

        QLineF testLine(points[0] + t1*(points[1] - points[0]), points[0] + t2*(points[1] - points[0]));

        QLineF expectedLine = testLine;
        bool expectedResult = true;
        if (onEdge) {
            if (points[0].x() == points[1].x()) /*vertical*/ {
                if (points[0].y() < points[1].y()) {
                    if (points[1].y() < testRect.y() || points[0].y() > testRect.y() + testRect.height()) {
                        expectedResult = false;
                    } else {
                        expectedLine.setP1(QPointF(points[1].x(), qMax(testLine.p1().y(), (double)testRect.y())));
                        expectedLine.setP2(QPointF(points[1].x(), qMin(testLine.p2().y(), (double)testRect.y() + testRect.height())));
                    }
                } else {
                    if (points[0].y() < testRect.y() || points[1].y() > testRect.y() + testRect.height()) {
                        expectedResult = false;
                    } else {
                        expectedLine.setP2(QPointF(points[1].x(), qMax(testLine.p2().y(), (double)testRect.y())));
                        expectedLine.setP1(QPointF(points[1].x(), qMin(testLine.p1().y(), (double)testRect.y() + testRect.height())));
                    }
                }
            } else /* horizontal */ {
                if (points[0].x() < points[1].x()) {
                    if (points[1].x() < testRect.x() || points[0].x() > testRect.x() + testRect.width()) {
                        expectedResult = false;
                    } else {
                        expectedLine.setP1(QPointF(qMax(testLine.p1().x(), (double)testRect.x()), points[1].y()));
                        expectedLine.setP2(QPointF(qMin(testLine.p2().x(), (double)testRect.x() + testRect.width()), points[1].y()));
                    }
                } else {
                    if (points[0].x() < testRect.x() || points[1].x() > testRect.x() + testRect.width()) {
                        expectedResult = false;
                    } else {
                        expectedLine.setP2(QPointF(qMax(testLine.p2().x(), (double)testRect.x()), points[1].y()));
                        expectedLine.setP1(QPointF(qMin(testLine.p1().x(), (double)testRect.x() + testRect.width()), points[1].y()));
                    }
                }
            }
            testLineRectIntersection(testRect, testLine, expectedLine, false, false, expectedResult);
        } else if ((t2 < 0) || (t1 > 1)) {
            if (!onEdge) {
                testLineRectIntersection(testRect, testLine, testLine, false, false, false);
            }
        } else {
            if (t1 < 0) {
                expectedLine.setP1(points[0]);
            } else {
                expectedLine.setP1(testLine.p1());
            }
            if (t2 > 1) {
                expectedLine.setP2(points[1]);
            } else {
                expectedLine.setP2(testLine.p2());
            }
            testLineRectIntersection(testRect, testLine, expectedLine, false, false, true);
            testLineRectIntersection(testRect, testLine, QLineF(points[0], expectedLine.p2()), true, false, true);
            testLineRectIntersection(testRect, testLine, QLineF(expectedLine.p1(), points[1]), false, true, true);
            testLineRectIntersection(testRect, testLine, QLineF(points[0], points[1]), true, true, true);
        }
    }

}


void testLinePolygonIntersectionImpl(QPolygonF poly, QLineF line, QLineF expected, bool extendFirst, bool extendSecond, bool intersectsExpected)
{
    QLineF result = line;
    bool intersects = KisAlgebra2D::intersectLineConvexPolygon(result, poly, extendFirst, extendSecond);

    float epsilon = 1e-4; // good enough for most usecases // I don't use qFuzzyCompare because it has too small epsilon

    auto fuzzyCompare = [epsilon] (float a, float b) -> bool {
            return (qAbs(a - b) < epsilon); };
    auto fuzzyComparePoint = [&] (QPointF a, QPointF b) -> bool {
            return fuzzyCompare(a.x(), b.x()) && fuzzyCompare(a.y(), b.y()); };
    auto fuzzyCompareLine = [&] (QLineF a, QLineF b) -> bool {
            return fuzzyComparePoint(a.p1(), b.p1()) && fuzzyComparePoint(a.p2(), b.p2()); };


    bool success;
    if (!intersectsExpected) {
        qCritical() << "Checking the first case";
        success = intersectsExpected == intersects;
    } else {
        qCritical() << "Checking the second case, with comparing the line";
        success = fuzzyCompareLine(result, expected) && intersectsExpected == intersects;
    }


    if (!success) {
        qCritical() << "FAILURE";
        qCritical() << "intersects: " << intersects;
        qCritical() << "intersects expected: " << intersectsExpected;
        qCritical() << "Extend: " << extendFirst << extendSecond;
        qCritical();

        qCritical() << "original: " << line;
        //qCritical() << "rectangle was: " << ;
        qCritical() << "expected: " << expected;
        qCritical() << "result: " << result;
    }

    QVERIFY(success);

}

void testLinePolygonIntersection(QRect poly, QLineF line, QLineF expected, bool extendFirst, bool extendSecond, bool intersectsExpected)
{
    QPolygonF polygon = QRectF(poly);
    testLinePolygonIntersectionImpl(polygon, line, expected, extendFirst, extendSecond, intersectsExpected);
}

void KisAlgebra2DTest::testLinePolygonIntersectionsManual()
{
    int x1 = 100, x2 = 400; // for x values
    int y1 = 130, y2 = 430; // for y values


    QRect rect1(x1, y1, x2 - x1, y2 - y1);
    QLineF line1original(x1, y1, x2, y2);
    QLineF line1expected = line1original;

    testLinePolygonIntersection(rect1, line1original, line1expected, false, false, true);

    //return;

    QRect rect2(x1, y1, x2, y2);
    QLineF line2original(x1, y1, x2, y2);
    QLineF line2expected = line2original;

    testLinePolygonIntersection(rect2, line2original, line2expected, false, false, true);

    QRect rect3 = QRect(0,0, 2616, 1748);
    QLineF line3original = QLineF(QPointF(1110.74,398.93), QPointF(0,394.068));
    QLineF line3expected = line3original;

    testLinePolygonIntersection(rect3, line3original, line3expected, false, false, true);

    QRect rect4 = rect1;
    QLineF line4 = QLineF(x2, y1, x1, y2);
    testLinePolygonIntersection(rect4, line4, line4, false, false, true);


    QRect rect5 = rect1;
    QLineF line5 = QLineF(x2, y2, x1, y1);
    testLinePolygonIntersection(rect5, line5, line5, false, false, true);

    QRect rect6 = rect1;
    QLineF line6 = QLineF(x2, y1, x2, y2);
    testLinePolygonIntersection(rect6, line6, line6, false, false, true);

    QRect rect7 = rect3;
    QLineF line7 = QLineF(QPointF(338.69,421.014),QPointF(983.456,421.014));
    testLinePolygonIntersection(rect7, line7, line7, false, false, true);

    QRect rect8 = rect3;
    QLineF line8 = QLineF(QPointF(-3545.25,618.966),QPointF(102.104,618.966));
    QLineF line8expected = QLineF(QPointF(0,618.966),QPointF(102.104,618.966));
    testLinePolygonIntersection(rect8, line8, line8expected, false, false, true);



    // vertical
    //   inside the line
    testLinePolygonIntersection(QRect(0, 0, 20, 20), QLineF(0, 5, 0, 15), QLineF(0, 5, 0, 15), false, false, true);
    //   exactly the line
    testLinePolygonIntersection(QRect(0, 0, 20, 20), QLineF(0, 0, 0, 20), QLineF(0, 0, 0, 20), false, false, true);
    //   extending the line
    testLinePolygonIntersection(QRect(0, 0, 20, 20), QLineF(0, -5, 0, 25), QLineF(0, 0, 0, 20), false, false, true);

    // diagonal
    //   exactly
    testLinePolygonIntersection(QRect(0, 0, 20, 20), QLineF(0, 0, 20, 20), QLineF(0, 0, 20, 20), false, false, true);
    //   inside
    testLinePolygonIntersection(QRect(0, 0, 20, 20), QLineF(5, 5, 15, 15), QLineF(5, 5, 15, 15), false, false, true);
    //   extending
    testLinePolygonIntersection(QRect(0, 0, 20, 20), QLineF(-5, -5, 30, 30), QLineF(0, 0, 20, 20), false, false, true);



    testLinePolygonIntersection(QRect(0, 0, 20, 20), QLineF(-5, -100, -5, 100), QLineF(), false, false, false);

    testLinePolygonIntersection(QRect(0, 0, 20, 20), QLineF(-5, -100, -3, 100), QLineF(), false, false, false);



    // start: (-1, -2)
    // end: (4, 5);
    // width, height: 5, 7
    QRect testRect = QRect(-1, -2, 5, 7);

    // first item: line, second item: result after cropping
    QList<QLineF> linesListIntersecting = QList<QLineF>({
                                                            // horizontal
                                                            QLineF(-1, -2, 4, -2), QLineF(-1, -2, 4, -2), // on the edge, exact
                                                            QLineF(-1, 0, 4, 0), QLineF(-1, 0, 4, 0), // below the edge, exact
                                                            QLineF(-10, -2, 10, -2), QLineF(-1, -2, 4, -2), // on the edge, longer
                                                            QLineF(-10, 0, 10, 0), QLineF(-1, 0, 4, 0), // below the edge, longer
                                                            QLineF(0, -2, 3, -2), QLineF(0, -2, 3, -2), // on the edge, shorter
                                                            QLineF(0, 0, 3, -2), QLineF(0, 0, 3, -2), // below the edge, shorter

                                                            // vertical
                                                            QLineF(-1, -2, -1, 5), QLineF(-1, -2, -1, 5), // on the edge, exact
                                                            QLineF(0, -2, 0, 5), QLineF(0, -2, 0, 5), // below the edge, exact
                                                            QLineF(-1, -10, -1, 10), QLineF(-1, -2, -1, 5), // on the edge, longer
                                                            QLineF(0, -10, 0, 10), QLineF(0, -2, 0, 5), // below the edge, longer
                                                            QLineF(-1, 0, -1, 3), QLineF(-1, 0, -1, 3), // on the edge, shorter
                                                            QLineF(0, 0, 0, 3), QLineF(0, 0, 0, 3), // below the edge, shorter


                                                            // skewed
                                                            QLineF(-6, 0, 9, 3), QLineF(-1, 1, 4, 2), // very horizontal-like
                                                            QLineF(-1, 1, 4, 2), QLineF(-1, 1, 4, 2), // very horizontal-like, exact
                                                            QLineF(-6, 0, 4, 2), QLineF(-1, 1, 4, 2), // very horizontal-like, half-exact

                                                            QLineF(-6, -9, 9, 12), QLineF(-1, -2, 4, 5), // perfectly diagonal
                                                            QLineF(-1, -2, 4, 5), QLineF(-1, -2, 4, 5), // perfectly diagonal, exact
                                                            QLineF(-1, -2, 9, 12), QLineF(-1, -2, 4, 5), // perfectly diagonal, half-exact

                                                            QLineF(9, 12, -6, -9), QLineF(4, 5, -1, -2), // perfectly diagonal, p1 and p2 reversed

                                                            QLineF(1, 3, 3, 4), QLineF(1, 3, 3, 4), // skewed, fully inside
                                                            QLineF(2, 2, 6, 4), QLineF(2, 2, 4, 3), // skewed, partially inside


                                                        });

    for (int i = 0; i < linesListIntersecting.size()/2; i++)
    {
        testLinePolygonIntersection(testRect, linesListIntersecting[2*i], linesListIntersecting[2*i + 1], false, false, true);
    }

    // now with extend = true

    // first item: line, second item: result after cropping
    QList<QLineF> linesListIntersectingExtend = QList<QLineF>({
                                                            // horizontal
                                                            QLineF(-1, -2, 4, -2), QLineF(-1, -2, 4, -2), // on the edge, exact
                                                            QLineF(-1, 0, 4, 0), QLineF(-1, 0, 4, 0), // below the edge, exact
                                                            QLineF(-10, -2, 10, -2), QLineF(-1, -2, 4, -2), // on the edge, longer
                                                            QLineF(-10, 0, 10, 0), QLineF(-1, 0, 4, 0), // below the edge, longer
                                                            QLineF(0, -2, 3, -2), QLineF(-1, -2, 4, -2), // on the edge, shorter
                                                            QLineF(0, 0, 3, 0), QLineF(-1, 0, 4, 0), // below the edge, shorter

                                                            // vertical
                                                            QLineF(-1, -2, -1, 5), QLineF(-1, -2, -1, 5), // on the edge, exact
                                                            QLineF(0, -2, 0, 5), QLineF(0, -2, 0, 5), // below the edge, exact
                                                            QLineF(-1, -10, -1, 10), QLineF(-1, -2, -1, 5), // on the edge, longer
                                                            QLineF(0, -10, 0, 10), QLineF(0, -2, 0, 5), // below the edge, longer
                                                            QLineF(-1, 0, -1, 3), QLineF(-1, -2, -1, 5), // on the edge, shorter
                                                            QLineF(0, 0, 0, 3), QLineF(0, -2, 0, 5), // below the edge, shorter


                                                            // skewed
                                                            QLineF(-6, 0, 9, 3), QLineF(-1, 1, 4, 2), // very horizontal-like
                                                            QLineF(-1, 1, 4, 2), QLineF(-1, 1, 4, 2), // very horizontal-like, exact
                                                            QLineF(-6, 0, 4, 2), QLineF(-1, 1, 4, 2), // very horizontal-like, half-exact

                                                            QLineF(-6, -9, 9, 12), QLineF(-1, -2, 4, 5), // perfectly diagonal
                                                            QLineF(-1, -2, 4, 5), QLineF(-1, -2, 4, 5), // perfectly diagonal, exact
                                                            QLineF(-1, -2, 9, 12), QLineF(-1, -2, 4, 5), // perfectly diagonal, half-exact

                                                            QLineF(9, 12, -6, -9), QLineF(4, 5, -1, -2), // perfectly diagonal, p1 and p2 reversed

                                                            QLineF(1, 3, 3, 4), QLineF(-1, 2, 4, 4.5), // skewed, fully inside
                                                            QLineF(2, 2, 6, 4), QLineF(-1, 0.5, 4, 3), // skewed, partially inside

                                                        });

    for (int i = 0; i < linesListIntersectingExtend.size()/2; i++)
    {
        testLinePolygonIntersection(testRect, linesListIntersectingExtend[2*i], linesListIntersectingExtend[2*i + 1], true, true, true);
    }

    QList<QLineF> linesListIntersectingExtendOneSide = QList<QLineF>({
                                                            // horizontal

                                                            QLineF(-1, -2, 4, -2), QLineF(-1, -2, 4, -2), // on the edge, exact
                                                            QLineF(-1, 0, 4, 0), QLineF(-1, 0, 4, 0), // below the edge, exact
                                                            QLineF(-10, -2, 10, -2), QLineF(-1, -2, 4, -2), // on the edge, longer
                                                            QLineF(-10, 0, 10, 0), QLineF(-1, 0, 4, 0), // below the edge, longer
                                                            QLineF(0, -2, 3, -2), QLineF(-1, -2, 3, -2), // on the edge, shorter
                                                            QLineF(0, 0, 3, 0), QLineF(-1, 0, 3, 0), // below the edge, shorter

                                                            // vertical
                                                            QLineF(-1, -2, -1, 5), QLineF(-1, -2, -1, 5), // on the edge, exact
                                                            QLineF(0, -2, 0, 5), QLineF(0, -2, 0, 5), // below the edge, exact
                                                            QLineF(-1, -10, -1, 10), QLineF(-1, -2, -1, 5), // on the edge, longer
                                                            QLineF(0, -10, 0, 10), QLineF(0, -2, 0, 5), // below the edge, longer
                                                            QLineF(-1, 0, -1, 3), QLineF(-1, -2, -1, 3), // on the edge, shorter
                                                            QLineF(0, 0, 0, 3), QLineF(0, -2, 0, 3), // below the edge, shorter


                                                            // skewed
                                                            QLineF(-6, 0, 9, 3), QLineF(-1, 1, 4, 2), // very horizontal-like
                                                            QLineF(-1, 1, 4, 2), QLineF(-1, 1, 4, 2), // very horizontal-like, exact
                                                            QLineF(-6, 0, 4, 2), QLineF(-1, 1, 4, 2), // very horizontal-like, half-exact

                                                            QLineF(-6, -9, 9, 12), QLineF(-1, -2, 4, 5), // perfectly diagonal
                                                            QLineF(-1, -2, 4, 5), QLineF(-1, -2, 4, 5), // perfectly diagonal, exact
                                                            QLineF(-1, -2, 9, 12), QLineF(-1, -2, 4, 5), // perfectly diagonal, half-exact

                                                            QLineF(9, 12, -6, -9), QLineF(4, 5, -1, -2), // perfectly diagonal, p1 and p2 reversed

                                                            QLineF(1, 3, 3, 4), QLineF(-1, 2, 3, 4), // skewed, fully inside
                                                            QLineF(2, 2, 6, 4), QLineF(-1, 0.5, 4, 3), // skewed, partially inside


                                                        });

    for (int i = 0; i < linesListIntersectingExtend.size()/2; i++)
    {
        // first from one side
        testLinePolygonIntersection(testRect, linesListIntersectingExtendOneSide[2*i], linesListIntersectingExtendOneSide[2*i + 1], true, false, true);

        // and then reversed
        QLineF l = QLineF(linesListIntersectingExtendOneSide[2*i].p2(), linesListIntersectingExtendOneSide[2*i].p1());
        QLineF lexp = QLineF(linesListIntersectingExtendOneSide[2*i + 1].p2(), linesListIntersectingExtendOneSide[2*i + 1].p1());
        testLinePolygonIntersection(testRect, l, lexp, false, true, true);
    }

}

bool fuzzyComparePolygons(const QPolygonF &one, const QPolygonF &other, qreal eps = 1e-05) {
    if (one.count() != other.count()) {
        qCritical() << "Counts don't match: " << one.count() << other.count();
        return false;
    }

    auto compare = [eps] (const QPointF& a, const QPointF& b) {
        if (KisAlgebra2D::fuzzyPointCompare(a, b)) {
            return true;
        }
        return qAbs(a.x() - b.x()) < eps && qAbs(a.y() - b.y()) < eps;
    };

    bool firstWay = true;
    for (int i = 0; i < one.count(); i++) {
        if (!compare(one[i], other[i])) {
            firstWay = false;
            //qCritical() << "Mismatch on: " << one[i] << other[i] << "difference: " << one[i] - other[i];
            break;
        }
    }
    if (firstWay) {
        return true;
    }
    for (int i = 0; i < one.count(); i++) {
        if (!compare(one[one.count() - i - 1], other[i])) {
            //qCritical() << "Mismatch on (second way): " << one[one.count() - i - 1] << other[i];
            return false;
        }
    }
    return true;
}

void KisAlgebra2DTest::testCalculateConvexHull()
{
    using KisAlgebra2D::calculateConvexHull;

    QPolygonF first;
    first << QPointF(0, 0) << QPointF(0, 100) << QPointF(100, 100) << QPointF(100, 0) << QPointF(0, 0);
    QPolygonF firstResult = calculateConvexHull(first);
    qCritical() << first;
    qCritical() << firstResult;
    assert(fuzzyComparePolygons(first, firstResult));

    QPolygonF second;
    second << QPointF(0, 0) << QPointF(100, 0) << QPointF(100, 10) << QPointF(50, 10) << QPointF(50, 90) << QPointF(100, 90) << QPointF(100, 100) << QPointF(0, 100) << QPointF(0, 0);
    QPolygonF secondResultExpected;
    secondResultExpected << QPointF(0, 0) << QPointF(100, 0) << QPointF(100, 100) << QPointF(0, 100) << QPointF(0, 0);
    QPolygonF secondResult = calculateConvexHull(second);

    qCritical() << "second result was: " << secondResult;
    qCritical() << "while expected was: " << secondResultExpected;
    assert(fuzzyComparePolygons(secondResultExpected, secondResult));


}

void KisAlgebra2DTest::testGetPathsFromRectangleCutThrough()
{
    QRectF rect = QRectF(QPointF(0, 0), QPointF(200, 100));
    QLineF left = QLineF(QPointF(0, 50), QPointF(50, 0));
    QLineF right = QLineF(QPointF(0, 80), QPointF(80, 0));

    QList<QPointF> points = QPolygonF(rect).toList();
    //QPainterPath path = KisAlgebra2D::getOnePathFromRectangleCutThrough(points, left, true);
    //qCritical() << path;

    //path = KisAlgebra2D::getOnePathFromRectangleCutThrough(points, right, false);
    //qCritical() << path;

    QList<QPainterPath> paths = KisAlgebra2D::getPathsFromRectangleCutThrough(rect, left, right);
    qCritical() << paths;
    assert(paths.length() == 2 && "Must be two paths returned");
    assert(paths[0].elementCount() == 4 && "First path must have 4 elements (it's a closed triangle)");
    assert(paths[1].elementCount() == 6 && "First path must have 6 elements");


}

void KisAlgebra2DTest::testGetParallelLines()
{
    QList<QLineF> lines = KisAlgebra2D::getParallelLines(QLineF(QPointF(100, 200), QPointF(200, 200)), 100);
    qCritical() << lines;
    lines = KisAlgebra2D::getParallelLines(QLineF(QPointF(100, 200), QPointF(500, 200)), 100);
    qCritical() << lines;

    lines = KisAlgebra2D::getParallelLines(QLineF(QPointF(100, 200), QPointF(100, 400)), 100);
    qCritical() << lines;

    lines = KisAlgebra2D::getParallelLines(QLineF(QPointF(347.16,190.8),QPointF(366.72,190.8)), 50);
    qCritical() << lines;


    // QLineF(QPointF(356.64,90.72),QPointF(356.64,104.64)

    lines = KisAlgebra2D::getParallelLines(QLineF(QPointF(356.64,90.72),QPointF(356.64,104.64)), 50);
    qCritical() << lines;
    qCritical() << "distances: " << sqrt(KisAlgebra2D::pointToLineDistSquared(QPointF(356.64,90.72), lines[0])) << sqrt(KisAlgebra2D::pointToLineDistSquared(QPointF(356.64,90.72), lines[1]));

    QPointF intersection;
    auto type = QLineF(QPointF(0, 0), QPointF(100, 0)).intersects(QLineF(QPointF(50, -50), QPointF(50, 100)), &intersection);
    qCritical() << "Intersecting lines: " << intersection << type;

}

void KisAlgebra2DTest::testGetParallelLines2()
{
    qreal gutterWidth = 20;

    QLineF gutterCenterLine = QLineF(QPointF(0, 0), QPointF(100, 1));

    QPointF gutterLineNormalVector = QPointF(-gutterCenterLine.dy(), gutterCenterLine.dx());
    QLineF line1 = QLineF(KisAlgebra2D::movePointInTheDirection(gutterCenterLine.p1(), gutterLineNormalVector, gutterWidth/2), KisAlgebra2D::movePointInTheDirection(gutterCenterLine.p2(), gutterLineNormalVector, gutterWidth/2));
    QLineF line2 = QLineF(KisAlgebra2D::movePointInTheDirection(gutterCenterLine.p1(), -gutterLineNormalVector, gutterWidth/2), KisAlgebra2D::movePointInTheDirection(gutterCenterLine.p2(), -gutterLineNormalVector, gutterWidth/2));

    qCritical() << line1 << line2;


}

void KisAlgebra2DTest::testVectorPathReversed()
{
    using VPoint = KisAlgebra2D::VectorPath::VectorPathPoint;
    QList<VPoint> path;
    path << VPoint::moveTo(QPointF(0, 0));
    path << VPoint::lineTo(QPointF(-10, 20));
    path << VPoint::lineTo(QPointF(-5, 20));
    path << VPoint::bezierTo(QPointF(5, 20), QPointF(-5, 30), QPointF(5, 30));
    path << VPoint::lineTo(QPointF(10, 20));
    path << VPoint::lineTo(QPointF(0, 0));

    KisAlgebra2D::VectorPath result = KisAlgebra2D::VectorPath(path).reversed();

    for(int i = 0; i < result.pointsCount(); i++) {
        VPoint p = result.pointAt(i);
        qCritical() << ppVar(p.type) << ppVar(p.endPoint) << ppVar(p.controlPoint1) << ppVar(p.controlPoint2);
    }

    qCritical() << result.asPainterPath();


}

void KisAlgebra2DTest::testVectorPathIndexes_data()
{

    using VPoint = KisAlgebra2D::VectorPath::VectorPathPoint;
    QTest::addColumn<QList<VPoint>>("path");

    QList<VPoint> path;
    path << VPoint::moveTo(QPointF(0, 0));
    path << VPoint::lineTo(QPointF(-10, 20));
    path << VPoint::bezierTo(QPointF(5, 20), QPointF(-5, 30), QPointF(5, 30));
    path << VPoint::lineTo(QPointF(10, 20));
    path << VPoint::bezierTo(QPointF(5, 20), QPointF(-5, 30), QPointF(5, 30));
    path << VPoint::lineTo(QPointF(0, 0));


    QTest::addRow("path1") << path;

    QList<VPoint> path2;
    path2 << VPoint::moveTo(QPointF(0, 0));
    path2 << VPoint::bezierTo(QPointF(5, 20), QPointF(-5, 30), QPointF(5, 30));
    path2 << VPoint::lineTo(QPointF(10, 20));
    path2 << VPoint::bezierTo(QPointF(0, 0), QPointF(-5, 30), QPointF(5, 30));

    QTest::addRow("path2") << path2;

}



void KisAlgebra2DTest::testVectorPathIndexes()
{
    using VPoint = KisAlgebra2D::VectorPath::VectorPathPoint;

    QFETCH(QList<VPoint>, path);

    KisAlgebra2D::VectorPath vpath(path);

    QPainterPath ppath = vpath.asPainterPath();
    //ENTER_FUNCTION() << ppath;

    for (int i = 0; i < ppath.elementCount(); i++) {
        int pathIndex = i;
        int segmentIndex = vpath.pathIndexToSegmentIndex(pathIndex);
        int pathIndexRev = vpath.segmentIndexToPathIndex(segmentIndex);
        //ENTER_FUNCTION() << ppVar(pathIndex) << ppVar(segmentIndex) << ppVar(pathIndexRev) << ppVar(ppath.elementAt(i));
        if (ppath.elementAt(i).type != QPainterPath::CurveToDataElement) {
            // if it is, then the reversed path index won't match, because it will point to the actual curve point
            QCOMPARE(pathIndex, pathIndexRev);
        }
    }

    for (int i = 0; i < vpath.segmentsCount(); i++) {
        int segmentIndex = i;
        int pathIndex = vpath.segmentIndexToPathIndex(segmentIndex);
        int segmentIndexRev = vpath.pathIndexToSegmentIndex(pathIndex);
        //ENTER_FUNCTION() << ppVar(segmentIndex) << ppVar(pathIndex) << ppVar(segmentIndexRev);
        QCOMPARE(segmentIndex, segmentIndexRev);
    }

}

void KisAlgebra2DTest::testIsInsideShape_data()
{

    using VPoint = KisAlgebra2D::VectorPath::VectorPathPoint;
    QTest::addColumn<QList<VPoint>>("path");
    QTest::addColumn<QPointF>("point");
    QTest::addColumn<bool>("result");


    qreal optimalDistanceMagicNumber = 4*(sqrt(2) - 1)/3;

    QList<VPoint> squareDiag = QList<VPoint> {
            VPoint(VPoint::MoveTo, QPointF(1, 0)),
            VPoint(VPoint::LineTo, QPointF(0, 1)),
            VPoint(VPoint::LineTo, QPointF(-1, 0)),
            VPoint(VPoint::LineTo, QPointF(0, -1)),
            VPoint(VPoint::LineTo, QPointF(1, 0)),
    };


    QList<VPoint> circle = QList<VPoint> {
            VPoint(VPoint::MoveTo, QPointF(1, 0)),
            VPoint(VPoint::BezierTo, QPointF(0, 1), QPointF(1, optimalDistanceMagicNumber), QPointF(optimalDistanceMagicNumber, 1)),
            VPoint(VPoint::BezierTo, QPointF(-1, 0), QPointF(-optimalDistanceMagicNumber, 1), QPointF(-1, optimalDistanceMagicNumber)),
            VPoint(VPoint::BezierTo, QPointF(0, -1), QPointF(-1, -optimalDistanceMagicNumber), QPointF(-optimalDistanceMagicNumber, -1)),
            VPoint(VPoint::BezierTo, QPointF(1, 0), QPointF(optimalDistanceMagicNumber, -1), QPointF(1, -optimalDistanceMagicNumber)),
    };

    QTest::addRow("perfect circle and center") << circle << QPointF(0, 0) << true;
    QTest::addRow("perfect circle, not center") << circle << QPointF(0.5, 0.5) << true;
    QTest::addRow("perfect circle, outside") << circle << QPointF(1, 1) << false;

    QTest::addRow("square diag, perfect center") << squareDiag << QPointF(0, 0) << true;
    QTest::addRow("square diag, not center") << squareDiag << QPointF(0.2, 0.2) << true;
    QTest::addRow("square diag, outside") << squareDiag << QPointF(1, 1) << false;



}


void KisAlgebra2DTest::testIsInsideShape()
{

    using VPoint = KisAlgebra2D::VectorPath::VectorPathPoint;
    QFETCH(QList<VPoint>, path);
    QFETCH(QPointF, point);
    QFETCH(bool, result);

    bool res = KisAlgebra2D::isInsideShape(path, point);
    QCOMPARE(res, result);


}


QPainterPath makeSpecialShape(bool left)
{
    // left has teeth on the left
    int m = left ? 1 : -1; // multiplier
    QPainterPath path;
    path.moveTo(m*100, 100);
    path.lineTo(m*400, 100);

    path.lineTo(m*400, 200);
    path.lineTo(m*100, 200);

    path.lineTo(m*150, 175);
    path.lineTo(m*100, 150);
    path.lineTo(m*150, 125);
    path.lineTo(m*100, 100);


    if (!left) {
        path.translate(QPointF(1000, 0));
    }

    return path;

}

QPainterPath fromPolygon(QPolygonF poly) {
    QPainterPath path;
    path.addPolygon(poly);
    path.closeSubpath();
    return path;
}

QPainterPath fromPolygon(QVector<QPointF> poly) {
    return fromPolygon(QPolygonF(poly));
}

QPainterPath fromPolygon(std::initializer_list<QPointF> poly) {
    return fromPolygon(QPolygonF(poly));
}

QPolygonF toPolygon(const QPainterPath& path) {
    return path.toFillPolygon();
}

void testRemoveGutterOneCase(const QString testName, const QPainterPath& shape1, const QPainterPath& shape2, const QLineF& mouseLine, bool isSameShape = false, int expectedSize = -1, const QPainterPath &expectedPath = QPainterPath())
{
    qreal eps = 1e-2;
    QList<int> indexes1 = KisAlgebra2D::getLineSegmentCrossingLineIndexes(mouseLine, shape1);
    QList<int> indexes2 = KisAlgebra2D::getLineSegmentCrossingLineIndexes(mouseLine, shape2);

    QVERIFY(expectedSize >= 0);
    QVERIFY(!KisAlgebra2D::fuzzyPointCompare(expectedPath.toFillPolygon(), QPainterPath().toFillPolygon(), 1e-7));

    if (isSameShape) {
        QVERIFY(indexes1.length()%2 == 0);
        QVERIFY(indexes2.length()%2 == 0);
        QVERIFY(indexes1.length() > 0);
        QVERIFY(indexes2.length() > 0);

    } else {
        QVERIFY(indexes1.length() == 1);
        QVERIFY(indexes2.length() == 1);
    }

    QPainterPath result;
    if (isSameShape) {
        result = KisAlgebra2D::removeGutterSmart(shape1, indexes1[0], shape1, indexes1[1], isSameShape);
    } else {
        result = KisAlgebra2D::removeGutterSmart(shape1, indexes1[0], shape2, indexes2[0], isSameShape);
    }

    KisAlgebra2D::VectorPath expectedVector(expectedPath);
    KisAlgebra2D::VectorPath actualVector(result);

//#define KNIFE_DEBUG
#ifdef KNIFE_DEBUG
                QRectF boundRect = actualVector.asPainterPath().boundingRect() | expectedVector.asPainterPath().boundingRect();
                QImage img = QImage((int)(boundRect.right())+ 50, (int)(boundRect.bottom()) + 50, QImage::Format::Format_ARGB32);
                img.fill(Qt::white);
                QPainter painter = QPainter(&img);
                painter.setPen(Qt::red);
                //painter.drawPath(actualVector.asPainterPath());
                QColor green = Qt::green;
                green.setAlphaF(0.5);
                painter.setPen(green);
                //painter.drawPath(expectedVector.asPainterPath());

                QColor blue = Qt::blue;
                blue.setAlphaF(0.3);
                QColor magenta = Qt::magenta;
                magenta.setAlphaF(0.3);
                painter.setPen(blue);
                painter.drawPath(shape1);
                painter.setPen(magenta);
                painter.drawPath(shape2);
                img.save(testName + ".png");


                qCritical() << "Expected:" << expectedVector;
                qCritical() << "Actual:" << actualVector;
#else
    Q_UNUSED(testName);
#endif

    QCOMPARE(actualVector.segmentsCount(), expectedSize);
    QVERIFY(fuzzyComparePolygons(actualVector.asPainterPath().toFillPolygon(), expectedVector.asPainterPath().toFillPolygon(), eps));


}

void convertShapeToDebugArray(const QPainterPath& shape) {
    // useful to use with Geogebra
    for (int i = 0; i < shape.elementCount(); i++) {
        QPainterPath::Element el = shape.elementAt(i);
        qCritical() << el.x << "\t" << el.y << "\t" << el.type;
    }
}


void KisAlgebra2DTest::testRemoveGutter_data()
{
    QTest::addColumn<QString>("testName");
    QTest::addColumn<QPainterPath>("shape1");
    QTest::addColumn<QPainterPath>("shape2");
    QTest::addColumn<QLineF>("mouseLine");
    QTest::addColumn<bool>("isSameShape");
    QTest::addColumn<int>("expectedSize");
    QTest::addColumn<QPainterPath>("expectedPath");


    // simple shape: a rectangle with a middle very easy gutter
    QPolygonF poly;
    poly << QPointF(0, 0) << QPointF(100, 0) << QPointF(100, 100) << QPointF(0, 100);
    QPainterPath polyPath;
    polyPath.addPolygon(poly);
    polyPath.closeSubpath();

    QPolygonF poly2;

    poly2 << QPointF(200, 0) << QPointF(300, 0) << QPointF(300, 100) << QPointF(200, 100);
    QPainterPath polyPath2;
    polyPath2.addPolygon(poly2);
    polyPath2.closeSubpath();

    QLineF mouseLine = QLineF(QPointF(50, 50), QPointF(250, 50));

    // Case1, Case2

    QTest::addRow("Case 1: simple case") << "Case1" << polyPath << polyPath2 << mouseLine << false << 4 << fromPolygon(QRectF(0, 0, 300, 100));
    QTest::addRow("Case 2: simple case reversed") << "Case2" << polyPath << polyPath2.toReversed() << mouseLine << false << 4 << fromPolygon(QRectF(0, 0, 300, 100));

    QPainterPath special1 = makeSpecialShape(true);
    QPainterPath special2 = makeSpecialShape(false);
    QPainterPath specialResult = fromPolygon(QVector<QPointF>({QPointF(100, 100), QPointF(900, 100), QPointF(850, 125), QPointF(900, 150), QPointF(850, 175), QPointF(900, 200), QPointF(100, 200), QPointF(150, 175),
                                             QPointF(100, 150), QPointF(150, 125), QPointF(100, 100)}));

    // Teeth cases
    QTest::addRow("Case 3: teeth shapes") << "Case3" << special1 << special2 << QLineF(QPointF(300, 150), QPointF(700, 150)) << false << 10 << specialResult;

    QTest::addRow("Case 3a: teeth shapes except without teeth") << "Case3a" << fromPolygon(QVector<QPointF>({QPointF(100, 100), QPointF(400, 100), QPointF(400, 200), QPointF(100, 200), QPointF(100, 100)}))
                                    << fromPolygon(QVector<QPointF>({QPointF(900, 100), QPointF(600, 100), QPointF(600, 200), QPointF(900, 200), QPointF(900, 100)}))
                                    << QLineF(QPointF(300, 150), QPointF(700, 150)) << false << 4
                                    << fromPolygon(QVector<QPointF>({QPointF(100, 100), QPointF(100, 200), QPointF(900, 200), QPointF(900, 100), QPointF(100, 100)}));



    QTest::addRow("Case 4: teeth shapes reversed") << "Case4" << special1 << special2.toReversed() << QLineF(QPointF(300, 150), QPointF(700, 150)) << false << 10 << specialResult;

    QPainterPath bigOnTop = fromPolygon(QRectF(0, 3, 10, 3));
    QPainterPath smallRight = fromPolygon(QRectF(7, 0, 3, 2));
    QPainterPath smallMiddle = fromPolygon(QRectF(3, 0, 3, 2));
    QPainterPath smallLeft = fromPolygon(QRectF(0, 0, 2, 2));

    QTest::addRow("Case 5: big on top with small right") << "Case5" << bigOnTop << smallRight << QLineF(QPointF(8, 1), QPointF(8, 5)) << false << 6 << fromPolygon(QVector<QPointF>({QPointF(0, 3), QPointF(7, 3), QPointF(7, 0), QPointF(10, 0), QPointF(10, 6), QPointF(0, 6), QPointF(0, 3)}));
    QTest::addRow("Case 6: big on top with small middle") << "Case6" << bigOnTop << smallMiddle << QLineF(QPointF(5, 1), QPointF(5, 5)) << false << 8 << fromPolygon(QVector<QPointF>({QPointF(0,3), QPointF(3,3), QPointF(3,0), QPointF(6,0), QPointF(6,3), QPointF(10,3), QPointF(10,6), QPointF(0,6), QPointF(0,3)}));
    QTest::addRow("Case 7: big on top with small left") << "Case7" << bigOnTop << smallLeft << QLineF(QPointF(1, 1), QPointF(1, 5)) << false << 6 << fromPolygon(QVector<QPointF>({QPointF(0,0), QPointF(2,0), QPointF(2,3), QPointF(10,3), QPointF(10,6), QPointF(0,6)}));

    QPainterPath onePoly = fromPolygon(QVector<QPointF>({QPointF(2275.28,763.721), QPointF(3270,757.3), QPointF(3270,210), QPointF(1127.23,210),
                                       QPointF(1133.64,771.092), QPointF(2236.91,763.971), QPointF(2275.28,763.721)}));

    QPainterPath otherPoly = fromPolygon(QVector<QPointF>({QPointF(1794.87,819.433), QPointF(2264.27,816.404), QPointF(2664.69,1583.79), QPointF(2126.14,1598.38),
                                         QPointF(1794.87,819.433)}));


    //qCritical() << "First poly:";
    //convertShapeToDebugArray(onePoly);
    //qCritical() << "Second poly:";
    //convertShapeToDebugArray(otherPoly);

    QTest::addRow("Case 8") << "Case8" << onePoly << otherPoly << QLineF(QPointF(2010,662),QPointF(2006,938)) << false << 8 <<
                               fromPolygon(QVector<QPointF>({QPointF(3270,757.3), QPointF(3270,210), QPointF(1127.23,210), QPointF(1133.64,771.092), QPointF(1772.56,766.968),
                                                             QPointF(2126.14,1598.38), QPointF(2664.69,1583.79), QPointF(2236.91,763.971), QPointF(3270,757.3)}));

    QPainterPath disappearingOne = fromPolygon(QVector<QPointF>({QPointF(2302.64, 816.154), QPointF(3270, 809.908), QPointF(3270, 1567.38), QPointF(2702.65, 1582.76), QPointF(2302.64, 816.154)}));
    QPainterPath disappearingTwo = fromPolygon(QVector<QPointF>({QPointF(3077.96, 2307), QPointF(2427.5, 2307), QPointF(2148.5, 1650.95), QPointF(3073.03, 1625.89), QPointF(3077.96, 2307)}));

    QTest::addRow("Case 9: disappearing shapes") << "Case9" << disappearingOne << disappearingTwo << QLineF(QPointF(2892,1773),QPointF(3027,1245)) << false << 8 <<
                                                    fromPolygon(QVector<QPointF>({QPointF(2302.64,816.154), QPointF(3270,809.908), QPointF(3270,1567.38), QPointF(3072.65,1572.73), QPointF(3077.96,2307),
                                                                                  QPointF(2427.5,2307), QPointF(2148.5,1650.95), QPointF(2730.01,1635.19), QPointF(2302.64,816.154)}));


    //shape1.toFillPolygon() = QPolygonF(QPointF(3077.96,2307)QPointF(2427.5,2307)QPointF(2148.5,1650.95)QPointF(3073.03,1625.89)QPointF(3077.96,2307))
    // shape2.toFillPolygon() = QPolygonF(QPointF(2302.64,816.154)QPointF(3270,809.908)QPointF(3270,1567.38)QPointF(2702.65,1582.76)QPointF(2302.64,816.154))
    // gutterShape.toFillPolygon() = QPolygonF(QPointF(2148.5,1650.95)QPointF(2730.01,1635.19)QPointF(2702.65,1582.76)QPointF(3270,1567.38)QPointF(3072.64,1572.73)QPointF(3073.03,1625.89)QPointF(2148.5,1650.95))

    QPainterPath shape1 = fromPolygon(QPolygonF(QVector<QPointF>({QPointF(1794.87,819.433), QPointF(2264.27,816.404), QPointF(2664.69,1583.79), QPointF(2126.14,1598.38), QPointF(1794.87,819.433)})));
    QPainterPath shape2 = fromPolygon(QPolygonF(QVector<QPointF>({QPointF(2302.64,816.154), QPointF(3270,809.908), QPointF(3270,1567.38), QPointF(2702.65,1582.76), QPointF(2302.64,816.154)})));


    QTest::addRow("Case 10") << "Case10" << shape1 << shape2 << QLineF(QPointF(2606,1028),QPointF(2164,1134)) << false << 4
                             << fromPolygon(QPolygonF(QVector<QPointF>({QPointF(1794.87,819.433), QPointF(3270,809.908), QPointF(3270,1567.38), QPointF(2126.14,1598.38), QPointF(1794.87,819.433)})));

    shape1 = fromPolygon(QPolygonF(QVector<QPointF>({QPointF(1134.24,823.696),QPointF(2302.64,816.155),QPointF(2275.28,763.723),QPointF(1133.64,771.092),QPointF(1127.23,210),QPointF(3270,210),QPointF(3270,1567.38),
                                                         QPointF(1143.4,1625.02),QPointF(1134.24,823.696)})));

    QTest::addRow("Case 11") << "Case11" << shape1 << shape1 << QLineF(QPointF(1512,526), QPointF(1666,1178)) << true << 4
                             << fromPolygon({QPointF(1127.23,210), QPointF(3270,210), QPointF(3270,1567.38), QPointF(1143.4,1625.02), QPointF(1127.23,210)});

    shape1 = fromPolygon(QPolygonF(QVector<QPointF>({QPointF(1, 0),QPointF(3, 0),QPointF(3, 3),QPointF(0, 3),QPointF(0, 2),QPointF(2, 2),QPointF(2, 1),
                                                         QPointF(1, 1),QPointF(1, 0)})));
    shape2 = fromPolygon(QPolygonF(QVector<QPointF>({QPointF(10, 0),QPointF(30, 0),QPointF(30, 30),QPointF(0, 30),QPointF(0, 20),QPointF(20, 20),QPointF(20, 10),
                                                             QPointF(10, 10),QPointF(10, 0)})));

    QTest::addRow("Case 12") << "Case12" << shape1 << shape1 << QLineF(QPointF(1.5, 0.5), QPointF(1.5, 2.5)) << true << 6
                             << fromPolygon({QPointF(1,0), QPointF(3,0), QPointF(3,3), QPointF(0,3), QPointF(0,2), QPointF(1,2), QPointF(1,0)});
    QTest::addRow("Case 12a") << "Case12a" << shape2 << shape2 << QLineF(QPointF(15, 5), QPointF(15, 25)) << true << 6
                              << fromPolygon({QPointF(10,0), QPointF(30,0), QPointF(30,30), QPointF(0,30), QPointF(0,20), QPointF(10,20), QPointF(10,0)});

    QTest::addRow("Case 13") << "Case13" << fromPolygon(QPolygonF(QVector<QPointF>({QPointF(164.458,155.319), QPointF(2324.45,155.319), QPointF(2324.45,2475.31), QPointF(164.458,2475.31), QPointF(164.458,155.319)})))
                            << fromPolygon(QPolygonF(QVector<QPointF>({QPointF(164.458,2563.25), QPointF(1204.46,2563.25), QPointF(1204.46,3282.25), QPointF(164.458,3282.25), QPointF(164.458,2563.25)})))
                            << QLineF(QPointF(800, 1800), QPointF(800, 3000)) << false << 6
                            << fromPolygon({QPointF(164.458,155.319), QPointF(2324.45,155.319), QPointF(2324.45,2475.31), QPointF(1204.46,2475.31), QPointF(1204.46,3282.25), QPointF(164.458,3282.25), QPointF(164.458,155.319)});


}



void KisAlgebra2DTest::testRemoveGutter()
{

    QFETCH(QString, testName);
    QFETCH(QPainterPath, shape1);
    QFETCH(QPainterPath, shape2);
    QFETCH(QLineF, mouseLine);
    QFETCH(bool, isSameShape);
    QFETCH(int, expectedSize);
    QFETCH(QPainterPath, expectedPath);


    testRemoveGutterOneCase(testName, shape1, shape2, mouseLine, isSameShape, expectedSize, expectedPath);

}


void KisAlgebra2DTest::testMergingPainterPaths()
{
    QPainterPath first; // counter-clockwise
    first.moveTo(0, 0);
    first.lineTo(100, 0);
    first.lineTo(100, 100);
    first.lineTo(0, 100);
    first.lineTo(0, 0);


    QPainterPath second;
    second.moveTo(100, 0);
    second.lineTo(200, 0);
    second.lineTo(200, 100);
    second.lineTo(100, 100);
    second.lineTo(100, 0);


    qCritical() << ppVar(first | second);
    qCritical() << ppVar(first | second.toReversed());

    qCritical() << ppVar((first | second).simplified());
    qCritical() << ppVar((first | second.toReversed()).simplified());

    first.setFillRule(Qt::OddEvenFill);
    second.setFillRule(Qt::OddEvenFill);


    qCritical() << ppVar(first | second);
    qCritical() << ppVar(first | second.toReversed());

    qCritical() << ppVar((first | second).simplified());
    qCritical() << ppVar((first | second.toReversed()).simplified());

    // this is from the real life example, how to make it work?

    QPainterPath shape1;
    shape1.moveTo(QPointF(2302.6416666667, 816.1541666667));
    shape1.lineTo(QPointF(3270, 809.9083333333));
    shape1.lineTo(QPointF(3270, 1567.3791666667));
    shape1.lineTo(QPointF(2702.6541666667, 1582.7583333333));
    shape1.lineTo(QPointF(2302.6416666667, 816.1541666667));

    QPainterPath shape2;
    shape2.moveTo(QPointF(3077.9625000000, 2307));
    shape2.lineTo(QPointF(2427.4958333333, 2307));
    shape2.lineTo(QPointF(2148.4958333333, 1650.95));
    shape2.lineTo(QPointF(3073.0250000000, 1625.8916666667));
    shape2.lineTo(QPointF(3077.9625000000, 2307));

    QPainterPath gutter;
    //gutter.moveTo(QPointF(3270, 1567.38));
    //gutter.lineTo(QPointF(2702.65, 1582.76));
    //gutter.lineTo(QPointF(2730.01, 1635.19));
    //gutter.lineTo(QPointF(2148.5, 1650.95));
    //gutter.lineTo(QPointF(3073.03, 1625.89));
    //gutter.lineTo(QPointF(3072.64, 1572.73));
    //gutter.lineTo(QPointF(3270, 1567.38));

    gutter.moveTo(QPointF(2148.4958333333, 1650.9500000000));
    gutter.lineTo(QPointF(3073.0250000000, 1625.8916666667));
    gutter.lineTo(QPointF(3072.6395905126, 1572.7290581307));
    gutter.lineTo(QPointF(3270.0000000000, 1567.3791666667));
    gutter.lineTo(QPointF(2702.6541666667, 1582.7583333333));
    gutter.lineTo(QPointF(2730.0121775463, 1635.1886471758));
    gutter.lineTo(QPointF(2148.4958333333, 1650.9500000000));



    qCritical() << Qt::fixed << qSetRealNumberPrecision(10) << ppVar(shape1 | shape2 | gutter);

    // TODO: check if we could skip QVector entirely
    QPainterPath toUnite1 = fromPolygon(QPolygonF(QVector<QPointF>({QPointF(3077.96,2307), QPointF(2427.5,2307), QPointF(2148.5,1650.95), QPointF(3073.03,1625.89), QPointF(3077.96,2307)})));
    QPainterPath toUnite2 = fromPolygon(QPolygonF(QVector<QPointF>({QPointF(2302.64,816.154), QPointF(3270,809.908), QPointF(3270,1567.38), QPointF(2702.65,1582.76), QPointF(2302.64,816.154)})));
    QPainterPath toUniteGutter = fromPolygon(QPolygonF(QVector<QPointF>({QPointF(2148.5,1650.95), QPointF(2730.01,1635.19), QPointF(2702.65,1582.76), QPointF(3270,1567.38), QPointF(3072.64,1572.73), QPointF(3073.03,1625.89), QPointF(2148.5,1650.95)})));

    qCritical() << "Is it really so incorrect>? " << (toUnite1 | toUnite2 | toUniteGutter);



}

void KisAlgebra2DTest::testFindTrianglePoint()
{
    using KisAlgebra2D::findTrianglePoint;
    using KisAlgebra2D::findTrianglePointNearest;

    findTrianglePoint(QPointF(100, 900), QPointF(900, 100), 810, 800);

    findTrianglePoint(QPointF(), QPointF(10, 0), 5, 5);
    findTrianglePoint(QPointF(), QPointF(10, 0), 4, 6);
    findTrianglePoint(QPointF(), QPointF(10, 0), 4, 4);
    findTrianglePoint(QPointF(), QPointF(10, 0), 4, 6 + 1e-3);
    findTrianglePoint(QPointF(), QPointF(10, 0), 4, 6 - 1e-3);
    findTrianglePoint(QPointF(), QPointF(10, 0), 6, 6);
    findTrianglePoint(QPointF(), QPointF(10, 0), 7, 6);

    findTrianglePoint(QPointF(), QPointF(0, 10), 5, 5);
    findTrianglePoint(QPointF(), QPointF(0, 10), 4, 6);
    findTrianglePoint(QPointF(), QPointF(0, 10), 4, 4);
    findTrianglePoint(QPointF(), QPointF(0, 10), 4, 6 + 1e-3);
    findTrianglePoint(QPointF(), QPointF(0, 10), 4, 6 - 1e-3);
    findTrianglePoint(QPointF(), QPointF(0, 10), 6, 6);
    findTrianglePoint(QPointF(), QPointF(0, 10), 7, 6);
}

void KisAlgebra2DTest::testTriangularMotion()
{
    using KisAlgebra2D::moveElasticPoint;

    moveElasticPoint(QPointF(0,10),
                     QPointF(0,0), QPointF(0, 0.1),
                     QPointF(-5, 10), QPointF(5, 11));
}

void KisAlgebra2DTest::testElasticMotion()
{
    using KisAlgebra2D::norm;
    using KisAlgebra2D::dotProduct;
    using KisAlgebra2D::crossProduct;

    const QPointF oldBasePos(70,70);
    QPointF oldResultPoint(100,100);
    const QPointF offset(-2,4);

    QVector<QPointF> anchorPoints;
    anchorPoints << QPointF(0,0);
    anchorPoints << QPointF(100,0);
    anchorPoints << QPointF(0,100);
    anchorPoints << QPointF(50,0);
    anchorPoints << QPointF(0,50);

    const QPointF newBasePos = oldBasePos + offset;
    QPointF newResultPoint = KisAlgebra2D::moveElasticPoint(oldResultPoint, oldBasePos, newBasePos, anchorPoints);

    ENTER_FUNCTION() << ppVar(newResultPoint);
}

void KisAlgebra2DTest::testHaltonSequence()
{
    {

        KisAlgebra2D::HaltonSequenceGenerator g(2);

        for (int i = 0; i < 10; i++) {
            qDebug() << g.generate();
        }
    }

    {
        KisAlgebra2D::HaltonSequenceGenerator g(2);
        for (int i = 0; i < 10; i++) {
            qDebug() << g.generate(10);
        }
    }
}

#include "kis_wrapped_rect.h"

void KisAlgebra2DTest::testMultiplyWrappedRect_data()
{
    QTest::addColumn<QRect>("rc");
    QTest::addColumn<QRect>("wrapRect");
    QTest::addColumn<QRect>("limitRect");
    QTest::addColumn<QVector<QRect>>("expected");

    QTest::newRow("simple")
        << QRect(10,10,30,30)
        << QRect(0,0,100,100)
        << QRect(QPoint(-100, -100), QPoint(199,199))
        << QVector<QRect>(
               {QRect(-90, -90, 30, 30),
                QRect(10, -90, 30, 30),
                QRect(110, -90, 30, 30),
                QRect(-90, 10, 30, 30),
                QRect(10, 10, 30, 30),
                QRect(110, 10, 30, 30),
                QRect(-90, 110, 30, 30),
                QRect(10, 110, 30, 30),
                QRect(110, 110, 30, 30)});

    QTest::newRow("non-full-rect")
        << QRect(10,10,30,30)
        << QRect(0,0,100,100)
        << QRect(QPoint(-150, -150), QPoint(249, 249))
        << QVector<QRect>(
               {QRect(-90, -90, 30, 30),
                QRect(10, -90, 30, 30),
                QRect(110, -90, 30, 30),
                QRect(210, -90, 30, 30),
                QRect(-90, 10, 30, 30),
                QRect(10, 10, 30, 30),
                QRect(110, 10, 30, 30),
                QRect(210, 10, 30, 30),
                QRect(-90, 110, 30, 30),
                QRect(10, 110, 30, 30),
                QRect(110, 110, 30, 30),
                QRect(210, 110, 30, 30),
                QRect(-90, 210, 30, 30),
                QRect(10, 210, 30, 30),
                QRect(110, 210, 30, 30),
                QRect(210, 210, 30, 30)});

    QTest::newRow("minus-one")
        << QRect(10,10,30,30)
        << QRect(0,0,100,100)
        << QRect(QPoint(-99, -99), QPoint(199, 199))
        << QVector<QRect>(
               {QRect(-90, -90, 30, 30),
                QRect(10, -90, 30, 30),
                QRect(110, -90, 30, 30),
                QRect(-90, 10, 30, 30),
                QRect(10, 10, 30, 30),
                QRect(110, 10, 30, 30),
                QRect(-90, 110, 30, 30),
                QRect(10, 110, 30, 30),
                QRect(110, 110, 30, 30)});

    QTest::newRow("simple-neg-rect")
        << QRect(-10,-10,30,30)
        << QRect(0,0,100,100)
        << QRect(QPoint(-100, -100), QPoint(199,199))
        << QVector<QRect>(
               {QRect(-10, -10, 10, 10),
                QRect(90, -10, 10, 10),
                QRect(190, -10, 10, 10),
                QRect(-10, 90, 10, 10),
                QRect(90, 90, 10, 10),
                QRect(190, 90, 10, 10),
                QRect(-10, 190, 10, 10),
                QRect(90, 190, 10, 10),
                QRect(190, 190, 10, 10),
                QRect(-100, -10, 20, 10),
                QRect(0, -10, 20, 10),
                QRect(100, -10, 20, 10),
                QRect(-100, 90, 20, 10),
                QRect(0, 90, 20, 10),
                QRect(100, 90, 20, 10),
                QRect(-100, 190, 20, 10),
                QRect(0, 190, 20, 10),
                QRect(100, 190, 20, 10),
                QRect(-10, -100, 10, 20),
                QRect(90, -100, 10, 20),
                QRect(190, -100, 10, 20),
                QRect(-10, 0, 10, 20),
                QRect(90, 0, 10, 20),
                QRect(190, 0, 10, 20),
                QRect(-10, 100, 10, 20),
                QRect(90, 100, 10, 20),
                QRect(190, 100, 10, 20),
                QRect(-100, -100, 20, 20),
                QRect(0, -100, 20, 20),
                QRect(100, -100, 20, 20),
                QRect(-100, 0, 20, 20),
                QRect(0, 0, 20, 20),
                QRect(100, 0, 20, 20),
                QRect(-100, 100, 20, 20),
                QRect(0, 100, 20, 20),
                QRect(100, 100, 20, 20)});

    QTest::newRow("simple-neg-long-rect")
        << QRect(-10,-10,130,30)
        << QRect(0,0,100,100)
        << QRect(QPoint(-100, -100), QPoint(199,199))
        << QVector<QRect>(
               {QRect(-10, -10, 10, 10),
                QRect(90, -10, 10, 10),
                QRect(190, -10, 10, 10),
                QRect(-10, 90, 10, 10),
                QRect(90, 90, 10, 10),
                QRect(190, 90, 10, 10),
                QRect(-10, 190, 10, 10),
                QRect(90, 190, 10, 10),
                QRect(190, 190, 10, 10),
                QRect(-100, -10, 90, 10),
                QRect(0, -10, 90, 10),
                QRect(100, -10, 90, 10),
                QRect(-100, 90, 90, 10),
                QRect(0, 90, 90, 10),
                QRect(100, 90, 90, 10),
                QRect(-100, 190, 90, 10),
                QRect(0, 190, 90, 10),
                QRect(100, 190, 90, 10),
                QRect(-10, -100, 10, 20),
                QRect(90, -100, 10, 20),
                QRect(190, -100, 10, 20),
                QRect(-10, 0, 10, 20),
                QRect(90, 0, 10, 20),
                QRect(190, 0, 10, 20),
                QRect(-10, 100, 10, 20),
                QRect(90, 100, 10, 20),
                QRect(190, 100, 10, 20),
                QRect(-100, -100, 90, 20),
                QRect(0, -100, 90, 20),
                QRect(100, -100, 90, 20),
                QRect(-100, 0, 90, 20),
                QRect(0, 0, 90, 20),
                QRect(100, 0, 90, 20),
                QRect(-100, 100, 90, 20),
                QRect(0, 100, 90, 20),
                QRect(100, 100, 90, 20)});
}

void KisAlgebra2DTest::testMultiplyWrappedRect()
{
    QFETCH(QRect, rc);
    QFETCH(QRect, wrapRect);
    QFETCH(QRect, limitRect);
    QFETCH(QVector<QRect>, expected);

    QVector<QRect> result =
        KisWrappedRect::multiplyWrappedRect(rc, wrapRect, limitRect, WRAPAROUND_BOTH);

#if 1
    QCOMPARE(result, expected);
#else
    /// use this code to dump reference values
    QString res;
    Q_FOREACH (const QRect &rc, result) {
        res += QString("QRect(%1, %2, %3, %4), ").arg(rc.x()).arg(rc.y()).arg(rc.width()).arg(rc.height());
    }
    qDebug() << res;
#endif
}

void KisAlgebra2DTest::testMultiplyWrappedRectHorizontal_data()
{
    QTest::addColumn<QRect>("rc");
    QTest::addColumn<QRect>("wrapRect");
    QTest::addColumn<QRect>("limitRect");
    QTest::addColumn<QVector<QRect>>("expected");

    QTest::newRow("simple")
        << QRect(10,10,30,30)
        << QRect(0,0,100,100)
        << QRect(QPoint(-100, -100), QPoint(199,199))
        << QVector<QRect>(
               {QRect(-90, 10, 30, 30),
                QRect(10, 10, 30, 30),
                QRect(110, 10, 30, 30)});

    QTest::newRow("non-full-rect")
        << QRect(10,10,30,30)
        << QRect(0,0,100,100)
        << QRect(QPoint(-150, -150), QPoint(249, 249))
        << QVector<QRect>(
               {QRect(-90, 10, 30, 30),
                QRect(10, 10, 30, 30),
                QRect(110, 10, 30, 30),
                QRect(210, 10, 30, 30)});

    QTest::newRow("minus-one")
        << QRect(10,10,30,30)
        << QRect(0,0,100,100)
        << QRect(QPoint(-99, -99), QPoint(199, 199))
        << QVector<QRect>(
               {QRect(-90, 10, 30, 30),
                QRect(10, 10, 30, 30),
                QRect(110, 10, 30, 30)});

    QTest::newRow("simple-neg-rect")
        << QRect(-10,-10,30,30)
        << QRect(0,0,100,100)
        << QRect(QPoint(-100, -100), QPoint(199,199))
        << QVector<QRect>(
               {QRect(-10, -10, 10, 30),
                QRect(90, -10, 10, 30),
                QRect(190, -10, 10, 30),
                QRect(-100, -10, 20, 30),
                QRect(0, -10, 20, 30),
                QRect(100, -10, 20, 30)});

    QTest::newRow("simple-neg-long-rect")
        << QRect(-10,-10,130,30)
        << QRect(0,0,100,100)
        << QRect(QPoint(-100, -100), QPoint(199,199))
        << QVector<QRect>(
               {QRect(-10, -10, 10, 30),
                QRect(90, -10, 10, 30),
                QRect(190, -10, 10, 30),
                QRect(-100, -10, 90, 30),
                QRect(0, -10, 90, 30),
                QRect(100, -10, 90, 30)});
}

void KisAlgebra2DTest::testMultiplyWrappedRectHorizontal()
{
    QFETCH(QRect, rc);
    QFETCH(QRect, wrapRect);
    QFETCH(QRect, limitRect);
    QFETCH(QVector<QRect>, expected);

    QVector<QRect> result =
        KisWrappedRect::multiplyWrappedRect(rc, wrapRect, limitRect, WRAPAROUND_HORIZONTAL);

#if 1
    QCOMPARE(result, expected);
#else
    /// use this code to dump reference values
    QString res;
    Q_FOREACH (const QRect &rc, result) {
        res += QString("QRect(%1, %2, %3, %4), ").arg(rc.x()).arg(rc.y()).arg(rc.width()).arg(rc.height());
    }
    qDebug() << res;
#endif
}

void KisAlgebra2DTest::testMultiplyWrappedRectVertical_data()
{
    QTest::addColumn<QRect>("rc");
    QTest::addColumn<QRect>("wrapRect");
    QTest::addColumn<QRect>("limitRect");
    QTest::addColumn<QVector<QRect>>("expected");

    QTest::newRow("simple")
        << QRect(10,10,30,30)
        << QRect(0,0,100,100)
        << QRect(QPoint(-100, -100), QPoint(199,199))
        << QVector<QRect>(
               {QRect(10, -90, 30, 30),
                QRect(10, 10, 30, 30),
                QRect(10, 110, 30, 30)});

    QTest::newRow("non-full-rect")
        << QRect(10,10,30,30)
        << QRect(0,0,100,100)
        << QRect(QPoint(-150, -150), QPoint(249, 249))
        << QVector<QRect>(
               {QRect(10, -90, 30, 30),
                QRect(10, 10, 30, 30),
                QRect(10, 110, 30, 30),
                QRect(10, 210, 30, 30)});

    QTest::newRow("minus-one")
        << QRect(10,10,30,30)
        << QRect(0,0,100,100)
        << QRect(QPoint(-99, -99), QPoint(199, 199))
        << QVector<QRect>(
               {QRect(10, -90, 30, 30),
                QRect(10, 10, 30, 30),
                QRect(10, 110, 30, 30)});

    QTest::newRow("simple-neg-rect")
        << QRect(-10,-10,30,30)
        << QRect(0,0,100,100)
        << QRect(QPoint(-100, -100), QPoint(199,199))
        << QVector<QRect>(
               {QRect(-10, -10, 30, 10),
                QRect(-10, 90, 30, 10),
                QRect(-10, 190, 30, 10),
                QRect(-10, -100, 30, 20),
                QRect(-10, 0, 30, 20),
                QRect(-10, 100, 30, 20)});

    QTest::newRow("simple-neg-long-rect")
        << QRect(-10,-10,130,30)
        << QRect(0,0,100,100)
        << QRect(QPoint(-100, -100), QPoint(199,199))
        << QVector<QRect>(
               {QRect(-10, -10, 130, 10),
                QRect(-10, 90, 130, 10),
                QRect(-10, 190, 130, 10),
                QRect(-10, -100, 130, 20),
                QRect(-10, 0, 130, 20),
                QRect(-10, 100, 130, 20)});
}

void KisAlgebra2DTest::testMultiplyWrappedRectVertical()
{
    QFETCH(QRect, rc);
    QFETCH(QRect, wrapRect);
    QFETCH(QRect, limitRect);
    QFETCH(QVector<QRect>, expected);

    QVector<QRect> result =
        KisWrappedRect::multiplyWrappedRect(rc, wrapRect, limitRect, WRAPAROUND_VERTICAL);

#if 1
    QCOMPARE(result, expected);
#else
    /// use this code to dump reference values
    QString res;
    Q_FOREACH (const QRect &rc, result) {
        res += QString("QRect(%1, %2, %3, %4), ").arg(rc.x()).arg(rc.y()).arg(rc.width()).arg(rc.height());
    }
    qDebug() << res;
#endif
}

void KisAlgebra2DTest::testIsPolygonTrulyConvex_data()
{
    QTest::addColumn<QPolygonF>("polygon");
    QTest::addColumn<bool>("isConvex");

    QTest::addRow("nearly rectangle") << QPolygonF(QVector<QPointF> {QPointF(0,64), QPointF(8.00001,64), QPointF(8.00001,72), QPointF(0,72)}) << true;
    QTest::addRow("concave polygon") << QPolygonF(QVector<QPointF> {QPointF(0,0), QPointF(10,0), QPointF(10, 10), QPointF(-10, -5)}) << false;
    QTest::addRow("concave polygon 2") << QPolygonF(QVector<QPointF> {QPointF(10,0), QPointF(10, 10), QPointF(-10, -5), QPointF(0,0)}) << false;

    QTest::addRow("tangled polygon") << QPolygonF(QVector<QPointF> {QPointF(9,3), QPointF(8, 6), QPointF(4, 4), QPointF(7,3), QPointF(4,8), QPointF(1,5), QPointF(2,2), QPointF(4,1), QPointF(6,1)}) << false;
    QTest::addRow("untangled polygon") << QPolygonF(QVector<QPointF> {QPointF(9,3), QPointF(8, 6), QPointF(4, 8), QPointF(1,5), QPointF(2,2), QPointF(4,1), QPointF(6,1)}) << true;
    QTest::addRow("untangled polygon 2") << QPolygonF(QVector<QPointF> {QPointF(9,3), QPointF(8, 6), QPointF(4, 4), QPointF(6,1)}) << true;



    QTest::addRow("tangled polygon ccw") << QPolygonF(QVector<QPointF> {QPointF(6,1), QPointF(4,1), QPointF(2,2), QPointF(1,5), QPointF(4,8), QPointF(7,3), QPointF(4, 4), QPointF(8, 6), QPointF(9,3)}) << false;
    QTest::addRow("untangled polygon ccw") << QPolygonF(QVector<QPointF> {QPointF(6,1), QPointF(4,1), QPointF(2,2), QPointF(1,5), QPointF(4, 8), QPointF(8, 6), QPointF(9,3)}) << true;
    QTest::addRow("untangled polygon 2 ccw") << QPolygonF(QVector<QPointF> {QPointF(6,1), QPointF(4, 4), QPointF(8, 6), QPointF(9,3)}) << true;


    QTest::addRow("tangled polygon ccw, double start point") << QPolygonF(QVector<QPointF> {QPointF(6,1), QPointF(4,1), QPointF(2,2), QPointF(1,5), QPointF(4,8), QPointF(7,3), QPointF(4, 4), QPointF(8, 6), QPointF(9,3), QPointF(6,1)}) << false;
    QTest::addRow("untangled polygon ccw, double start point") << QPolygonF(QVector<QPointF> {QPointF(6,1), QPointF(4,1), QPointF(2,2), QPointF(1,5), QPointF(4, 8), QPointF(8, 6), QPointF(9,3), QPointF(6,1)}) << true;
}


void KisAlgebra2DTest::testIsPolygonTrulyConvex()
{

    QFETCH(QPolygonF, polygon);
    QFETCH(bool, isConvex);

    QCOMPARE(isConvex, KisAlgebra2D::isPolygonTrulyConvex(polygon));

}

SIMPLE_TEST_MAIN(KisAlgebra2DTest)
