/*
    This file is part of Kleopatra, the KDE keymanager
    SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB

    SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
    SPDX-FileContributor: Intevation GmbH

    SPDX-License-Identifier: GPL-2.0-or-later
*/

#include <config-kleopatra.h>

#include "kleopatraapplication.h"

#include "kleopatra_options.h"
#include "mainwindow.h"
#include "settings.h"
#include "systrayicon.h"

#include <conf/configuredialog.h>
#include <conf/groupsconfigdialog.h>
#include <dialogs/smartcardwindow.h>
#include <smartcard/readerstatus.h>

#include <Libkleo/GnuPG>
#include <utils/kdpipeiodevice.h>
#include <utils/log.h>
#include <utils/userinfo.h>

#include <gpgme++/key.h>

#include <Libkleo/Classify>
#include <Libkleo/DnAttributes>
#include <Libkleo/FileSystemWatcher>
#include <Libkleo/KeyCache>
#include <Libkleo/KeyFilterManager>
#include <Libkleo/KeyGroupConfig>
#include <Libkleo/SystemInfo>

#include <uiserver/uiserver.h>

#include "commands/checksumcreatefilescommand.h"
#include "commands/checksumverifyfilescommand.h"
#include "commands/decryptverifyfilescommand.h"
#include "commands/detailscommand.h"
#include "commands/importcertificatefromfilecommand.h"
#include "commands/lookupcertificatescommand.h"
#include "commands/newcertificatesigningrequestcommand.h"
#include "commands/newopenpgpcertificatecommand.h"
#include "commands/signencryptfilescommand.h"

#include "dialogs/updatenotification.h"

#ifdef Q_OS_WIN
#include <utils/winapi-helpers.h>
#endif

#include "kleopatra_debug.h"
#include <KAboutApplicationDialog>
#include <KAboutData>
#include <KLocalizedString>
#include <KMessageBox>
#include <KWindowSystem>

#if __has_include(<KWaylandExtras>)
#include <KWaylandExtras>
#define HAVE_WAYLAND
#endif

#include <QDesktopServices>
#include <QDir>
#include <QFile>
#include <QFocusFrame>
#include <QProxyStyle>
#if QT_CONFIG(graphicseffect)
#include <QGraphicsEffect>
#endif
#include <QPointer>
#include <QSettings>
#include <QStyleOption>
#include <QStylePainter>
#include <QTemporaryDir>

#include <KSharedConfig>

#ifdef Q_OS_WIN
#include <windows.h>
#endif

using namespace Kleo;
using namespace Kleo::Commands;

using namespace Qt::Literals::StringLiterals;

static void add_resources()
{
    const QStringList iconDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, u"libkleopatra/pics"_s, QStandardPaths::LocateDirectory);
    // qCDebug(KLEOPATRA_LOG) << "Adding icon search paths:" << iconDirs;
    QIcon::setFallbackSearchPaths(QIcon::fallbackSearchPaths() << iconDirs);
}

static QList<QByteArray> default_logging_options()
{
    QList<QByteArray> result;
    result.push_back("io");
    return result;
}

namespace
{
class FocusFrame : public QFocusFrame
{
    Q_OBJECT
public:
    using QFocusFrame::QFocusFrame;

protected:
    void paintEvent(QPaintEvent *event) override;
};

static QRect effectiveWidgetRect(const QWidget *w)
{
    // based on QWidgetPrivate::effectiveRectFor
#if QT_CONFIG(graphicseffect)
    if (auto graphicsEffect = w->graphicsEffect(); graphicsEffect && graphicsEffect->isEnabled())
        return graphicsEffect->boundingRectFor(w->rect()).toAlignedRect();
#endif // QT_CONFIG(graphicseffect)
    return w->rect();
}

static QRect clipRect(const QWidget *w)
{
    // based on QWidgetPrivate::clipRect
    if (!w->isVisible()) {
        return QRect();
    }
    QRect r = effectiveWidgetRect(w);
    int ox = 0;
    int oy = 0;
    while (w && w->isVisible() && !w->isWindow() && w->parentWidget()) {
        ox -= w->x();
        oy -= w->y();
        w = w->parentWidget();
        r &= QRect(ox, oy, w->width(), w->height());
    }
    return r;
}

void FocusFrame::paintEvent(QPaintEvent *)
{
    if (!widget()) {
        return;
    }

    QStylePainter p(this);
    QStyleOptionFocusRect option;
    initStyleOption(&option);
    const int vmargin = style()->pixelMetric(QStyle::PM_FocusFrameVMargin, &option);
    const int hmargin = style()->pixelMetric(QStyle::PM_FocusFrameHMargin, &option);
    const QRect rect = clipRect(widget()).adjusted(0, 0, hmargin * 2, vmargin * 2);
    p.setClipRect(rect);
    p.drawPrimitive(QStyle::PE_FrameFocusRect, option);
}
}

class KleopatraApplication::Private
{
    friend class ::KleopatraApplication;
    KleopatraApplication *const q;

public:
    explicit Private(KleopatraApplication *qq)
        : q(qq)
        , ignoreNewInstance(true)
        , firstNewInstance(true)
#ifndef QT_NO_SYSTEMTRAYICON
        , sysTray(nullptr)
#endif
    {
    }
    ~Private()
    {
#ifndef QT_NO_SYSTEMTRAYICON
        delete sysTray;
#endif
    }
    void setUpSysTrayIcon()
    {
#ifndef QT_NO_SYSTEMTRAYICON
        Q_ASSERT(readerStatus);
        sysTray = new SysTrayIcon();
        sysTray->setFirstCardWithNullPin(readerStatus->firstCardWithNullPin());
        connect(readerStatus.get(), &SmartCard::ReaderStatus::firstCardWithNullPinChanged, sysTray, &SysTrayIcon::setFirstCardWithNullPin);
#endif
    }

private:
    void connectConfigureDialog()
    {
        if (configureDialog) {
            if (q->mainWindow()) {
                connect(configureDialog, SIGNAL(configCommitted()), q->mainWindow(), SLOT(slotConfigCommitted()));
            }
            connect(configureDialog, &ConfigureDialog::configCommitted, q, &KleopatraApplication::configurationChanged);
        }
    }
    void disconnectConfigureDialog()
    {
        if (configureDialog) {
            if (q->mainWindow()) {
                disconnect(configureDialog, SIGNAL(configCommitted()), q->mainWindow(), SLOT(slotConfigCommitted()));
            }
            disconnect(configureDialog, &ConfigureDialog::configCommitted, q, &KleopatraApplication::configurationChanged);
        }
    }

public:
    bool isStandalone = false;
    bool ignoreNewInstance;
    bool firstNewInstance;
    QPointer<FocusFrame> focusFrame;
    QPointer<KAboutApplicationDialog> aboutDialog;
    QPointer<ConfigureDialog> configureDialog;
    QPointer<GroupsConfigDialog> groupsConfigDialog;
    QPointer<MainWindow> mainWindow;
    QPointer<SmartCardWindow> smartCardWindow;
    std::unique_ptr<SmartCard::ReaderStatus> readerStatus;
#ifndef QT_NO_SYSTEMTRAYICON
    SysTrayIcon *sysTray;
#endif
    std::shared_ptr<KeyGroupConfig> groupConfig;
    std::shared_ptr<KeyCache> keyCache;
    std::shared_ptr<Log> log;
    std::shared_ptr<FileSystemWatcher> watcher;
    std::shared_ptr<QSettings> distroSettings;
    std::vector<std::shared_ptr<QTemporaryDir>> temporaryDirectories;

public:
    void setupKeyCache()
    {
        keyCache = KeyCache::mutableInstance();
        keyCache->setRefreshInterval(Settings{}.refreshInterval());
        watcher.reset(new FileSystemWatcher);

        watcher->whitelistFiles(gnupgFileWhitelist());
        watcher->addPaths(gnupgFolderWhitelist());
        watcher->setDelay(1000);
        keyCache->addFileSystemWatcher(watcher);
        keyCache->setGroupConfig(groupConfig);
        keyCache->setGroupsEnabled(Settings().groupsEnabled());
        // always enable remarks (aka tags); in particular, this triggers a
        // relisting of the keys with signatures and signature notations
        // after the initial (fast) key listing
        keyCache->enableRemarks(true);
    }

    void setUpFilterManager()
    {
        if (!Settings{}.cmsEnabled()) {
            KeyFilterManager::instance()->alwaysFilterByProtocol(GpgME::OpenPGP);
        }
    }

    void setupLogging()
    {
        log = Log::mutableInstance();

        const QByteArray envOptions = qgetenv("KLEOPATRA_LOGOPTIONS");
        const bool logAll = envOptions.trimmed() == "all";
        const QList<QByteArray> options = envOptions.isEmpty() ? default_logging_options() : envOptions.split(',');

        const QByteArray dirNative = qgetenv("KLEOPATRA_LOGDIR");
        if (dirNative.isEmpty()) {
            return;
        }
        const QString dir = QFile::decodeName(dirNative);
        const QString logFileName = QDir(dir).absoluteFilePath(QStringLiteral("kleopatra.log.%1").arg(QCoreApplication::applicationPid()));
        std::unique_ptr<QFile> logFile(new QFile(logFileName));
        if (!logFile->open(QIODevice::WriteOnly | QIODevice::Append)) {
            qCDebug(KLEOPATRA_LOG) << "Could not open file for logging: " << logFileName << "\nLogging disabled";
            return;
        }

        log->setOutputDirectory(dir);
        if (logAll || options.contains("io")) {
            log->setIOLoggingEnabled(true);
        }
        qInstallMessageHandler(Log::messageHandler);

        if (logAll || options.contains("pipeio")) {
            KDPipeIODevice::setDebugLevel(KDPipeIODevice::Debug);
        }
        UiServer::setLogStream(log->logFile());
    }

    void updateFocusFrame(QWidget *focusWidget)
    {
        if (focusWidget && focusWidget->inherits("QLabel") && focusWidget->window()->testAttribute(Qt::WA_KeyboardFocusChange)) {
            if (!focusFrame) {
                focusFrame = new FocusFrame{focusWidget};
            }
            focusFrame->setWidget(focusWidget);
        } else if (focusFrame) {
            focusFrame->setWidget(nullptr);
        }
    }
    MainWindow *getOrCreateMainWindow()
    {
        auto mw = q->mainWindow();
        if (!mw) {
            mw = new MainWindow;
            mw->setAttribute(Qt::WA_DeleteOnClose);
            q->setMainWindow(mw);
        }
        return mw;
    }
    void exportFocusWindow()
    {
#ifdef HAVE_WAYLAND
        KWaylandExtras::self()->exportWindow(QGuiApplication::focusWindow());
#endif
    }
};

class KleopatraProxyStyle : public QProxyStyle
{
public:
    int styleHint(StyleHint hint, const QStyleOption *option = nullptr, const QWidget *widget = nullptr, QStyleHintReturn *returnData = nullptr) const override
    {
        // disable parent<->child navigation in tree views with left/right arrow keys
        // because this interferes with column by column navigation that is required
        // for accessibility
        if (hint == QStyle::SH_ItemView_ArrowKeysNavigateIntoChildren)
            return 0;

        if (hint == QStyle::SH_ItemView_ActivateItemOnSingleClick)
            return 0;

        return QProxyStyle::styleHint(hint, option, widget, returnData);
    }
};

KleopatraApplication::KleopatraApplication(int &argc, char *argv[])
    : QApplication(argc, argv)
    , d(new Private(this))
{
    setStyle(new KleopatraProxyStyle);
    connect(this, &QApplication::focusChanged, this, [this](QWidget *, QWidget *now) {
        d->updateFocusFrame(now);
    });
}

void KleopatraApplication::init()
{
    const QString groupConfigPath = Kleo::gnupgHomeDirectory() + QStringLiteral("/kleopatra/kleopatragroupsrc");
    d->groupConfig = std::make_shared<KeyGroupConfig>(groupConfigPath);

    const auto blockedUrlSchemes = Settings{}.blockedUrlSchemes();
    for (const auto &scheme : blockedUrlSchemes) {
        QDesktopServices::setUrlHandler(scheme, this, "blockUrl");
    }
    add_resources();
    DNAttributes::setOrder(Settings{}.attributeOrder());
    /* Start the gpg-agent early, this is done explicitly
     * because on an empty keyring our keylistings wont start
     * the agent. In that case any assuan-connect calls to
     * the agent will fail. The requested start via the
     * connection is additionally done in case the gpg-agent
     * is killed while Kleopatra is running. */
    startGpgAgent();
    d->readerStatus.reset(new SmartCard::ReaderStatus);
    connect(d->readerStatus.get(), &SmartCard::ReaderStatus::startOfGpgAgentRequested, this, &KleopatraApplication::startGpgAgent);
    d->setupKeyCache();
    if (!d->isStandalone) {
        d->setUpSysTrayIcon();
    }
    d->setUpFilterManager();
    d->setupLogging();

#ifndef QT_NO_SYSTEMTRAYICON
    if (d->sysTray) {
        d->sysTray->show();
    }
#endif

#ifdef HAVE_WAYLAND
    connect(KWaylandExtras::self(), &KWaylandExtras::windowExported, this, [](const auto, const auto &token) {
        qputenv("PINENTRY_GEOM_HINT", QUrl::toPercentEncoding(token));
    });
    connect(qApp, &QGuiApplication::focusWindowChanged, this, [this](auto w) {
        if (!w) {
            return;
        }
        d->exportFocusWindow();
    });

    QMetaObject::invokeMethod(
        this,
        [this]() {
            d->exportFocusWindow();
        },
        Qt::QueuedConnection);
#endif

    // If Kleopatra is running in standalone mode or if it is running
    // with elevated permissions on Windows then we quit the application
    // when the last window is closed.
    setQuitOnLastWindowClosed(isStandalone() || Kleo::userIsElevated());

    // Sync config when we are about to quit
    connect(this, &QApplication::aboutToQuit, this, []() {
        KSharedConfig::openConfig()->sync();
    });
}

KleopatraApplication::~KleopatraApplication()
{
    delete d->groupsConfigDialog;
    delete d->smartCardWindow;
    delete d->mainWindow;
}

void KleopatraApplication::setIsStandalone(bool standalone)
{
    d->isStandalone = standalone;
}

bool KleopatraApplication::isStandalone() const
{
    return d->isStandalone;
}

namespace
{
using Func = void (KleopatraApplication::*)(const QStringList &, GpgME::Protocol);
}

void KleopatraApplication::slotActivateRequested(const QStringList &arguments, const QString &workingDirectory)
{
    QCommandLineParser parser;
    KAboutData::applicationData().setupCommandLine(&parser);
    kleopatra_options(&parser);
    QString err;
    if (!arguments.isEmpty() && !parser.parse(arguments)) {
        err = parser.errorText();
    } else if (arguments.isEmpty()) {
        // KDBusServices omits the application name if no other
        // arguments are provided. In that case the parser prints
        // a warning.
        parser.parse(QStringList() << QCoreApplication::applicationFilePath());
    }

    if (err.isEmpty()) {
        err = newInstance(parser, workingDirectory);
    }

    if (!err.isEmpty()) {
        KMessageBox::error(nullptr, err.toHtmlEscaped(), i18nc("@title:window", "Failed to execute command"));
        Q_EMIT setExitValue(1);
        return;
    }
    Q_EMIT setExitValue(0);
}

QString KleopatraApplication::newInstance(const QCommandLineParser &parser, const QString &workingDirectory)
{
    if (d->ignoreNewInstance) {
        qCDebug(KLEOPATRA_LOG) << "New instance ignored because of ignoreNewInstance";
        return QString();
    }

    // handle standard Qt options and KAboutData options
    if (parser.isSet(u"help"_s)) {
        KMessageBox::information(nullptr, parser.helpText());
        return QString();
    }
    if (parser.isSet(u"help-all"_s)) {
        // Qt doesn't provide the text for --help-all
        KMessageBox::error(nullptr,
                           xi18nc("@info",
                                  "For technical reasons you can only use this option if <application>%1</application> is not already running.",
                                  qApp->applicationDisplayName()));
        return QString();
    }
    if (parser.isSet(u"version"_s) || parser.isSet(u"author"_s) || parser.isSet(u"license"_s)) {
        showAboutDialog();
        return QString();
    }

    QStringList files;
    const QDir cwd = QDir(workingDirectory);
    bool queryMode = parser.isSet(QStringLiteral("query")) || parser.isSet(QStringLiteral("search"));

    // Query and Search treat positional arguments differently, see below.
    if (!queryMode) {
        const auto positionalArguments = parser.positionalArguments();
        for (const QString &file : positionalArguments) {
            // We do not check that file exists here. Better handle
            // these errors in the UI.
            if (QFileInfo(file).isAbsolute()) {
                files << QDir::fromNativeSeparators(file);
            } else {
                files << cwd.absoluteFilePath(file);
            }
        }
    }

    GpgME::Protocol protocol = GpgME::UnknownProtocol;

    if (parser.isSet(QStringLiteral("openpgp"))) {
        qCDebug(KLEOPATRA_LOG) << "found OpenPGP";
        protocol = GpgME::OpenPGP;
    }

    if (parser.isSet(QStringLiteral("cms"))) {
        qCDebug(KLEOPATRA_LOG) << "found CMS";
        if (protocol == GpgME::OpenPGP) {
            return i18n("Ambiguous protocol: --openpgp and --cms");
        }
        protocol = GpgME::CMS;
    }

    // Check for Parent Window id
    WId parentId = 0;
    if (parser.isSet(QStringLiteral("parent-windowid"))) {
#ifdef Q_OS_WIN
        // WId is not a portable type as it is a pointer type on Windows.
        // casting it from an integer is ok though as the values are guaranteed to
        // be compatible in the documentation.
        parentId = static_cast<WId>((parser.value(QStringLiteral("parent-windowid")).toUInt()));
#else
        parentId = parser.value(QStringLiteral("parent-windowid")).toUInt();
#endif
    }

    // Handle openpgp4fpr URI scheme
    QString needle;
    if (queryMode) {
        needle = parser.positionalArguments().join(QLatin1Char(' '));
    }
    if (needle.startsWith(QLatin1StringView("openpgp4fpr:"))) {
        needle.remove(0, 12);
    }

    // Check for --search command.
    if (parser.isSet(QStringLiteral("search"))) {
        // This is an extra command instead of a combination with the
        // similar query to avoid changing the older query commands behavior
        // and query's "show details if a certificate exist or search on a
        // keyserver" logic is hard to explain and use consistently.
        if (needle.isEmpty()) {
            return i18n("No search string specified for --search");
        }
        auto const cmd = new LookupCertificatesCommand(needle, nullptr);
        cmd->setParentWId(parentId);
        cmd->start();
        return QString();
    }

    // Check for --query command
    if (parser.isSet(QStringLiteral("query"))) {
        if (needle.isEmpty()) {
            return i18n("No fingerprint argument specified for --query");
        }
        auto cmd = Command::commandForQuery(needle);
        cmd->setParentWId(parentId);
        cmd->start();
        return QString();
    }

    // Check for --gen-key command
    if (parser.isSet(QStringLiteral("gen-key"))) {
        if (protocol == GpgME::CMS) {
            const Kleo::Settings settings{};
            if (settings.cmsEnabled() && settings.cmsCertificateCreationAllowed()) {
                auto cmd = new NewCertificateSigningRequestCommand;
                cmd->setParentWId(parentId);
                cmd->start();
            } else {
                return i18n("You are not allowed to create S/MIME certificate signing requests.");
            }
        } else {
            auto cmd = new NewOpenPGPCertificateCommand;
            cmd->setParentWId(parentId);
            cmd->start();
        }
        return QString();
    }

    // Check for --config command
    if (parser.isSet(QStringLiteral("config"))) {
        openConfigDialogWithForeignParent(parentId);
        return QString();
    }

    struct FuncInfo {
        QString optionName;
        Func func;
    };

    // While most of these options can be handled by the content autodetection
    // below it might be useful to override the autodetection if the input is in
    // doubt and you e.g. only want to import .asc files or fail and not decrypt them
    // if they are actually encrypted data.
    static const std::vector<FuncInfo> funcMap{
        {QStringLiteral("import-certificate"), &KleopatraApplication::importCertificatesFromFile},
        {QStringLiteral("encrypt"), &KleopatraApplication::encryptFiles},
        {QStringLiteral("sign"), &KleopatraApplication::signFiles},
        {QStringLiteral("encrypt-sign"), &KleopatraApplication::signEncryptFiles},
        {QStringLiteral("sign-encrypt"), &KleopatraApplication::signEncryptFiles},
        {QStringLiteral("decrypt"), &KleopatraApplication::decryptFiles},
        {QStringLiteral("verify"), &KleopatraApplication::verifyFiles},
        {QStringLiteral("decrypt-verify"), &KleopatraApplication::decryptVerifyFiles},
        {QStringLiteral("checksum"), &KleopatraApplication::checksumFiles},
    };

    QString found;
    Func foundFunc = nullptr;
    for (const auto &[opt, fn] : funcMap) {
        if (parser.isSet(opt) && found.isEmpty()) {
            found = opt;
            foundFunc = fn;
        } else if (parser.isSet(opt)) {
            return i18n(R"(Ambiguous commands "%1" and "%2")", found, opt);
        }
    }

    QStringList errors;
    if (!found.isEmpty()) {
        if (files.empty()) {
            return i18n("No files specified for \"%1\" command", found);
        }
        qCDebug(KLEOPATRA_LOG) << "found" << found;
        (this->*foundFunc)(files, protocol);
    } else {
        if (files.empty()) {
            if (!(d->firstNewInstance && isSessionRestored())) {
                qCDebug(KLEOPATRA_LOG) << "openOrRaiseMainWindow";
                openOrRaiseMainWindow();
            }
        } else {
            for (const QString &fileName : std::as_const(files)) {
                QFileInfo fi(fileName);
                if (!fi.isReadable()) {
                    errors << i18n("Cannot read \"%1\"", fileName);
                }
            }
            handleFiles(files, parentId);
        }
    }
    d->firstNewInstance = false;

#ifdef Q_OS_WIN
    // On Windows we might be started from the
    // explorer in any working directory. E.g.
    // a double click on a file. To avoid preventing
    // the folder from deletion we set the
    // working directory to the users homedir.
    QDir::setCurrent(QDir::homePath());
#endif

    return errors.join(QLatin1Char('\n'));
}

void KleopatraApplication::handleFiles(const QStringList &files, WId parentId)
{
    auto mw = d->getOrCreateMainWindow();
    const QList<Command *> allCmds = Command::commandsForFiles(files, mw->keyListController());
    for (Command *cmd : allCmds) {
        if (parentId) {
            cmd->setParentWId(parentId);
        } else {
            cmd->setParentWidget(mw);
        }
        if (dynamic_cast<ImportCertificateFromFileCommand *>(cmd)) {
            openOrRaiseMainWindow();
        }
        cmd->start();
    }
}

const MainWindow *KleopatraApplication::mainWindow() const
{
    return d->mainWindow;
}

MainWindow *KleopatraApplication::mainWindow()
{
    return d->mainWindow;
}

void KleopatraApplication::setMainWindow(MainWindow *mainWindow)
{
    if (mainWindow == d->mainWindow) {
        return;
    }

    d->disconnectConfigureDialog();

    d->mainWindow = mainWindow;
#ifndef QT_NO_SYSTEMTRAYICON
    if (d->sysTray) {
        d->sysTray->setMainWindow(mainWindow);
    }
#endif

    d->connectConfigureDialog();
}

static void open_or_raise(QWidget *w)
{
#ifdef Q_OS_WIN
    if (w->isMinimized()) {
        qCDebug(KLEOPATRA_LOG) << __func__ << "unminimizing and raising window";
        w->raise();
    } else if (w->isVisible()) {
        qCDebug(KLEOPATRA_LOG) << __func__ << "raising window";
        w->raise();
#else
    if (w->isVisible()) {
        qCDebug(KLEOPATRA_LOG) << __func__ << "activating window";
        KWindowSystem::updateStartupId(w->windowHandle());
        KWindowSystem::activateWindow(w->windowHandle());
#endif
    } else {
        qCDebug(KLEOPATRA_LOG) << __func__ << "showing window";
        w->show();
    }
}

void KleopatraApplication::toggleMainWindowVisibility()
{
    if (mainWindow()) {
        mainWindow()->setVisible(!mainWindow()->isVisible());
    } else {
        openOrRaiseMainWindow();
    }
}

void KleopatraApplication::restoreMainWindow()
{
    qCDebug(KLEOPATRA_LOG) << "restoring main window";

    // Sanity checks
    if (!isSessionRestored()) {
        qCDebug(KLEOPATRA_LOG) << "Not in session restore";
        return;
    }

    if (mainWindow()) {
        qCDebug(KLEOPATRA_LOG) << "Already have main window";
        return;
    }

    auto mw = d->getOrCreateMainWindow();
    if (KMainWindow::canBeRestored(1)) {
        // restore to hidden state, Mainwindow::readProperties() will
        // restore saved visibility.
        mw->restore(1, false);
    }
}

void KleopatraApplication::openOrRaiseMainWindow()
{
    auto mw = d->getOrCreateMainWindow();
    open_or_raise(mw);
    UpdateNotification::checkUpdate(mw);
}

void KleopatraApplication::openOrRaiseSmartCardWindow()
{
    if (!d->smartCardWindow) {
        d->smartCardWindow = new SmartCardWindow;
        d->smartCardWindow->setAttribute(Qt::WA_DeleteOnClose);
    }
    open_or_raise(d->smartCardWindow);
}

void KleopatraApplication::openConfigDialogWithForeignParent(WId parentWId)
{
    if (!d->configureDialog) {
        d->configureDialog = new ConfigureDialog;
        d->configureDialog->setAttribute(Qt::WA_DeleteOnClose);
        d->connectConfigureDialog();
    }

    // This is similar to what the commands do.
    if (parentWId) {
        if (QWidget *pw = QWidget::find(parentWId)) {
            d->configureDialog->setParent(pw, d->configureDialog->windowFlags());
        } else {
            d->configureDialog->setAttribute(Qt::WA_NativeWindow, true);
            KWindowSystem::setMainWindow(d->configureDialog->windowHandle(), parentWId);
        }
    }

    open_or_raise(d->configureDialog);

    // If we have a parent we want to raise over it.
    if (parentWId) {
        d->configureDialog->raise();
    }
}

void KleopatraApplication::openOrRaiseConfigDialog()
{
    openConfigDialogWithForeignParent(0);
}

void KleopatraApplication::openOrRaiseGroupsConfigDialog(QWidget *parent)
{
    if (!d->groupsConfigDialog) {
        d->groupsConfigDialog = new GroupsConfigDialog{parent};
        d->groupsConfigDialog->setAttribute(Qt::WA_DeleteOnClose);
    } else {
        // reparent the dialog to ensure it's shown on top of the (modal) parent
        d->groupsConfigDialog->setParent(parent, Qt::Dialog);
    }
    open_or_raise(d->groupsConfigDialog);
}

#ifndef QT_NO_SYSTEMTRAYICON
void KleopatraApplication::startMonitoringSmartCard()
{
    Q_ASSERT(d->readerStatus);
    d->readerStatus->startMonitoring();
}
#endif // QT_NO_SYSTEMTRAYICON

void KleopatraApplication::importCertificatesFromFile(const QStringList &files, GpgME::Protocol /*proto*/)
{
    openOrRaiseMainWindow();
    if (!files.empty()) {
        mainWindow()->importCertificatesFromFile(files);
    }
}

void KleopatraApplication::encryptFiles(const QStringList &files, GpgME::Protocol proto)
{
    auto const cmd = new SignEncryptFilesCommand(files, nullptr);
    cmd->setEncryptionPolicy(Force);
    cmd->setSigningPolicy(Allow);
    if (proto != GpgME::UnknownProtocol) {
        cmd->setProtocol(proto);
    }
    cmd->start();
}

void KleopatraApplication::signFiles(const QStringList &files, GpgME::Protocol proto)
{
    auto const cmd = new SignEncryptFilesCommand(files, nullptr);
    cmd->setSigningPolicy(Force);
    cmd->setEncryptionPolicy(Deny);
    if (proto != GpgME::UnknownProtocol) {
        cmd->setProtocol(proto);
    }
    cmd->start();
}

void KleopatraApplication::signEncryptFiles(const QStringList &files, GpgME::Protocol proto)
{
    auto const cmd = new SignEncryptFilesCommand(files, nullptr);
    if (proto != GpgME::UnknownProtocol) {
        cmd->setProtocol(proto);
    }
    cmd->start();
}

void KleopatraApplication::decryptFiles(const QStringList &files, GpgME::Protocol /*proto*/)
{
    auto const cmd = new DecryptVerifyFilesCommand(files, nullptr);
    cmd->setOperation(Decrypt);
    cmd->start();
}

void KleopatraApplication::verifyFiles(const QStringList &files, GpgME::Protocol /*proto*/)
{
    auto const cmd = new DecryptVerifyFilesCommand(files, nullptr);
    cmd->setOperation(Verify);
    cmd->start();
}

void KleopatraApplication::decryptVerifyFiles(const QStringList &files, GpgME::Protocol /*proto*/)
{
    auto const cmd = new DecryptVerifyFilesCommand(files, nullptr);
    cmd->start();
}

void KleopatraApplication::checksumFiles(const QStringList &files, GpgME::Protocol /*proto*/)
{
    QStringList verifyFiles, createFiles;

    for (const QString &file : files) {
        if (isChecksumFile(file)) {
            verifyFiles << file;
        } else {
            createFiles << file;
        }
    }

    if (!verifyFiles.isEmpty()) {
        auto const cmd = new ChecksumVerifyFilesCommand(verifyFiles, nullptr);
        cmd->start();
    }
    if (!createFiles.isEmpty()) {
        auto const cmd = new ChecksumCreateFilesCommand(createFiles, nullptr);
        cmd->start();
    }
}

void KleopatraApplication::setIgnoreNewInstance(bool ignore)
{
    d->ignoreNewInstance = ignore;
}

bool KleopatraApplication::ignoreNewInstance() const
{
    return d->ignoreNewInstance;
}

void KleopatraApplication::blockUrl(const QUrl &url)
{
    qCDebug(KLEOPATRA_LOG) << "Blocking URL" << url;
    KMessageBox::error(mainWindow(), i18n("Opening an external link is administratively prohibited."), i18nc("@title:window", "Prohibited"));
}

void KleopatraApplication::startGpgAgent()
{
    Kleo::launchGpgAgent();
}

void KleopatraApplication::setDistributionSettings(const std::shared_ptr<QSettings> &settings)
{
    d->distroSettings = settings;
    Q_EMIT distributionSettingsChanged();
}

std::shared_ptr<QSettings> KleopatraApplication::distributionSettings() const
{
    return d->distroSettings;
}

std::weak_ptr<const QTemporaryDir> KleopatraApplication::createTemporaryDirectory()
{
    auto tempDir = std::make_shared<QTemporaryDir>();
    if (tempDir->isValid()) {
        d->temporaryDirectories.push_back(tempDir);
        return tempDir;
    }
    qCWarning(KLEOPATRA_LOG) << "Failed to create a temporary directory";
    return {};
}

void KleopatraApplication::showAboutDialog()
{
    // we show the About dialog ourselves so that we can pass up-to-date about data to it;
    // KXmlGuiWindow takes a copy of the about data on creation and this copy might not
    // contain the backend version information that's set by a background thread
    if (!d->aboutDialog) {
        qCDebug(KLEOPATRA_LOG) << __func__ << "Creating About dialog";
        d->aboutDialog = new KAboutApplicationDialog(KAboutData::applicationData(), mainWindow());
        d->aboutDialog->setAttribute(Qt::WA_DeleteOnClose);
    }
    if (d->aboutDialog->isMinimized()) {
        qCDebug(KLEOPATRA_LOG) << __func__ << "Unminimizing About dialog";
        d->aboutDialog->setWindowState((d->aboutDialog->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive);
    }
    qCDebug(KLEOPATRA_LOG) << __func__ << "Showing About dialog";
    d->aboutDialog->show();
}

#include "kleopatraapplication.moc"

#include "moc_kleopatraapplication.cpp"
