// SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
// SPDX-FileCopyrightText: 2022-2023 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later

#include "homescreen.h"

#include "windowlistener.h"

#include <virtualdesktopinfo.h>

#include <KWindowSystem>

#include <QDBusConnection>
#include <QDBusInterface>
#include <QDBusMessage>
#include <QDBusReply>
#include <QDBusVariant>
#include <QDebug>
#include <QQmlEngine>
#include <QQmlExtensionPlugin>
#include <QQuickItem>
#include <QTimer>

K_PLUGIN_CLASS_WITH_JSON(HomeScreen, "metadata.json")

namespace
{

const QString s_kwinService = QStringLiteral("org.kde.KWin");
const QString s_kwinEffectsPath = QStringLiteral("/Effects");
const QString s_kwinEffectsInterface = QStringLiteral("org.kde.kwin.Effects");
const QString s_dbusPropertiesInterface = QStringLiteral("org.freedesktop.DBus.Properties");

QStringList effectListFromVariant(const QVariant &value)
{
    QVariant effectValue = value;
    if (effectValue.canConvert<QDBusVariant>()) {
        effectValue = effectValue.value<QDBusVariant>().variant();
    }

    if (effectValue.canConvert<QStringList>()) {
        return effectValue.toStringList();
    }

    QStringList effects;
    const QVariantList effectList = effectValue.toList();
    effects.reserve(effectList.size());
    for (const QVariant &effect : effectList) {
        effects.append(effect.toString());
    }
    return effects;
}

}

HomeScreen::HomeScreen(QObject *parent, const KPluginMetaData &data, const QVariantList &args)
    : Plasma::Containment{parent, data, args}
    , m_folioSettings{new FolioSettings{this}}
    , m_homeScreenState{new HomeScreenState{this}}
    , m_widgetsManager{new WidgetsManager{this}}
    , m_applicationListModel{new ApplicationListModel{this}}
    , m_applicationListSearchModel{new ApplicationListSearchModel{this, m_applicationListModel}}
    , m_applicationUsageStore{new ApplicationUsageStore{this}}
    , m_recentApplicationsModel{new ApplicationUsageModel{this, m_applicationUsageStore, ApplicationUsageModel::RecentUsage}}
    , m_mostUsedApplicationsModel{new ApplicationUsageModel{this, m_applicationUsageStore, ApplicationUsageModel::MostUsed}}
    , m_favouritesModel{new FavouritesModel{this}}
    , m_pageListModel{new PageListModel{this}}
{
    // HomeScreenState init() has dependencies on other objects
    m_homeScreenState->init();

    setHasConfigurationInterface(true);

    connect(KWindowSystem::self(), &KWindowSystem::showingDesktopChanged, this, &HomeScreen::showingDesktopChanged);

    updateOverviewActive();
    QDBusConnection::sessionBus().connect(s_kwinService,
                                          s_kwinEffectsPath,
                                          s_dbusPropertiesInterface,
                                          QStringLiteral("PropertiesChanged"),
                                          this,
                                          SLOT(onOverviewEffectsChanged(QString, QVariantMap, QStringList)));

    auto overviewRefreshTimer = new QTimer(this);
    overviewRefreshTimer->setInterval(250);
    overviewRefreshTimer->setTimerType(Qt::CoarseTimer);
    connect(overviewRefreshTimer, &QTimer::timeout, this, &HomeScreen::updateOverviewActive);
    overviewRefreshTimer->start();

    connect(this, &Plasma::Containment::appletAdded, this, &HomeScreen::onAppletAdded);
    connect(this, &Plasma::Containment::appletAboutToBeRemoved, this, &HomeScreen::onAppletAboutToBeRemoved);
    connect(WindowListener::instance(), &WindowListener::windowChanged, this, [this](QString storageId) {
        if (!WindowListener::instance()->windowsFromStorageId(storageId).empty()) {
            m_applicationUsageStore->recordUsage(storageId);
        }
    });
}

HomeScreen::~HomeScreen() = default;

void HomeScreen::configChanged()
{
    Plasma::Containment::configChanged();
}

void HomeScreen::onAppletAdded(Plasma::Applet *applet, const QRectF &geometryHint)
{
    Q_UNUSED(geometryHint)
    widgetsManager()->addWidget(applet);
}

void HomeScreen::onAppletAboutToBeRemoved(Plasma::Applet *applet)
{
    widgetsManager()->removeWidget(applet);
}

FolioSettings *HomeScreen::folioSettings()
{
    return m_folioSettings;
}

HomeScreenState *HomeScreen::homeScreenState()
{
    return m_homeScreenState;
}

WidgetsManager *HomeScreen::widgetsManager()
{
    return m_widgetsManager;
}

ApplicationListModel *HomeScreen::applicationListModel()
{
    return m_applicationListModel;
}

ApplicationListSearchModel *HomeScreen::applicationListSearchModel()
{
    return m_applicationListSearchModel;
}

ApplicationUsageModel *HomeScreen::recentApplicationsModel()
{
    return m_recentApplicationsModel;
}

ApplicationUsageModel *HomeScreen::mostUsedApplicationsModel()
{
    return m_mostUsedApplicationsModel;
}

FavouritesModel *HomeScreen::favouritesModel()
{
    return m_favouritesModel;
}

PageListModel *HomeScreen::pageListModel()
{
    return m_pageListModel;
}

bool HomeScreen::overviewActive() const
{
    return m_overviewActive;
}

void HomeScreen::setOverviewActive(bool overviewActive)
{
    if (m_overviewActive == overviewActive) {
        return;
    }

    m_overviewActive = overviewActive;
    Q_EMIT overviewActiveChanged();
}

void HomeScreen::updateOverviewActive()
{
    QDBusInterface propIface(s_kwinService, s_kwinEffectsPath, s_dbusPropertiesInterface, QDBusConnection::sessionBus());
    if (!propIface.isValid()) {
        setOverviewActive(false);
        return;
    }

    QDBusReply<QDBusVariant> activeEffectsReply = propIface.call(QStringLiteral("Get"), s_kwinEffectsInterface, QStringLiteral("activeEffects"));
    if (!activeEffectsReply.isValid()) {
        setOverviewActive(false);
        return;
    }

    setOverviewActive(effectListFromVariant(activeEffectsReply.value().variant()).contains(QStringLiteral("overview")));
}

void HomeScreen::onOverviewEffectsChanged(const QString &interface, const QVariantMap &changed, const QStringList &invalidated)
{
    if (interface != s_kwinEffectsInterface) {
        return;
    }

    if (changed.contains(QStringLiteral("activeEffects"))) {
        setOverviewActive(effectListFromVariant(changed.value(QStringLiteral("activeEffects"))).contains(QStringLiteral("overview")));
        return;
    }

    if (invalidated.contains(QStringLiteral("activeEffects"))) {
        updateOverviewActive();
    }
}

void HomeScreen::triggerOverview()
{
    setOverviewActive(true);

    QDBusMessage message = QDBusMessage::createMethodCall("org.kde.kglobalaccel", "/component/kwin", "org.kde.kglobalaccel.Component", "invokeShortcut");
    message.setArguments({QStringLiteral("Overview")});
    QDBusConnection::sessionBus().send(message);
}

void HomeScreen::triggerMinimizeAll() const
{
    QDBusMessage message = QDBusMessage::createMethodCall("org.kde.kglobalaccel", "/component/kwin", "org.kde.kglobalaccel.Component", "invokeShortcut");
    message.setArguments({QStringLiteral("MinimizeAll")});
    QDBusConnection::sessionBus().send(message);
}

void HomeScreen::activateVirtualDesktop(const QVariant &desktop) const
{
    if (!desktop.isValid() || desktop.toString().isEmpty()) {
        return;
    }

    TaskManager::VirtualDesktopInfo virtualDesktopInfo;
    virtualDesktopInfo.requestActivate(desktop);
}

void HomeScreen::createVirtualDesktop() const
{
    TaskManager::VirtualDesktopInfo virtualDesktopInfo;
    virtualDesktopInfo.requestCreateDesktop(virtualDesktopInfo.numberOfDesktops());
}

void HomeScreen::removeLastVirtualDesktop() const
{
    TaskManager::VirtualDesktopInfo virtualDesktopInfo;
    if (virtualDesktopInfo.numberOfDesktops() <= 1) {
        return;
    }

    virtualDesktopInfo.requestRemoveDesktop(virtualDesktopInfo.numberOfDesktops() - 1);
}

void HomeScreen::emptyTrash() const
{
    QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kio.trash"),
                                                          QStringLiteral("/trash"),
                                                          QStringLiteral("org.kde.KIO.Trash"),
                                                          QStringLiteral("emptyTrash"));
    QDBusConnection::sessionBus().send(message);
}

#include "homescreen.moc"
