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

#include <QtTest/QTest>
#include <QtTest/QSignalSpy>
#include <QtQuickShapesDesignHelpers/private/qquickrectangleshape_p.h>
#include <QtQuickTest/quicktest.h>
#include <QtQuickTestUtils/private/visualtestutils_p.h>

using namespace QQuickVisualTestUtils;

class tst_QQuickRectangleShape : public QQmlDataTest
{
    Q_OBJECT
public:
    tst_QQuickRectangleShape();

private slots:
    void initTestCase() override;

    void changeSignals_data();
    void changeSignals();
    void radii();
    void bevel();
    void fillItem();

private:
    // If we load the QML from scratch every test, each row takes ~350ms.
    // However, we still want it to be data-driven (so that e.g. you can
    // run individual rows), so we store it here and create it once at startup
    // rather than loop through a list in changeSignals.
    // TODO: clean this up when we have a callback that is run after all rows
    // have been run for a test function: QTBUG-134198
    std::unique_ptr<QQuickApplicationHelper> changeSignalTestAppHelper;
};

tst_QQuickRectangleShape::tst_QQuickRectangleShape()
    : QQmlDataTest(QT_QMLTEST_DATADIR, QQmlDataTest::FailOnWarningsPolicy::FailOnWarnings)
{
}

void tst_QQuickRectangleShape::initTestCase()
{
    QQmlDataTest::initTestCase();
    changeSignalTestAppHelper.reset(new QQuickApplicationHelper(this, "default.qml"));
}

void tst_QQuickRectangleShape::changeSignals_data()
{
    QTest::addColumn<QVariant>("propertyValue");
    QTest::addColumn<QMetaMethod>("changeSignal");

    // radius and bevel are more complex and therefore tested separately.
    QTest::newRow("width") << QVariant::fromValue(150)
        << QMetaMethod::fromSignal(&QQuickItem::widthChanged);
    QTest::newRow("height") << QVariant::fromValue(100)
        << QMetaMethod::fromSignal(&QQuickItem::heightChanged);
    QTest::newRow("drawTop") << QVariant::fromValue(false)
        << QMetaMethod::fromSignal(&QQuickRectangleShape::drawTopChanged);
    QTest::newRow("drawRight") << QVariant::fromValue(false)
        << QMetaMethod::fromSignal(&QQuickRectangleShape::drawRightChanged);
    QTest::newRow("drawBottom") << QVariant::fromValue(false)
        << QMetaMethod::fromSignal(&QQuickRectangleShape::drawBottomChanged);
    QTest::newRow("drawLeft") << QVariant::fromValue(false)
        << QMetaMethod::fromSignal(&QQuickRectangleShape::drawLeftChanged);
    QTest::newRow("strokeColor") << QVariant::fromValue(QColorConstants::Svg::purple)
        << QMetaMethod::fromSignal(&QQuickRectangleShape::strokeColorChanged);
    QTest::newRow("strokeWidth") << QVariant::fromValue(0)
        << QMetaMethod::fromSignal(&QQuickRectangleShape::strokeWidthChanged);
    QTest::newRow("fillColor") << QVariant::fromValue(QColorConstants::Svg::purple)
        << QMetaMethod::fromSignal(&QQuickRectangleShape::fillColorChanged);
    QTest::newRow("joinStyle") << QVariant::fromValue(QQuickShapePath::RoundJoin)
        << QMetaMethod::fromSignal(&QQuickRectangleShape::joinStyleChanged);
    QTest::newRow("capStyle") << QVariant::fromValue(QQuickShapePath::RoundCap)
        << QMetaMethod::fromSignal(&QQuickRectangleShape::capStyleChanged);
    QTest::newRow("strokeStyle") << QVariant::fromValue(QQuickShapePath::DashLine)
        << QMetaMethod::fromSignal(&QQuickRectangleShape::strokeStyleChanged);
    QTest::newRow("dashOffset") << QVariant::fromValue(4)
        << QMetaMethod::fromSignal(&QQuickRectangleShape::dashOffsetChanged);
    QTest::newRow("dashPattern") << QVariant::fromValue(QList<qreal>{1, 2})
        << QMetaMethod::fromSignal(&QQuickRectangleShape::dashPatternChanged);
    QTest::newRow("borderMode") << QVariant::fromValue(QQuickRectangleShape::BorderMode::Outside)
        << QMetaMethod::fromSignal(&QQuickRectangleShape::borderModeChanged);
}

void tst_QQuickRectangleShape::changeSignals()
{
    QFETCH(const QVariant, propertyValue);
    QFETCH(const QMetaMethod, changeSignal);

    QVERIFY2(changeSignalTestAppHelper->ready, changeSignalTestAppHelper->failureMessage());
    QQuickWindow *window = changeSignalTestAppHelper->window;
    window->show();
    QVERIFY(QTest::qWaitForWindowExposed(window));

    auto *rectangleShape = window->property("rectangleShape").value<QQuickRectangleShape *>();
    QVERIFY(rectangleShape);
    const QSignalSpy signalSpy(rectangleShape, changeSignal);
    QVERIFY(signalSpy.isValid());
    QVERIFY(rectangleShape->setProperty(QTest::currentDataTag(), propertyValue));
    QCOMPARE(signalSpy.count(), 1);
}

void tst_QQuickRectangleShape::radii()
{
    QQuickApplicationHelper helper(this, "default.qml");
    QVERIFY2(helper.ready, helper.failureMessage());
    QQuickWindow *window = helper.window;
    window->show();
    QVERIFY(QTest::qWaitForWindowExposed(window));

    auto *rectangleShape = window->property("rectangleShape").value<QQuickRectangleShape *>();
    QVERIFY(rectangleShape);

    const QSignalSpy radiusSignalSpy(rectangleShape, &QQuickRectangleShape::radiusChanged);
    QVERIFY(radiusSignalSpy.isValid());
    const QSignalSpy topLeftRadiusSignalSpy(rectangleShape, &QQuickRectangleShape::topLeftRadiusChanged);
    QVERIFY(topLeftRadiusSignalSpy.isValid());
    const QSignalSpy topRightRadiusSignalSpy(rectangleShape, &QQuickRectangleShape::topRightRadiusChanged);
    QVERIFY(topRightRadiusSignalSpy.isValid());
    const QSignalSpy bottomLeftRadiusSignalSpy(rectangleShape, &QQuickRectangleShape::bottomLeftRadiusChanged);
    QVERIFY(bottomLeftRadiusSignalSpy.isValid());
    const QSignalSpy bottomRightRadiusSignalSpy(rectangleShape, &QQuickRectangleShape::bottomRightRadiusChanged);
    QVERIFY(bottomRightRadiusSignalSpy.isValid());

    // Setting radius should affect all of the other radii properties that aren't explicitly set.
    rectangleShape->setRadius(1);
    QCOMPARE(rectangleShape->radius(), 1);
    QCOMPARE(radiusSignalSpy.count(), 1);
    QCOMPARE(rectangleShape->topLeftRadius(), 1);
    QCOMPARE(topLeftRadiusSignalSpy.count(), 1);
    QCOMPARE(rectangleShape->topRightRadius(), 1);
    QCOMPARE(topRightRadiusSignalSpy.count(), 1);
    QCOMPARE(rectangleShape->bottomLeftRadius(), 1);
    QCOMPARE(bottomLeftRadiusSignalSpy.count(), 1);
    QCOMPARE(rectangleShape->bottomRightRadius(), 1);
    QCOMPARE(bottomRightRadiusSignalSpy.count(), 1);

    // Setting individual radii should override their values.
    rectangleShape->setTopLeftRadius(2);
    QCOMPARE(rectangleShape->topLeftRadius(), 2);
    QCOMPARE(topLeftRadiusSignalSpy.count(), 2);

    rectangleShape->setTopRightRadius(3);
    QCOMPARE(rectangleShape->topRightRadius(), 3);
    QCOMPARE(topRightRadiusSignalSpy.count(), 2);

    rectangleShape->setBottomLeftRadius(4);
    QCOMPARE(rectangleShape->bottomLeftRadius(), 4);
    QCOMPARE(bottomLeftRadiusSignalSpy.count(), 2);

    rectangleShape->setBottomRightRadius(5);
    QCOMPARE(rectangleShape->bottomRightRadius(), 5);
    QCOMPARE(bottomRightRadiusSignalSpy.count(), 2);

    // Setting radius should no longer have any effect on the other radii properties.
    rectangleShape->setRadius(100);
    QCOMPARE(rectangleShape->radius(), 100);
    QCOMPARE(radiusSignalSpy.count(), 2);
    QCOMPARE(rectangleShape->topLeftRadius(), 2);
    QCOMPARE(topLeftRadiusSignalSpy.count(), 2);
    QCOMPARE(rectangleShape->topRightRadius(), 3);
    QCOMPARE(topRightRadiusSignalSpy.count(), 2);
    QCOMPARE(rectangleShape->bottomLeftRadius(), 4);
    QCOMPARE(bottomLeftRadiusSignalSpy.count(), 2);
    QCOMPARE(rectangleShape->bottomRightRadius(), 5);
    QCOMPARE(bottomRightRadiusSignalSpy.count(), 2);

    // Neither should resetting it.
    rectangleShape->resetRadius();
    QCOMPARE(rectangleShape->radius(), 10);
    QCOMPARE(radiusSignalSpy.count(), 3);
    QCOMPARE(rectangleShape->topLeftRadius(), 2);
    QCOMPARE(topLeftRadiusSignalSpy.count(), 2);
    QCOMPARE(rectangleShape->topRightRadius(), 3);
    QCOMPARE(topRightRadiusSignalSpy.count(), 2);
    QCOMPARE(rectangleShape->bottomLeftRadius(), 4);
    QCOMPARE(bottomLeftRadiusSignalSpy.count(), 2);
    QCOMPARE(rectangleShape->bottomRightRadius(), 5);
    QCOMPARE(bottomRightRadiusSignalSpy.count(), 2);

    // Resetting individual radii should return their values to that of radius.
    rectangleShape->resetTopLeftRadius();
    QCOMPARE(rectangleShape->topLeftRadius(), 10);
    QCOMPARE(topLeftRadiusSignalSpy.count(), 3);

    rectangleShape->resetTopRightRadius();
    QCOMPARE(rectangleShape->topRightRadius(), 10);
    QCOMPARE(topRightRadiusSignalSpy.count(), 3);

    rectangleShape->resetBottomLeftRadius();
    QCOMPARE(rectangleShape->bottomLeftRadius(), 10);
    QCOMPARE(bottomLeftRadiusSignalSpy.count(), 3);

    rectangleShape->resetBottomRightRadius();
    QCOMPARE(rectangleShape->bottomRightRadius(), 10);
    QCOMPARE(bottomRightRadiusSignalSpy.count(), 3);
}

void tst_QQuickRectangleShape::bevel()
{
    QQuickApplicationHelper helper(this, "default.qml");
    QVERIFY2(helper.ready, helper.failureMessage());
    QQuickWindow *window = helper.window;
    window->show();
    QVERIFY(QTest::qWaitForWindowExposed(window));

    auto *rectangleShape = window->property("rectangleShape").value<QQuickRectangleShape *>();
    QVERIFY(rectangleShape);

    const QSignalSpy bevelSignalSpy(rectangleShape, &QQuickRectangleShape::bevelChanged);
    QVERIFY(bevelSignalSpy.isValid());
    const QSignalSpy topLeftBevelSignalSpy(rectangleShape, &QQuickRectangleShape::topLeftBevelChanged);
    QVERIFY(topLeftBevelSignalSpy.isValid());
    const QSignalSpy topRightBevelSignalSpy(rectangleShape, &QQuickRectangleShape::topRightBevelChanged);
    QVERIFY(topRightBevelSignalSpy.isValid());
    const QSignalSpy bottomLeftBevelSignalSpy(rectangleShape, &QQuickRectangleShape::bottomLeftBevelChanged);
    QVERIFY(bottomLeftBevelSignalSpy.isValid());
    const QSignalSpy bottomRightBevelSignalSpy(rectangleShape, &QQuickRectangleShape::bottomRightBevelChanged);
    QVERIFY(bottomRightBevelSignalSpy.isValid());

    // Setting bevel should affect all of the other bevel properties that aren't explicitly set.
    rectangleShape->setBevel(true);
    QCOMPARE(rectangleShape->hasBevel(), true);
    QCOMPARE(bevelSignalSpy.count(), 1);
    QCOMPARE(rectangleShape->hasTopLeftBevel(), true);
    QCOMPARE(topLeftBevelSignalSpy.count(), 1);
    QCOMPARE(rectangleShape->hasTopRightBevel(), true);
    QCOMPARE(topRightBevelSignalSpy.count(), 1);
    QCOMPARE(rectangleShape->hasBottomLeftBevel(), true);
    QCOMPARE(bottomLeftBevelSignalSpy.count(), 1);
    QCOMPARE(rectangleShape->hasBottomRightBevel(), true);
    QCOMPARE(bottomRightBevelSignalSpy.count(), 1);

    // Setting individual bevels should override their values.
    rectangleShape->setTopLeftBevel(false);
    QCOMPARE(rectangleShape->hasTopLeftBevel(), false);
    QCOMPARE(topLeftBevelSignalSpy.count(), 2);

    rectangleShape->setTopRightBevel(false);
    QCOMPARE(rectangleShape->hasTopRightBevel(), false);
    QCOMPARE(topRightBevelSignalSpy.count(), 2);

    rectangleShape->setBottomLeftBevel(false);
    QCOMPARE(rectangleShape->hasBottomLeftBevel(), false);
    QCOMPARE(bottomLeftBevelSignalSpy.count(), 2);

    rectangleShape->setBottomRightBevel(false);
    QCOMPARE(rectangleShape->hasBottomRightBevel(), false);
    QCOMPARE(bottomRightBevelSignalSpy.count(), 2);

    // Setting bevel should no longer have any effect on the other bevel properties.
    // (we only have two possible values to change between, and since the individual
    // properties are already false, we need to change its value twice to properly test it).
    rectangleShape->setBevel(false);
    rectangleShape->setBevel(true);
    QCOMPARE(rectangleShape->hasBevel(), true);
    QCOMPARE(bevelSignalSpy.count(), 3);
    QCOMPARE(rectangleShape->hasTopLeftBevel(), false);
    QCOMPARE(topLeftBevelSignalSpy.count(), 2);
    QCOMPARE(rectangleShape->hasTopRightBevel(), false);
    QCOMPARE(topRightBevelSignalSpy.count(), 2);
    QCOMPARE(rectangleShape->hasBottomLeftBevel(), false);
    QCOMPARE(bottomLeftBevelSignalSpy.count(), 2);
    QCOMPARE(rectangleShape->hasBottomRightBevel(), false);
    QCOMPARE(bottomRightBevelSignalSpy.count(), 2);

    // Neither should resetting it.
    rectangleShape->resetBevel();
    QCOMPARE(rectangleShape->hasBevel(), false);
    QCOMPARE(bevelSignalSpy.count(), 4);
    // (These were false to begin with, but we test it anyway; at least the signal count
    // will help to catch regressions)
    QCOMPARE(rectangleShape->hasTopLeftBevel(), false);
    QCOMPARE(topLeftBevelSignalSpy.count(), 2);
    QCOMPARE(rectangleShape->hasTopRightBevel(), false);
    QCOMPARE(topRightBevelSignalSpy.count(), 2);
    QCOMPARE(rectangleShape->hasBottomLeftBevel(), false);
    QCOMPARE(bottomLeftBevelSignalSpy.count(), 2);
    QCOMPARE(rectangleShape->hasBottomRightBevel(), false);
    QCOMPARE(bottomRightBevelSignalSpy.count(), 2);

    // Resetting individual bevels should return their values to that of bevel.
    // First, set bevel to true so we have a state change that we can observe.
    rectangleShape->setBevel(true);
    QCOMPARE(bevelSignalSpy.count(), 5);

    rectangleShape->resetTopLeftBevel();
    QCOMPARE(rectangleShape->hasTopLeftBevel(), true);
    QCOMPARE(topLeftBevelSignalSpy.count(), 3);

    rectangleShape->resetTopRightBevel();
    QCOMPARE(rectangleShape->hasTopRightBevel(), true);
    QCOMPARE(topRightBevelSignalSpy.count(), 3);

    rectangleShape->resetBottomLeftBevel();
    QCOMPARE(rectangleShape->hasBottomLeftBevel(), true);
    QCOMPARE(bottomLeftBevelSignalSpy.count(), 3);

    rectangleShape->resetBottomRightBevel();
    QCOMPARE(rectangleShape->hasBottomRightBevel(), true);
    QCOMPARE(bottomRightBevelSignalSpy.count(), 3);
}

void tst_QQuickRectangleShape::fillItem()
{
    QQuickApplicationHelper helper(this, "fillitem.qml");
    QVERIFY2(helper.ready, helper.failureMessage());
    QQuickWindow *window = helper.window;
    window->show();
    QVERIFY(QTest::qWaitForWindowExposed(window));

    auto *theShape = window->property("theShape").value<QQuickRectangleShape *>();
    QVERIFY(theShape);

    auto *fillItem = window->findChild<QQuickItem *>("fillItem");
    QVERIFY(fillItem);
}

QTEST_MAIN(tst_QQuickRectangleShape)

#include "tst_qquickrectangleshape.moc"
