/**********************************************************************
 *  settings.cpp
 **********************************************************************
 * Copyright (C) 2020-2024 MX Authors
 *
 * Authors: Adrian
 *          MX Linux <http://mxlinux.org>
 *
 * This file is part of MX Snapshot.
 *
 * MX Snapshot 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, either version 3 of the License, or
 * (at your option) any later version.
 *
 * MX Snapshot 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 MX Snapshot.  If not, see <http://www.gnu.org/licenses/>.
 **********************************************************************/

#include "settings.h"

#include <QCoreApplication>
#include <QDate>
#include <QDebug>
#include <QRegularExpression>
#include <QSettings>
#include <QStandardPaths>

#ifndef CLI_BUILD
#include <QMessageBox>
#endif

Settings::Settings(const QCommandLineParser &arg_parser)
{
    if (QFileInfo::exists("/tmp/installed-to-live/cleanup.conf")) { // cleanup installed-to-live from other sessions
        QString elevate {QFile::exists("/usr/bin/pkexec") ? "/usr/bin/pkexec" : "/usr/bin/gksu"};
        Cmd().run(elevate + " /usr/lib/" + QCoreApplication::applicationName() + "/snapshot-lib cleanup");
    }
    loadConfig(); // load settings from .conf file
    setVariables();
    processArgs(arg_parser);
    if (arg_parser.isSet("month")) {
        setMonthlySnapshot(arg_parser);
    }
    override_size = arg_parser.isSet("override-size");
    processExclArgs(arg_parser);
}

// check if compression is available in the kernel (lz4, lzo, xz)
bool Settings::checkCompression() const
{
    if (compression == QLatin1String("gzip")) { // don't check for gzip
        return true;
    }
    if (!QFileInfo::exists("/boot/config-" + kernel)) { // return true if cannot check config file
        return true;
    }
    return (Cmd().run("grep ^CONFIG_SQUASHFS_" + compression.toUpper() + "=y /boot/config-" + kernel));
}

// Adds or removes exclusion to the exclusion string
void Settings::addRemoveExclusion(bool add, QString exclusion)
{
    if (exclusion.startsWith(QLatin1String("/"))) {
        exclusion.remove(0, 1); // remove preceding slash
    }
    if (add) {
        if (session_excludes.isEmpty()) {
            session_excludes.append("-e \"" + exclusion + "\"");
        } else {
            session_excludes.append(" \"" + exclusion + "\"");
        }
    } else {
        session_excludes.remove(" \"" + exclusion + "\"");
        if (session_excludes == QLatin1String("-e")) {
            session_excludes = QLatin1String("");
        }
    }
}

bool Settings::checkSnapshotDir() const
{
    qDebug() << "+++" << __PRETTY_FUNCTION__ << "+++";
    if (!Cmd().runAsRoot("mkdir -p \"" + snapshot_dir + "\"", false)) {
        qDebug() << QObject::tr("Could not create working directory. ") + snapshot_dir;
        return false;
    }
    Cmd().runAsRoot("chown $(logname): \"" + snapshot_dir + "\"");
    return true;
}

bool Settings::checkTempDir()
{
    qDebug() << "+++" << __PRETTY_FUNCTION__ << "+++";
    // Set workdir location if not defined in .conf file, doesn't exist, or not supported partition
    if (tempdir_parent.isEmpty() || !QFile::exists(tempdir_parent) || !isOnSupportedPart(tempdir_parent)) {
        tempdir_parent = snapshot_dir;
        if (!isOnSupportedPart(
                snapshot_dir)) { // if not saving snapshot on a supported partition, put working dir in /tmp or /home
            tempdir_parent = largerFreeSpace("/tmp", "/home");
        } else {
            tempdir_parent = largerFreeSpace("/tmp", "/home", snapshot_dir);
        }
    }
    if (tempdir_parent == "/home") { // replace /home with user home path
        tempdir_parent = "/home/" + Cmd().getOut("logname", true).trimmed();
    }
    tmpdir.reset(new QTemporaryDir(tempdir_parent + "/mx-snapshot-XXXXXXXX"));
    if (!tmpdir->isValid()) {
        qDebug() << QObject::tr("Could not create temp directory. ") + tmpdir.data()->path();
        return false;
    }
    work_dir = tmpdir.data()->path();
    free_space_work = getFreeSpace(work_dir);

    QDir().mkpath(work_dir + "/iso-template/antiX");
    qDebug() << "Work directory is placed in" << tempdir_parent;
    return true;
}

QString Settings::getEditor() const
{
    QString editor = gui_editor;
    QString desktop_file;
    QProcess proc;
    // if specified editor doesn't exist get the default one
    if (editor.isEmpty() || QStandardPaths::findExecutable(editor, {path}).isEmpty()) {
        QString default_editor = Cmd().getOut("xdg-mime query default text/plain");
        // find first app with .desktop name that matches default_editors
        desktop_file
            = QStandardPaths::locate(QStandardPaths::ApplicationsLocation, default_editor, QStandardPaths::LocateFile);
        QFile file(desktop_file);
        if (file.open(QIODevice::ReadOnly)) {
            QString line;
            while (!file.atEnd()) {
                line = file.readLine();
                if (line.contains(QRegularExpression("^Exec="))) {
                    break;
                }
            }
            file.close();
            editor = line.remove(QRegularExpression("^Exec=|%u|%U|%f|%F|%c|%C|-b")).trimmed();
        }
        if (editor.isEmpty()) { // use nano as backup editor
            editor = "nano";
        }
    }
    bool isEditorThatElevates = QRegularExpression("(kate|kwrite|featherpad)$").match(editor).hasMatch();
    bool isElectronBased = QRegularExpression("(atom\\.desktop|code\\.desktop)$").match(desktop_file).hasMatch();
    bool isCliEditor = QRegularExpression("nano|vi|vim|nvim|micro|emacs").match(editor).hasMatch();

    QString elevate {QFile::exists("/usr/bin/pkexec") ? "/usr/bin/pkexec" : "/usr/bin/gksu"};
    if (isEditorThatElevates || isElectronBased) {
        return editor;
    } else if (isCliEditor) {
        return "x-terminal-emulator -e " + elevate + " " + editor;
    } else {
        return elevate + " env DISPLAY=$DISPLAY XAUTHORITY=$XAUTHORITY " + editor;
    }
}

// return the size of the snapshot folder
QString Settings::getSnapshotSize() const
{
    if (QFileInfo::exists(snapshot_dir)) {
        QString cmd
            = QString("find \"%1\" -maxdepth 1 -type f -name '*.iso' -exec du -shc {} + |tail -1 |awk '{print $1}'")
                  .arg(snapshot_dir);
        auto size = Cmd().getOut(cmd);
        if (!size.isEmpty()) {
            return size;
        }
    }
    return "0";
}

// Number of snapshots in snapshot_dir
int Settings::getSnapshotCount() const
{
    if (QFileInfo::exists(snapshot_dir)) {
        QFileInfoList list = QDir(snapshot_dir).entryInfoList(QStringList("*.iso"), QDir::Files);
        return list.size();
    }
    return 0;
}

quint64 Settings::getFreeSpace(const QString &path)
{
    bool ok = false;
    quint64 result {};
    if (Cmd().getOut("stat --file-system --format=%T \"" + path + "\"") == "ramfs") {
        result = Cmd().getOut("LC_ALL=C free |awk '/^Mem/ {print $7}'").toULongLong(&ok);
    } else {
        result = Cmd().getOut(QString("df -k --output=avail \"%1\" |tail -n1").arg(path)).toULongLong(&ok);
    }
    if (!ok) {
        qDebug() << "Can't calculate free space on" << path;
        return 0;
    }
    return result;
}

// return the XDG User Directory for each user with different localizations than English
QString Settings::getXdgUserDirs(const QString &folder)
{
    QString result;
    for (const QString &user : qAsConst(users)) {
        QString dir = Cmd().getOutAsRoot("runuser " + user + " -c \"xdg-user-dir " + folder + "\"");
        if (!dir.isEmpty()) {
            if (englishDirs.value(folder) == dir.section("/", -1) || dir == "/home/" + user
                || dir == "/home/" + user + "/") { // skip if English name or of return folder is the home
                                                   // folder (if XDG-USER-DIR not defined)
                continue;
            }
            if (dir.startsWith(QLatin1String("/"))) {
                dir.remove(0, 1); // remove training slash
            }
            (folder == QLatin1String("DESKTOP")) ? dir.append("/!(minstall.desktop)")
                                                 : dir.append("/*\" \"" + dir + "/.*");
            (result.isEmpty()) ? result.append("\" \"" + dir) : result.append(" \"" + dir);
            result.append("\""); // close the quote for each user, will strip the last one before returning;
        }
    }
    result.chop(1); // chop the last quote, will be added later on in addRemoveExclusion
    return result;
}

void Settings::selectKernel()
{
    qDebug() << "+++" << __PRETTY_FUNCTION__ << "+++";
    kernel.remove(QRegularExpression("^/boot/vmlinuz-")); // remove path and part of name if passed as arg
    if (kernel.isEmpty()
        || !QFileInfo::exists("/boot/vmlinuz-" + kernel)) { // if kernel version not passed as arg, or incorrect
        kernel = current_kernel;
        if (!QFileInfo::exists(
                "/boot/vmlinuz-"
                + kernel)) { // if current kernel doesn't exist for some reason (e.g. WSL) in /boot pick latest kernel
            kernel
                = Cmd().getOut("ls -1 /boot/vmlinuz-* |sort |tail -n1").remove(QRegularExpression("^/boot/vmlinuz-"));
            if (!QFileInfo::exists("/boot/vmlinuz-" + kernel)) {
                QString message = QObject::tr("Could not find a usable kernel");
                if (qApp->metaObject()->className() != QLatin1String("QApplication")) {
                    qDebug().noquote() << message;
                }
#ifndef CLI_BUILD
                else {
                    QMessageBox::critical(nullptr, QObject::tr("Error"), message);
                }
#endif
                exit(EXIT_FAILURE);
            }
        }
    }
    // Check if SQUASHFS is available
    if (QProcess::execute("grep", {"-q", "^CONFIG_SQUASHFS=[ym]", "/boot/config-" + kernel}) != 0) {
        QString message = QObject::tr("Current kernel doesn't support Squashfs, cannot continue.");
        if (qApp->metaObject()->className() != QLatin1String("QApplication")) {
            qDebug().noquote() << message;
        }
#ifndef CLI_BUILD
        else {
            QMessageBox::critical(nullptr, QObject::tr("Error"), message);
        }
#endif
        exit(EXIT_FAILURE);
    }
}

void Settings::setVariables()
{
    englishDirs = {
        {"DOCUMENTS", "Documents"}, {"DOWNLOAD", "Downloads"}, {"DESKTOP", "Desktop"},
        {"MUSIC", "Music"},         {"PICTURES", "Pictures"},  {"VIDEOS", "Videos"},
    };

    live = isLive();
    users = listUsers();
    x86 = isi386();

    QString distro_version_file;
    if (QFileInfo::exists("/etc/mx-version")) {
        distro_version_file = "/etc/mx-version";
    } else if (QFileInfo::exists("/etc/antix-version")) {
        distro_version_file = "/etc/antix-version";
    }

    if (QFileInfo::exists("/etc/lsb-release")) {
        project_name = Cmd().getOut("grep -oP '(?<=DISTRIB_ID=).*' /etc/lsb-release");
    } else {
        project_name = Cmd().getOut("lsb_release -i | cut -f2");
    }
    project_name.replace(QLatin1String("\""), QLatin1String(""));
    if (!distro_version_file.isEmpty()) {
        distro_version = Cmd().getOut("cut -f1 -d'_' " + distro_version_file);
        distro_version.remove(QRegularExpression("^" + project_name + "_|^" + project_name + "-"));
    } else {
        distro_version = Cmd().getOut("lsb_release -r | cut -f2");
    }
    full_distro_name = project_name + "-" + distro_version + "_" + QString(x86 ? "386" : "x64");
    release_date = QDate::currentDate().toString("MMMM dd, yyyy");
    if (QFileInfo::exists("/etc/lsb-release")) {
        codename = Cmd().getOut("grep -oP '(?<=DISTRIB_CODENAME=).*' /etc/lsb-release");
    } else {
        codename = Cmd().getOut("lsb_release -c | cut -f2");
    }
    codename.replace(QLatin1String("\""), QLatin1String(""));
    boot_options = readKernelOpts();
}

QString Settings::getFilename() const
{
    qDebug() << "+++" << __PRETTY_FUNCTION__ << "+++";
    if (stamp == QLatin1String("datetime")) {
        return snapshot_basename + "-" + QDateTime::currentDateTime().toString("yyyyMMdd_HHmm") + ".iso";
    } else {
        QString name;
        QDir dir;
        int n = 1;
        do {
            name = snapshot_basename + QString::number(n) + ".iso";
            dir.setPath("\"" + snapshot_dir + "/" + name + "\"");
            n++;
        } while (QFileInfo::exists(dir.absolutePath()));
        return name;
    }
}

quint64 Settings::getLiveRootSpace()
{
    // rootspaceneeded is the size of the linuxfs file * a compression factor + contents of the rootfs.  conservative
    // but fast factors are same as used in live-remaster

    // load some live variables
    QSettings livesettings("/live/config/initrd.out", QSettings::NativeFormat);
    QString sqfile_full = livesettings.value("SQFILE_FULL", "/live/boot-dev/antiX/linuxfs").toString();

    // get compression factor by reading the linuxfs squasfs file, if available
    QString linuxfs_compression_type
        = Cmd().getOut("dd if=" + sqfile_full + " bs=1 skip=20 count=2 status=none 2>/dev/null |od -An -tdI");
    const quint8 default_factor = 30;
    quint8 c_factor = default_factor;
    // gzip, xz, or lz4
    QMap<QString, QString> compression_types
        = {{"1", "gzip"}, {"2", "lzo"}, {"3", "lzma"}, {"4", "xz"}, {"5", "lz4"}, {"6", "zstd"}};
    if (compression_types.contains(linuxfs_compression_type)) {
        c_factor = compression_factor.value(compression_types.value(linuxfs_compression_type));
    } else {
        qWarning() << "Unknown compression type:" << linuxfs_compression_type;
    }
    quint64 rootfs_file_size = 0;
    quint64 linuxfs_file_size
        = Cmd().getOut("df -k /live/linux --output=used --total |tail -n1").toULongLong() * 100 / c_factor;
    if (QFileInfo::exists("/live/persist-root")) {
        rootfs_file_size = Cmd().getOut("df -k /live/persist-root --output=used --total |tail -n1").toULongLong();
    }

    // add rootfs file size to the calculated linuxfs file size.  probaby conservative, as rootfs will likely have some
    // overlap with linuxfs
    return linuxfs_file_size + rootfs_file_size;
}

QString Settings::getUsedSpace()
{
    constexpr float factor = 1024 * 1024;
    QString out = "\n- " + QObject::tr("Used space on / (root): ");
    if (bool ok = false; live) {
        root_size = getLiveRootSpace();
        out += QString::number(static_cast<double>(root_size) / factor, 'f', 2) + "GiB -- " + QObject::tr("estimated");
    } else {
        root_size = Cmd().getOut("df -k --output=used / |tail -n1").toULongLong(&ok);
        if (!ok) {
            return "Can't calculate free space on root";
        }
        out += QString::number(static_cast<double>(root_size) / factor, 'f', 2) + "GiB";
    }
    if (bool ok = false; Cmd().run("mountpoint -q /home")) {
        home_size = Cmd().getOut("df -k --output=used /home |tail -n1").toULongLong(&ok);
        if (!ok) {
            return "Can't calculate free space on /home";
        }
        out.append("\n- " + QObject::tr("Used space on /home: ")
                   + QString::number(static_cast<double>(home_size) / factor, 'f', 2) + "GiB");
    } else {
        home_size = 0; // /home on root
    }
    return out;
}

// Check if running from a 32bit environment
bool Settings::isi386()
{
    return (QSysInfo::currentCpuArchitecture() == QLatin1String("i386"));
}

int Settings::getDebianVerNum()
{
    QFile file("/etc/debian_version");
    QStringList list;
    if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        QTextStream in(&file);
        QString line = in.readLine();
        list = line.split(".");
        file.close();
    } else {
        qCritical() << "Could not open /etc/debian_version:" << file.errorString() << "Assumes Bullseye";
        return Release::Bullseye;
    }
    bool ok = false;
    int ver = list.at(0).toInt(&ok);
    if (ok) {
        return ver;
    } else {
        QString verName = list.at(0).split("/").at(0);
        if (verName == QLatin1String("bullseye")) {
            return Release::Bullseye;
        } else if (verName == QLatin1String("bookworm")) {
            return Release::Bookworm;
        } else {
            qCritical() << "Unknown Debian version:" << ver << "Assumes Bullseye";
            return Release::Bullseye;
        }
    }
}

// Check if running from a live envoronment
bool Settings::isLive()
{
    return (QProcess::execute("mountpoint", {"-q", "/live/aufs"}) == 0);
}

// checks if the directory is on a Linux partition
bool Settings::isOnSupportedPart(const QString &dir)
{
    qDebug() << "+++" << __PRETTY_FUNCTION__ << "+++";
    // Supported partition types (NTFS returns fuseblk)
    QStringList supported_partitions {"ext2/ext3", "btrfs", "jfs", "reiserfs", "xfs", "fuseblk", "ramfs", "tmpfs"};
    QString part_type = Cmd().getOut("stat --file-system --format=%T \"" + dir + "\"");
    qDebug() << "detected partition" << part_type << "supported part:" << supported_partitions.contains(part_type);
    return supported_partitions.contains(part_type);
}

// return the directory that has more free space available
QString Settings::largerFreeSpace(const QString &dir1, const QString &dir2)
{
    qDebug() << "+++" << __PRETTY_FUNCTION__ << "+++";
    if (Cmd().getOut("stat -c '%d' " + dir1) == Cmd().getOut("stat -c '%d' " + dir2)) {
        return dir1;
    }
    quint64 dir1_free = getFreeSpace(dir1);
    quint64 dir2_free = getFreeSpace(dir2);
    return dir1_free >= dir2_free ? dir1 : dir2;
}

// return the directory that has more free space available
QString Settings::largerFreeSpace(const QString &dir1, const QString &dir2, const QString &dir3)
{
    qDebug() << "+++" << __PRETTY_FUNCTION__ << "+++";
    return largerFreeSpace(largerFreeSpace(dir1, dir2), dir3);
}

QString Settings::getFreeSpaceStrings(const QString &path)
{
    constexpr float factor = 1024 * 1024;
    free_space = getFreeSpace(path);
    QString out = QString::number(static_cast<double>(free_space) / factor, 'f', 2) + "GiB";

    qDebug().noquote() << QString("- " + QObject::tr("Free space on %1, where snapshot folder is placed: ").arg(path)
                                  + out)
                       << "\n";

    qDebug().noquote() << QObject::tr(
                              "The free space should be sufficient to hold the compressed data from / and /home\n\n"
                              "      If necessary, you can create more available space\n"
                              "      by removing previous snapshots and saved copies:\n"
                              "      %1 snapshots are taking up %2 of disk space.\n")
                              .arg(QString::number(getSnapshotCount()), getSnapshotSize());
    return out;
}

// Return a list of users that have folders in /home
QStringList Settings::listUsers()
{
    return Cmd().getOut("lslogins --noheadings -u -o user |grep -vw root", true).split("\n");
}

void Settings::excludeItem(const QString &item)
{
    if (item == QObject::tr("Desktop") || item == QLatin1String("Desktop")) {
        excludeDesktop(true);
    }
    if (item == QObject::tr("Documents") || item == QLatin1String("Documents")) {
        excludeDocuments(true);
    }
    if (item == QObject::tr("Downloads") || item == QLatin1String("Downloads")) {
        excludeDownloads(true);
    }
    if (item == QObject::tr("Music") || item == QLatin1String("Music")) {
        excludeMusic(true);
    }
    if (item == QObject::tr("Networks") || item == QLatin1String("Networks")) {
        excludeNetworks(true);
    }
    if (item == QObject::tr("Pictures") || item == QLatin1String("Pictures")) {
        excludePictures(true);
    }
    if (item == QLatin1String("Steam")) {
        excludeSteam(true);
    }
    if (item == QObject::tr("Videos") || item == QLatin1String("Videos")) {
        excludeVideos(true);
    }
    if (item == QLatin1String("VirtualBox")) {
        excludeVirtualBox(true);
    }
}

void Settings::excludeDesktop(bool exclude)
{
    qDebug() << "+++" << __PRETTY_FUNCTION__ << "+++";
    if (exclude) {
        exclusions.setFlag(Exclude::Desktop);
    }
    QString exclusion = "/home/*/Desktop/!(minstall.desktop)" + getXdgUserDirs("DESKTOP");
    addRemoveExclusion(exclude, exclusion);
}

void Settings::excludeDocuments(bool exclude)
{
    qDebug() << "+++" << __PRETTY_FUNCTION__ << "+++";
    if (exclude) {
        exclusions.setFlag(Exclude::Documents);
    }
    QString folder {"home/*/Documents/"};
    QString xdg_name {"DOCUMENTS"};
    QString exclusion = folder + "*\" \"" + folder + ".*" + getXdgUserDirs(xdg_name);
    addRemoveExclusion(exclude, exclusion);
}

void Settings::excludeDownloads(bool exclude)
{
    qDebug() << "+++" << __PRETTY_FUNCTION__ << "+++";
    if (exclude) {
        exclusions.setFlag(Exclude::Downloads);
    }
    QString folder {"home/*/Downloads/"};
    QString xdg_name {"DOWNLOAD"};
    QString exclusion = folder + "*\" \"" + folder + ".*" + getXdgUserDirs(xdg_name);
    addRemoveExclusion(exclude, exclusion);
}

void Settings::excludeMusic(bool exclude)
{
    qDebug() << "+++" << __PRETTY_FUNCTION__ << "+++";
    if (exclude) {
        exclusions.setFlag(Exclude::Music);
    }
    QString folder {"home/*/Music/"};
    QString xdg_name {"MUSIC"};
    QString exclusion = folder + "*\" \"" + folder + ".*" + getXdgUserDirs(xdg_name);
    addRemoveExclusion(exclude, exclusion);
}

void Settings::excludeNetworks(bool exclude)
{
    qDebug() << "+++" << __PRETTY_FUNCTION__ << "+++";
    if (exclude) {
        exclusions.setFlag(Exclude::Networks);
    }
    addRemoveExclusion(exclude, QStringLiteral("/etc/NetworkManager/system-connections/*"));
    addRemoveExclusion(exclude, QStringLiteral("/etc/wicd/*"));
    addRemoveExclusion(exclude, QStringLiteral("/var/lib/connman/*"));
}

void Settings::excludePictures(bool exclude)
{
    qDebug() << "+++" << __PRETTY_FUNCTION__ << "+++";
    if (exclude) {
        exclusions.setFlag(Exclude::Pictures);
    }
    QString folder {"home/*/Pictures/"};
    QString xdg_name {"PICTURES"};
    QString exclusion = folder + "*\" \"" + folder + ".*" + getXdgUserDirs(xdg_name);
    addRemoveExclusion(exclude, exclusion);
}

void Settings::excludeSteam(bool exclude)
{
    qDebug() << "+++" << __PRETTY_FUNCTION__ << "+++";
    if (exclude) {
        exclusions.setFlag(Exclude::Steam);
    }
    addRemoveExclusion(exclude, QStringLiteral("home/*/.steam"));
    addRemoveExclusion(exclude, QStringLiteral("home/*/.local/share/Steam"));
}

void Settings::excludeSwapFile()
{
    QFile file("/etc/fstab");
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        qWarning() << "Failed to open /etc/fstab";
        return;
    }

    while (!file.atEnd()) {
        QString line = QString::fromUtf8(file.readLine()).trimmed();
        if (line.startsWith("/") && !line.startsWith("/dev/")) {
            QStringList parts = line.split(QRegExp("\\s+"));
            if (parts.size() > 3) {
                if (parts.at(2) == "swap") {
                    addRemoveExclusion(true, parts[0].remove(0, 1));
                }
            }
        }
    }
}

void Settings::excludeVideos(bool exclude)
{
    qDebug() << "+++" << __PRETTY_FUNCTION__ << "+++";
    if (exclude) {
        exclusions.setFlag(Exclude::Videos);
    }
    QString folder {"home/*/Videos/"};
    QString xdg_name {"VIDEOS"};
    QString exclusion = folder + "*\" \"" + folder + ".*" + getXdgUserDirs(xdg_name);
    addRemoveExclusion(exclude, exclusion);
}

void Settings::excludeVirtualBox(bool exclude)
{
    qDebug() << "+++" << __PRETTY_FUNCTION__ << "+++";
    if (exclude) {
        exclusions.setFlag(Exclude::VirtualBox);
    }
    addRemoveExclusion(exclude, QStringLiteral("home/*/VirtualBox VMs"));
}

// Load settings from config file
void Settings::loadConfig()
{
    config_file.setFileName("/etc/" + qApp->applicationName() + ".conf");
    QSettings settingsSystem(config_file.fileName(), QSettings::IniFormat);
    QSettings settingsUser;

    // Read all keys from system settings
    settingsSystem.beginGroup("");
    QStringList systemKeys = settingsSystem.allKeys();
    settingsSystem.endGroup();

    // Merge system settings into user settings
    foreach (const QString &key, systemKeys) {
        if (!settingsUser.contains(key)) {
            QVariant value = settingsSystem.value(key);
            settingsUser.setValue(key, value);
        }
    }

    session_excludes = QLatin1String("");
    snapshot_dir = settingsUser.value("snapshot_dir", "/home/snapshot").toString();
    if (!snapshot_dir.endsWith(QLatin1String("/snapshot"))) {
        snapshot_dir += (snapshot_dir.endsWith(QLatin1String("/")) ? "snapshot" : "/snapshot");
    }
    snapshot_excludes.setFileName(
        settingsUser
            .value("snapshot_excludes", "/usr/local/share/excludes/" + qApp->applicationName() + "-exclude.list")
            .toString());
    snapshot_basename = settingsUser.value("snapshot_basename", "snapshot").toString();
    make_md5sum = settingsUser.value("make_md5sum", "no").toString() != QLatin1String("no");
    make_sha512sum = settingsUser.value("make_sha512sum", "no").toString() != QLatin1String("no");
    make_isohybrid = settingsUser.value("make_isohybrid", "yes").toString() == QLatin1String("yes");
    compression = settingsUser.value("compression", "zstd").toString();
    mksq_opt = settingsUser.value("mksq_opt").toString();
    edit_boot_menu = settingsUser.value("edit_boot_menu", "no").toString() != QLatin1String("no");
    gui_editor = settingsUser.value("gui_editor").toString();
    stamp = settingsUser.value("stamp").toString();
    force_installer = settingsUser.value("force_installer", "true").toBool();
    tempdir_parent = settingsUser.value("workdir").toString();
    cores = settingsUser.value("cores", max_cores).toUInt();
    throttle = settingsUser.value("throttle", 0).toUInt();
    reset_accounts = false;
}

void Settings::excludeAll()
{
    qDebug() << "+++" << __PRETTY_FUNCTION__ << "+++";
    excludeDesktop(true);
    excludeDocuments(true);
    excludeDownloads(true);
    excludeMusic(true);
    excludeNetworks(true);
    excludePictures(true);
    excludeSteam(true);
    excludeVideos(true);
    excludeVirtualBox(true);
}

void Settings::otherExclusions()
{
    qDebug() << "+++" << __PRETTY_FUNCTION__ << "+++";
    // add exclusions snapshot and work dirs
    addRemoveExclusion(true, snapshot_dir);
    addRemoveExclusion(true, work_dir);

    if (reset_accounts) {
        addRemoveExclusion(true, QStringLiteral("/etc/minstall.conf"));
        // exclude /etc/localtime if link and timezone not America/New_York
        if (Cmd().run("test -L /etc/localtime")
            && Cmd().getOut("cat /etc/timezone") != QLatin1String("America/New_York")) {
            addRemoveExclusion(true, "/etc/localtime");
        }
    }
    excludeSwapFile();
}

void Settings::processArgs(const QCommandLineParser &arg_parser)
{
    shutdown = arg_parser.isSet("shutdown");
    kernel = arg_parser.value("kernel");
    preempt = arg_parser.isSet("preempt");
    QDir dir;
    if (!arg_parser.value("directory").isEmpty() && QFileInfo::exists(arg_parser.value("directory"))) {
        dir.setPath(arg_parser.value("directory"));
        snapshot_dir = dir.absolutePath() + "/snapshot";
    }

    if (!arg_parser.value("workdir").isEmpty() && QFileInfo::exists(arg_parser.value("workdir"))) {
        dir.setPath(arg_parser.value("workdir"));
        tempdir_parent = dir.absolutePath();
    }

    if (!arg_parser.value(QStringLiteral("file")).isEmpty()) {
        snapshot_name
            = arg_parser.value(QStringLiteral("file"))
              + (arg_parser.value(QStringLiteral("file")).endsWith(QLatin1String(".iso")) ? QString()
                                                                                          : QStringLiteral(".iso"));
    } else {
        snapshot_name = getFilename();
    }
    if (QFile::exists(snapshot_dir + "/" + snapshot_name)) {
        QString message
            = QObject::tr("Output file %1 already exists. Please use another file name, or delete the existent file.")
                  .arg(snapshot_dir + "/" + snapshot_name);
        if (qApp->metaObject()->className() != QLatin1String("QApplication")) {
            qDebug().noquote() << message;
        }
#ifndef CLI_BUILD
        else {
            QMessageBox::critical(nullptr, QObject::tr("Error"), message);
        }
#endif
        exit(EXIT_FAILURE);
    }
    reset_accounts = arg_parser.isSet("reset");
    if (reset_accounts) {
        excludeAll();
    }
    if (arg_parser.isSet("month")) {
        reset_accounts = true;
    }
    if (arg_parser.isSet("checksums")) {
        make_sha512sum = make_md5sum = true;
    }
    if (arg_parser.isSet("month")) {
        make_sha512sum = true;
        make_md5sum = false;
    }
    if (arg_parser.isSet("no-checksums")) {
        make_sha512sum = make_md5sum = false;
    }
    if (!arg_parser.value("compression").isEmpty()) {
        compression = arg_parser.value("compression");
    }
    if (!arg_parser.value("compression-level").isEmpty()) {
        mksq_opt = arg_parser.value("compression-level");
    }
    if (!arg_parser.value("cores").isEmpty()) {
        bool ok {false};
        uint val = arg_parser.value("cores").toUInt(&ok);
        if (!ok || val == 0 || val > max_cores) {
            qDebug() << "Invalid number of cores argument, will use the default:" << cores;
        } else {
            cores = val;
        }
    }
    if (!arg_parser.value("throttle").isEmpty()) {
        bool ok {false};
        uint val = arg_parser.value("throttle").toUInt(&ok);
        if (!ok || val > 99) {
            qDebug() << "Invalid argument for throttle, will use the default:" << throttle;
        } else {
            throttle = val;
        }
    }
    selectKernel();
}

void Settings::processExclArgs(const QCommandLineParser &arg_parser)
{
    if (!arg_parser.values("exclude").isEmpty()) {
        QStringList options = arg_parser.values("exclude");
        QStringList valid_options {"Desktop",  "Documents", "Downloads", "Music",     "Networks",
                                   "Pictures", "Steam",     "Videos",    "VirtualBox"};
        for (const QString &option : options) {
            if (valid_options.contains(option)) {
                excludeItem(option);
            }
        }
    }
}

// Use script to return useful kernel options
QString Settings::readKernelOpts()
{
    return Cmd().getOut((QString("/usr/share/%1/scripts/snapshot-bootparameter.sh | tr '\n' ' '")
                             .arg(QCoreApplication::applicationName())));
}

void Settings::setMonthlySnapshot(const QCommandLineParser &arg_parser)
{
    QString name;
    if (QFileInfo::exists(QStringLiteral("/etc/mx-version"))) {
        name = Cmd().getOut("cat /etc/mx-version |cut -f1 -d' '");
    } else {
        qDebug() << "/etc/mx-version not found. Not MX Linux?";
        name = "MX_" + QString(x86 ? "386" : "x64");
    }
    if (arg_parser.value("file").isEmpty()) {
        auto month = QDate::currentDate().toString("MMMM");
        auto suffix = name.section("_", 1, 1);
        if (qgetenv("DESKTOP_SESSION") == "plasma") {
            suffix = "KDE";
        }
        snapshot_name = name.section("_", 0, 0) + "_" + month + "_" + suffix + ".iso";
    }
    if (QFile::exists(snapshot_dir + "/" + snapshot_name)) {
        QString message
            = QObject::tr("Output file %1 already exists. Please use another file name, or delete the existent file.")
                  .arg(snapshot_dir + "/" + snapshot_name);
        if (qApp->metaObject()->className() != QLatin1String("QApplication")) {
            qDebug().noquote() << message;
        }
#ifndef CLI_BUILD
        else {
            QMessageBox::critical(nullptr, QObject::tr("Error"), message);
        }
#endif
        exit(EXIT_FAILURE);
    }
    if (arg_parser.value("compression").isEmpty()) {
        compression = "zstd";
    }
    reset_accounts = true;
    boot_options.remove("toram");
    excludeAll();
}
