#include "mainwizard.hpp"

#include <algorithm>

#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QMessageBox>
#include <QProcess>

#include <components/debug/debugging.hpp>
#include <components/files/qtconfigpath.hpp>
#include <components/files/qtconversion.hpp>
#include <components/misc/utf8qtextstream.hpp>
#include <components/process/processinvoker.hpp>

#include "componentselectionpage.hpp"
#include "conclusionpage.hpp"
#include "existinginstallationpage.hpp"
#include "importpage.hpp"
#include "installationtargetpage.hpp"
#include "intropage.hpp"
#include "languageselectionpage.hpp"
#include "methodselectionpage.hpp"

#ifdef OPENMW_USE_UNSHIELD
#include "installationpage.hpp"
#endif

Wizard::MainWizard::MainWizard(Files::ConfigurationManager&& cfgMgr, QWidget* parent)
    : QWizard(parent)
    , mCfgMgr(cfgMgr)
    , mImporterInvoker(new Process::ProcessInvoker())
    , mGameSettings(mCfgMgr)
{
#ifndef Q_OS_MAC
    setWizardStyle(QWizard::ModernStyle);
#else
    setWizardStyle(QWizard::ClassicStyle);
#endif

    setWindowTitle(tr("OpenMW Wizard"));
    setWindowIcon(QIcon(QStringLiteral(":/images/openmw-wizard.png")));
    setMinimumWidth(550);

    // Set the property for comboboxes to the text instead of index
    setDefaultProperty("QComboBox", "currentText", "currentIndexChanged");

    connect(mImporterInvoker->getProcess(), qOverload<int, QProcess::ExitStatus>(&QProcess::finished), this,
        &MainWizard::importerFinished);

    Log(Debug::Info) << "Started OpenMW Wizard on " << QDateTime::currentDateTime().toString().toUtf8().constData();

    std::filesystem::create_directories(mCfgMgr.getUserConfigPath());

    const QString userPath(Files::pathToQString(mCfgMgr.getUserConfigPath()));
    if (!QDir(userPath).exists())
    {
        const QString title = tr("Error creating OpenMW configuration directory");
        const QString message = tr(
            "<html><head/><body><p><b>Could not create %1</b></p>"
            "<p>Please make sure you have the right permissions and try again.</p></body></html>");
        QMessageBox::critical(nullptr, title, message.arg(userPath));
        QApplication::quit();
        return;
    }

    std::filesystem::create_directories(mCfgMgr.getUserDataPath());

    setupGameSettings();
    setupLauncherSettings();
    setupInstallations();
    setupPages();

    for (const std::filesystem::path& installationPath : mCfgMgr.getInstallPaths())
    {
        addInstallation(Files::pathToQString(installationPath / "Data Files"));
    }
}

Wizard::MainWizard::~MainWizard() = default;

void Wizard::MainWizard::setupGameSettings()
{
    const QString title = tr("Error opening OpenMW configuration file");
    const QString message = tr(
        "<html><head/><body><p><b>Could not open %1 for reading</b></p>"
        "<p>Please make sure you have the right permissions and try again.</p></body></html>");

    // Load the user config file first, separately
    // So we can write it properly, uncontaminated
    const QString path(Files::getUserConfigPathQString(mCfgMgr));
    QFile file(path);

    qDebug() << "Loading config file:" << path.toUtf8().constData();

    if (file.exists())
    {
        if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
        {
            QMessageBox::critical(nullptr, title, message.arg(file.fileName()));
            QApplication::quit();
            return;
        }
        QTextStream stream(&file);
        Misc::ensureUtf8Encoding(stream);

        mGameSettings.readUserFile(stream, QFileInfo(path).dir().path());
        file.close();
    }

    // Now the rest
    const QStringList paths = Files::getActiveConfigPathsQString(mCfgMgr);

    for (const QString& path2 : paths)
    {
        qDebug() << "Loading config file:" << path2.toUtf8().constData();

        file.setFileName(path2);
        if (file.exists())
        {
            if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
            {
                QMessageBox::critical(nullptr, title, message.arg(file.fileName()));
                QApplication::quit();
                return;
            }
            QTextStream stream(&file);
            Misc::ensureUtf8Encoding(stream);

            mGameSettings.readFile(stream, QFileInfo(path2).dir().path());
            file.close();
        }
    }
}

void Wizard::MainWizard::setupLauncherSettings()
{
    const std::filesystem::path configPath = mCfgMgr.getUserConfigPath();
    const QString path(Files::pathToQString(configPath / Config::LauncherSettings::sLauncherConfigFileName));

    QFile file(path);

    qDebug() << "Loading config file:" << path.toUtf8().constData();

    if (file.exists())
    {
        if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
        {
            const QString title = tr("Error opening OpenMW configuration file");
            const QString message = tr(
                "<html><head/><body><p><b>Could not open %1 for reading</b></p>"
                "<p>Please make sure you have the right permissions "
                "and try again.</p></body></html>");
            QMessageBox::critical(nullptr, title, message.arg(file.fileName()));
            QApplication::quit();
            return;
        }
        QTextStream stream(&file);
        Misc::ensureUtf8Encoding(stream);

        mLauncherSettings.readFile(stream);
    }
}

void Wizard::MainWizard::setupInstallations()
{
    // Check if the paths actually contain a Morrowind installation
    for (const auto& path : mGameSettings.getDataDirs())
    {
        if (findFiles(QStringLiteral("Morrowind"), path.value))
            addInstallation(path.value);
    }
}

void Wizard::MainWizard::runSettingsImporter()
{
    const QString path(field(QStringLiteral("installation.path")).toString());
    const bool retailDisc(field(QStringLiteral("installation.retailDisc")).toBool());

    QFile file(Files::getUserConfigPathQString(mCfgMgr));

    // Construct the arguments to run the importer
    QStringList arguments;

    // Import plugin selection?
    if (retailDisc || field(QStringLiteral("installation.import-addons")).toBool())
        arguments.append(QStringLiteral("--game-files"));

    arguments.append(QStringLiteral("--encoding"));

    // Set encoding
    const QString language(field(QStringLiteral("installation.language")).toString());
    if (language == QStringLiteral("Polish"))
    {
        arguments.append(QStringLiteral("win1250"));
    }
    else if (language == QStringLiteral("Russian"))
    {
        arguments.append(QStringLiteral("win1251"));
    }
    else
    {
        arguments.append(QStringLiteral("win1252"));
    }

    // Import fonts
    if (field(QStringLiteral("installation.import-fonts")).toBool())
        arguments.append(QStringLiteral("--fonts"));

    // Now the paths
    arguments.append(QStringLiteral("--ini"));

    if (retailDisc)
    {
        arguments.append(QDir(path).filePath(QStringLiteral("Morrowind.ini")));
    }
    else
    {
        arguments.append(mInstallations[path].iniPath);
    }

    arguments.append(QStringLiteral("--cfg"));
    arguments.append(Files::getUserConfigPathQString(mCfgMgr));

    if (!mImporterInvoker->startProcess(QStringLiteral("openmw-iniimporter"), arguments, false))
        return qApp->quit();
}

void Wizard::MainWizard::addInstallation(const QString& path)
{
    QDir dir(path);
    if (!dir.exists())
        return;

    Installation install;

    install.hasMorrowind = findFiles(QStringLiteral("Morrowind"), path);
    install.hasTribunal = findFiles(QStringLiteral("Tribunal"), path);
    install.hasBloodmoon = findFiles(QStringLiteral("Bloodmoon"), path);

    QFile file(dir.filePath(QStringLiteral("Morrowind.ini")));

    // Try the parent directory
    // In normal Morrowind installations that's where Morrowind.ini is
    if (!file.exists() && dir.cdUp())
        file.setFileName(dir.filePath(QStringLiteral("Morrowind.ini")));

    if (file.exists())
        install.iniPath = file.fileName();

    mInstallations.insert(QDir::toNativeSeparators(path), install);
}

void Wizard::MainWizard::setupPages()
{
    setPage(Page_Intro, new IntroPage(this));
    setPage(Page_MethodSelection, new MethodSelectionPage(this));
    setPage(Page_LanguageSelection, new LanguageSelectionPage(this));
    setPage(Page_ExistingInstallation, new ExistingInstallationPage(this));
    setPage(Page_InstallationTarget, new InstallationTargetPage(this, mCfgMgr));
    setPage(Page_ComponentSelection, new ComponentSelectionPage(this));
#ifdef OPENMW_USE_UNSHIELD
    setPage(Page_Installation, new InstallationPage(this, mGameSettings));
#endif
    setPage(Page_Import, new ImportPage(this));
    setPage(Page_Conclusion, new ConclusionPage(this));
    setStartId(Page_Intro);
}

void Wizard::MainWizard::importerFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
    if (exitCode != 0 || exitStatus == QProcess::CrashExit)
        return;

    // Re-read the settings
    setupGameSettings();
}

void Wizard::MainWizard::accept()
{
    writeSettings();
    QWizard::accept();
}

void Wizard::MainWizard::reject()
{
    const QString title = tr("Quit Wizard");
    const QString message = tr("Are you sure you want to exit the Wizard?");
    if (QMessageBox::question(this, title, message) == QMessageBox::Yes)
        QWizard::reject();
}

void Wizard::MainWizard::writeSettings()
{
    // Write the encoding and language settings
    const QString language(field(QStringLiteral("installation.language")).toString());
    mLauncherSettings.setLanguage(language);

    if (language == QStringLiteral("Polish"))
    {
        mGameSettings.setValue(QStringLiteral("encoding"), { "win1250" });
    }
    else if (language == QStringLiteral("Russian"))
    {
        mGameSettings.setValue(QStringLiteral("encoding"), { "win1251" });
    }
    else
    {
        mGameSettings.setValue(QStringLiteral("encoding"), { "win1252" });
    }

    // Write the installation path so that openmw can find them
    const QString path(field(QStringLiteral("installation.path")).toString());
    const QList<Config::SettingValue> dirs = mGameSettings.getDataDirs();

    const QString canonical = QDir(path).canonicalPath();

    // Don't write the path if it was already in the file
    if (std::none_of(dirs.begin(), dirs.end(), [&](const Config::SettingValue& dir) { return dir.value == canonical; }))
        mGameSettings.setMultiValue(QStringLiteral("data"), { path });

    // Game settings
    QFile file(Files::getUserConfigPathQString(mCfgMgr));

    const QString writeTitle = tr("Error writing OpenMW configuration file");
    const QString writeMessage = tr(
        "<html><head/><body><p><b>Could not open %1 for writing</b></p>"
        "<p>Please make sure you have the right permissions "
        "and try again.</p></body></html>");

    if (!file.open(QIODevice::ReadWrite | QIODevice::Text))
    {
        QMessageBox::critical(this, writeTitle, writeMessage.arg(file.fileName()));
        QApplication::quit();
        return;
    }

    mGameSettings.writeFileWithComments(file);
    file.close();

    // Launcher settings
    file.setFileName(
        Files::pathToQString(mCfgMgr.getUserConfigPath() / Config::LauncherSettings::sLauncherConfigFileName));

    if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate))
    {
        QMessageBox::critical(this, writeTitle, writeMessage.arg(file.fileName()));
        QApplication::quit();
        return;
    }

    QTextStream stream(&file);
    Misc::ensureUtf8Encoding(stream);

    mLauncherSettings.writeFile(stream);
    file.close();
}

bool Wizard::MainWizard::findFiles(const QString& name, const QString& path)
{
    const QDir dir(path);

    if (!dir.exists())
        return false;

    const QStringList entries = dir.entryList();
    // TODO: add MIME handling to make sure the files are real
    return entries.contains(name + QStringLiteral(".esm"), Qt::CaseInsensitive)
        && entries.contains(name + QStringLiteral(".bsa"), Qt::CaseInsensitive);
}
