/*
 *   SPDX-FileCopyrightText: 2019 Méven Car (meven.car@kdemail.net)
 *
 *   SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "GtkEventSpy.h"

#include <QFile>
#include <QFileInfo>
#include <QStandardPaths>
#include <QString>
#include <QUrl>
#include <QXmlStreamReader>

#include <KApplicationTrader>
#include <KDirWatch>

#include "DebugPluginGtkEventSpy.h"

K_PLUGIN_CLASS_WITH_JSON(GtkEventSpyPlugin, "kactivitymanagerd-plugin-gtk-eventspy.json")

GtkEventSpyPlugin::GtkEventSpyPlugin(QObject *parent, const QVariantList &args)
    : Plugin(parent)
    , m_resources(nullptr)
    , m_dirWatcher(new KDirWatch(this))
    , m_lastUpdate(QDateTime::currentDateTime())
{
    Q_UNUSED(args);

    // gtk xml history file
    // usually $HOME/.local/share/recently-used.xbel
    QString filename = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/recently-used.xbel");

    m_dirWatcher->addFile(filename);
    connect(m_dirWatcher.get(), &KDirWatch::dirty, this, &GtkEventSpyPlugin::fileUpdated);
    connect(m_dirWatcher.get(), &KDirWatch::created, this, &GtkEventSpyPlugin::fileUpdated);
}

struct Application {
    QString name;
    QDateTime modified;
};

class Bookmark
{
public:
    QUrl href;
    QDateTime added;
    QDateTime modified;
    QDateTime visited;
    QString mimetype;
    QList<Application> applications;

    QString latestApplication() const;
};

QString Bookmark::latestApplication() const
{
    Application current = applications.first();
    for (const Application &app : applications) {
        if (app.modified > current.modified) {
            current = app;
        }
    }
    return current.name;
}

void GtkEventSpyPlugin::fileUpdated(const QString &filename)
{
    QFile file(filename);
    if (!file.open(QFile::ReadOnly | QFile::Text)) {
        qCWarning(KAMD_LOG_PLUGIN_GTK_EVENTSPY) << "Could not read" << filename;
        return;
    }

    // must parse the xbel xml file
    QXmlStreamReader reader(&file);
    QList<Bookmark> bookmarks;
    Bookmark current;
    while (!reader.atEnd()) {
        const auto token = reader.readNext();
        if (token == QXmlStreamReader::StartElement) {
            if (reader.qualifiedName() == QLatin1String("bookmark")) {
                current = Bookmark();
                current.href = QUrl(reader.attributes().value("href").toString());
                QString added = reader.attributes().value("added").toString();
                QString modified = reader.attributes().value("modified").toString();
                QString visited = reader.attributes().value("visited").toString();
                current.added = QDateTime::fromString(added, Qt::ISODate);
                current.modified = QDateTime::fromString(modified, Qt::ISODate);
                current.visited = QDateTime::fromString(visited, Qt::ISODate);

                // application for the current bookmark
            } else if (reader.qualifiedName() == QLatin1String("bookmark:application")) {
                Application app;

                QString exec = reader.attributes().value("exec").toString();

                if (exec.startsWith(QLatin1Char('\'')) && exec.endsWith(QLatin1Char('\''))) {
                    // remove "'" characters wrapping the command
                    exec = exec.mid(1, exec.size() - 2);
                }

                // Search for applications which are executable and case-insensitively match the search term
                const KService::List services = KApplicationTrader::query([exec](const KService::Ptr &app) {
                    return app->exec().compare(exec, Qt::CaseInsensitive) == 0;
                });

                if (!services.isEmpty()) {
                    // use the first item matching
                    const auto &service = services.first();
                    app.name = service->desktopEntryName();
                } else {
                    // when no services are found, sanitize a little the exec
                    // remove space and any character after
                    const int spaceIndex = exec.indexOf(" ");
                    if (spaceIndex != -1) {
                        exec = exec.mid(0, spaceIndex);
                    }
                    app.name = exec;
                }

                app.modified = QDateTime::fromString(reader.attributes().value("modified").toString(), Qt::ISODate);

                current.applications.append(app);
            } else if (reader.qualifiedName() == QLatin1String("mime:mime-type")) {
                current.mimetype = reader.attributes().value("type").toString();
            }
        } else if (token == QXmlStreamReader::EndElement) {
            if (reader.qualifiedName() == QLatin1String("bookmark")) {
                // keep track of the finished parsed bookmark
                bookmarks << current;
            }
        }
    }

    if (reader.hasError()) {
        qCWarning(KAMD_LOG_PLUGIN_GTK_EVENTSPY) << "could not parse" << file.fileName() << "error was " << reader.errorString();
        return;
    }

    // then find the files that were accessed since last run
    for (const Bookmark &mark : bookmarks) {
        if (mark.added > m_lastUpdate || mark.modified > m_lastUpdate || mark.visited > m_lastUpdate) {
            addDocument(mark.href, mark.latestApplication(), mark.mimetype);
        }
    }

    m_lastUpdate = QDateTime::currentDateTime();
}

void GtkEventSpyPlugin::addDocument(const QUrl &url, const QString &application, const QString &mimetype)
{
    const QString name = url.fileName();

    Plugin::invoke<Qt::QueuedConnection>(m_resources,
                                         "RegisterResourceEvent",
                                         Q_ARG(QString, application), // Application
                                         Q_ARG(uint, 0), // Window ID
                                         Q_ARG(QString, url.toString()), // URI
                                         Q_ARG(uint, 0) // Event Activities::Accessed
    );

    Plugin::invoke<Qt::QueuedConnection>(m_resources,
                                         "RegisteredResourceMimetype",
                                         Q_ARG(QString, url.toString()), // uri
                                         Q_ARG(QString, mimetype) // mimetype
    );
}

GtkEventSpyPlugin::~GtkEventSpyPlugin()
{
}

bool GtkEventSpyPlugin::init(QHash<QString, QObject *> &modules)
{
    Plugin::init(modules);

    m_resources = modules["resources"];

    return true;
}

#include "GtkEventSpy.moc"
