/* Copyright (C) 2018 by Intevation GmbH <info@intevation.de>
 *
 * This file is free software under the GNU GPL (v>=2)
 * and comes with ABSOLUTELY NO WARRANTY!
 * See LICENSE.txt for details.
 */

#include "gpgoldebugpage.h"
#include "w32-gettext.h"
#include "w32-util.h"

#include <QDebug>
#include <QGroupBox>
#include <QLabel>
#include <QCheckBox>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QToolTip>
#include <QPushButton>
#include <QFileDialog>
#include <QComboBox>
#include <QLineEdit>
#include <QStandardPaths>
#include <QDir>
#include <QThread>
#include <Libkleo/GnuPG>

/* See gpgol/src/debug.h */
#define DBG_OOM            (1<<1)
#define DBG_MEMORY         (1<<2)
#define DBG_TRACE          (1<<3)
#define DBG_DATA           (1<<4)

GpgOLDebugPage::GpgOLDebugPage(QWidget *parent):
    QWidget(parent)
{
    setupGUI();

    load();
}

class VersionLoader: public QThread
{
    Q_OBJECT
    void run() override {
        const auto vers = Kleo::gpg4winVersion();
        const auto desc = Kleo::gpg4winDescription();
        const auto longDesc = Kleo::gpg4winLongDescription();
        emit resultReady(vers, desc, longDesc);
        deleteLater();
    }
signals:
    void resultReady(const QString &vers,
                     const QString &desc,
                     const QString &ldesc);
};

void GpgOLDebugPage::setupGUI()
{
    auto baseLay = new QVBoxLayout(this);

    // The debugging group
    mDbgGrp = new QGroupBox(_("Enable Logging"));
    mDbgGrp->setCheckable(true);

    mDbgCombo = new QComboBox;
    mDbgCombo->addItem(_("Default"), 1);
    mDbgCombo->addItem(_("+Outlook API calls"), (DBG_OOM));
    mDbgCombo->addItem(_("+Memory analysis"), (DBG_OOM | DBG_MEMORY));
    mDbgCombo->addItem(_("+Call tracing"), (DBG_OOM | DBG_MEMORY | DBG_TRACE));

    mDbgVerboseWarningLabel = new QLabel(_("<b>Warning:</b> Decreased performance. Huge logs!"));

    mDbgComboLabel = new QLabel(_("Log level:"));

    mDbgLogFileLabel = new QLabel(_("Log File (required):"));

    mDbgDataChk = new QCheckBox(_("Include Mail contents (decrypted!) and meta information."));

    mDbgLogFileName = new QLineEdit;

    auto dbgLay = new QVBoxLayout(mDbgGrp);

    auto logFileLay = new QHBoxLayout;

    mDbgLogFileBtn = new QPushButton;
    mDbgLogFileBtn->setIcon(style()->standardIcon(QStyle::SP_FileDialogStart));

    logFileLay->addWidget(mDbgLogFileLabel, 0);
    logFileLay->addWidget(mDbgLogFileName, 1);
    logFileLay->addWidget(mDbgLogFileBtn, 0);
    dbgLay->addLayout(logFileLay);

    auto dbgComboLay = new QHBoxLayout;
    dbgLay->addLayout(dbgComboLay);

    dbgComboLay->addWidget(mDbgComboLabel);
    dbgComboLay->addWidget(mDbgCombo);
    dbgComboLay->addWidget(mDbgVerboseWarningLabel);
    dbgComboLay->addStretch(1);

    dbgLay->addWidget(mDbgDataChk);

    baseLay->addWidget(mDbgGrp);


    connect(mDbgGrp, &QGroupBox::toggled, [this] (bool) {
            enableDisableDbgWidgets();
        });

    connect(mDbgCombo, &QComboBox::currentTextChanged, [this] (QString) {
            mDbgVerboseWarningLabel->setVisible((mDbgCombo->currentData().toInt() & DBG_TRACE));
        });

    connect(mDbgLogFileBtn, &QPushButton::clicked, [this] () {
        const auto fileName = QFileDialog::getSaveFileName(this, _("Select log file"),
                              mDbgLogFileName->text(),
                              "(*.txt)");
        if (!fileName.isEmpty()) {
            mDbgLogFileName->setText(QDir::toNativeSeparators(fileName));
        }
    });
    enableDisableDbgWidgets();

    // End debugging group
    auto othersGrp = new QGroupBox(_("Potential workarounds"));
    mSyncEncChk = new QCheckBox (_("Block Outlook during encrypt / sign"));
    mSyncDecChk = new QCheckBox (_("Block Outlook during decrypt / verify"));

    auto othersLay = new QVBoxLayout(othersGrp);
    othersLay->addWidget(mSyncDecChk);
    othersLay->addWidget(mSyncEncChk);

    baseLay->addWidget(othersGrp);

    auto aboutGroup = new QGroupBox();
    auto aboutLay = new QVBoxLayout(aboutGroup);
    auto descLabel = new QLabel;
    auto desc2Label = new QLabel;
    descLabel->setOpenExternalLinks(true);
    aboutLay->addWidget(descLabel);
    aboutLay->addWidget(desc2Label);
    aboutGroup->setVisible(false);
    baseLay->addWidget(aboutGroup);

    auto bugReportLabel = new QLabel(QStringLiteral("<a href=\"https://www.gpg4win.org/reporting-bugs.html\">%1</a>").arg(_("How to report a problem?")));
    bugReportLabel->setOpenExternalLinks(true);
    baseLay->addWidget(bugReportLabel);
    baseLay->addStretch(1);

    auto loader = new VersionLoader;
    connect(loader, &VersionLoader::resultReady,
            this, [descLabel, desc2Label, aboutGroup, bugReportLabel] (const QString &version, const QString &desc, const QString &desc2) {
        if (version.isEmpty()) {
            return;
        }
        aboutGroup->setTitle(_("About GpgOL") + QLatin1Char(' ') + version);
        qDebug() << "Verified version" << version;
        descLabel->setText(desc);
        desc2Label->setText(desc2);
        aboutGroup->setVisible(true);
        bugReportLabel->setText(QStringLiteral("<a href=\"%1\">%2</a>").arg(_("https://gnupg.com/vsd/report.html")).arg(_("How to report a problem?")));
    });
    loader->start();
}

static bool loadBool(const char *name, bool defaultVal)
{
    bool forced;
    return strToBool(W32::readRegStr(nullptr, GPGOL_REG_PATH, name), defaultVal, forced);
}

static const QMap<QString, bool> defaultMap {
    { QStringLiteral("syncEnc"), false },
    { QStringLiteral("syncDec"), false },
};

void GpgOLDebugPage::updateGUI(const QMap<QString, bool> &values)
{
    mSyncDecChk->setChecked(values["syncDec"]);
    mSyncEncChk->setChecked(values["syncEnc"]);
}

void GpgOLDebugPage::load()
{
    QMap<QString, bool> confValues;

    for (const auto &key: defaultMap.keys()) {
        confValues[key] = loadBool(key.toLocal8Bit().constData(), defaultMap[key]);
    }
    updateGUI(confValues);

    const auto logFile = W32::readRegStr(nullptr, GPGOL_REG_PATH, "logFile");
    mDbgLogFileName->setText(logFile.empty() ?
            QDir::toNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/gpgol.txt") :
            QString::fromStdString(logFile));

    const auto logLevelS = W32::readRegStr(nullptr, GPGOL_REG_PATH, "enableDebug");

    bool ok;
    int logLevel = QString::fromStdString(logLevelS).toInt(&ok);
    if (!ok) {
        logLevel = 0;
    }

    mDbgGrp->setChecked(logLevel > 0);

    int idx = 0;
    if ((logLevel & DBG_OOM)) {
        idx++;
    }
    if ((logLevel & DBG_MEMORY)) {
        idx++;
    }
    if ((logLevel & DBG_TRACE)) {
        idx++;
    }

    mDbgCombo->setCurrentIndex(idx);

    mDbgDataChk->setChecked((logLevel & DBG_DATA));
}

void GpgOLDebugPage::defaults()
{
    updateGUI(defaultMap);
    mDbgGrp->setChecked(false);
}

static void saveBool(const char *name, bool value)
{
    const char *val = value ? "1" : "0";

    if (!W32::writeRegStr(nullptr, GPGOL_REG_PATH, name, val)) {
        qWarning() << "Failed to write registry value for" << name;
    }
}

static void saveInt(const char *name, int value)
{
    const std::string val = std::to_string(value);

    if (!W32::writeRegStr(nullptr, GPGOL_REG_PATH, name, val.c_str())) {
        qWarning() << "Failed to write registry value for" << name;
    }
}

void GpgOLDebugPage::save() const
{
    saveBool("syncEnc", mSyncEncChk->isChecked());
    saveBool("syncDec", mSyncDecChk->isChecked());

    int logLevel = 0;
    if (mDbgGrp->isChecked()) {
        logLevel = mDbgCombo->currentData().toInt();
        logLevel |= mDbgDataChk->isChecked() ? DBG_DATA : 0;
    }

    saveInt("enableDebug", logLevel);
    W32::writeRegStr(nullptr, GPGOL_REG_PATH, "logFile", QDir::toNativeSeparators(
                mDbgLogFileName->text()).toLocal8Bit().constData());
}

void GpgOLDebugPage::enableDisableDbgWidgets()
{
    bool vis = mDbgGrp->isChecked();

    mDbgDataChk->setVisible(vis);
    mDbgCombo->setVisible(vis);
    mDbgComboLabel->setVisible(vis);

    mDbgLogFileName->setVisible(vis);
    mDbgLogFileLabel->setVisible(vis);
    mDbgLogFileBtn->setVisible(vis);

    mDbgVerboseWarningLabel->setVisible(vis && (mDbgCombo->currentData().toInt() & DBG_TRACE));
}

#include "gpgoldebugpage.moc"
