/*
    SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>

    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "kwindoweffects_dummy_p.h"
#include "kwindowinfo_dummy_p.h"
#include "kwindowshadow_dummy_p.h"
#include "kwindowsystem_debug.h"
#include "kwindowsystem_dummy_p.h"
#include "kwindowsystemplugininterface_p.h"
#include "pluginwrapper_p.h"

#include <QDir>
#include <QGuiApplication>
#include <QJsonArray>
#include <QLibrary>
#include <QPluginLoader>

Q_GLOBAL_STATIC(KWindowSystemPluginWrapper, s_pluginWrapper)

static QStringList pluginCandidates()
{
    QStringList ret;
    const auto paths = QCoreApplication::libraryPaths();
    for (const QString &path : paths) {
        static const QStringList searchFolders{
            QStringLiteral("/kf" QT_STRINGIFY(QT_VERSION_MAJOR)) + QStringLiteral("/org.kde.kwindowsystem.platforms"),
            QStringLiteral("/kf" QT_STRINGIFY(QT_VERSION_MAJOR)) + QStringLiteral("/kwindowsystem"),
        };
        for (const QString &searchFolder : searchFolders) {
            QDir pluginDir(path + searchFolder);
            if (!pluginDir.exists()) {
                continue;
            }
            const auto entries = pluginDir.entryList(QDir::Files | QDir::NoDotAndDotDot);
            for (const QString &entry : entries) {
                ret << pluginDir.absoluteFilePath(entry);
            }
        }
    }
    return ret;
}

static bool checkPlatform(const QJsonObject &metadata, const QString &platformName)
{
    const QJsonArray platforms = metadata.value(QStringLiteral("MetaData")).toObject().value(QStringLiteral("platforms")).toArray();
    return std::any_of(platforms.begin(), platforms.end(), [&platformName](const QJsonValue &value) {
        return QString::compare(platformName, value.toString(), Qt::CaseInsensitive) == 0;
    });
}

static KWindowSystemPluginInterface *loadPlugin()
{
    if (!qobject_cast<QGuiApplication *>(QCoreApplication::instance())) {
        qCWarning(LOG_KWINDOWSYSTEM) << "Cannot use KWindowSystem without a QGuiApplication";
        return nullptr;
    }

    QString platformName = QGuiApplication::platformName();
    if (platformName == QLatin1String("flatpak")) {
        // here we cannot know what is the actual windowing system, let's try it's env variable
        const auto flatpakPlatform = QString::fromLocal8Bit(qgetenv("QT_QPA_FLATPAK_PLATFORM"));
        if (!flatpakPlatform.isEmpty()) {
            platformName = flatpakPlatform;
        }
    }

    const QVector<QStaticPlugin> staticPlugins = QPluginLoader::staticPlugins();
    for (const QStaticPlugin &staticPlugin : staticPlugins) {
        const QJsonObject metadata = staticPlugin.metaData();
        if (metadata.value(QLatin1String("IID")) != QLatin1String(KWindowSystemPluginInterface_iid)) {
            continue;
        }
        if (checkPlatform(metadata, platformName)) {
            KWindowSystemPluginInterface *interface = qobject_cast<KWindowSystemPluginInterface *>(staticPlugin.instance());
            if (interface) {
                qCDebug(LOG_KWINDOWSYSTEM) << "Loaded a static plugin for platform" << platformName;
                return interface;
            }
        }
    }

    const auto candidates = pluginCandidates();
    for (const QString &candidate : candidates) {
        if (!QLibrary::isLibrary(candidate)) {
            continue;
        }
        QPluginLoader loader(candidate);
        if (checkPlatform(loader.metaData(), platformName)) {
            KWindowSystemPluginInterface *interface = qobject_cast<KWindowSystemPluginInterface *>(loader.instance());
            if (interface) {
                qCDebug(LOG_KWINDOWSYSTEM) << "Loaded plugin" << candidate << "for platform" << platformName;
                return interface;
            }
        }
    }

    qCWarning(LOG_KWINDOWSYSTEM) << "Could not find any platform plugin";
    return nullptr;
}

KWindowSystemPluginWrapper::KWindowSystemPluginWrapper()
    : m_plugin(loadPlugin())
    , m_effects()
{
    if (m_plugin) {
        m_effects.reset(m_plugin->createEffects());
    }
    if (!m_effects) {
        m_effects.reset(new KWindowEffectsPrivateDummy());
    }
}

KWindowSystemPluginWrapper::~KWindowSystemPluginWrapper()
{
}

KWindowEffectsPrivate *KWindowSystemPluginWrapper::effects() const
{
    return m_effects.get();
}

KWindowSystemPrivate *KWindowSystemPluginWrapper::createWindowSystem() const
{
    KWindowSystemPrivate *p = nullptr;
    if (m_plugin) {
        p = m_plugin->createWindowSystem();
    }
    if (!p) {
        p = new KWindowSystemPrivateDummy();
    }
    return p;
}

KWindowInfoPrivate *KWindowSystemPluginWrapper::createWindowInfo(WId window, NET::Properties properties, NET::Properties2 properties2) const
{
    KWindowInfoPrivate *p = nullptr;
    if (m_plugin) {
        p = m_plugin->createWindowInfo(window, properties, properties2);
    }
    if (!p) {
        p = new KWindowInfoPrivateDummy(window, properties, properties2);
    }
    return p;
}

KWindowShadowPrivate *KWindowSystemPluginWrapper::createWindowShadow() const
{
    KWindowShadowPrivate *p = nullptr;
    if (m_plugin) {
        p = m_plugin->createWindowShadow();
    }
    if (!p) {
        p = new KWindowShadowPrivateDummy();
    }
    return p;
}

KWindowShadowTilePrivate *KWindowSystemPluginWrapper::createWindowShadowTile() const
{
    KWindowShadowTilePrivate *p = nullptr;
    if (m_plugin) {
        p = m_plugin->createWindowShadowTile();
    }
    if (!p) {
        p = new KWindowShadowTilePrivateDummy();
    }
    return p;
}

const KWindowSystemPluginWrapper &KWindowSystemPluginWrapper::self()
{
    return *s_pluginWrapper;
}
