/***************************************************************************
 *   Copyright (c) 2021 Chris Hennes <chennes@pioneerlibrarysystem.org>    *
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Library General Public           *
 *   License as published by the Free Software Foundation; either          *
 *   version 2 of the License, or (at your option) any later version.      *
 *                                                                         *
 *   This library  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 Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this library; see the file COPYING.LIB. If not,    *
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
 *   Suite 330, Boston, MA  02111-1307, USA                                *
 *                                                                         *
 ***************************************************************************/


#include <memory>
#include <string_view>
#include <mutex>


#include <filesystem>
#include <boost/algorithm/string.hpp>
#include <QDir>

#include "PreferencePackManager.h"
#include "App/Metadata.h"
#include "Base/Parameter.h"
#include "Base/Interpreter.h"
#include "Base/Console.h"
#include "DockWindowManager.h"
#include "ToolBarManager.h"

#include <Application.h>
#include <App/Application.h>

#include <ctime>  // For generating a timestamped filename
#include <Base/ServiceProvider.h>
#include <Dialogs/DlgThemeEditor.h>


using namespace Gui;
using namespace xercesc;
namespace fs = std::filesystem;

static std::filesystem::path getSavedPrefPacksPath()
{
    return fs::path(Base::FileInfo::stringToPath(App::Application::getUserAppDataDir()))
        / "SavedPreferencePacks";
}

static std::filesystem::path getResourcePrefPacksPath()
{
    return fs::path(Base::FileInfo::stringToPath(App::Application::getResourceDir())) / "Gui"
        / "PreferencePacks";
}

PreferencePack::PreferencePack(const fs::path& path, const App::Metadata& metadata)
    : _path(path)
    , _metadata(metadata)
{
    if (!fs::exists(_path)) {
        throw std::runtime_error {"Cannot access " + path.string()};
    }

    auto qssPaths = QDir::searchPaths(QStringLiteral("qss"));
    auto cssPaths = QDir::searchPaths(QStringLiteral("css"));
    auto overlayPaths = QDir::searchPaths(QStringLiteral("overlay"));

    qssPaths.append(QString::fromStdString(Base::FileInfo::pathToString(_path)));
    cssPaths.append(QString::fromStdString(Base::FileInfo::pathToString(_path)));
    overlayPaths.append(QString::fromStdString(Base::FileInfo::pathToString(_path) + "/overlay"));

    QDir::setSearchPaths(QStringLiteral("qss"), qssPaths);
    QDir::setSearchPaths(QStringLiteral("css"), cssPaths);
    QDir::setSearchPaths(QStringLiteral("overlay"), overlayPaths);
}

std::string PreferencePack::name() const
{
    return _metadata.name();
}

bool PreferencePack::apply() const
{
    // Run the pre.FCMacro, if it exists: if it raises an exception, abort the process
    auto preMacroPath = _path / "pre.FCMacro";
    if (fs::exists(preMacroPath)) {
        try {
            Base::Interpreter().runFile(Base::FileInfo::pathToString(preMacroPath).c_str(), false);
        }
        catch (...) {
            Base::Console().message(
                "PreferencePack application aborted by the preferencePack's pre.FCMacro"
            );
            return false;
        }
    }

    // Back up the old config file
    auto savedPreferencePacksDirectory = getSavedPrefPacksPath();
    auto backupFile = savedPreferencePacksDirectory / "user.cfg.backup";
    try {
        fs::remove(backupFile);
    }
    catch (...) {
    }
    App::GetApplication().GetUserParameter().SaveDocument(
        Base::FileInfo::pathToString(backupFile).c_str()
    );

    // Apply the config settings
    applyConfigChanges();

    // Run the Post.FCMacro, if it exists
    auto postMacroPath = _path / "post.FCMacro";
    if (fs::exists(postMacroPath)) {
        try {
            Base::Interpreter().runFile(Base::FileInfo::pathToString(postMacroPath).c_str(), false);
        }
        catch (...) {
            Base::Console().message(
                "PreferencePack application reverted by the preferencePack's post.FCMacro"
            );
            App::GetApplication().GetUserParameter().LoadDocument(
                Base::FileInfo::pathToString(backupFile).c_str()
            );
            return false;
        }
    }

    return true;
}


App::Metadata Gui::PreferencePack::metadata() const
{
    return _metadata;
}

void PreferencePack::applyConfigChanges() const
{
    auto configFile = _path / (_metadata.name() + ".cfg");
    if (fs::exists(configFile)) {
        auto newParameters = ParameterManager::Create();
        newParameters->LoadDocument(Base::FileInfo::pathToString(configFile).c_str());
        auto baseAppGroup = App::GetApplication().GetUserParameter().GetGroup("BaseApp");
        newParameters->GetGroup("BaseApp")->insertTo(baseAppGroup);
    }
}

PreferencePackManager::PreferencePackManager()
    : _preferencePackPaths(modPaths())
{
    auto savedPath = getSavedPreferencePacksPath();
    auto resourcePath = getResourcePreferencePacksPath();
    _preferencePackPaths.insert(_preferencePackPaths.begin(), resourcePath);
    _preferencePackPaths.push_back(savedPath);
    rescan();

    // Housekeeping:
    DeleteOldBackups();
}

void PreferencePackManager::rescan()
{
    std::lock_guard<std::mutex> lock(_mutex);
    _preferencePacks.clear();
    for (const auto& path : _preferencePackPaths) {
        if (fs::exists(path) && fs::is_directory(path)) {
            FindPreferencePacksInPackage(path);
            for (const auto& mod : fs::directory_iterator(path)) {
                if (fs::is_directory(mod)) {
                    FindPreferencePacksInPackage(mod);
                }
            }
        }
    }
}

void Gui::PreferencePackManager::AddPackToMetadata(const std::string& packName) const
{
    std::lock_guard<std::mutex> lock(_mutex);
    auto savedPreferencePacksDirectory = getSavedPreferencePacksPath();
    fs::path preferencePackDirectory(savedPreferencePacksDirectory / packName);
    if (fs::exists(preferencePackDirectory) && !fs::is_directory(preferencePackDirectory)) {
        throw std::runtime_error(
            "Cannot create " + savedPreferencePacksDirectory.string()
            + ": file with that name exists already"
        );
    }

    if (!fs::exists(preferencePackDirectory)) {
        fs::create_directories(preferencePackDirectory);
    }

    // Create or update the saved user preferencePacks package.xml metadata file
    std::unique_ptr<App::Metadata> metadata;
    if (fs::exists(savedPreferencePacksDirectory / "package.xml")) {
        metadata = std::make_unique<App::Metadata>(savedPreferencePacksDirectory / "package.xml");
    }
    else {
        metadata = std::make_unique<App::Metadata>();
        metadata->setName("User-Saved Preference Packs");
        std::stringstream str;
        str << "Generated automatically -- edits may be lost when saving new preference packs. To "
            << "distribute one or more of these packs:\n"
            << "    1) copy the entire SavedPreferencePacks directory to a convenient location,\n"
            << "    2) rename the directory (usually to the name of the preference pack you are "
            << "distributing),\n"
            << "    3) delete any subfolders containing packs you don't want to distribute,\n"
            << "    4) use git to initialize the directory as a git repository,\n"
            << "    5) push it to a remote git host,\n"
            << "    6) activate Developer Mode in the Addon Manager,\n"
            << "    7) use Developer Tools in the Addon Manager to update the metadata file,\n"
            << "    8) add, commit, and push the updated package.xml file,\n"
            << "    9) add your remote host to the custom repositories list in the Addon Manager"
            << " preferences,\n"
            << "   10) use the Addon Manager to install your preference pack locally for testing.";
        metadata->setDescription(str.str());
        metadata->addLicense(App::Meta::License("All Rights Reserved", fs::path()));
    }
    for (const auto& item : metadata->content()) {
        if (item.first == "preferencepack") {
            if (item.second.name() == packName) {
                // A pack with this name exists already, bail out
                return;
            }
        }
    }
    App::Metadata newPreferencePackMetadata;
    newPreferencePackMetadata.setName(packName);

    metadata->addContentItem("preferencepack", newPreferencePackMetadata);
    metadata->write(savedPreferencePacksDirectory / "package.xml");
}

void Gui::PreferencePackManager::importConfig(
    const std::string& packName,
    const std::filesystem::path& path
)
{
    AddPackToMetadata(packName);

    auto savedPreferencePacksDirectory = getSavedPreferencePacksPath();
    auto cfgFilename = savedPreferencePacksDirectory / packName / (packName + ".cfg");
    fs::copy_file(path, cfgFilename, fs::copy_options::overwrite_existing);
    rescan();
}

// TODO(Shvedov): Is this suitable place for this method? It is more generic,
// and maybe more suitable place at Application?
std::vector<std::filesystem::path> Gui::PreferencePackManager::modPaths() const
{
    auto userModPath = fs::path(Base::FileInfo::stringToPath(App::Application::getUserAppDataDir()))
        / "Mod";

    auto& config = App::Application::Config();
    auto additionalModules = config.find("AdditionalModulePaths");
    std::vector<std::filesystem::path> result;

    if (additionalModules != config.end()) {
        boost::split(result, additionalModules->second, boost::is_any_of(";"), boost::token_compress_on);
    }
    result.emplace_back(userModPath);
    return result;
}

std::filesystem::path Gui::PreferencePackManager::getSavedPreferencePacksPath() const
{
    return getSavedPrefPacksPath();
}

std::filesystem::path Gui::PreferencePackManager::getResourcePreferencePacksPath() const
{
    return getResourcePrefPacksPath();
}

std::vector<std::string> Gui::PreferencePackManager::getPacksFromDirectory(const fs::path& path) const
{
    std::vector<std::string> results;
    auto packageMetadataFile = path / "package.xml";
    if (fs::exists(packageMetadataFile) && fs::is_regular_file(packageMetadataFile)) {
        try {
            App::Metadata metadata(packageMetadataFile);
            auto content = metadata.content();
            for (const auto& item : content) {
                if (item.first == "preferencepack") {
                    results.push_back(item.second.name());
                }
            }
        }
        catch (...) {
            // Failed to read the metadata, or to create the preferencePack based on it...
            Base::Console().error(("Failed to read " + packageMetadataFile.string()).c_str());
        }
    }
    return results;
}

void Gui::PreferencePackManager::FindPreferencePacksInPackage(const fs::path& mod)
{
    try {
        TryFindPreferencePacksInPackage(mod);
    }
    catch (const std::exception& e) {
        Base::Console().error("%s\n", e.what());
    }
    catch (...) {
        // Failed to read the metadata, or to create the preferencePack based on it...
        auto packageMetadataFile = mod / "package.xml";
        Base::Console().error(
            "Failed to read %s\n",
            Base::FileInfo::pathToString(packageMetadataFile).c_str()
        );
    }
}

void PreferencePackManager::TryFindPreferencePacksInPackage(const std::filesystem::path& mod)
{
    auto packageMetadataFile = mod / "package.xml";
    static const auto modDirectory
        = fs::path(Base::FileInfo::stringToPath(App::Application::getUserAppDataDir())) / "Mod"
        / "SavedPreferencePacks";
    static const auto resourcePath = getResourcePreferencePacksPath();

    if (fs::exists(packageMetadataFile) && fs::is_regular_file(packageMetadataFile)) {
        App::Metadata metadata(packageMetadataFile);
        auto content = metadata.content();
        auto basename = Base::FileInfo::pathToString(mod.filename());
        if (mod == modDirectory) {
            basename = "##USER_SAVED##";
        }
        else if (mod == resourcePath) {
            basename = "##BUILT_IN##";
        }
        for (const auto& item : content) {
            if (item.first == "preferencepack") {
                if (isVisible(basename, item.second.name())) {
                    PreferencePack newPreferencePack(mod / item.second.name(), item.second);
                    _preferencePacks.insert(
                        std::make_pair(newPreferencePack.name(), newPreferencePack)
                    );
                }
            }
        }
    }
}

std::vector<std::string> PreferencePackManager::preferencePackNames() const
{
    std::lock_guard<std::mutex> lock(_mutex);
    std::vector<std::string> names;
    for (const auto& preferencePack : _preferencePacks) {
        names.push_back(preferencePack.first);
    }
    return names;
}

std::map<std::string, PreferencePack> Gui::PreferencePackManager::preferencePacks() const
{
    return _preferencePacks;
}

bool PreferencePackManager::apply(const std::string& preferencePackName) const
{
    std::lock_guard<std::mutex> lock(_mutex);
    if (auto preferencePack = _preferencePacks.find(preferencePackName);
        preferencePack != _preferencePacks.end()) {
        BackupCurrentConfig();
        bool wasApplied = preferencePack->second.apply();
        if (wasApplied) {
            // If the visibility state of the dock windows was changed we have to manually reload
            // their state
            Gui::DockWindowManager* pDockMgr = Gui::DockWindowManager::instance();
            pDockMgr->loadState();

            // Same goes for toolbars:
            Gui::ToolBarManager* pToolbarMgr = Gui::ToolBarManager::getInstance();
            pToolbarMgr->restoreState();

            // TODO: Are there other things that have to be manually triggered?
        }
        return wasApplied;
    }
    else {
        throw std::runtime_error("No such Preference Pack: " + preferencePackName);
    }
}

static std::string findUnusedName(const std::string& basename, ParameterGrp::handle parent)
{
    int i = 1;
    while (true) {
        std::ostringstream nameToTest;
        nameToTest << basename << "_" << i;
        if (!parent->HasGroup(nameToTest.str().c_str())) {
            return nameToTest.str();
        }
        ++i;
    }
}

bool PreferencePackManager::isVisible(
    const std::string& addonName,
    const std::string& preferencePackName
) const
{
    if (addonName.empty() || preferencePackName.empty()) {
        return true;
    }

    auto pref = App::GetApplication().GetParameterGroupByPath(
        "User parameter:BaseApp/Preferences/General/HiddenPreferencePacks"
    );
    auto hiddenPacks = pref->GetGroups();
    auto hiddenPack = std::find_if(
        hiddenPacks.begin(),
        hiddenPacks.end(),
        [addonName, preferencePackName](ParameterGrp::handle handle) {
            return (handle->GetASCII("addonName", "") == addonName)
                && (handle->GetASCII("preferencePackName", "") == preferencePackName);
        }
    );
    if (hiddenPack == hiddenPacks.end()) {
        return true;
    }
    else {
        return false;
    }
}

void PreferencePackManager::toggleVisibility(
    const std::string& addonName,
    const std::string& preferencePackName
)
{
    if (preferencePackName.empty()) {
        return;
    }
    auto pref = App::GetApplication().GetParameterGroupByPath(
        "User parameter:BaseApp/Preferences/General/HiddenPreferencePacks"
    );
    auto hiddenPacks = pref->GetGroups();
    auto hiddenPack = std::find_if(
        hiddenPacks.begin(),
        hiddenPacks.end(),
        [addonName, preferencePackName](ParameterGrp::handle handle) {
            return (handle->GetASCII("addonName", "") == addonName)
                && (handle->GetASCII("preferencePackName", "") == preferencePackName);
        }
    );
    if (hiddenPack == hiddenPacks.end()) {
        auto name = findUnusedName("PreferencePack", pref);
        auto group = pref->GetGroup(name.c_str());
        group->SetASCII("addonName", addonName.c_str());
        group->SetASCII("preferencePackName", preferencePackName.c_str());
    }
    else {
        auto groupName = (*hiddenPack)->GetGroupName();
        hiddenPacks.clear();  // To decrement the reference count of the group we are about the remove...
        pref->RemoveGrp(groupName);
    }
    rescan();
}

void Gui::PreferencePackManager::deleteUserPack(const std::string& name)
{
    if (name.empty()) {
        return;
    }
    auto savedPreferencePacksDirectory = getSavedPreferencePacksPath();
    auto savedPath = savedPreferencePacksDirectory / name;
    std::unique_ptr<App::Metadata> metadata;
    if (fs::exists(savedPreferencePacksDirectory / "package.xml")) {
        metadata = std::make_unique<App::Metadata>(savedPreferencePacksDirectory / "package.xml");
    }
    else {
        throw std::runtime_error("Lost the user-saved preference packs metadata file!");
    }
    metadata->removeContentItem("preferencepack", name);
    metadata->write(savedPreferencePacksDirectory / "package.xml");
    if (fs::exists(savedPath)) {
        fs::remove_all(savedPath);
    }
    rescan();
}

static void copyTemplateParameters(
    Base::Reference<ParameterGrp> templateGroup,
    const std::string& path,
    Base::Reference<ParameterGrp> outputGroup
)
{
    auto userParameterHandle = App::GetApplication().GetParameterGroupByPath(path.c_str());

    // Ensure that the DockWindowManager has saved its current state:
    Gui::DockWindowManager* pDockMgr = Gui::DockWindowManager::instance();
    pDockMgr->saveState();

    // Do the same for ToolBars
    Gui::ToolBarManager* pToolbarMgr = Gui::ToolBarManager::getInstance();
    pToolbarMgr->saveState();

    auto boolMap = templateGroup->GetBoolMap();
    for (const auto& kv : boolMap) {
        auto currentValue = userParameterHandle->GetBool(kv.first.c_str(), kv.second);
        outputGroup->SetBool(kv.first.c_str(), currentValue);
    }

    auto intMap = templateGroup->GetIntMap();
    for (const auto& kv : intMap) {
        auto currentValue = userParameterHandle->GetInt(kv.first.c_str(), kv.second);
        outputGroup->SetInt(kv.first.c_str(), currentValue);
    }

    auto uintMap = templateGroup->GetUnsignedMap();
    for (const auto& kv : uintMap) {
        auto currentValue = userParameterHandle->GetUnsigned(kv.first.c_str(), kv.second);
        outputGroup->SetUnsigned(kv.first.c_str(), currentValue);
    }

    auto floatMap = templateGroup->GetFloatMap();
    for (const auto& kv : floatMap) {
        auto currentValue = userParameterHandle->GetFloat(kv.first.c_str(), kv.second);
        outputGroup->SetFloat(kv.first.c_str(), currentValue);
    }

    auto asciiMap = templateGroup->GetASCIIMap();
    for (const auto& kv : asciiMap) {
        auto currentValue = userParameterHandle->GetASCII(kv.first.c_str(), kv.second.c_str());
        outputGroup->SetASCII(kv.first.c_str(), currentValue.c_str());
    }

    // Recurse...
    auto templateSubgroups = templateGroup->GetGroups();
    for (auto& templateSubgroup : templateSubgroups) {
        std::string sgName = templateSubgroup->GetGroupName();
        auto outputSubgroupHandle = outputGroup->GetGroup(sgName.c_str());
        copyTemplateParameters(templateSubgroup, path + "/" + sgName, outputSubgroupHandle);
    }
}

static void copyTemplateParameters(/*const*/ ParameterManager& templateParameterManager,
                                   ParameterManager& outputParameterManager)
{
    auto groups = templateParameterManager.GetGroups();
    for (auto& group : groups) {
        std::string name = group->GetGroupName();
        auto groupHandle = outputParameterManager.GetGroup(name.c_str());
        copyTemplateParameters(group, "User parameter:" + name, groupHandle);
    }
}

void PreferencePackManager::save(
    const std::string& name,
    const std::string& directory,
    const std::vector<TemplateFile>& templates
)
{
    if (templates.empty()) {
        return;
    }


    // Create the config file
    auto outputParameterManager = ParameterManager::Create();
    outputParameterManager->CreateDocument();
    for (const auto& t : templates) {
        auto templateParameterManager = ParameterManager::Create();
        templateParameterManager->LoadDocument(Base::FileInfo::pathToString(t.path).c_str());
        copyTemplateParameters(*templateParameterManager, *outputParameterManager);
    }

    std::string cfgFilename;
    if (directory.empty()) {
        AddPackToMetadata(name);
        auto savedPreferencePacksDirectory = getSavedPreferencePacksPath();
        cfgFilename = Base::FileInfo::pathToString(
            savedPreferencePacksDirectory / name / (name + ".cfg")
        );
    }
    else {
        cfgFilename = Base::FileInfo::pathToString(fs::path(directory) / (name + ".cfg"));
    }
    outputParameterManager->SaveDocument(cfgFilename.c_str());
}

static std::vector<fs::path> scanForTemplateFolders(const std::string& groupName, const fs::path& entry)
{
    // From this location, find the folder(s) called "PreferencePackTemplates"
    std::vector<fs::path> templateFolders;
    if (fs::exists(entry)) {
        if (fs::is_directory(entry)) {
            if (entry.filename() == "PreferencePackTemplates"
                || entry.filename() == "preference_pack_templates") {
                templateFolders.push_back(entry);
            }
            else {
                std::string subgroupName = groupName + "/"
                    + Base::FileInfo::pathToString(entry.filename());
                for (const auto& subentry : fs::directory_iterator(entry)) {
                    auto contents = scanForTemplateFolders(subgroupName, subentry);
                    std::copy(contents.begin(), contents.end(), std::back_inserter(templateFolders));
                }
            }
        }
    }
    return templateFolders;
}

static std::vector<PreferencePackManager::TemplateFile> scanForTemplateFiles(
    const std::string& groupName,
    const fs::path& entry
)
{
    auto templateFolders = scanForTemplateFolders(groupName, entry);

    std::vector<PreferencePackManager::TemplateFile> templateFiles;
    for (const auto& templateDir : templateFolders) {
        if (!fs::exists(templateDir) || !fs::is_directory(templateDir)) {
            continue;
        }
        for (const auto& entry : fs::directory_iterator(templateDir)) {
            if (entry.path().extension() == ".cfg") {
                auto name = Base::FileInfo::pathToString(entry.path().filename().stem());
                std::replace(name.begin(), name.end(), '_', ' ');
                // Make sure we don't insert the same thing twice...
                if (std::find_if(
                        templateFiles.begin(),
                        templateFiles.end(),
                        [groupName, name](const auto& rhs) -> bool {
                            return groupName == rhs.group && name == rhs.name;
                        }
                    )
                    != templateFiles.end()) {
                    continue;
                }
                templateFiles.push_back({groupName, name, entry});
            }
        }
    }
    return templateFiles;
}

std::vector<PreferencePackManager::TemplateFile> PreferencePackManager::templateFiles(bool rescan)
{
    std::lock_guard<std::mutex> lock(_mutex);
    if (!_templateFiles.empty() && !rescan) {
        return _templateFiles;
    }

    // Locate all of the template files available on this system
    // Template files end in ".cfg" -- They are located in:
    // * $INSTALL_DIR/data/Gui/PreferencePackTemplates/(Appearance|Behavior)/*
    // * $DATA_DIR/Mod/**/PreferencePackTemplates/(Appearance|Behavior)/*
    // (alternate spellings are provided for packages using CamelCase and snake_case, and both major
    // English dialects)

    auto resourcePath = fs::path(Base::FileInfo::stringToPath(App::Application::getResourceDir()))
        / "Gui";

    std::string group = "Built-In";
    if (fs::exists(resourcePath) && fs::is_directory(resourcePath)) {
        const auto localFiles = scanForTemplateFiles(group, resourcePath);
        std::copy(localFiles.begin(), localFiles.end(), std::back_inserter(_templateFiles));
    }

    for (const auto& modPath : modPaths()) {
        if (fs::exists(modPath) && fs::is_directory(modPath)) {
            for (const auto& mod : fs::directory_iterator(modPath)) {
                group = Base::FileInfo::pathToString(mod.path().filename());
                const auto localFiles = scanForTemplateFiles(group, mod);
                std::copy(localFiles.begin(), localFiles.end(), std::back_inserter(_templateFiles));
            }
        }
    }

    return _templateFiles;
}

void Gui::PreferencePackManager::BackupCurrentConfig() const
{
    auto backupDirectory = getSavedPreferencePacksPath() / "Backups";
    fs::create_directories(backupDirectory);

    // Create a timestamped filename:
    auto time = std::time(nullptr);
    std::ostringstream timestampStream;
    timestampStream << "user." << time << ".cfg";
    auto filename = backupDirectory / timestampStream.str();

    // Save the current config:
    App::GetApplication().GetUserParameter().SaveDocument(
        Base::FileInfo::pathToString(filename).c_str()
    );
}

// FIXME: Replace with more accurate C++20 solution once its usable: https://stackoverflow.com/a/68593141
template<typename TP>
static std::time_t to_time_t(TP tp)
{
    using namespace std::chrono;
    auto sctp = time_point_cast<system_clock::duration>(tp - TP::clock::now() + system_clock::now());
    return system_clock::to_time_t(sctp);
}

void Gui::PreferencePackManager::DeleteOldBackups() const
{
    constexpr auto oneWeek = 60.0 * 60.0 * 24.0 * 7.0;
    const auto now = std::time(nullptr);
    auto backupDirectory = getSavedPreferencePacksPath() / "Backups";
    if (fs::exists(backupDirectory) && fs::is_directory(backupDirectory)) {
        for (const auto& backup : fs::directory_iterator(backupDirectory)) {
            if (std::difftime(now, to_time_t(fs::last_write_time(backup))) > oneWeek) {
                try {
                    fs::remove(backup);
                }
                catch (...) {
                }
            }
        }
    }
}

// FIXME: Replace with more accurate C++20 solution once its usable: https://stackoverflow.com/a/68593141
std::vector<std::filesystem::path> Gui::PreferencePackManager::configBackups() const
{
    std::vector<std::filesystem::path> results;
    auto backupDirectory = getSavedPreferencePacksPath() / "Backups";
    if (fs::exists(backupDirectory) && fs::is_directory(backupDirectory)) {
        for (const auto& backup : fs::directory_iterator(backupDirectory)) {
            results.push_back(backup);
        }
    }
    return results;
}
