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

#include <qtest.h>
#include <QtTest/qsignalspy.h>
#include <QtCore/qmath.h>
#include <QtCore/qsize.h>
#include <QtGui/private/qhighdpiscaling_p.h>
#include <QtQml/qqmlengine.h>
#include <QtQuick/qquickitem.h>
#include <QtQuick/qquickview.h>
#include <QtQuick/qquickitemgrabresult.h>
#include <QtQuick/private/qquickimage_p.h>
#include <QtQuick/private/qquickimage_p_p.h>
#include <QtQuickTestUtils/private/qmlutils_p.h>
#include <QtQuickTestUtils/private/visualtestutils_p.h>
#include <QtGui/private/qrhi_p.h>

using namespace QQuickVisualTestUtils;

class tst_qquickninepatchimage : public QQmlDataTest
{
    Q_OBJECT

public:
    tst_qquickninepatchimage();

private slots:
    void ninePatch_data();
    void ninePatch();
    void padding_data();
    void padding();
    void inset_data();
    void inset();
    void implicitSize_data();
    void implicitSize();
    void hwCompressedImages_data();
    void hwCompressedImages();
    void relativeSource();
};

static QImage grabItemToImage(QQuickItem *item)
{
    QSharedPointer<QQuickItemGrabResult> result = item->grabToImage();
    QSignalSpy spy(result.data(), SIGNAL(ready()));
    spy.wait();
    return result->image();
}

tst_qquickninepatchimage::tst_qquickninepatchimage()
    : QQmlDataTest(QT_QMLTEST_DATADIR)
{
}

void tst_qquickninepatchimage::ninePatch_data()
{
    QTest::addColumn<int>("dpr");
    QTest::addColumn<QSize>("size");

    // original size, downsized, stretched
    const QList<QSize> sizes = QList<QSize>()
            << QSize(40, 40) // original
            << QSize(10, 40) // downsized (h)
            << QSize(40, 10) // downsized (v)
            << QSize(10, 10) // downsized (h & v)
            << QSize(80, 40) // stretched (h)
            << QSize(40, 80) // stretched (v)
            << QSize(80, 80) // stretched (h & v)
            << QSize(8, 8);  // minimal (borders only)

    for (const QSize &sz : sizes) {
        for (int dpr = 1; dpr <= 4; ++dpr)
            QTest::newRow(qPrintable(QString::fromLatin1("DPR=%1, %2x%3").arg(dpr).arg(sz.width()).arg(sz.height()))) << dpr << sz;
    }
}

void tst_qquickninepatchimage::ninePatch()
{
    QFETCH(int, dpr);
    QFETCH(QSize, size);

    QHighDpiScaling::setGlobalFactor(dpr);

    QQuickView view(testFileUrl("ninepatchimage.qml"));
    QCOMPARE(view.status(), QQuickView::Ready);
    view.show();
    QVERIFY(QTest::qWaitForWindowActive(&view));

    QQuickImage *ninePatchImage = qobject_cast<QQuickImage *>(view.rootObject());
    QVERIFY(ninePatchImage);
    ninePatchImage->setSource(testFileUrl("foo.9.png"));
    ninePatchImage->setSize(size);

    QImage ninePatchImageGrab = grabItemToImage(ninePatchImage).scaled(size * dpr);
    // grabItemToImage() may have device's DPR as metadata, override it to setGlobalFactor()'s DPR.
    ninePatchImageGrab.setDevicePixelRatio(dpr);

    // Generate an image to compare against the actual 9-patch image.
    QImage generatedImage(size * dpr, ninePatchImageGrab.format());
    generatedImage.setDevicePixelRatio(dpr);
    generatedImage.fill(Qt::red);

    QImage blueRect(4 * dpr, 4 * dpr, ninePatchImageGrab.format());
    blueRect.setDevicePixelRatio(dpr);
    blueRect.fill(Qt::blue);

    const QSizeF generatedPaintedSize = generatedImage.deviceIndependentSize();
    const QSizeF blueRectPaintedSize = blueRect.deviceIndependentSize();

    QPainter painter(&generatedImage);
    // Top-left
    painter.drawImage(0, 0, blueRect);
    // Top-right
    painter.drawImage(generatedPaintedSize.width() - blueRectPaintedSize.width(), 0, blueRect);
    // Bottom-right
    painter.drawImage(generatedPaintedSize.width() - blueRectPaintedSize.width(), generatedPaintedSize.height() - blueRectPaintedSize.height(), blueRect);
    // Bottom-left
    painter.drawImage(0, generatedPaintedSize.height() - blueRectPaintedSize.height(), blueRect);

    // This test fails when running with the offscreen and minimal platform plugins,
    // so we can't use SKIP_IF_NO_WINDOW_GRAB here.
    if ((QGuiApplication::platformName() == QLatin1String("offscreen"))
        || (QGuiApplication::platformName() == QLatin1String("minimal")))
        QSKIP("Skipping due to grabWindow not functional on offscreen/minimal platforms");

    QCOMPARE(ninePatchImageGrab, generatedImage);
}

void tst_qquickninepatchimage::padding_data()
{
    QTest::addColumn<int>("dpr");

    for (int dpr = 1; dpr <= 4; ++dpr)
        QTest::newRow(qPrintable(QString::fromLatin1("DPR=%1").arg(dpr))) << dpr;
}

void tst_qquickninepatchimage::padding()
{
    QFETCH(int, dpr);

    QHighDpiScaling::setGlobalFactor(dpr);

    QQuickView view(testFileUrl("ninepatchimage.qml"));
    QCOMPARE(view.status(), QQuickView::Ready);
    view.show();
    QVERIFY(QTest::qWaitForWindowActive(&view));

    QQuickImage *ninePatchImage = qobject_cast<QQuickImage *>(view.rootObject());
    QVERIFY(ninePatchImage);
    ninePatchImage->setSource(testFileUrl("padding.9.png"));

    QCOMPARE(ninePatchImage->property("topPadding").toReal(), 8);
    QCOMPARE(ninePatchImage->property("leftPadding").toReal(), 18);
    QCOMPARE(ninePatchImage->property("rightPadding").toReal(), 20);
    QCOMPARE(ninePatchImage->property("bottomPadding").toReal(), 10);
}

void tst_qquickninepatchimage::inset_data()
{
    QTest::addColumn<int>("dpr");
    QTest::addColumn<QString>("file");
    QTest::addColumn<QMarginsF>("insets");

    const QStringList files = QStringList() << "inset-all.9.png" << "inset-topleft.9.png" << "inset-bottomright.9.png";
    const QList<QMarginsF> insets = QList<QMarginsF>() << QMarginsF(2, 1, 3, 4) << QMarginsF(2, 1, 0, 0) << QMarginsF(0, 0, 3, 4);

    for (int i = 0; i < files.size(); ++i) {
        QString file = files.at(i);
        for (int dpr = 1; dpr <= 4; ++dpr)
            QTest::newRow(qPrintable(QString::fromLatin1("%1 DPR=%2").arg(file).arg(dpr))) << dpr << file << insets.at(i);
    }
}

Q_DECLARE_METATYPE(QMarginsF)

void tst_qquickninepatchimage::inset()
{
    QFETCH(int, dpr);
    QFETCH(QString, file);
    QFETCH(QMarginsF, insets);

    QHighDpiScaling::setGlobalFactor(dpr);

    QQuickView view(testFileUrl("ninepatchimage.qml"));
    QCOMPARE(view.status(), QQuickView::Ready);
    view.show();
    QVERIFY(QTest::qWaitForWindowActive(&view));

    QQuickImage *ninePatchImage = qobject_cast<QQuickImage *>(view.rootObject());
    QVERIFY(ninePatchImage);
    ninePatchImage->setSource(testFileUrl(file));

    QCOMPARE(ninePatchImage->property("topInset").toReal(), insets.top());
    QCOMPARE(ninePatchImage->property("leftInset").toReal(), insets.left());
    QCOMPARE(ninePatchImage->property("rightInset").toReal(), insets.right());
    QCOMPARE(ninePatchImage->property("bottomInset").toReal(), insets.bottom());
}

void tst_qquickninepatchimage::implicitSize_data()
{
    QTest::addColumn<int>("dpr");
    QTest::addColumn<QString>("file");
    QTest::addColumn<QSizeF>("implicitSize");

    const struct TestFile {
        QString name;
        QSizeF sizeHint;
    } testFiles [] = {
        { "foo.9.png", QSizeF(40, 40) },
        { "padding.9.png", QSizeF(40, 40) },
        { "inset-all.9.png", QSizeF(45, 45) },
        { "inset-topleft.9.png", QSizeF(42, 41) },
        { "inset-bottomright.9.png", QSizeF(43, 44) }
    };

    for (const TestFile &file : testFiles) {
        for (int dpr = 1; dpr <= 4; ++dpr)
            QTest::newRow(qPrintable(QString::fromLatin1("%1 DPR=%2").arg(file.name).arg(dpr))) << dpr << file.name << file.sizeHint;
    }
}

void tst_qquickninepatchimage::implicitSize()
{
    QFETCH(int, dpr);
    QFETCH(QString, file);
    QFETCH(QSizeF, implicitSize);

    QHighDpiScaling::setGlobalFactor(dpr);

    QQuickView view(testFileUrl("ninepatchimage.qml"));
    QCOMPARE(view.status(), QQuickView::Ready);
    view.show();
    QVERIFY(QTest::qWaitForWindowActive(&view));

    QQuickImage *ninePatchImage = qobject_cast<QQuickImage *>(view.rootObject());
    QVERIFY(ninePatchImage);
    ninePatchImage->setSource(testFileUrl(file));

    QCOMPARE(ninePatchImage->implicitWidth(), implicitSize.width());
    QCOMPARE(ninePatchImage->implicitHeight(), implicitSize.height());
}

void tst_qquickninepatchimage::hwCompressedImages_data()
{
    QTest::addColumn<int>("dpr");
    QTest::addColumn<QString>("file");
    QTest::addColumn<QSize>("size");
    QTest::addColumn<QRhiTexture::Format>("format");

    const struct TestFile {
        QString name;
        QSize size;
        QRhiTexture::Format format;
    } testFiles [] = {
        { "o1_bc1.ktx", QSize(64, 64), QRhiTexture::BC1 },
        { "logo.pkm", QSize(256, 256), QRhiTexture::ETC2_RGB8 },
        { "qt4.astc", QSize(250, 200), QRhiTexture::ASTC_8x8 }
    };

    for (const TestFile &file : testFiles) {
        for (int dpr = 1; dpr <= 4; ++dpr)
            QTest::newRow(qPrintable(QString::fromLatin1("%1 DPR=%2").arg(file.name).arg(dpr))) << dpr << file.name << file.size << file.format;
    }
}

void tst_qquickninepatchimage::hwCompressedImages()
{
    QFETCH(int, dpr);
    QFETCH(QString, file);
    QFETCH(QSize, size);
    QFETCH(QRhiTexture::Format, format);

    QHighDpiScaling::setGlobalFactor(dpr);

    QQuickView view(testFileUrl("ninepatchimage.qml"));
    QCOMPARE(view.status(), QQuickView::Ready);
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));

    if (!QSGRendererInterface::isApiRhiBased(view.rendererInterface()->graphicsApi()))
        QSKIP("Skipping due to using software backend");

    QRhi *rhi = static_cast<QRhi *>(view.rendererInterface()->getResource(&view, QSGRendererInterface::RhiResource));
    if (!rhi->isTextureFormatSupported(format))
        QSKIP(qPrintable(QString::fromLatin1("%1 not supported, skip").arg(format)));

    QQuickImage *ninePatchImage = qobject_cast<QQuickImage *>(view.rootObject());
    QVERIFY(ninePatchImage);
    ninePatchImage->setSource(testFileUrl(file));
    ninePatchImage->setSize(size);
    QSignalSpy spy(&view, SIGNAL(afterSynchronizing()));
    QTRY_VERIFY(spy.size() >= 1);

    QQuickImagePrivate *ninePatchImagePrivate = static_cast<QQuickImagePrivate *>(QQuickItemPrivate::get(ninePatchImage));
    QVERIFY(ninePatchImagePrivate->paintNode);
}

void tst_qquickninepatchimage::relativeSource()
{
    QQuickView view(testFileUrl("relativeSource.qml"));
    QCOMPARE(view.status(), QQuickView::Ready);
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));

    auto *ninePatchImage = qobject_cast<QQuickImage *>(view.rootObject());
    QVERIFY(ninePatchImage);
    QVERIFY2(ninePatchImage->source().isValid(), qPrintable(QLatin1String("source: ") + ninePatchImage->source().toString()));
    QVERIFY2(!ninePatchImage->source().isRelative(), qPrintable(QLatin1String("source: ") + ninePatchImage->source().toString()));
}

QTEST_MAIN(tst_qquickninepatchimage)

#include "tst_qquickninepatchimage.moc"
