#include "installationpage.hpp"

#include <QComboBox>
#include <QFileDialog>
#include <QMessageBox>
#include <QThread>

#include <components/debug/debugging.hpp>

#include "mainwizard.hpp"

Wizard::InstallationPage::InstallationPage(QWidget* parent, Config::GameSettings& gameSettings)
    : QWizardPage(parent)
    , mGameSettings(gameSettings)
{
    mWizard = qobject_cast<MainWizard*>(parent);

    setupUi(this);

    mFinished = false;

    mThread = std::make_unique<QThread>();
    mUnshield = std::make_unique<UnshieldWorker>(mGameSettings.value("morrowind-bsa-filesize").value.toLongLong());
    mUnshield->moveToThread(mThread.get());

    connect(mThread.get(), &QThread::started, mUnshield.get(), &UnshieldWorker::extract);

    connect(mUnshield.get(), &UnshieldWorker::finished, mThread.get(), &QThread::quit);

    connect(mUnshield.get(), &UnshieldWorker::finished, this, &InstallationPage::installationFinished,
        Qt::QueuedConnection);

    connect(mUnshield.get(), &UnshieldWorker::error, this, &InstallationPage::installationError, Qt::QueuedConnection);

    connect(
        mUnshield.get(), &UnshieldWorker::textChanged, installProgressLabel, &QLabel::setText, Qt::QueuedConnection);

    connect(mUnshield.get(), &UnshieldWorker::textChanged, logTextEdit, &QPlainTextEdit::appendPlainText,
        Qt::QueuedConnection);

    connect(
        mUnshield.get(), &UnshieldWorker::textChanged, this,
        [](const QString& text) { Log(Debug::Info) << qUtf8Printable(text); }, Qt::QueuedConnection);

    connect(mUnshield.get(), &UnshieldWorker::progressChanged, installProgressBar, &QProgressBar::setValue,
        Qt::QueuedConnection);

    connect(mUnshield.get(), &UnshieldWorker::requestFileDialog, this, &InstallationPage::showFileDialog,
        Qt::QueuedConnection);

    connect(mUnshield.get(), &UnshieldWorker::requestOldVersionDialog, this, &InstallationPage::showOldVersionDialog,
        Qt::QueuedConnection);
}

Wizard::InstallationPage::~InstallationPage()
{
    if (mThread->isRunning())
    {
        mUnshield->stopWorker();
        mThread->quit();
        mThread->wait();
    }
}

void Wizard::InstallationPage::initializePage()
{
    const bool morrowind = field(QStringLiteral("installation.installMorrowind")).toBool();
    const bool tribunal = field(QStringLiteral("installation.installTribunal")).toBool();
    const bool bloodmoon = field(QStringLiteral("installation.installBloodmoon")).toBool();

    const QString path = field(QStringLiteral("installation.path")).toString();
    const MainWizard::Installation& installation = mWizard->mInstallations[path];

    QStringList installing;
    if (morrowind)
        installing << QStringLiteral("Morrowind");
    if (tribunal)
        installing << QStringLiteral("Tribunal");
    if (bloodmoon)
        installing << QStringLiteral("Bloodmoon");

    logTextEdit->appendPlainText(QString("Installing to %1").arg(path));
    logTextEdit->appendPlainText(QString("Installing %1.").arg(installing.join(", ")));

    // Set the progressbar maximum to a multiple of 100
    // That way installing all three components would yield 300%
    // When one component is done the bar will be filled by 33%

    int steps = 0;
    if (!field(QStringLiteral("installation.retailDisc")).toBool())
    {
        steps += tribunal && !installation.hasTribunal;
        steps += bloodmoon && !installation.hasBloodmoon;
    }
    else
    {
        steps = installing.count();
    }

    installProgressBar->setMinimum(0);
    installProgressBar->setMaximum(steps * 100);

    startInstallation();
}

void Wizard::InstallationPage::startInstallation()
{
    const QString path = field(QStringLiteral("installation.path")).toString();

    bool hasMorrowind = false;
    bool hasTribunal = false;
    bool hasBloodmoon = false;
    if (!field(QStringLiteral("installation.retailDisc")).toBool())
    {
        const MainWizard::Installation& installation = mWizard->mInstallations[path];

        // Morrowind should already be installed
        hasMorrowind = true;
        hasTribunal = installation.hasTribunal;
        hasBloodmoon = installation.hasBloodmoon;

        // Set the location of the Morrowind.ini to update
        mUnshield->setIniPath(installation.iniPath);
        mUnshield->setupSettings();
    }

    if (!hasMorrowind)
        mUnshield->setInstallComponent(Wizard::Component_Morrowind, true);

    if (!hasTribunal && field(QStringLiteral("installation.installTribunal")).toBool())
        mUnshield->setInstallComponent(Wizard::Component_Tribunal, true);

    if (!hasBloodmoon && field(QStringLiteral("installation.installBloodmoon")).toBool())
        mUnshield->setInstallComponent(Wizard::Component_Bloodmoon, true);

    // Set the installation target path
    mUnshield->setPath(path);

    // Set the right codec to use for Morrowind.ini
    const QString language(field(QStringLiteral("installation.language")).toString());

    if (language == QStringLiteral("Polish"))
    {
        mUnshield->setIniEncoding(ToUTF8::FromType::WINDOWS_1250);
    }
    else if (language == QStringLiteral("Russian"))
    {
        mUnshield->setIniEncoding(ToUTF8::FromType::WINDOWS_1251);
    }
    else
    {
        mUnshield->setIniEncoding(ToUTF8::FromType::WINDOWS_1252);
    }

    mThread->start();
}

void Wizard::InstallationPage::showFileDialog(Wizard::Component component)
{
    QString name;
    switch (component)
    {
        case Wizard::Component_Morrowind:
            name = QStringLiteral("Morrowind");
            break;
        case Wizard::Component_Tribunal:
            name = QStringLiteral("Tribunal");
            break;
        case Wizard::Component_Bloodmoon:
            name = QStringLiteral("Bloodmoon");
            break;
    }
    logTextEdit->appendHtml(tr("<p>Attempting to install component %1.</p>").arg(name));
    Log(Debug::Info) << "Attempting to install component " << qUtf8Printable(name) << ".";

    QMessageBox msgBox(this);
    msgBox.setWindowTitle(tr("%1 Installation").arg(name));
    msgBox.setIcon(QMessageBox::Information);
    msgBox.setText(
        QObject::tr("Select a valid %1 installation media.<br><b>Hint</b>: make sure that it contains at least one "
                    "<b>.cab</b> file.")
            .arg(name));
    msgBox.exec();

    const QString path
        = QFileDialog::getExistingDirectory(this, tr("Select %1 installation media").arg(name), QDir::rootPath());

    if (path.isEmpty())
    {
        logTextEdit->appendHtml(
            tr("<p><br/><span style=\"color:red;\">"
               "<b>Error: The installation was aborted by the user</b></span></p>"));

        Log(Debug::Error) << "Error: The installation was aborted by the user";
        mWizard->mError = true;

        emit completeChanged();
        return;
    }

    mUnshield->setDiskPath(path);
}

void Wizard::InstallationPage::showOldVersionDialog()
{
    logTextEdit->appendHtml(tr("<p>Detected old version of component Morrowind.</p>"));
    Log(Debug::Info) << "Detected old version of component Morrowind.";

    QMessageBox msgBox(this);
    msgBox.setWindowTitle(tr("Morrowind Installation"));
    msgBox.setIcon(QMessageBox::Information);
    msgBox.setText(
        QObject::tr("<br><b>There may be a more recent version of Morrowind available.</b><br><br>"
                    "Do you wish to continue anyway?<br>"));
    msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
    msgBox.setDefaultButton(QMessageBox::No);

    if (msgBox.exec() == QMessageBox::No)
    {
        logTextEdit->appendHtml(
            tr("<p><br/><span style=\"color:red;\">"
               "<b>Error: The installation was aborted by the user</b></span></p>"));

        Log(Debug::Error) << "Error: The installation was aborted by the user";
        mWizard->mError = true;

        emit completeChanged();
        return;
    }

    mUnshield->wakeAll();
}

void Wizard::InstallationPage::installationFinished()
{
    QMessageBox msgBox(this);
    msgBox.setWindowTitle(tr("Installation finished"));
    msgBox.setIcon(QMessageBox::Information);
    msgBox.setStandardButtons(QMessageBox::Ok);
    msgBox.setText(tr("Installation completed successfully!"));

    msgBox.exec();

    mFinished = true;
    emit completeChanged();
}

void Wizard::InstallationPage::installationError(const QString& text, const QString& details)
{
    installProgressLabel->setText(tr("Installation failed!"));

    logTextEdit->appendHtml(tr("<p><br/><span style=\"color:red;\"><b>Error: %1</b></p>").arg(text));
    logTextEdit->appendHtml(tr("<p><span style=\"color:red;\"><b>%1</b></p>").arg(details));

    Log(Debug::Error) << "Error: " << qUtf8Printable(text);
    Log(Debug::Error) << qUtf8Printable(details);

    mWizard->mError = true;
    QMessageBox msgBox(this);
    msgBox.setWindowTitle(tr("An error occurred"));
    msgBox.setIcon(QMessageBox::Critical);
    msgBox.setStandardButtons(QMessageBox::Ok);
    msgBox.setText(
        tr("<html><head/><body><p><b>The Wizard has encountered an error</b></p>"
           "<p>The error reported was:</p><p>%1</p>"
           "<p>Press &quot;Show Details...&quot; for more information.</p></body></html>")
            .arg(text));

    msgBox.setDetailedText(details);
    msgBox.exec();

    emit completeChanged();
}

bool Wizard::InstallationPage::isComplete() const
{
    return mWizard->mError || mFinished;
}

int Wizard::InstallationPage::nextId() const
{
    if (!field(QStringLiteral("installation.retailDisc")).toBool() && !mWizard->mError)
        return MainWizard::Page_Import;

    return MainWizard::Page_Conclusion;
}
