/*
 * Copyright 2016 Canonical Ltd.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; version 3.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Author: Zsombor Egri <zsombor.egri@canonical.com>
 */

#include "lomiritoolkitmodule.h"

#include <sys/types.h>
#include <unistd.h>

#include <stdexcept>
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
#include <qqml.h>
#endif

#include <QtCore/QPointer>
#include <QtCore/QTimer>
#include <QtQml/QQmlContext>
#include <QtQml/QQmlEngine>
#include <QtQml/QQmlExtensionPlugin>
#include <QtQuick/private/qquickimagebase_p.h>
#include <QtDBus/QDBusConnection>
#include <QtGui/QGuiApplication>
#include <QtGui/QScreen>
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#include <LomiriMetrics-Qt6/applicationmonitor.h>
#else
#include <LomiriMetrics/applicationmonitor.h>
#endif

#include "actionlist_p.h"
#include "colorutils_p.h"
#include "exclusivegroup_p.h"
#include "i18n_p.h"
#include "inversemouseareatype_p.h"
#include "listener_p.h"
#include "livetimer_p.h"
#include "ucmainwindow_p.h"
#include "menu_p.h"
#include "menubar_p.h"
#include "menugroup_p.h"
#include "privates/appheaderbase_p.h"
#include "privates/frame_p.h"
#include "privates/ucpagewrapper_p.h"
#include "privates/ucscrollbarutils_p.h"
#include "qquickclipboard_p.h"
#include "qquickmimedata_p.h"
#include "quickutils_p.h"
#include "sortbehavior_p.h"
#include "sortfiltermodel_p.h"
#include "splitview_p.h"
#include "tree_p.h"
#include "ucabstractbutton_p.h"
#include "ucaction_p.h"
#include "ucactioncontext_p.h"
#include "ucactionitem_p.h"
#include "ucactionmanager_p.h"
// FIXME: depends on QtPim
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#include "ucalarm_p.h"
#include "ucalarmmodel_p.h"
#endif
#include "ucapplication_p.h"
#include "ucargument_p.h"
#include "ucarguments_p.h"
#include "ucbottomedge_p.h"
#include "ucbottomedgehint_p.h"
#include "ucbottomedgeregion_p.h"
#include "ucbottomedgestyle_p.h"
#include "ucdeprecatedtheme_p.h"
#include "ucfontutils_p.h"
#include "ucfontutils_p.h"
#include "uchaptics_p.h"
#include "ucheader_p.h"
#include "ucinversemouse_p.h"
#include "uclabel_p.h"
#include "uclistitem_p.h"
#include "uclistitemactions_p.h"
#include "uclistitemlayout_p.h"
#include "ucmainviewbase_p.h"
#include "ucmargins_p.h"
#include "ucmathutils_p.h"
#include "ucmouse_p.h"
#include "ucnamespace_p.h"
#include "ucpagetreenode_p.h"
#include "ucperformancemonitor_p.h"
#include "ucproportionalshape_p.h"
#include "ucqquickimageextension_p.h"
#include "ucscalingimageprovider_p.h"
#include "ucserviceproperties_p.h"
#include "ucslotslayout_p.h"
#include "ucstatesaver_p.h"
#include "ucstyleditembase_p.h"
#include "ucstylehints_p.h"
#include "uctheme_p.h"
#include "uctheme_p.h"
#include "uclomirianimation_p.h"
#include "uclomirishape_p.h"
#include "uclomirishapeoverlay_p.h"
#include "ucunits_p.h"
#include "ucurihandler_p.h"
#include "lomirithemeiconprovider_p.h"

#ifdef LOMIRI_UITK_WANT_UBUNTU_COMPAT
#   include "deprecationprinter_p.h"
#endif

UT_NAMESPACE_BEGIN

static const QString notInstantiatable = QStringLiteral("Not instantiatable");
static const char engineProperty[] = "__lomiri_toolkit_plugin_data";

/******************************************************************************
 * LomiriToolkitModule
 */
LomiriToolkitModule* LomiriToolkitModule::create(QQmlEngine *engine, const QUrl &baseUrl)
{
    if (!engine) {
        return Q_NULLPTR;
    }
    LomiriToolkitModule *data = new LomiriToolkitModule(engine);
    data->m_baseUrl = QUrl(baseUrl.toString() + QStringLiteral("/"));
    engine->setProperty(engineProperty, QVariant::fromValue(data));
    return data;
}

void LomiriToolkitModule::registerWindowContextProperty()
{
    setWindowContextProperty(QGuiApplication::focusWindow());

    // listen to QGuiApplication::focusWindowChanged
    /* Ensure that setWindowContextProperty is called in the same thread (the
       main thread) otherwise it segfaults. Reference:
       https://bugs.launchpad.net/lomiri-ui-toolkit/+bug/1205556
    */
    QGuiApplication* application = static_cast<QGuiApplication*>(QCoreApplication::instance());
    QObject::connect(application, SIGNAL(focusWindowChanged(QWindow*)),
                     this, SLOT(setWindowContextProperty(QWindow*)),
                     Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection));

}

void LomiriToolkitModule::setWindowContextProperty(QWindow* focusWindow)
{
    QQuickView* view = qobject_cast<QQuickView*>(focusWindow);

    if (view != NULL) {
        view->rootContext()->setContextProperty(QStringLiteral("window"), view);
    }
}

void LomiriToolkitModule::initializeContextProperties(QQmlEngine *engine)
{
    UCUnits::instance(engine);
    QuickUtils::instance(engine);
    LomiriI18n::instance(engine);
    UCApplication::instance(engine);
    UCFontUtils::instance(engine);
    UCTheme::defaultTheme(engine);

    QQmlContext* context = engine->rootContext();

    // register root object watcher that sets a global property with the root object
    // that can be accessed from any object
    context->setContextProperty(QStringLiteral("QuickUtils"), QuickUtils::instance());

    UCDeprecatedTheme::registerToContext(context);

    context->setContextProperty(QStringLiteral("i18n"), LomiriI18n::instance());
    ContextPropertyChangeListener *i18nChangeListener =
        new ContextPropertyChangeListener(context, QStringLiteral("i18n"));
    QObject::connect(LomiriI18n::instance(), SIGNAL(domainChanged()),
                     i18nChangeListener, SLOT(updateContextProperty()));
    QObject::connect(LomiriI18n::instance(), SIGNAL(languageChanged()),
                     i18nChangeListener, SLOT(updateContextProperty()));

    // We can't use 'Application' because it exists (undocumented)
    context->setContextProperty(QStringLiteral("LomiriApplication"), UCApplication::instance());
    ContextPropertyChangeListener *applicationChangeListener =
        new ContextPropertyChangeListener(context, QStringLiteral("LomiriApplication"));
    QObject::connect(UCApplication::instance(), SIGNAL(applicationNameChanged()),
                     applicationChangeListener, SLOT(updateContextProperty()));

    #ifdef LOMIRI_UITK_WANT_UBUNTU_COMPAT
    /* Note: This provides UbuntuApplication whether we're loaded as Ubuntu.Components
     * or Lomiri.Components. I don't think it matters much though. */
    context->setContextProperty(QStringLiteral("UbuntuApplication"), UCApplication::instance());
    ContextPropertyChangeListener *applicationChangeListener1 =
        new ContextPropertyChangeListener(context, QStringLiteral("UbuntuApplication"));
    QObject::connect(UCApplication::instance(), SIGNAL(applicationNameChanged()),
                     applicationChangeListener1, SLOT(updateContextProperty()));
    #endif

    // Give the application object access to the engine
    UCApplication::instance()->setContext(context);

    context->setContextProperty(QStringLiteral("units"), UCUnits::instance());
    ContextPropertyChangeListener *unitsChangeListener =
        new ContextPropertyChangeListener(context, QStringLiteral("units"));
    QObject::connect(UCUnits::instance(), SIGNAL(gridUnitChanged()),
                     unitsChangeListener, SLOT(updateContextProperty()));

    // register FontUtils
    context->setContextProperty(QStringLiteral("FontUtils"), UCFontUtils::instance());
    ContextPropertyChangeListener *fontUtilsListener =
        new ContextPropertyChangeListener(context, QStringLiteral("FontUtils"));
    QObject::connect(UCUnits::instance(), SIGNAL(gridUnitChanged()),
                     fontUtilsListener, SLOT(updateContextProperty()));

    // Make the context property 'window' available even before there is a window,
    // so that in QML we do not have to check whether 'window' is defined, and no new
    // context property will be added after all components are completed (bug #1621509).
    context->setContextProperty(QStringLiteral("window"), Q_NULLPTR);
}

void LomiriToolkitModule::registerTypesToVersion(const char *uri, int major, int minor)
{
    qmlRegisterType<UCAction>(uri, major, minor, "Action");
    qmlRegisterType<UCActionContext>(uri, major, minor, "ActionContext");
    qmlRegisterUncreatableType<UCApplication>(
        uri, major, minor, "UCApplication", notInstantiatable);
    qmlRegisterType<UCActionManager>(uri, major, minor, "ActionManager");
    qmlRegisterUncreatableType<UCFontUtils>(uri, major, minor, "UCFontUtils", notInstantiatable);
    qmlRegisterType<UCStyledItemBase>(uri, major, minor, "StyledItem");
    qmlRegisterUncreatableType<LomiriI18n>(
       uri, major, minor, "I18n", QStringLiteral("Singleton object"));
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    /*
     * Registering the UCQQuickImageExtension can silently fail if QtQuick is not yet
     * loaded when this is running. Not sure how to fix this properly. Adding a `depends`
     * to the qmldir doesn't work, using the qml engine to load QtQuick here is not
     * allowed and if we defer the registration it might be too late. The workaround
     * on the user side is to import QtQuick before Lomiri.Components, so we can at
     * least print a warning here if we detect that QtQuick is missing.
     */
    qmlRegisterExtendedType<
        QQuickImageBase, UCQQuickImageExtension>(uri, major, minor, "QQuickImageBase");
    if (qmlTypeId("QtQuick", 2, 0, "Image") == -1) {
        qWarning() << "Lomiri.Components: Resolving images with @gu suffix may not work. "
                      "As a workaround, make sure to import QtQuick before Lomiri.Components.";
    }
#endif
    qmlRegisterUncreatableType<UCUnits>(uri, major, minor, "UCUnits", notInstantiatable);
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
    if (uri == QLatin1String("Ubuntu.Components")) {
#else
    if (QLatin1String(uri) == QLatin1String("Ubuntu.Components")) {
#endif
        // FIXME/DEPRECATED: Ubuntu.* is exported for backwards compatibility only
        qmlRegisterType<UCLomiriShape>(uri, major, minor, "UbuntuShape");
    } else {
        qmlRegisterType<UCLomiriShape>(uri, major, minor, "LomiriShape");
    }
    // FIXME/DEPRECATED: Shape is exported for backwards compatibility only
    qmlRegisterType<UCLomiriShape>(uri, major, minor, "Shape");
    qmlRegisterType<InverseMouseAreaType>(uri, major, minor, "InverseMouseArea");
    qmlRegisterType<QQuickMimeData>(uri, major, minor, "MimeData");
    qmlRegisterSimpleSingletonType<QQuickClipboard>(uri, major, minor, "Clipboard");
    if (QLatin1String(uri) == QLatin1String("Ubuntu.Components")) {
        // FIXME/DEPRECATED: Ubuntu.* is exported for backwards compatibility only
        qmlRegisterSimpleSingletonType<UCLomiriAnimation>(uri, major, minor, "UbuntuAnimation");
    } else {
        qmlRegisterSimpleSingletonType<UCLomiriAnimation>(uri, major, minor, "LomiriAnimation");
    }
    qmlRegisterType<UCArguments>(uri, major, minor, "Arguments");
    qmlRegisterType<UCArgument>(uri, major, minor, "Argument");
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
    qmlRegisterType<QQmlPropertyMap>();
#else
    qmlRegisterAnonymousType<QQmlPropertyMap>(uri, major);
#endif
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    qmlRegisterType<UCAlarm>(uri, major, minor, "Alarm");
    qmlRegisterType<UCAlarmModel>(uri, major, minor, "AlarmModel");
#endif
    qmlRegisterType<UCStateSaver>(uri, major, minor, "StateSaver");
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
    qmlRegisterType<UCStateSaverAttached>();
#else
    qmlRegisterAnonymousType<UCStateSaverAttached>(uri, major);
#endif
    qmlRegisterSimpleSingletonType<UCUriHandler>(uri, major, minor, "UriHandler");
    qmlRegisterType<UCMouse>(uri, major, minor, "Mouse");
    qmlRegisterType<UCInverseMouse>(uri, major, minor, "InverseMouse");
    qmlRegisterType<UCActionItem>(uri, major, minor, "ActionItem");
    qmlRegisterSimpleSingletonType<UCHaptics>(uri, major, minor, "Haptics");
    qmlRegisterSimpleSingletonType<UCMathUtils>(uri, major, minor, "MathUtils");
    qmlRegisterSimpleSingletonType<ColorUtils>(uri, major, minor, "ColorUtils");
}

/*
 * public API
 */
LomiriToolkitModule::LomiriToolkitModule(QObject *parent)
    : QObject(parent)
{
}

QUrl LomiriToolkitModule::baseUrl(QQmlEngine *engine)
{
    if (!engine) {
        return QUrl();
    }
    LomiriToolkitModule *data = engine->property(engineProperty).value<LomiriToolkitModule*>();
    return !data ? QUrl() : data->m_baseUrl;
}

void LomiriToolkitModule::initializeModule(QQmlEngine *engine, const QUrl &pluginBaseUrl)
{
    // Ensure that the module is initialized once per engine.
    if (!LomiriToolkitModule::baseUrl(engine).isEmpty())
        return;

    LomiriToolkitModule *module = create(engine, pluginBaseUrl);

    // Register private types.
    const char *privateUri = "Lomiri.Components.Private";
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    qmlRegisterType<UCFrame>(privateUri, 1, 3, "Frame");
    qmlRegisterType<UCPageWrapper>(privateUri, 1, 3, "PageWrapper");
    qmlRegisterType<UCAppHeaderBase>(privateUri, 1, 3, "AppHeaderBase");
    qmlRegisterType<Tree>(privateUri, 1, 3, "Tree");

    //FIXME: move to a more generic location, i.e StyledItem or QuickUtils
    qmlRegisterSimpleSingletonType<UCScrollbarUtils>(privateUri, 1, 3, "PrivateScrollbarUtils");
#else
    qmlRegisterType<UCFrame>(privateUri, 2, 0, "Frame");
    qmlRegisterType<UCPageWrapper>(privateUri, 2, 0, "PageWrapper");
    qmlRegisterType<UCAppHeaderBase>(privateUri, 2, 0, "AppHeaderBase");
    qmlRegisterType<Tree>(privateUri, 2, 0, "Tree");

    //FIXME: move to a more generic location, i.e StyledItem or QuickUtils
    qmlRegisterSimpleSingletonType<UCScrollbarUtils>(privateUri, 2, 0, "PrivateScrollbarUtils");
#endif

#   ifdef LOMIRI_UITK_WANT_UBUNTU_COMPAT
    // Used by QML forwarder types to trigger deprecation warnings.
    qmlRegisterSingletonType<DeprecationPrinter>(privateUri, 1, 3,
        "DeprecationPrinter",
        [] (QQmlEngine *, QJSEngine *) {
            QObject * printer = DeprecationPrinter::instance();
            QQmlEngine::setObjectOwnership(printer, QQmlEngine::CppOwnership);

            return printer;
        });
#   endif

    // allocate all context property objects prior we register them
    initializeContextProperties(engine);

    HapticsProxy::instance(engine);

#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    engine->addImageProvider(QLatin1String("scaling"), new UCScalingImageProvider);
#endif

    // register icon provider
    engine->addImageProvider(QLatin1String("theme"), new LomiriThemeIconProvider);

#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    // Necessary for Screen.orientation (from import QtQuick.Window 2.0) to work
    QGuiApplication::primaryScreen()->setOrientationUpdateMask( Qt::ScreenOrientations(
            Qt::PortraitOrientation |
            Qt::LandscapeOrientation |
            Qt::InvertedPortraitOrientation |
            Qt::InvertedLandscapeOrientation));
#endif

    module->registerWindowContextProperty();

    // Application monitoring.
    UMApplicationMonitor* applicationMonitor = UMApplicationMonitor::instance();
    const QString metricsLoggingFilter =
        QString::fromLocal8Bit(qgetenv("UC_METRICS_LOGGING_FILTER"));
    if (!metricsLoggingFilter.isNull()) {
        QStringList filterList =
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
            metricsLoggingFilter.split(QStringLiteral(","), QString::SkipEmptyParts);
#else
            metricsLoggingFilter.split(QStringLiteral(","), Qt::SkipEmptyParts);
#endif

#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
        UMApplicationMonitor::LoggingFilters filter =0;
#else
        UMApplicationMonitor::LoggingFilters filter;
#endif
        const int size = filterList.size();
        for (int i = 0; i < size; ++i) {
            if (filterList[i] == QStringLiteral("*")) {
                filter |= UMApplicationMonitor::AllEvents;
                break;
            } else if (filterList[i] == QStringLiteral("window")) {
                filter |= UMApplicationMonitor::WindowEvent;
            } else if (filterList[i] == QStringLiteral("process")) {
                filter |= UMApplicationMonitor::ProcessEvent;
            } else if (filterList[i] == QStringLiteral("frame")) {
                filter |= UMApplicationMonitor::FrameEvent;
            } else if (filterList[i] == QStringLiteral("generic")) {
                filter |= UMApplicationMonitor::GenericEvent;
            }
        }
        applicationMonitor->setLoggingFilter(filter);
    }
    const QByteArray metricsLogging = qgetenv("UC_METRICS_LOGGING");
    if (!metricsLogging.isNull()) {
        UMLogger* logger;
        if (metricsLogging.isEmpty() || metricsLogging == "stdout") {
            logger = new UMFileLogger(stdout);
#if defined(Q_OS_LINUX) && defined(LTTNG_PLUGIN_BUILD_PATH)
        } else if (metricsLogging == "lttng") {
            logger = new UMLTTNGLogger();
#endif  // defined(Q_OS_LINUX) && defined(LTTNG_PLUGIN_BUILD_PATH)
        } else {
            logger = new UMFileLogger(QString::fromLocal8Bit(metricsLogging));
        }
        if (logger->isOpen()) {
            applicationMonitor->installLogger(logger);
            applicationMonitor->setLogging(true);
        } else {
            delete logger;
        }
    }
    if (qEnvironmentVariableIsSet("UC_METRICS_OVERLAY")) {
        applicationMonitor->setOverlay(true);
    }

    // register performance monitor
    engine->rootContext()->setContextProperty(
        QStringLiteral("performanceMonitor"), new UCPerformanceMonitor(engine));

    // Check if the default theme exists.
    // A timer is used to defer this check because verifyDefaultThemeExists()
    // might load another QML module, which cannot happen within initializeEngine().
    QPointer<QQmlEngine> weakEngine(engine);
    QTimer::singleShot(0, [weakEngine]() {
        if (weakEngine) {
            UCDefaultTheme::verifyDefaultThemeExists(weakEngine);
        }
    });
}

void LomiriToolkitModule::defineModule(const char *uri)
{

#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
    registerTypesToVersion(uri, 2, 0);
#else
    // register 0.1 for backward compatibility
    registerTypesToVersion(uri, 0, 1);
    registerTypesToVersion(uri, 1, 0);
#endif

    // register custom event
    ForwardedEvent::registerForwardedEvent();

    // register parent type so that properties can get/ set it
    qmlRegisterUncreatableType<QAbstractItemModel>(
        uri, 1, 1, "QAbstractItemModel", notInstantiatable);

#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    // register 1.1 only API
    qmlRegisterType<UCStyledItemBase, 1>(uri, 1, 1, "StyledItem");
    qmlRegisterType<QSortFilterProxyModelQML>(uri, 1, 1, "SortFilterModel");
    qmlRegisterUncreatableType<FilterBehavior>(uri, 1, 1, "FilterBehavior", notInstantiatable);
    qmlRegisterUncreatableType<SortBehavior>(uri, 1, 1, "SortBehavior", notInstantiatable);
    qmlRegisterType<UCServiceProperties, 1>(uri, 1, 1, "ServiceProperties");

    // register 1.2 only API
    qmlRegisterType<UCListItem>(uri, 1, 2, "ListItem");
    qmlRegisterAnonymousType<UCListItemDivider>(uri, 1);
    qmlRegisterUncreatableType<UCSwipeEvent>(
        uri, 1, 2, "SwipeEvent", QStringLiteral("This is an event object."));
    qmlRegisterUncreatableType<UCDragEvent>(
        uri, 1, 2, "ListItemDrag", QStringLiteral("This is an event object"));
    qmlRegisterType<UCListItemActions>(uri, 1, 2, "ListItemActions");
    qmlRegisterUncreatableType<UCViewItemsAttached>(uri, 1, 2, "ViewItems", notInstantiatable);
    if (QLatin1String(uri) == QLatin1String("Ubuntu.Components")) {
        // FIXME/DEPRECATED: Ubuntu.* is exported for backwards compatibility only
        qmlRegisterType<UCLomiriShape, 1>(uri, 1, 2, "UbuntuShape");
        qmlRegisterType<UCLomiriShapeOverlay>(uri, 1, 2, "UbuntuShapeOverlay");
        qmlRegisterSimpleSingletonType<UT_PREPEND_NAMESPACE(UCNamespace)>(uri, 1, 2, "Ubuntu");
    } else {
        qmlRegisterType<UCLomiriShape, 1>(uri, 1, 2, "LomiriShape");
        qmlRegisterType<UCLomiriShapeOverlay>(uri, 1, 2, "LomiriShapeOverlay");
        qmlRegisterSimpleSingletonType<UT_PREPEND_NAMESPACE(UCNamespace)>(uri, 1, 2, "Lomiri");
    }
    // register 1.3 API
    qmlRegisterType<UCListItem, 1>(uri, 1, 3, "ListItem");
    qmlRegisterAnonymousType<UCListItemExpansion>(uri, 1);
    qmlRegisterType<UCTheme>(uri, 1, 3, "ThemeSettings");
    qmlRegisterType<UCStyledItemBase, 2>(uri, 1, 3, "StyledItem");
    qmlRegisterType<UCStyledItemBase, 2>(uri, 1, 3, "StyledItem");
    qmlRegisterCustomType<UCStyleHints>(uri, 1, 3, "StyleHints", new UCStyleHintsParser);
    qmlRegisterType<UCAction, 1>(uri, 1, 3, "Action");
    qmlRegisterType<UCSlotsLayout>(uri, 1, 3, "SlotsLayout");
    if (QLatin1String(uri) == QLatin1String("Ubuntu.Components")) {
        // FIXME/DEPRECATED: Ubuntu.* is exported for backwards compatibility only
        qmlRegisterType<UCLomiriShape, 2>(uri, 1, 3, "UbuntuShape");
        qmlRegisterSimpleSingletonType<UT_PREPEND_NAMESPACE(UCNamespaceV13)>(uri, 1, 3, "Ubuntu");
    } else {
        qmlRegisterType<UCLomiriShape, 2>(uri, 1, 3, "LomiriShape");
        qmlRegisterSimpleSingletonType<UT_PREPEND_NAMESPACE(UCNamespaceV13)>(uri, 1, 3, "Lomiri");
    }
    qmlRegisterType<UCProportionalShape>(uri, 1, 3, "ProportionalShape");
    qmlRegisterType<LiveTimer>(uri, 1, 3, "LiveTimer");
    qmlRegisterType<UCAbstractButton>(uri, 1, 3, "AbstractButton");
    qmlRegisterAnonymousType<UCMargins>(uri, 1);
    qmlRegisterUncreatableType<UCSlotsAttached>(uri, 1, 3, "SlotsAttached", notInstantiatable);
    qmlRegisterUncreatableType<UCSlotsLayoutPadding>(
        uri, 1, 3, "SlotsLayoutPadding", notInstantiatable);
    qmlRegisterType<UCListItemLayout>(uri, 1, 3, "ListItemLayout");
    qmlRegisterType<UCHeader>(uri, 1, 3, "Header");
    qmlRegisterType<UCLabel>(uri, 1, 3, "Label");
    qmlRegisterType<UCBottomEdgeHint>(uri, 1, 3, "BottomEdgeHint");
    qmlRegisterType<UCBottomEdge>(uri, 1, 3, "BottomEdge");
    qmlRegisterType<UCBottomEdgeRegion>(uri, 1, 3, "BottomEdgeRegion");
    qmlRegisterType<UCPageTreeNode>(uri, 1, 3, "PageTreeNode");
    qmlRegisterType<UCPopupContext>(uri, 1, 3, "PopupContext");
    qmlRegisterType<UCMainViewBase>(uri, 1, 3, "MainViewBase");
    qmlRegisterType<ActionList>(uri, 1, 3, "ActionList");
    qmlRegisterType<ExclusiveGroup>(uri, 1, 3, "ExclusiveGroup");

#else
    // Foreign base classes need to be registered in order to expose
    // all of their revisioned properties, regardless of the module version.
    // See: https://github.com/qt/qtdeclarative/commit/c3999f96536ac359ba94d80755230e85238d578a
    qmlRegisterAnonymousType<QQuickItem, 254>(uri, 2);      // inh. by UCStyledItemBase
    qmlRegisterAnonymousType<QQuickWindow, 254>(uri, 2);    // inh. by LomiriTestCase
    qmlRegisterAnonymousType<QQuickMouseArea, 254>(uri, 2); // inh. by InverseMouseAreaType
    qmlRegisterAnonymousType<QQuickText, 254>(uri, 2);      // inh. by UCLabel

    qmlRegisterUncreatableType<UCSwipeEvent>(
        uri, 2, 0, "SwipeEvent", QStringLiteral("This is an event object."));
    qmlRegisterUncreatableType<UCDragEvent>(
        uri, 2, 0, "ListItemDrag", QStringLiteral("This is an event object"));
    qmlRegisterType<UCListItemActions>(uri, 2, 0, "ListItemActions");
    qmlRegisterUncreatableType<UCViewItemsAttached>(uri, 2, 0, "ViewItems", notInstantiatable);
    qmlRegisterType<QSortFilterProxyModelQML>(uri, 2, 0, "SortFilterModel");
    qmlRegisterUncreatableType<FilterBehavior>(uri, 2, 0, "FilterBehavior", notInstantiatable);
    qmlRegisterUncreatableType<SortBehavior>(uri, 2, 0, "SortBehavior", notInstantiatable);
    qmlRegisterType<UCServiceProperties, 1>(uri, 2, 0, "ServiceProperties");
    // register 2.0 API
    qmlRegisterType<UCListItem, 1>(uri, 2, 0, "ListItem");
    qmlRegisterAnonymousType<UCListItemExpansion>(uri, 1);
    qmlRegisterType<UCTheme>(uri, 2, 0, "ThemeSettings");
    qmlRegisterType<UCStyledItemBase, 2>(uri, 2, 0, "StyledItem");
    qmlRegisterType<UCStyledItemBase, 2>(uri, 2, 0, "StyledItem");
    qmlRegisterCustomType<UCStyleHints>(uri, 2, 0, "StyleHints", new UCStyleHintsParser);
    qmlRegisterType<UCAction, 1>(uri, 2, 0, "Action");
    qmlRegisterType<UCSlotsLayout>(uri, 2, 0, "SlotsLayout");
    qmlRegisterType<UCLomiriShape, 2>(uri, 2, 0, "LomiriShape");
    qmlRegisterType<UCLomiriShapeOverlay>(uri, 2, 0, "LomiriShapeOverlay");
    qmlRegisterSimpleSingletonType<UT_PREPEND_NAMESPACE(UCNamespaceV13)>(uri, 2, 0, "Lomiri");
    qmlRegisterType<UCProportionalShape>(uri, 2, 0, "ProportionalShape");
    qmlRegisterType<LiveTimer>(uri, 2, 0, "LiveTimer");
    qmlRegisterType<UCAbstractButton>(uri, 2, 0, "AbstractButton");
    qmlRegisterAnonymousType<UCMargins>(uri, 1);
    qmlRegisterUncreatableType<UCSlotsAttached>(uri, 2, 0, "SlotsAttached", notInstantiatable);
    qmlRegisterUncreatableType<UCSlotsLayoutPadding>(
        uri, 2, 0, "SlotsLayoutPadding", notInstantiatable);
    qmlRegisterType<UCListItemLayout>(uri, 2, 0, "ListItemLayout");
    qmlRegisterType<UCHeader>(uri, 2, 0, "Header");
    qmlRegisterType<UCLabel>(uri, 2, 0, "Label");
    qmlRegisterType<UCBottomEdgeHint>(uri, 2, 0, "BottomEdgeHint");
    qmlRegisterType<UCBottomEdge>(uri, 2, 0, "BottomEdge");
    qmlRegisterType<UCBottomEdgeRegion>(uri, 2, 0, "BottomEdgeRegion");
    qmlRegisterType<UCPageTreeNode>(uri, 2, 0, "PageTreeNode");
    qmlRegisterType<UCPopupContext>(uri, 2, 0, "PopupContext");
    qmlRegisterType<UCMainViewBase>(uri, 2, 0, "MainViewBase");
    qmlRegisterType<ActionList>(uri, 2, 0, "ActionList");
    qmlRegisterType<ExclusiveGroup>(uri, 2, 0, "ExclusiveGroup");

#endif

#   ifdef LOMIRI_UITK_WANT_UBUNTU_COMPAT
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    if (QLatin1String(uri) == QLatin1String("Ubuntu.Components")) {
        DeprecationPrinter::instance()->printDeprecation(
            DeprecationPrinter::COMPONENTS);
    }
#endif
#   endif
}

void LomiriToolkitModule::undefineModule()
{
    // nothing yet
}

/******************************************************************************
 * Styles module
 */
void LomiriStylesModule::defineModule(const char *uri)
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    // 1.2 styles
    qmlRegisterType<UCListItemStyle>(uri, 1, 2, "ListItemStyle");

    // 1.3 styles
    qmlRegisterType<UCListItemStyle, 1>(uri, 1, 3, "ListItemStyle");
    qmlRegisterType<UCBottomEdgeStyle>(uri, 1, 3, "BottomEdgeStyle");
#else
    qmlRegisterType<UCListItemStyle, 1>(uri, 2, 0, "ListItemStyle");
    qmlRegisterType<UCBottomEdgeStyle>(uri, 2, 0, "BottomEdgeStyle");
#endif
}

void LomiriStylesModule::undefineModule()
{
    // nothing yet
}

/******************************************************************************
 * Labs module
 */
void LomiriLabsModule::initializeModule(QQmlEngine *engine, QQmlExtensionPlugin *plugin)
{
    Q_UNUSED(plugin);

    // qmlplugindump requires that quickutils is initialized.
    QuickUtils::instance(engine);
}

void LomiriLabsModule::defineModule(const char *uri)
{
    qmlRegisterType<SplitView>(uri, 1, 0, "SplitView");
    qmlRegisterType<SplitViewLayout>(uri, 1, 0, "SplitViewLayout");
    qmlRegisterType<ViewColumn>(uri, 1, 0, "ViewColumn");
    qmlRegisterType<UCMainWindow>(uri, 1, 0, "MainWindow");
    qmlRegisterType<Menu>(uri, 1, 0, "Menu");
    qmlRegisterType<MenuBar>(uri, 1, 0, "MenuBar");
    qmlRegisterType<MenuGroup>(uri, 1, 0, "MenuGroup");
}

void LomiriLabsModule::undefineModule()
{
    // nothing yet
}

UT_NAMESPACE_END
