/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include <qtest.h>
#include <QDebug>

#include "testclasses.h"

#include <QtCore/qscopedpointer.h>
#include <QtQml/qqml.h>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlcomponent.h>
#include <QtQuick/qquickitem.h>
#include <QtCore/qproperty.h>
#include <QtQuickTestUtils/private/qmlutils_p.h>

#include <private/qqmlengine_p.h>
#include <private/qqmltypedata_p.h>
#include <private/qqmlvmemetaobject_p.h>

#include <array>
#include <memory>

class tst_qmltc_manual : public QQmlDataTest
{
    Q_OBJECT

public:
    tst_qmltc_manual();

private slots:
    void cppBinding();
    void signalHandlers();
    void signalHandlers_qmlcachegen();
    void jsFunctions();
    void changingBindings();
    void propertyAlias();
    void propertyChangeHandler();
    void propertyReturningFunction();
    void locallyImported();
    void localImport();
    void neighbors();

private:
    void signalHandlers_impl(const QUrl &url);
};

tst_qmltc_manual::tst_qmltc_manual()
    : QQmlDataTest(QT_QMLTEST_DATADIR)
{
}

void tst_qmltc_manual::cppBinding()
{
    QQmlEngine e;
    HelloWorld::url = testFileUrl("HelloWorld.qml");
    HelloWorld created(&e);

    QCOMPARE(created.property("hello").toString(), QStringLiteral("Hello, World"));
    QCOMPARE(created.getGreeting(), QStringLiteral("Hello, World!"));
    QCOMPARE(created.property("greeting").toString(), QStringLiteral("Hello, World!"));

    created.setProperty("hello", QStringLiteral("Hello, Qml"));

    QCOMPARE(created.property("hello").toString(), QStringLiteral("Hello, Qml"));
    QCOMPARE(created.property("greeting").toString(), QStringLiteral("Hello, Qml!"));
    QCOMPARE(created.getGreeting(), QStringLiteral("Hello, Qml!"));
}

void tst_qmltc_manual::signalHandlers_impl(const QUrl &url)
{
    QQmlEngine e;
    ANON_signalHandlers::url = url;
    ANON_signalHandlers created(&e);

    // signal emission works from C++
    emit created.signal1();
    emit created.signal2(QStringLiteral("42"), 42);

    QCOMPARE(created.property("signal1P").toInt(), 1);
    QCOMPARE(created.property("signal2P1").toString(), QStringLiteral("42"));
    QCOMPARE(created.property("signal2P2").toInt(), 42);
    QCOMPARE(created.property("signal2P3").toString(), QStringLiteral("4242"));

    // signal emission works through meta object system
    QMetaObject::invokeMethod(&created, "signal1");
    QMetaObject::invokeMethod(&created, "signal2", Q_ARG(QString, QStringLiteral("foo")),
                              Q_ARG(int, 23));

    QCOMPARE(created.property("signal1P").toInt(), 2);
    QCOMPARE(created.property("signal2P1").toString(), QStringLiteral("foo"));
    QCOMPARE(created.property("signal2P2").toInt(), 23);
    QCOMPARE(created.property("signal2P3").toString(), QStringLiteral("foo23"));

    // signal emission works through QML/JS
    created.qmlEmitSignal1();
    created.qmlEmitSignal2();

    QCOMPARE(created.property("signal1P").toInt(), 3);
    QCOMPARE(created.property("signal2P1").toString(), QStringLiteral("xyz"));
    QCOMPARE(created.property("signal2P2").toInt(), 123);
    QCOMPARE(created.property("signal2P3").toString(), QStringLiteral("xyz123"));

    created.qmlEmitSignal2WithArgs(QStringLiteral("abc"), 0);
    QCOMPARE(created.property("signal2P1").toString(), QStringLiteral("abc"));
    QCOMPARE(created.property("signal2P2").toInt(), 0);
    QCOMPARE(created.property("signal2P3").toString(), QStringLiteral("abc0"));
}

void tst_qmltc_manual::signalHandlers()
{
    // use QQmlTypeCompiler's compilation unit
    signalHandlers_impl(testFileUrl("signalHandlers.qml"));
}

void tst_qmltc_manual::signalHandlers_qmlcachegen()
{
    // use qmlcachegen's compilation unit
    signalHandlers_impl(QUrl("qrc:/QmltcManualTests/data/signalHandlers.qml"));
}

void tst_qmltc_manual::jsFunctions()
{
    QQmlEngine e;
    ANON_javaScriptFunctions::url = testFileUrl("javaScriptFunctions.qml");
    ANON_javaScriptFunctions created(&e);

    created.func1();
    created.func2(QStringLiteral("abc"));

    QCOMPARE(created.property("func1P").toInt(), 1);
    QCOMPARE(created.property("func2P").toString(), QStringLiteral("abc"));
    QCOMPARE(created.func3(), false);

    created.setProperty("func3P", true);
    QCOMPARE(created.func3(), true);
}

void tst_qmltc_manual::changingBindings()
{
    QQmlEngine e;
    ANON_changingBindings::url = testFileUrl("changingBindings.qml");
    ANON_changingBindings created(&e);

    // test initial binding
    QCOMPARE(created.initialBindingCallCount, 1); // eager evaluation
    QCOMPARE(created.property("p2").toInt(), 2); // p1 + 1
    QCOMPARE(created.initialBindingCallCount, 1);

    // test JS constant value
    created.resetToConstant();
    QCOMPARE(created.property("p2").toInt(), 42); // p2 = 42
    QCOMPARE(created.initialBindingCallCount, 1);

    // test Qt.binding()
    created.resetToNewBinding();
    created.setProperty("p1", 100);
    QCOMPARE(created.property("p2").toInt(), 200); // p1 * 2
    QCOMPARE(created.initialBindingCallCount, 1);

    // test setting initial (C++) binding
    created.setProperty("p1", 11);
    created.resetToInitialBinding();
    QCOMPARE(created.initialBindingCallCount, 2); // eager evaluation
    QCOMPARE(created.property("p2").toInt(), 12); // p1 + 1 (again)
    QCOMPARE(created.initialBindingCallCount, 2);

    // test resetting value through C++
    created.setP2(0);
    created.setP1(-10);

    QCOMPARE(created.property("p2").toInt(), 0);
    QCOMPARE(created.initialBindingCallCount, 2);

    created.setProperty("p2", 1);
    QCOMPARE(created.property("p2").toInt(), 1);
    QCOMPARE(created.initialBindingCallCount, 2);

    // test binding can be set again even after reset from C++
    created.resetToNewBinding();
    QCOMPARE(created.property("p2").toInt(), -20);
    QCOMPARE(created.initialBindingCallCount, 2);
}

void tst_qmltc_manual::propertyAlias()
{
    QQmlEngine e;
    ANON_propertyAlias::url = testFileUrl("propertyAlias.qml");
    ANON_propertyAlias created(&e);

    // test initial binding
    QCOMPARE(created.property("origin").toInt(), 6); // dummy / 2
    QCOMPARE(created.property("aliasToOrigin").toInt(), 6);

    QCOMPARE(created.getAliasValue(), 6);
    QCOMPARE(created.getAliasToOrigin(), 6);
    created.setDummy(10);
    QCOMPARE(created.property("aliasToOrigin").toInt(), 5);
    QCOMPARE(created.getAliasValue(), 5);
    QCOMPARE(created.getAliasToOrigin(), 5);

    // test the C++ setter
    created.setOrigin(7);
    QCOMPARE(created.property("aliasToOrigin").toInt(), 7);
    QCOMPARE(created.getAliasValue(), 7);
    QCOMPARE(created.getAliasToOrigin(), 7);

    // test meta-object setter
    created.setProperty("origin", 1);
    QCOMPARE(created.property("aliasToOrigin").toInt(), 1);
    QCOMPARE(created.getAliasValue(), 1);
    QCOMPARE(created.getAliasToOrigin(), 1);

    // test QML/JS setter
    created.resetOriginToConstant();
    QCOMPARE(created.property("aliasToOrigin").toInt(), 189);
    QCOMPARE(created.getAliasValue(), 189);
    QCOMPARE(created.getAliasToOrigin(), 189);

    // test QML/JS alias setter
    created.resetAliasToConstant();
    QCOMPARE(created.property("origin").toInt(), 42);
    QCOMPARE(created.getOrigin(), 42);
    // check the alias just to make sure it also works
    QCOMPARE(created.property("aliasToOrigin").toInt(), 42);
    QCOMPARE(created.getAliasValue(), 42);
    QCOMPARE(created.getAliasToOrigin(), 42);

    // test QML/JS binding reset
    created.resetOriginToNewBinding(); // dummy
    created.setDummy(99);
    QCOMPARE(created.property("aliasToOrigin").toInt(), 99);
    QCOMPARE(created.getAliasValue(), 99);
    QCOMPARE(created.getAliasToOrigin(), 99);

    // test QML/JS binding reset through alias
    created.resetAliasToNewBinding(); // dummy * 3
    created.setDummy(-8);
    QCOMPARE(created.property("origin").toInt(), -24);
    QCOMPARE(created.getOrigin(), -24);
    QCOMPARE(created.property("aliasToOrigin").toInt(), -24);
    QCOMPARE(created.getAliasValue(), -24);
    QCOMPARE(created.getAliasToOrigin(), -24);
}

void tst_qmltc_manual::propertyChangeHandler()
{
    QQmlEngine e;
    ANON_propertyChangeHandler::url = testFileUrl("propertyChangeHandler.qml");
    ANON_propertyChangeHandler created(&e);

    // test that fetching "dirty" property value doesn't trigger property change
    // handler
    QCOMPARE(created.getWatcher(), 0);
    QCOMPARE(created.getP(), 42); // due to binding
    QCOMPARE(created.getWatcher(), 0);
    QCOMPARE(created.property("watcher").toInt(), 0);

    // test that binding triggers property change handler
    created.setDummy(20);
    QCOMPARE(created.getWatcher(), 20);
    QCOMPARE(created.property("watcher").toInt(), 20);

    // test that property setting (through C++) triggers property change handler
    created.setWatcher(-100);
    created.setProperty("p", 18);
    QCOMPARE(created.getWatcher(), 18);

    // test that property setting triggers property change handler
    created.setWatcher(-47);
    created.setP(96);
    QCOMPARE(created.property("watcher").toInt(), 96);
}

void tst_qmltc_manual::propertyReturningFunction()
{
    QQmlEngine e;
    ANON_propertyReturningFunction::url = testFileUrl("propertyReturningFunction.qml");
    ANON_propertyReturningFunction created(&e);

    QCOMPARE(created.getCounter(), 0);
    QVariant function = created.getF();
    Q_UNUSED(function); // ignored as it can't be used currently
    QCOMPARE(created.getCounter(), 0);

    created.property("f");
    QCOMPARE(created.getCounter(), 0);
}

void tst_qmltc_manual::locallyImported()
{
    QQmlEngine e;
    LocallyImported::url = testFileUrl("LocallyImported.qml");

    LocallyImported created(&e);
    QCOMPARE(created.getCount(), 1);

    QQmlContext *ctx = e.contextForObject(&created);
    QVERIFY(ctx);
    QCOMPARE(qvariant_cast<QObject *>(ctx->contextProperty("foo")), &created);
}

void tst_qmltc_manual::localImport()
{
    // NB: compare object creation compiler against QQmlComponent
    {
        QQmlEngine e;
        QQmlComponent comp(&e);
        comp.loadUrl(testFileUrl("localImport.qml"));
        QScopedPointer<QObject> root(comp.create());
        QVERIFY2(root, qPrintable(comp.errorString()));

        QQmlContext *ctx = e.contextForObject(root.get());
        QVERIFY(ctx);
        QVERIFY(ctx->parentContext());
        QCOMPARE(ctx->contextProperty("foo"), QVariant());
        QCOMPARE(qvariant_cast<QObject *>(ctx->contextProperty("bar")), root.get());
        QCOMPARE(ctx->objectForName("foo"), nullptr);
        QCOMPARE(ctx->objectForName("bar"), root.get());
        QCOMPARE(QQmlContextData::get(ctx)->parent(), QQmlContextData::get(e.rootContext()));

        int count = root->property("count").toInt();
        QVariant magicValue {};
        QMetaObject::invokeMethod(root.get(), "getMagicValue", Q_RETURN_ARG(QVariant, magicValue));
        QCOMPARE(magicValue.toInt(), (count * 3 + 1));

        count = root->property("count").toInt();
        magicValue = QVariant();
        QMetaObject::invokeMethod(root.get(), "localGetMagicValue",
                                  Q_RETURN_ARG(QVariant, magicValue));
        QCOMPARE(magicValue.toInt(), (count * 3 + 1));
    }

    // In this case, the context hierarchy is the following:
    // * LocallyImported: rootContext -> locallyImportedContext
    // * ANON_localImport: ... -> locallyImportedContext -> localImportContext
    //
    // this resembles the object hierarchy where LocallyImported is a base class
    // of ANON_localImport. having an explicit parent context (from
    // LocallyImported) guarantees that e.g. parent id / base class methods are
    // found during JavaScript (property) lookups
    {
        QQmlEngine e;
        LocallyImported::url = testFileUrl("LocallyImported.qml");
        ANON_localImport::url = testFileUrl("localImport.qml");

        ANON_localImport created(&e);
        QCOMPARE(created.getP1(), 41);
        QCOMPARE(created.getCount(), 42);

        QQmlContext *ctx = e.contextForObject(&created);
        QVERIFY(ctx);
        QVERIFY(ctx->parentContext());
        QEXPECT_FAIL("",
                     "Inconsistent with QQmlComponent: 'foo' could actually be found in generated "
                     "C++ base class context",
                     Continue);
        QCOMPARE(ctx->contextProperty("foo"), QVariant());
        QCOMPARE(qvariant_cast<QObject *>(ctx->contextProperty("bar")), &created);
        // NB: even though ctx->contextProperty("foo") finds the object,
        // objectForName("foo") still returns nullptr as "foo" exists in the
        // ctx->parent()
        QCOMPARE(ctx->objectForName("foo"), nullptr);
        QCOMPARE(ctx->objectForName("bar"), &created);
        QEXPECT_FAIL("",
                     "Inconsistent with QQmlComponent: LocallyImported is a _visible_ parent of "
                     "ANON_localImport, same stays true for context",
                     Continue);
        QCOMPARE(QQmlContextData::get(ctx)->parent(), QQmlContextData::get(e.rootContext()));
        QCOMPARE(QQmlContextData::get(ctx)->parent()->parent(),
                 QQmlContextData::get(e.rootContext()));

        int count = created.getCount();
        QCOMPARE(created.getMagicValue().toInt(), (count * 3 + 1));
        count = created.getCount();
        QCOMPARE(created.localGetMagicValue().toInt(), (count * 3 + 1));
    }
}

void tst_qmltc_manual::neighbors()
{
    {
        QQmlEngine e;
        QQmlComponent comp(&e);
        comp.loadUrl(testFileUrl("neighbors.qml"));
        QScopedPointer<QObject> root(comp.create());
        QVERIFY2(root, qPrintable(comp.errorString()));

        auto rootCtx = QQmlContextData::get(e.contextForObject(root.get()));
        QQmlListReference children(root.get(), "data");
        QCOMPARE(children.size(), 2);
        auto child1Ctx = QQmlContextData::get(e.contextForObject(children.at(0)));
        auto child2Ctx = QQmlContextData::get(e.contextForObject(children.at(1)));

        QCOMPARE(rootCtx->parent(), QQmlContextData::get(e.rootContext()));
        QCOMPARE(child1Ctx, rootCtx);
        QCOMPARE(child2Ctx, rootCtx);
        QCOMPARE(child2Ctx->parent(), QQmlContextData::get(e.rootContext()));

        QQmlContext *rootQmlCtx = rootCtx->asQQmlContext();
        QCOMPARE(rootQmlCtx->objectForName("root"), root.get());
        QCOMPARE(rootQmlCtx->objectForName("child1"), children.at(0));
        QCOMPARE(rootQmlCtx->objectForName("child2"), children.at(1));
    }

    // this case is different from tst_qmltc_manual::localImport() as
    // LocallyImported is not a parent of a document root. Thus, the context
    // hierarchy:
    // * ANON_neighbors: rootContext -> neighborsContext
    // * ANON_neighbors_QtObject: rootContext -> neighborsContext
    // * LocallyImported: ... -> neighborsContext -> locallyImportedContext
    // * ANON_neighbors_LocallyImported: ... -> locallyImportedContext
    //
    // this should resemble the context hierarchy that QQmlObjectCreator
    // assembles, but here the outer context of ANON_neighbors_LocallyImported
    // remains to be the one from LocallyImported base class, which guarantees
    // that we can lookup stuff that originates from LocallyImported.
    {
        QQmlEngine e;
        LocallyImported::url = testFileUrl("LocallyImported.qml");
        ANON_neighbors::url = testFileUrl("neighbors.qml");
        ANON_neighbors_QtObject::url = testFileUrl("neighbors.qml");
        ANON_neighbors_LocallyImported::url2 = testFileUrl("neighbors.qml");

        ANON_neighbors created(&e);
        QQmlListReference children(&created, "data");
        QCOMPARE(children.size(), 2);
        ANON_neighbors_QtObject *child1 = qobject_cast<ANON_neighbors_QtObject *>(children.at(0));
        ANON_neighbors_LocallyImported *child2 =
                qobject_cast<ANON_neighbors_LocallyImported *>(children.at(1));
        QVERIFY(child1 && child2);

        auto rootCtx = QQmlContextData::get(e.contextForObject(&created));
        auto child1Ctx = QQmlContextData::get(e.contextForObject(child1));
        auto child2Ctx = QQmlContextData::get(e.contextForObject(child2));

        QCOMPARE(rootCtx->parent(), QQmlContextData::get(e.rootContext()));
        QCOMPARE(child1Ctx, rootCtx);
        QEXPECT_FAIL("",
                     "Inconsistent with QQmlComponent: non-root object with generated C++ base has "
                     "the context of that base",
                     Continue);
        QCOMPARE(child2Ctx, rootCtx);
        QEXPECT_FAIL("",
                     "Inconsistent with QQmlComponent: non-root object with generated C++ base has "
                     "the context of that base",
                     Continue);
        QCOMPARE(child2Ctx->parent(), QQmlContextData::get(e.rootContext()));
        // the rootCtx is actually a parent in this case
        QCOMPARE(child2Ctx->parent(), rootCtx);

        QQmlContext *rootQmlCtx = rootCtx->asQQmlContext();
        QCOMPARE(rootQmlCtx->objectForName("root"), &created);
        QCOMPARE(rootQmlCtx->objectForName("child1"), child1);
        QCOMPARE(rootQmlCtx->objectForName("child2"), child2);

        QCOMPARE(child1->getP(), 41);
        QCOMPARE(child1->getP2(), child2->getCount() * 2);
        QCOMPARE(child2->getP(), child1->getP() + 1);

        child1->setP(44);
        QCOMPARE(child2->getP(), 45);

        child2->setCount(4);
        QCOMPARE(child1->getP2(), 8);

        int count = child2->getCount();
        QVariant magicValue {};
        QMetaObject::invokeMethod(child2, "getMagicValue", Q_RETURN_ARG(QVariant, magicValue));
        QCOMPARE(magicValue.toInt(), (count * 3 + 1));
    }
}

// test workaround: hardcode runtime function indices. because they could be
// rather unexpected and passing wrong ones leads to UB and flakiness.
//
// NB: if you update the QML files that are used by the QQmlEngine runtime
// function execution, make sure that the hardcoded values are in sync with
// those changes! An example of when things could go wrong: adding new, removing
// old or changing the order of e.g. bindings on properties, signal handlers, JS
// functions
namespace FunctionIndices {
static constexpr int HELLO_WORLD_GREETING_BINDING = 0;

static constexpr int SIGNAL_HANDLERS_ON_SIGNAL1 = 1;
static constexpr int SIGNAL_HANDLERS_ON_SIGNAL2 = 3;
static constexpr int SIGNAL_HANDLERS_QML_EMIT_SIGNAL1 = 4;
static constexpr int SIGNAL_HANDLERS_QML_EMIT_SIGNAL2 = 5;
static constexpr int SIGNAL_HANDLERS_QML_EMIT_SIGNAL2_WITH_ARGS = 6;

static constexpr int JS_FUNCTIONS_FUNC1 = 0;
static constexpr int JS_FUNCTIONS_FUNC2 = 1;
static constexpr int JS_FUNCTIONS_FUNC3 = 2;

static constexpr int CHANGING_BINDINGS_P2_BINDING = 0;
static constexpr int CHANGING_BINDINGS_RESET_TO_CONSTANT = 1;
static constexpr int CHANGING_BINDINGS_RESET_TO_NEW_BINDING = 2;

static constexpr int PROPERTY_ALIAS_ORIGIN_BINDING = 0;
static constexpr int PROPERTY_ALIAS_RESET_ALIAS_TO_CONSTANT = 1;
static constexpr int PROPERTY_ALIAS_RESET_ORIGIN_TO_CONSTANT = 2;
static constexpr int PROPERTY_ALIAS_RESET_ALIAS_TO_NEW_BINDING = 3;
static constexpr int PROPERTY_ALIAS_RESET_ORIGIN_TO_NEW_BINDING = 5;
static constexpr int PROPERTY_ALIAS_GET_ALIAS_VALUE = 7;

static constexpr int PROPERTY_CHANGE_HANDLER_P_BINDING = 0;
static constexpr int PROPERTY_CHANGE_HANDLER_ON_P_CHANGED = 1;

static constexpr int PROPERTY_RETURNING_FUNCTION_F_BINDING = 0;

static constexpr int LOCALLY_IMPORTED_GET_MAGIC_VALUE = 0;
static constexpr int LOCALLY_IMPORTED_ON_COMPLETED = 1;

static constexpr int LOCAL_IMPORT_COUNT_BINDING = 0;
static constexpr int LOCAL_IMPORT_LOCAL_GET_MAGIC_VALUE = 1;

static constexpr int NEIGHBOUR_IDS_CHILD1_P2_BINDING = 0;
static constexpr int NEIGHBOUR_IDS_CHILD2_P_BINDING = 1;
};

// test utility function for type erasure. the "real" code would be
// auto-generated by the compiler
template<typename... IOArgs, size_t Size = sizeof...(IOArgs) + 1>
static void typeEraseArguments(std::array<void *, Size> &a, std::array<QMetaType, Size> &t,
                               std::nullptr_t, IOArgs &&... args)
{
    a = { /* return value */ nullptr, /* rest */
          const_cast<void *>(
                  reinterpret_cast<const void *>(std::addressof(std::forward<IOArgs>(args))))... };
    t = { /* return type */ QMetaType::fromType<void>(),
          /* types */ QMetaType::fromType<std::decay_t<IOArgs>>()... };
}

template<typename... IOArgs, size_t Size = sizeof...(IOArgs)>
static void typeEraseArguments(std::array<void *, Size> &a, std::array<QMetaType, Size> &t,
                               IOArgs &&... args)
{
    a = { /* all values, including return value */ const_cast<void *>(
            reinterpret_cast<const void *>(std::addressof(std::forward<IOArgs>(args))))... };
    t = { /* types */ QMetaType::fromType<std::decay_t<IOArgs>>()... };
}

ContextRegistrator::ContextRegistrator(QQmlEngine *engine, QObject *This)
{
    Q_ASSERT(engine && This);
    if (engine->contextForObject(This)) // already set
        return;

    // use simple form of the logic done in create() and set(). this code
    // shouldn't actually be used in real generated classes. it just exists
    // here for convenience
    Q_ASSERT(!This->parent() || engine->contextForObject(This->parent()) || engine->rootContext());
    QQmlContext *parentContext = engine->contextForObject(This->parent());
    QQmlContext *context =
            parentContext ? parentContext : new QQmlContext(engine->rootContext(), This);
    Q_ASSERT(context);
    // NB: not only sets the context, but also seeds engine into This, so
    // that qmlEngine(This) works
    engine->setContextForObject(This, context);
    Q_ASSERT(qmlEngine(This));
}

HelloWorld::HelloWorld(QQmlEngine *e, QObject *parent)
    : QObject(parent), ContextRegistrator(e, this)
{
    hello = QStringLiteral("Hello, World");
    QPropertyBinding<QString> HelloWorldCpp_greeting_binding(
            [&]() {
                QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this));
                const auto index = FunctionIndices::HELLO_WORLD_GREETING_BINDING;
                constexpr int argc = 0;
                QString ret {};
                std::array<void *, argc + 1> a {};
                std::array<QMetaType, argc + 1> t {};
                typeEraseArguments(a, t, ret);
                e->executeRuntimeFunction(url, index, this, argc, a.data(), t.data());
                return ret;
            },
            QT_PROPERTY_DEFAULT_BINDING_LOCATION);
    bindableGreeting().setBinding(HelloWorldCpp_greeting_binding);
}

QUrl HelloWorld::url = QUrl(); // workaround

ANON_signalHandlers::ANON_signalHandlers(QQmlEngine *e, QObject *parent)
    : QObject(parent), ContextRegistrator(e, this)
{
    signal1P = 0;
    signal2P1 = QStringLiteral("");
    signal2P2 = 0;
    signal2P3 = QStringLiteral("");

    QObject::connect(this, &ANON_signalHandlers::signal1, this, &ANON_signalHandlers::onSignal1);
    QObject::connect(this, &ANON_signalHandlers::signal2, this, &ANON_signalHandlers::onSignal2);
}

void ANON_signalHandlers::onSignal1()
{
    QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this));
    const auto index = FunctionIndices::SIGNAL_HANDLERS_ON_SIGNAL1;
    e->executeRuntimeFunction(url, index, this);
}

void ANON_signalHandlers::onSignal2(QString x, int y)
{
    constexpr int argc = 2;
    std::array<void *, argc + 1> a {};
    std::array<QMetaType, argc + 1> t {};
    typeEraseArguments(a, t, nullptr, x, y);

    QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this));
    const qsizetype index = FunctionIndices::SIGNAL_HANDLERS_ON_SIGNAL2;
    e->executeRuntimeFunction(url, index, this, argc, a.data(), t.data());
}

void ANON_signalHandlers::qmlEmitSignal1()
{
    QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this));
    const auto index = FunctionIndices::SIGNAL_HANDLERS_QML_EMIT_SIGNAL1;
    e->executeRuntimeFunction(url, index, this);
}

void ANON_signalHandlers::qmlEmitSignal2()
{
    QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this));
    const auto index = FunctionIndices::SIGNAL_HANDLERS_QML_EMIT_SIGNAL2;
    e->executeRuntimeFunction(url, index, this);
}

void ANON_signalHandlers::qmlEmitSignal2WithArgs(QString x, int y)
{
    constexpr int argc = 2;
    std::array<void *, argc + 1> a {};
    std::array<QMetaType, argc + 1> t {};
    typeEraseArguments(a, t, nullptr, x, y);

    QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this));
    const auto index = FunctionIndices::SIGNAL_HANDLERS_QML_EMIT_SIGNAL2_WITH_ARGS;
    e->executeRuntimeFunction(url, index, this, argc, a.data(), t.data());
}

QUrl ANON_signalHandlers::url = QUrl(); // workaround

ANON_javaScriptFunctions::ANON_javaScriptFunctions(QQmlEngine *e, QObject *parent)
    : QObject(parent), ContextRegistrator(e, this)
{
    func1P = 0;
    func2P = QStringLiteral("");
    func3P = false;
}

void ANON_javaScriptFunctions::func1()
{
    QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this));
    const auto index = FunctionIndices::JS_FUNCTIONS_FUNC1;
    e->executeRuntimeFunction(url, index, this);
}

void ANON_javaScriptFunctions::func2(QString x)
{
    constexpr int argc = 1;
    std::array<void *, argc + 1> a {};
    std::array<QMetaType, argc + 1> t {};
    typeEraseArguments(a, t, nullptr, x);

    QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this));
    const auto index = FunctionIndices::JS_FUNCTIONS_FUNC2;
    e->executeRuntimeFunction(url, index, this, argc, a.data(), t.data());
}

bool ANON_javaScriptFunctions::func3()
{
    QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this));
    const auto index = FunctionIndices::JS_FUNCTIONS_FUNC3;
    constexpr int argc = 0;
    bool ret {};
    std::array<void *, argc + 1> a {};
    std::array<QMetaType, argc + 1> t {};
    typeEraseArguments(a, t, ret);
    e->executeRuntimeFunction(url, index, this, argc, a.data(), t.data());
    return ret;
}

QUrl ANON_javaScriptFunctions::url = QUrl(); // workaround

void ANON_changingBindings::resetToInitialBinding()
{
    QPropertyBinding<int> ANON_changingBindings_p2_binding(
            [&]() {
                initialBindingCallCount++;

                QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this));
                const auto index = FunctionIndices::CHANGING_BINDINGS_P2_BINDING;
                constexpr int argc = 0;
                int ret {};
                std::array<void *, argc + 1> a {};
                std::array<QMetaType, argc + 1> t {};
                typeEraseArguments(a, t, ret);
                e->executeRuntimeFunction(url, index, this, argc, a.data(), t.data());
                return ret;
            },
            QT_PROPERTY_DEFAULT_BINDING_LOCATION);
    bindableP2().setBinding(ANON_changingBindings_p2_binding);
}

ANON_changingBindings::ANON_changingBindings(QQmlEngine *e, QObject *parent)
    : QObject(parent), ContextRegistrator(e, this)
{
    p1 = 1;
    resetToInitialBinding();
}

void ANON_changingBindings::resetToConstant()
{
    QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this));
    const auto index = FunctionIndices::CHANGING_BINDINGS_RESET_TO_CONSTANT;
    e->executeRuntimeFunction(url, index, this);
}

void ANON_changingBindings::resetToNewBinding()
{
    QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this));
    const auto index = FunctionIndices::CHANGING_BINDINGS_RESET_TO_NEW_BINDING;
    e->executeRuntimeFunction(url, index, this);
}

QUrl ANON_changingBindings::url = QUrl(); // workaround

void ANON_propertyAlias::resetToInitialBinding()
{
    QPropertyBinding<int> ANON_propertyAlias_origin_binding(
            [&]() {
                QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this));
                const auto index = FunctionIndices::PROPERTY_ALIAS_ORIGIN_BINDING;
                constexpr int argc = 0;
                int ret {};
                std::array<void *, argc + 1> a {};
                std::array<QMetaType, argc + 1> t {};
                typeEraseArguments(a, t, ret);
                e->executeRuntimeFunction(url, index, this, argc, a.data(), t.data());
                return ret;
            },
            QT_PROPERTY_DEFAULT_BINDING_LOCATION);
    bindableOrigin().setBinding(ANON_propertyAlias_origin_binding);
}

ANON_propertyAlias::ANON_propertyAlias(QQmlEngine *e, QObject *parent)
    : QObject(parent), ContextRegistrator(e, this)
{
    dummy = 12;
    resetToInitialBinding();
}

void ANON_propertyAlias::resetAliasToConstant()
{
    QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this));
    const auto index = FunctionIndices::PROPERTY_ALIAS_RESET_ALIAS_TO_CONSTANT;
    e->executeRuntimeFunction(url, index, this);
}
void ANON_propertyAlias::resetOriginToConstant()
{
    QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this));
    const auto index = FunctionIndices::PROPERTY_ALIAS_RESET_ORIGIN_TO_CONSTANT;
    e->executeRuntimeFunction(url, index, this);
}
void ANON_propertyAlias::resetAliasToNewBinding()
{
    QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this));
    const auto index = FunctionIndices::PROPERTY_ALIAS_RESET_ALIAS_TO_NEW_BINDING;
    e->executeRuntimeFunction(url, index, this);
}
void ANON_propertyAlias::resetOriginToNewBinding()
{
    QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this));
    const auto index = FunctionIndices::PROPERTY_ALIAS_RESET_ORIGIN_TO_NEW_BINDING;
    e->executeRuntimeFunction(url, index, this);
}

int ANON_propertyAlias::getAliasValue()
{
    QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this));
    const auto index = FunctionIndices::PROPERTY_ALIAS_GET_ALIAS_VALUE;
    constexpr int argc = 0;
    int ret {};
    std::array<void *, argc + 1> a {};
    std::array<QMetaType, argc + 1> t {};
    typeEraseArguments(a, t, ret);
    e->executeRuntimeFunction(url, index, this, argc, a.data(), t.data());
    return ret;
}

QUrl ANON_propertyAlias::url = QUrl(); // workaround

ANON_propertyChangeHandler::ANON_propertyChangeHandler(QQmlEngine *e, QObject *parent)
    : QObject(parent), ContextRegistrator(e, this)
{
    dummy = 42;
    QPropertyBinding<int> ANON_propertyChangeHandler_p_binding(
            [&]() {
                QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this));
                const auto index = FunctionIndices::PROPERTY_CHANGE_HANDLER_P_BINDING;
                constexpr int argc = 0;
                int ret {};
                std::array<void *, argc + 1> a {};
                std::array<QMetaType, argc + 1> t {};
                typeEraseArguments(a, t, ret);
                e->executeRuntimeFunction(url, index, this, argc, a.data(), t.data());
                return ret;
            },
            QT_PROPERTY_DEFAULT_BINDING_LOCATION);
    bindableP().setBinding(ANON_propertyChangeHandler_p_binding);
    watcher = 0;

    // NB: make sure property change handler appears after setBinding().
    // this prevents preliminary binding evaluation (which would fail as
    // this object doesn't yet know about qmlEngine(this))
    pChangeHandler.reset(new QPropertyChangeHandler<ANON_propertyChangeHandler_p_changeHandler>(
            bindableP().onValueChanged(ANON_propertyChangeHandler_p_changeHandler(this))));
}

ANON_propertyChangeHandler::ANON_propertyChangeHandler_p_changeHandler::
        ANON_propertyChangeHandler_p_changeHandler(ANON_propertyChangeHandler *obj)
    : This(obj)
{
}

void ANON_propertyChangeHandler::ANON_propertyChangeHandler_p_changeHandler::operator()()
{
    QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(This));
    const auto index = FunctionIndices::PROPERTY_CHANGE_HANDLER_ON_P_CHANGED;
    e->executeRuntimeFunction(This->url, index, This);
}

QUrl ANON_propertyChangeHandler::url = QUrl(); // workaround

ANON_propertyReturningFunction::ANON_propertyReturningFunction(QQmlEngine *e, QObject *parent)
    : QObject(parent), ContextRegistrator(e, this)
{
    QPropertyBinding<QVariant> ANON_propertyReturningFunction_f_binding(
            [&]() {
                QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this));
                const auto index = FunctionIndices::PROPERTY_RETURNING_FUNCTION_F_BINDING;
                constexpr int argc = 0;
                QVariant ret {};
                std::array<void *, argc + 1> a {};
                std::array<QMetaType, argc + 1> t {};
                typeEraseArguments(a, t, ret);
                e->executeRuntimeFunction(url, index, this, argc, a.data(), t.data());
                return ret;
            },
            QT_PROPERTY_DEFAULT_BINDING_LOCATION);
    bindableF().setBinding(ANON_propertyReturningFunction_f_binding);
}

QUrl ANON_propertyReturningFunction::url = QUrl(); // workaround

LocallyImported::LocallyImported(QObject *parent) : QObject(parent) { }

LocallyImported::LocallyImported(QQmlEngine *e, QObject *parent) : LocallyImported(parent)
{
    init(e, QQmlContextData::get(e->rootContext()));
}

QQmlRefPointer<QQmlContextData>
LocallyImported::init(QQmlEngine *e, const QQmlRefPointer<QQmlContextData> &parentContext)
{
    // NB: this object is the root object of LocallyImported.qml
    constexpr int subComponentIndex = 0;
    auto context = parentContext;
    context = ContextRegistrator::create(e, url, context, subComponentIndex);
    ContextRegistrator::set(this, context, QQmlContextData::DocumentRoot);
    context->setIdValue(0, this);
    count = 0;
    finalize(e); // call here because it's document root
    return context;
}

Q_INVOKABLE QVariant LocallyImported::getMagicValue()
{
    QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this));
    const auto index = FunctionIndices::LOCALLY_IMPORTED_GET_MAGIC_VALUE;
    constexpr int argc = 0;
    QVariant ret {};
    std::array<void *, argc + 1> a {};
    std::array<QMetaType, argc + 1> t {};
    typeEraseArguments(a, t, ret);
    e->executeRuntimeFunction(url, index, this, argc, a.data(), t.data());
    return ret;
}

void LocallyImported::completedSlot()
{
    QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this));
    const auto index = FunctionIndices::LOCALLY_IMPORTED_ON_COMPLETED;
    constexpr int argc = 0;
    std::array<void *, argc + 1> a {};
    std::array<QMetaType, argc + 1> t {};
    typeEraseArguments(a, t, nullptr);
    e->executeRuntimeFunction(url, index, this, argc, a.data(), t.data());
}

void LocallyImported::finalize(QQmlEngine *e)
{
    Q_UNUSED(e);
    // 1. finalize children - no children here, so do nothing

    // 2. finalize self
    completedSlot();
}

QUrl LocallyImported::url = QUrl(); // workaround

ANON_localImport::ANON_localImport(QObject *parent) : LocallyImported(parent) { }

ANON_localImport::ANON_localImport(QQmlEngine *e, QObject *parent) : ANON_localImport(parent)
{
    // NB: always use e->rootContext() as parent context in the public ctor
    init(e, QQmlContextData::get(e->rootContext()));
}

QQmlRefPointer<QQmlContextData>
ANON_localImport::init(QQmlEngine *e, const QQmlRefPointer<QQmlContextData> &parentContext)
{
    // init function is a multi-step procedure:
    //
    // 0. [optional] call base class' init() method (when base class is also
    //    generated from QML), passing parentContext and getting back
    //    another context
    // 1. create child QQmlContextData from the context
    // 2. * EITHER: patch the context to be the parent context - when this
    //      type is not root and it has generated C++ base class
    //    * OR: set the context for this object
    // 3. [optional] set id by QmlIR::Object::id
    // 4. do the simple initialization bits (e.g. set property values, etc.)
    //    - might actually slip to finalize()?
    // 5. [document root only] call finalize() which finalizes all types in
    //    this document - after context and instances are set up correctly

    constexpr int componentIndex = 0; // root index
    auto context = parentContext;
    // 0.
    context = LocallyImported::init(e, context);
    // 1.
    context = ContextRegistrator::create(e, url, context, componentIndex);
    // 2.
    // if not root and parent is also generated C++ object, patch context
    // context = parentContext;
    // else:
    ContextRegistrator::set(this, context, QQmlContextData::DocumentRoot);
    // 3.
    context->setIdValue(0, this);
    // 4.
    p1 = 41;
    // 5.
    finalize(e); // call here because it's document root
    return context;
}

void ANON_localImport::finalize(QQmlEngine *e)
{
    Q_UNUSED(e);
    // 1. finalize children - no children here, so do nothing

    // 2. finalize self
    QPropertyBinding<int> ANON_localImport_count_binding(
            [&]() {
                QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this));
                const auto index = FunctionIndices::LOCAL_IMPORT_COUNT_BINDING;
                constexpr int argc = 0;
                int ret {};
                std::array<void *, argc + 1> a {};
                std::array<QMetaType, argc + 1> t {};
                typeEraseArguments(a, t, ret);
                e->executeRuntimeFunction(url, index, this, argc, a.data(), t.data());
                return ret;
            },
            QT_PROPERTY_DEFAULT_BINDING_LOCATION);
    bindableCount().setBinding(ANON_localImport_count_binding);
}

Q_INVOKABLE QVariant ANON_localImport::localGetMagicValue()
{
    QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this));
    const auto index = FunctionIndices::LOCAL_IMPORT_LOCAL_GET_MAGIC_VALUE;
    constexpr int argc = 0;
    QVariant ret {};
    std::array<void *, argc + 1> a {};
    std::array<QMetaType, argc + 1> t {};
    typeEraseArguments(a, t, ret);
    e->executeRuntimeFunction(url, index, this, argc, a.data(), t.data());
    return ret;
}

QUrl ANON_localImport::url = QUrl(); // workaround

ANON_neighbors_QtObject::ANON_neighbors_QtObject(QObject *parent) : QObject(parent) { }

ANON_neighbors_QtObject::ANON_neighbors_QtObject(QQmlEngine *e, QObject *parent)
    : ANON_neighbors_QtObject(parent)
{
    // NB: non-root of the document
    init(e, QQmlContextData::get(e->contextForObject(parent)));
}

QQmlRefPointer<QQmlContextData>
ANON_neighbors_QtObject::init(QQmlEngine *e, const QQmlRefPointer<QQmlContextData> &parentContext)
{
    constexpr int componentIndex = 1;
    auto context = ContextRegistrator::create(e, url, parentContext, componentIndex);
    ContextRegistrator::set(this, context, QQmlContextData::OrdinaryObject);
    context->setIdValue(componentIndex, this);

    p = 41;
    return context;
}

void ANON_neighbors_QtObject::finalize(QQmlEngine *e) // called by the document root
{
    Q_UNUSED(e);
    // 1. finalize children - empty as we don't have children here

    // 2. finalize self - call all "dynamic" code - e.g. script bindings,
    //    Component.onCompleted and so on - everything that may reference
    //    some random part of the document and thus needs to be delayed
    //    until all objects in the document are initialized
    QPropertyBinding<int> ANON_neighbors_QtObject_p2_binding(
            [&]() {
                QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this));
                const auto index = FunctionIndices::NEIGHBOUR_IDS_CHILD1_P2_BINDING;
                constexpr int argc = 0;
                int ret {};
                std::array<void *, argc + 1> a {};
                std::array<QMetaType, argc + 1> t {};
                typeEraseArguments(a, t, ret);
                e->executeRuntimeFunction(url, index, this, argc, a.data(), t.data());
                return ret;
            },
            QT_PROPERTY_DEFAULT_BINDING_LOCATION);
    bindableP2().setBinding(ANON_neighbors_QtObject_p2_binding);
}

QUrl ANON_neighbors_QtObject::url = QUrl(); // workaround

ANON_neighbors_LocallyImported::ANON_neighbors_LocallyImported(QObject *parent)
    : LocallyImported(parent)
{
}

ANON_neighbors_LocallyImported::ANON_neighbors_LocallyImported(QQmlEngine *e, QObject *parent)
    : ANON_neighbors_LocallyImported(parent)
{
    // NB: non-root of the document
    init(e, QQmlContextData::get(e->contextForObject(parent)));
}

QQmlRefPointer<QQmlContextData>
ANON_neighbors_LocallyImported::init(QQmlEngine *e,
                                     const QQmlRefPointer<QQmlContextData> &parentContext)
{
    constexpr int componentIndex = 2;
    auto context = LocallyImported::init(e, parentContext);
    context = ContextRegistrator::create(e, url2, context, componentIndex);
    // if not root and parent is also generated C++ object, patch context
    context = parentContext;
    // else: ContextRegistrator::set(this, context, QQmlContextData::OrdinaryObject);
    context->setIdValue(componentIndex, this);
    return context;
}

void ANON_neighbors_LocallyImported::finalize(QQmlEngine *e) // called by the document root
{
    Q_UNUSED(e);
    // 1. finalize children - empty as we don't have children here

    // 2. finalize self - call all "dynamic" code - e.g. script bindings,
    //    Component.onCompleted and so on - everything that may reference
    //    some random part of the document and thus needs to be delayed
    //    until all objects in the document are initialized
    QPropertyBinding<int> ANON_neighbors_LocallyImported_p_binding(
            [&]() {
                QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this));
                const auto index = FunctionIndices::NEIGHBOUR_IDS_CHILD2_P_BINDING;
                constexpr int argc = 0;
                int ret {};
                std::array<void *, argc + 1> a {};
                std::array<QMetaType, argc + 1> t {};
                typeEraseArguments(a, t, ret);
                e->executeRuntimeFunction(url2, index, this, argc, a.data(), t.data());
                return ret;
            },
            QT_PROPERTY_DEFAULT_BINDING_LOCATION);
    bindableP().setBinding(ANON_neighbors_LocallyImported_p_binding);
}
QUrl ANON_neighbors_LocallyImported::url2 = QUrl(); // workaround

// NB: only root subclasses the helper - as it initiates the finalization
ANON_neighbors::ANON_neighbors(QObject *parent) : QQuickItem()
{
    setParent(parent);
}

ANON_neighbors::ANON_neighbors(QQmlEngine *e, QObject *parent) : ANON_neighbors(parent)
{
    // NB: use e->rootContext() as this object is document root
    init(e, QQmlContextData::get(e->rootContext()));
}

QQmlRefPointer<QQmlContextData>
ANON_neighbors::init(QQmlEngine *e, const QQmlRefPointer<QQmlContextData> &parentContext)
{
    constexpr int componentIndex = 0; // root index
    auto context = ContextRegistrator::create(e, url, parentContext, componentIndex);
    ContextRegistrator::set(this, context, QQmlContextData::DocumentRoot);
    context->setIdValue(componentIndex, this);

    finalize(e); // call here because it's document root
    return context;
}

void ANON_neighbors::finalize(QQmlEngine *e)
{
    Q_UNUSED(e);
    // 0. set up object bindings and record all new objects for further
    //    finalization
    QList<QObject *> objectsToFinalize;
    objectsToFinalize.reserve(2); // we know it's 2 at compile time
    QQmlListReference listrefData(this, "data");
    {
        auto o = new ANON_neighbors_QtObject(e, this);
        listrefData.append(o);
        objectsToFinalize.append(o);
    }
    {
        auto o = new ANON_neighbors_LocallyImported(e, this);
        listrefData.append(o);
        objectsToFinalize.append(o);
    }

    // 1. finalize children
    // use static_cast instead of polymorphism as we know the types at
    // compile-time
    static_cast<ANON_neighbors_QtObject *>(objectsToFinalize.at(0))->finalize(e);
    static_cast<ANON_neighbors_LocallyImported *>(objectsToFinalize.at(1))->finalize(e);

    // 2. finalize self - empty as we don't have any bindings here
}

QUrl ANON_neighbors::url = QUrl(); // workaround

QTEST_MAIN(tst_qmltc_manual)

#include "tst_qmltc_manual.moc"
