#include "lotrodatmanager.h"
#include "filesystem.h"
#include "LotroDat/Subfiles/TextSubFile.h"
#include "models/patchdownloader.h"

#include <QtConcurrent/QtConcurrent>
#include <QFontDatabase>
#include <QMessageBox>
#include <QDebug>

#include <string>
#include <iostream>
#include <fstream>

Q_DECLARE_METATYPE(LOTRO_DAT::DatLocaleManager::LOCALE)
Q_DECLARE_METATYPE(QVector<QVariant>)

LotroDatManager::LotroDatManager(QSettings* settings, QObject *parent) :
    QObject(parent), app_settings(settings) {

    qRegisterMetaType<QVector<QVariant>>();
    qRegisterMetaType<LOTRO_DAT::DatLocaleManager::LOCALE>();
}

bool LotroDatManager::Initialised()
{
    return client_local_file.Initialized() && client_general_file.Initialized();
}

bool LotroDatManager::NotPatched()
{
    return !client_local_file.GetStatusModule().CheckIfNotPatched() && !client_local_file.GetStatusModule().CheckIfNotPatched();
}

unsigned LotroDatManager::getPercent()
{
    return client_local_file.GetStatusModule().GetPercentage();
}

QString LotroDatManager::getCurrentInstallingPatchName()
{
    return current_installing_patch_name;
}

void LotroDatManager::InitialiseManager()
{
    qDebug() << "Initialising .dat manager";
    emit processStarted("InitialiseManager");
    QString game_folder = app_settings->value("General/game_folder_path", QString()).toString();
    QString locale_prefix = app_settings->value("General/original_locale", "English").toString();
    if (game_folder.isEmpty() || !FileSystem::fileExists(game_folder + "client_local_" + locale_prefix + ".dat")
            || !FileSystem::fileExists(game_folder + "client_general.dat")) {

        qDebug() << "Finished initialisation .dat manager - error: required files not found!";

        emit caughtError(QString("InitialiseManager"), {"FileNotFound"});
        emit processFinished("InitialiseManager");
        return;
    }

    // Updating file permissions to be sure, that they're not in read-only mode

    if (!QFile::setPermissions((game_folder + "/client_local_" + locale_prefix + ".dat"), QFileDevice::Permission(0x6666))) {
        qDebug() << "Unable to update permissions on client_local_* file!";
    }

    if (!QFile::setPermissions((game_folder + "/client_general.dat"), QFileDevice::Permission(0x6666))) {
        qDebug() << "Unable to update permissions on client_general* file!";
    }

    // Initialising client_local_*.dat file and client_general.dat

    auto client_local_init_res = client_local_file.Initialise((game_folder + "/client_local_" + locale_prefix + ".dat").toStdString(), 0);
    auto client_general_init_res = client_general_file.Initialise((game_folder + "/client_general.dat").toStdString(), 1);

    if (client_local_init_res.result != LOTRO_DAT::SUCCESS
            || client_general_init_res.result != LOTRO_DAT::SUCCESS) {
        client_local_file.Deinitialize();
        client_general_file.Deinitialize();

        qDebug() << "Finished initialisation .dat manager - error: DatFile initialisation error!";

        emit caughtError(QString("InitialiseManager"), {"InitialisationError"});
        emit processFinished("InitialiseManager");
        return;
    }

    emit processFinished("InitialiseManager");
}

void LotroDatManager::DeinitialiseManager()
{
    emit processStarted("DeinitialiseManager");
    qDebug() << "Started DEinitialisation .dat manager.";
    client_local_file.Deinitialize();
    client_general_file.Deinitialize();
    qDebug() << "Finished DEinitialisation .dat manager.";
    emit processFinished("DeinitialiseManager");
}

void LotroDatManager::StartGame(LOTRO_DAT::DatLocaleManager::LOCALE locale)
{
    emit processStarted("StartGame");
    qDebug() << "Starting game!";

    client_general_file.GetLocaleManager().SetLocale(locale);
    client_local_file.GetLocaleManager().SetLocale(locale);

    QString game_folder = app_settings->value("General/game_folder_path", QString()).toString();

    if (!FileSystem::fileExists(QApplication::applicationDirPath() + "/Launcher.exe")) {
        emit caughtError("StartGame", {"NoGameLauncherInLegacyDir"});
        qDebug() << "Starting game FAILED - no game launcher in legacy directory found!";
        emit processFinished("StartGame");
        return;
    }

    if (locale == LOTRO_DAT::DatLocaleManager::PATCHED) {
        QFile::remove(game_folder + "/lotro_ru.exe");
        if (!QFile::copy(QApplication::applicationDirPath() + "/LotroLauncher.exe", game_folder + "/lotro_ru.exe")) {
            emit caughtError("StartGame", {"NoAccessToGameLauncher"});
            qDebug() << "Starting game FAILED - cannot copy LotroLauncher to LotroLauncher_fwd!!";
            emit processFinished("StartGame");
            return;
        }

        QFile::remove(game_folder + "/LotroLauncher.exe");
        if (!QFile::copy(QApplication::applicationDirPath() + "/Launcher.exe", game_folder + "/LotroLauncher.exe")) {
            emit caughtError("StartGame", {"NoAccessToGameLauncher"});
            qDebug() << "Starting game FAILED - cannot copy GameLauncher to LotroLauncher!!";
            emit processFinished("StartGame");
            return;
        }

        QFile file(game_folder + "/legacy_path.txt");
        file.open(QIODevice::WriteOnly);
        QTextStream out(&file);
        out << QApplication::applicationDirPath() + "/LegacyLauncher.exe";
        file.close();
    } else {
        QFile::remove(game_folder + "/LotroLauncher.exe");
        if (!QFile::copy(QApplication::applicationDirPath() + "/LotroLauncher.exe", game_folder + "/LotroLauncher.exe")) {
            qDebug() << QApplication::applicationDirPath() + "/LotroLauncher.exe";
            qDebug() << game_folder + "/LotroLauncher.exe";
            emit caughtError("StartGame", {"NoAccessToGameLauncher"});
            qDebug() << "Starting game FAILED - cannot copy LotroLauncher from working dir to LotroLauncher in lotro dir!!";
            emit processFinished("StartGame");
            return;
        }
    }

    qDebug() << "Starting game finished!!";
    startLotroLauncherWithParameters(locale);
    emit processFinished("StartGame");
}

void LotroDatManager::ChangeTranslationLanguage()
{
    DeinitialiseManager();
    InitialiseManager();
}

void LotroDatManager::InstallActivePatches()
{
    SetupAllPatches();   // Setup means inserting necessary data
    ApplyTexts();       // Applying means activating patched data
    ApplyImages();
    ApplySounds();
}

void LotroDatManager::SetupAllPatches()
{
    foreach (QString patch_name, all_patch_names) {
        if (app_settings->value("PatchDatabasesDownload/" + patch_name, "Disabled").toString() == "Disabled") {
            continue;
        }

        emit processStarted("SetupPatch", {patch_name});
        current_installing_patch_name = patch_name;

        if (patch_name == "video") {
            InstallVideos();
            app_settings->setValue("PatchApplied/" + patch_name, "True");
            current_installing_patch_name = "none";
            emit processFinished("SetupPatch", {patch_name});
            return;
        }

        if (patch_name == "loadscreen") {
            InstallLoadscreens();
            app_settings->setValue("PatchApplied/" + patch_name, "True");
            current_installing_patch_name = "none";
            emit processFinished("SetupPatch", {patch_name});
            return;
        }

        QString database_path = app_settings->value("PatchPath/" + patch_name, "none").toString(); //patch_downloader->getDatabasePathByPatchName(patch_name);
        if (database_path == "none") {
            qDebug() << __FUNCTION__ << "Database for " << patch_name << " has no database path! Skipping!";
            continue;
        }

        LOTRO_DAT::Database db;
        if (!db.InitDatabase(database_path.toStdString())) {
            emit caughtError("InstallPatches", {"ErrorInitDatabase"});
            continue;
        }

        if (client_local_file.GetPatcher().PatchAllDatabase(&db).result != LOTRO_DAT::SUCCESS)
            emit caughtError("InstallPatches", {"ErrorCannotPatch", "client_local"});

        if (client_general_file.GetPatcher().PatchAllDatabase(&db).result != LOTRO_DAT::SUCCESS)
            emit caughtError("InstallPatches", {"ErrorCannotPatch", "client_general"});

        db.CloseDatabase();

        app_settings->setValue("PatchApplied/" + patch_name, "True");
        current_installing_patch_name = "none";
        emit processFinished("SetupPatch", {patch_name});
    }
}

void LotroDatManager::InstallLoadscreens()
{
    emit processStarted("InstallLoadscreens");

    if (app_settings->value("PatchDatabasesDownload/loadscreen", "Disabled").toString() == "Disabled")
        return;

    QString game_folder = app_settings->value("General/game_folder_path", QString()).toString();
    QString locale_prefix = app_settings->value("General/original_locale", "English").toString();

    QString raw_folder;
    if (locale_prefix == "English")
        raw_folder = "en";
    if (locale_prefix == "DE")
        raw_folder = "de";
    if (locale_prefix == "FR")
        raw_folder = "fr";

    QString folder = game_folder + "/raw/" + raw_folder + "/logo/";

    QString mainscreen =
            game_folder == "en"
            ? "lotro_ad_pregame.jpg"
            : "lotro_ad_pregame_" + game_folder + ".jpg";

    QStringList filenames;
    filenames << mainscreen
              << "lotro_generic_teleport_screen_01.jpg"
              << "lotro_generic_teleport_screen_02.jpg"
              << "lotro_generic_teleport_screen_03.jpg"
              << "lotro_generic_teleport_screen_04.jpg"
              << "lotro_generic_teleport_screen_05.jpg"
              << "lotro_generic_teleport_screen_06.jpg"
              << "lotro_generic_teleport_screen_07.jpg"
              << "lotro_generic_teleport_screen_08.jpg"
              << "lotro_generic_teleport_screen_09.jpg"
              << "lotro_generic_teleport_screen_10.jpg";


    QString database_path = app_settings->value("PatchPath/loadscreen", "none").toString();
    if (database_path == "none") {
        qDebug() << __FUNCTION__ << "No path to loadscreens patch found!";
        return;
    }

    LOTRO_DAT::Database db;
    if (!db.InitDatabase(database_path.toStdString())) {
        emit caughtError("InstallLoadscreens", {"ErrorInitDatabase"});
        emit processFinished("InstallLoadscreens");
        return;
    }

    for (size_t i = 0; i < qMin(db.CountRows(), size_t(filenames.size())); i++) {
        LOTRO_DAT::SubfileData subfile = db.GetNextFile();

        if (subfile.binary_data.Empty())
            continue;

        QFile::remove(folder + filenames[i]);
        subfile.binary_data.WriteToFile((folder + filenames[i]).toStdString());
    }
    db.CloseDatabase();
    emit processFinished("InstallLoadscreens");
}

void LotroDatManager::InstallVideos()
{
    emit processStarted("InstallVideos");
    if (app_settings->value("PatchDatabasesDownload/video", "Disabled").toString() == "Disabled")
        return;

    QString game_folder = app_settings->value("General/game_folder_path", QString()).toString();

    QString database_path = app_settings->value("PatchPath/video", "none").toString();
    if (database_path == "none") {
        qDebug() << __FUNCTION__ << "No path to videos patch found!";
        return;
    }

    LOTRO_DAT::Database db;
    if (!db.InitDatabase(database_path.toStdString())) {
        emit caughtError("InstallVideos", {"ErrorInitDatabase"});
        emit processFinished("InstallVideos");
        return;
    }

    for(size_t i = 0; i < db.CountRows(); i++) {
        LOTRO_DAT::SubfileData subfile = db.GetNextFile();

        if (subfile.Empty())
            continue;

        QString filename = QString::fromStdString(subfile.options["name"].as<std::string>());
        QUrl url = QString::fromStdString(subfile.options["url"].as<std::string>());
        QString hash = QString::fromStdString(subfile.options["hash"].as<std::string>());
        QString target_folder = game_folder + "/" + QString::fromStdString(subfile.options["folder"].as<std::string>()) + "/";

        if (!QDir(target_folder).exists())
            QDir(target_folder).mkpath(target_folder);

        if (FileSystem::fileExists(target_folder + filename)) {
            if (FileSystem::fileHash(target_folder + filename) == hash) {
                continue;
            } else {
                QFile::remove(target_folder + filename);
            }
        }

        Downloader video_downloader;
        video_downloader.setUrl(url);
        video_downloader.targetFile = new QFile(target_folder + filename);
        video_downloader.targetFile->open(QIODevice::ReadWrite);
        video_downloader.start();
// TODO: Progress notification on video download!
//        connect(&video_downloader, &Downloader::progressChanged, this, &LotroDatManager::VideoDownloadProgressChanged);
        video_downloader.waitForDownloaded();
        video_downloader.targetFile->close();
        video_downloader.targetFile->deleteLater();

        if (FileSystem::fileHash(target_folder + filename) != hash) {
            emit caughtError("InstallVideos", {"IncorrectHash", hash, FileSystem::fileHash(target_folder + filename), target_folder, filename, url});
        }
    }
    db.CloseDatabase();

    emit processFinished("InstallVideos");
}

void LotroDatManager::InstallUpdates()
{
    emit processStarted("InstallUpdates");
    foreach (QString patch_name, all_patch_names) {
        if (app_settings->value("PatchDatabasesDownload/" + patch_name, "Disabled").toString() == "Disabled" ||
                app_settings->value("PatchApplied/" + patch_name, "False").toString() == "True") {
            continue;
        }

        emit processStarted("SetupPatch", {patch_name});
        current_installing_patch_name = patch_name;

        if (patch_name == "video") {
            InstallVideos();
            app_settings->setValue("PatchApplied/" + patch_name, "True");
            current_installing_patch_name = "none";
            emit processFinished("SetupPatch", {patch_name});
            return;
        }

        if (patch_name == "loadscreen") {
            InstallLoadscreens();
            app_settings->setValue("PatchApplied/" + patch_name, "True");
            current_installing_patch_name = "none";
            emit processFinished("SetupPatch", {patch_name});
            return;
        }

        QString database_path = app_settings->value("PatchPath/" + patch_name, "none").toString(); //patch_downloader->getDatabasePathByPatchName(patch_name);
        if (database_path == "none") {
            qDebug() << __FUNCTION__ << "Database for " << patch_name << " has no database path! Skipping!";
            continue;
        }

        LOTRO_DAT::Database db;
        if (!db.InitDatabase(database_path.toStdString())) {
            emit caughtError("InstallPatches", {"ErrorInitDatabase"});
            continue;
        }

        if (client_local_file.GetPatcher().PatchAllDatabase(&db).result != LOTRO_DAT::SUCCESS)
            emit caughtError("InstallPatches", {"ErrorCannotPatch", "client_local"});

        if (client_general_file.GetPatcher().PatchAllDatabase(&db).result != LOTRO_DAT::SUCCESS)
            emit caughtError("InstallPatches", {"ErrorCannotPatch", "client_general"});

        db.CloseDatabase();

        app_settings->setValue("PatchApplied/" + patch_name, "True");
        current_installing_patch_name = "none";
        emit processFinished("SetupPatch", {patch_name});
    }
}

void LotroDatManager::InstallMicroPatch()
{
    // TODO
}

void LotroDatManager::CreateBackup()
{
    emit processStarted("CreateBackup");
    QString locale_prefix = app_settings->value("General/original_locale", "English").toString();
    client_local_file.GetBackupManager().CreateBackup((QApplication::applicationDirPath() + "/backup/client_local_" + locale_prefix + ".dat").toStdString());
    client_general_file.GetBackupManager().CreateBackup((QApplication::applicationDirPath() + "/backup/client_general.dat").toStdString());
    emit processFinished("CreateBackup");
}

void LotroDatManager::RestoreFromBackup()
{
    emit processStarted("CreateBackup");
    QString locale_prefix = app_settings->value("General/original_locale", "English").toString();
    client_local_file.GetBackupManager().RestoreFromBackup((QApplication::applicationDirPath() + "/backup/client_local_" + locale_prefix + ".dat").toStdString());
    client_general_file.GetBackupManager().RestoreFromBackup((QApplication::applicationDirPath() + "/backup/client_general.dat").toStdString());
    emit processFinished("CreateBackup");
}

void LotroDatManager::RemoveBackup()
{
    emit processStarted("CreateBackup");
    QString locale_prefix = app_settings->value("General/original_locale", "English").toString();
    client_local_file.GetBackupManager().RemoveBackup((QApplication::applicationDirPath() + "/backup/client_local_" + locale_prefix + ".dat").toStdString());
    client_general_file.GetBackupManager().RemoveBackup((QApplication::applicationDirPath() + "/backup/client_general.dat").toStdString());
    emit processFinished("CreateBackup");
}

void LotroDatManager::ApplyTexts()
{
    if (app_settings->value("PatchApplyOptions/texts_general", "Disabled").toString() == "Enabled") {
        client_local_file.GetLocaleManager().EnableCategory(100);
    } else {
        client_local_file.GetLocaleManager().DisableCategory(100);
    }

    if (app_settings->value("PatchApplyOptions/texts_emotes", "Disabled").toString() == "Enabled") {
        client_local_file.GetLocaleManager().EnableCategory(101);
    } else {
        client_local_file.GetLocaleManager().DisableCategory(101);
    }

    if (app_settings->value("PatchApplyOptions/texts_items", "Disabled").toString() == "Enabled") {
        client_local_file.GetLocaleManager().EnableCategory(102);
    } else {
        client_local_file.GetLocaleManager().DisableCategory(102);
    }
}

void LotroDatManager::ApplyImages() {
    if (app_settings->value("PatchApplyOptions/maps", "Disabled").toString() == "Enabled") {
        client_local_file.GetLocaleManager().EnableCategory(200);
    } else {
        client_local_file.GetLocaleManager().DisableCategory(200);
    }
}

void LotroDatManager::ApplySounds() {
    emit processStarted("ApplySounds");
    if (app_settings->value("PatchApplyOptions/sounds", "Disabled").toString() == "Enabled") {
        client_local_file.GetLocaleManager().EnableCategory(300);
    } else {
        client_local_file.GetLocaleManager().DisableCategory(300);
    }

    if (app_settings->value("PatchApplyOptions/video", "Disabled").toString() == "Enabled") {
        client_local_file.GetLocaleManager().EnableCategory(103);
    } else {
        client_local_file.GetLocaleManager().DisableCategory(103);
    }
    emit processFinished("ApplySounds");
}

bool LotroDatManager::startLotroLauncherWithParameters(LOTRO_DAT::DatLocaleManager::LOCALE locale)
{
    QStringList args;
    args << "-skiprawdownload";
    if (locale == LOTRO_DAT::DatLocaleManager::PATCHED)
        args << "gamelaunch" << "-disablePatch";

    client_general_file.Deinitialize();
    client_local_file.Deinitialize();

    if(FileSystem::fileExists(QApplication::applicationDirPath() + "/user.ini")){
        QSettings login(QApplication::applicationDirPath() + "/user.ini", QSettings::IniFormat);
        login.beginGroup("Account");
        QString username = login.value("username", "").toString();
        QString password = login.value("password", "").toString();
        login.endGroup();
        args << "-username" << username << "-password" << password;
    }

    qDebug() << "Запускаем игру со следующими аргументами: " << args;

    QFile f(app_settings->value("General/game_folder_path", "none").toString() + "/LotroLauncher.exe");
    QProcess process;

    if (FileSystem::fileExists(f.fileName())) {
        if(f.fileName().contains(" ")) f.setFileName("\"" + f.fileName() + "\"");
        process.startDetached(f.fileName(), args);
        process.waitForFinished(-1);
        QApplication::quit();
        return true;
    } else {
        emit caughtError("startLotroLauncherWithParameters", {"LotroLauncherNotFound"});
        return false;
    }
}
