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

#include <qtest.h>

#include <QList>
#include <QByteArray>
#include <private/qquickshadereffect_p.h>
#include <QMatrix4x4>
#include <QtQml/QQmlEngine>
#include <QtQuick/QQuickView>
#include <QtQuick/private/qquickshadereffectsource_p.h>
#include <QtQuickTestUtils/private/qmlutils_p.h>
#include <QtQuickTestUtils/private/viewtestutils_p.h>

using namespace Qt::StringLiterals;

class TestShaderEffect : public QQuickShaderEffect
{
    Q_OBJECT
    Q_PROPERTY(QVariant source READ dummyRead NOTIFY sourceChanged)
    Q_PROPERTY(QVariant _0aA9zZ READ dummyRead NOTIFY dummyChanged)
    Q_PROPERTY(QVariant x86 READ dummyRead NOTIFY dummyChanged)
    Q_PROPERTY(QVariant X READ dummyRead NOTIFY dummyChanged)
    Q_PROPERTY(QMatrix4x4 mat4x4 READ mat4x4Read NOTIFY dummyChanged)

public:
    TestShaderEffect(QQuickItem* parent = nullptr) : QQuickShaderEffect(parent)
    {
    }

    QMatrix4x4 mat4x4Read() const { return QMatrix4x4(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1); }
    QVariant dummyRead() const { return QVariant(); }

    int signalsConnected = 0;

protected:
    void connectNotify(const QMetaMethod &s) override {
        if (s.name() == "sourceChanged")
            ++signalsConnected;
    }
    void disconnectNotify(const QMetaMethod &s) override
    {
        if (s.name() == "sourceChanged")
            --signalsConnected;
    }

signals:
    void dummyChanged();
    void sourceChanged();

private:
};

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

private slots:
    void initTestCase() override;
    void cleanupTestCase();
    void testConnection();
    void deleteSourceItem();
    void deleteShaderEffectSource();
    void twoImagesOneShaderEffect();

    void withoutQmlEngine();

    void hideParent();
    void testPropertyMappings();

private:
    enum PresenceFlags {
        VertexPresent = 0x01,
        TexCoordPresent = 0x02,
        MatrixPresent = 0x04,
        OpacityPresent = 0x08,
        SourcePresent = 0x10
    };
};

tst_qquickshadereffect::tst_qquickshadereffect()
    : QQmlDataTest(QT_QMLTEST_DATADIR)
{
    qmlRegisterType<TestShaderEffect>("ShaderEffectTest", 1, 0, "TestShaderEffect");
}

void tst_qquickshadereffect::initTestCase()
{
    QQmlDataTest::initTestCase();
}

void tst_qquickshadereffect::cleanupTestCase()
{
}

void tst_qquickshadereffect::testConnection()
{
    // verify that the property notify signal is connected
    QQuickView view;
    QVERIFY(QQuickTest::initView(view, QUrl{u"qrc:/data/connections.qml"_s}));

    auto *shaderEffectItem = qobject_cast<TestShaderEffect*>(view.rootObject());
    QVERIFY(shaderEffectItem);
    QCOMPARE(shaderEffectItem->signalsConnected, 0);

    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));

    QSGRendererInterface *rif = view.rendererInterface();
    if (rif && rif->graphicsApi() != QSGRendererInterface::Software)
        QCOMPARE(shaderEffectItem->signalsConnected, 1);
}

void tst_qquickshadereffect::deleteSourceItem()
{
    // purely to ensure that deleting the sourceItem of a shader doesn't cause a crash
    QQuickView view;
    QVERIFY(QQuickTest::showView(view, QUrl{u"qrc:/data/deleteSourceItem.qml"_s}));
    QObject *obj = view.rootObject();
    QVERIFY(obj);
    QMetaObject::invokeMethod(obj, "setDeletedSourceItem");
    const auto shaderEffectSource = obj->findChild<QQuickShaderEffectSource*>();
    QVERIFY(shaderEffectSource);
    QTRY_VERIFY(!shaderEffectSource->sourceItem());
}

void tst_qquickshadereffect::deleteShaderEffectSource()
{
    // purely to ensure that deleting the ShaderEffectSource doesn't cause a crash
    QQuickView view;
    QVERIFY(QQuickTest::showView(view, QUrl{u"qrc:/data/deleteShaderEffectSource.qml"_s}));
    QObject *obj = view.rootObject();
    QVERIFY(obj);
    const QPointer<QQuickShaderEffectSource> shaderEffectSource = obj->findChild<QQuickShaderEffectSource*>();
    QVERIFY(shaderEffectSource);
    QMetaObject::invokeMethod(obj, "setDeletedShaderEffectSource");
    QTRY_VERIFY(shaderEffectSource);
}

void tst_qquickshadereffect::twoImagesOneShaderEffect()
{
    // Don't crash when having a ShaderEffect and an Image sharing the texture via supportsAtlasTextures
    QQuickView view;
    QVERIFY(QQuickTest::showView(view, QUrl{u"qrc:/data/twoImagesOneShaderEffect.qml"_s}));
    QObject *obj = view.rootObject();
    QVERIFY(obj);
}

void tst_qquickshadereffect::withoutQmlEngine()
{
    // using a shader without QML engine used to crash
    const QQuickWindow window;
    auto shaderEffect = new TestShaderEffect(window.contentItem());
    shaderEffect->setVertexShader(QUrl());
    QVERIFY(shaderEffect->isComponentComplete());
}

// QTBUG-86402: hiding the parent of an item that uses an effect should not cause a crash.
void tst_qquickshadereffect::hideParent()
{
    QQuickView view;
    view.setSource(QUrl(QStringLiteral("qrc:/data/hideParent.qml")));
    QVERIFY(QQuickTest::showView(view, QUrl{u"qrc:/data/hideParent.qml"_s}));
    // Should finish without crashing.
    QTRY_VERIFY(view.rootObject()->property("finished").toBool());
}

void tst_qquickshadereffect::testPropertyMappings()
{
    QQuickView view;
    view.setSource(QUrl(QStringLiteral("qrc:/data/testProperties.qml")));
    QTRY_VERIFY_WITH_TIMEOUT(view.rootObject()->property("finished").toBool(), 2s);
}

QTEST_MAIN(tst_qquickshadereffect)

#include "tst_qquickshadereffect.moc"
