/*
 * Copyright (C) 2019 UBports Foundation
 * Author(s): Marius Gripsgard <marius@ubports.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "xdgwatcher.h"

#include <QDebug>
#include <QDir>
#include <QFile>
#include <QStandardPaths>
#include <QTextStream>

#include <gio/gdesktopappinfo.h>

XdgWatcher::XdgWatcher(QObject* parent)
    : QObject(parent),
      m_watcher(new QFileSystemWatcher(this))
{
    connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &XdgWatcher::onDirectoryChanged);
    connect(m_watcher, &QFileSystemWatcher::fileChanged, this, &XdgWatcher::onFileChanged);

    const auto paths = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation);
    for (const auto &path: paths) {
        const auto qdir = QDir(path);
        if (!qdir.exists()) {
            continue;
        }

        // Add the path itself to watch for newly added apps
        m_watcher->addPath(path);

        // Add watcher for eatch app to watch for changes
        const auto files = qdir.entryInfoList(QDir::Files);
        for (const auto &file: files) {
            if (file.suffix() == "desktop") {
                const auto path = file.absoluteFilePath();
                m_watcher->addPath(path);
                m_registry.insert(path, getAppId(file));
            }
        }
    }
}

// "Lomiri style" appID is filename without versionNumber after last "_"
const QString XdgWatcher::stripAppIdVersion(const QString rawAppID) const {
    auto appIdComponents = rawAppID.split("_");
    appIdComponents.removeLast();
    return appIdComponents.join("_");
}

// Standard appID see:
// https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id
const QString XdgWatcher::toStandardAppId(const QFileInfo fileInfo) const {
    const auto paths = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation);
    for (const auto &path: paths) {
        if (fileInfo.absolutePath() == path) {
            break;
        }
        if (fileInfo.absolutePath().contains(path)) {
            auto fileStr = fileInfo.absoluteFilePath();
            fileStr.replace(path, "");
            fileStr.replace("/", "-");
            fileStr.replace(".desktop", "");
            return fileStr;
        }
    }
    return fileInfo.completeBaseName();
}

const QString XdgWatcher::getAppId(const QFileInfo fileInfo) const {
    // We need to open the file to check if its and Ual application
    // as we cant just rely on the app name as "normal" apps can also
    // contain 3 _ causing us to belive its an lomiri app
    // Example kde_org_kate would become kde_org
    QFile qFile(fileInfo.absoluteFilePath());
    qFile.open(QIODevice::ReadOnly);
    QTextStream fileStream(&qFile);
    QString line;
    while (fileStream.readLineInto(&line)) {
        if (line.startsWith("X-Lomiri-Application-ID=")) {
            auto rawAppID = line.replace("X-Lomiri-Application-ID=", "");
            qFile.close();
            return stripAppIdVersion(rawAppID);
        }
    }
    qFile.close();

    // If it's not an "Lomiri" appID, we follow freedesktop standard
    return toStandardAppId(fileInfo);
}

// Returns true if the desktop file should be shown (check for NoDisplay)
bool XdgWatcher::shouldShow(const QFileInfo &file) const {
    GDesktopAppInfo *appinfo = g_desktop_app_info_new(file.fileName().toUtf8().constData());
    if (appinfo) {
        bool show = g_app_info_should_show(G_APP_INFO(appinfo));
        g_object_unref(appinfo);
        return show;
    } else {
        qWarning() << "XdgWatcher: appinfo is NULL for" << file.absoluteFilePath();
        return true; // Show the app if we can't determine its visibility
    }
}

// Watch for newly added apps
void XdgWatcher::onDirectoryChanged(const QString &path) {
    const auto files = QDir(path).entryInfoList(QDir::Files);
    const auto watchedFiles = m_watcher->files();
    for (const auto &file: files) {
        const auto appPath = file.absoluteFilePath();
        if (file.suffix() == "desktop" && !watchedFiles.contains(appPath) && shouldShow(file)) {
            m_watcher->addPath(appPath);

            const auto appId = getAppId(file);
            m_registry.insert(appPath, appId);
            Q_EMIT appAdded(appId);
        }
    }
}

void XdgWatcher::onFileChanged(const QString &path) {
    const auto watchedFiles = m_watcher->files();
    if (watchedFiles.contains(path)) {
        // The file is still watched, this must be an modify event
        Q_EMIT appInfoChanged(m_registry.value(path));
    } else {
        // File is not watched anymore, this is a remove event.
        // onDirectoryChanged will handle rename event
        Q_EMIT appRemoved(m_registry.take(path));
    }
}
