#include "lotrodatmanager.h"
#include "models/filesystem.h"
#include "models/settings.h"

#include <QDebug>
#include <QProcess>

Q_DECLARE_METATYPE(LOTRO_DAT::DatLocaleManager::LOCALE)
Q_DECLARE_METATYPE(LotroDatManager::Category)
Q_DECLARE_METATYPE(LotroDatManager::Status)


void debug::ClientLocalDatStatusChangedHandler(DatStatus::ProgressInfo info) {
    if (info.status == DatStatus::DAT_STATUS::E_FREE) {
        std::cout << "CLIENT_LOCAL: operation finished" << std::endl;
        return;
    }
    if (info.status == DatStatus::DAT_STATUS::E_BACKUP_CREATING) {
        std::cout << "CLIENT_LOCAL: creating backup " << info.percentage << "% "
        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
        return;
    }
    if (info.status == DatStatus::DAT_STATUS::E_BACKUP_REMOVING) {
        std::cout << "CLIENT_LOCAL: removing backup " << info.percentage << "% "
        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
        return;
    }
    if (info.status == DatStatus::DAT_STATUS::E_BACKUP_RESTORING) {
        std::cout << "CLIENT_LOCAL: restoring backup " << info.percentage << "% "
        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
        return;
    }
    if (info.status == DatStatus::DAT_STATUS::E_COMMITING) {
        std::cout << "CLIENT_LOCAL: applying locales " << info.percentage << "% "
        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
        return;
    }
    if (info.status == DatStatus::DAT_STATUS::E_EXTRACTING) {
        std::cout << "CLIENT_LOCAL: extracting data " << info.percentage << "% "
        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
        return;
    }
    if (info.status == DatStatus::DAT_STATUS::E_GATHERING_INFO) {
        std::cout << "CLIENT_LOCAL: gathering info " << info.percentage << "% "
        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
        return;
    }
    if (info.status == DatStatus::DAT_STATUS::E_INITIALISING) {
        std::cout << "CLIENT_LOCAL: initialising " << info.percentage << "% "
        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
        return;
    }
    if (info.status == DatStatus::DAT_STATUS::E_PATCHING) {
        std::cout << "CLIENT_LOCAL: applying patch " << info.percentage << "% "
        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
        return;
    }
}

void debug::ClientGeneralDatStatusChangedHandler(DatStatus::ProgressInfo info) {
    if (info.status == DatStatus::DAT_STATUS::E_FREE) {
        std::cout << "CLIENT_GENERAL: operation finished" << std::endl;
        return;
    }
    if (info.status == DatStatus::DAT_STATUS::E_BACKUP_CREATING) {
        std::cout << "CLIENT_GENERAL: creating backup " << info.percentage << "% "
        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
        return;
    }
    if (info.status == DatStatus::DAT_STATUS::E_BACKUP_REMOVING) {
        std::cout << "CLIENT_GENERAL: removing backup " << info.percentage << "% "
        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
        return;
    }
    if (info.status == DatStatus::DAT_STATUS::E_BACKUP_RESTORING) {
        std::cout << "CLIENT_GENERAL: restoring backup " << info.percentage << "% "
        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
        return;
    }
    if (info.status == DatStatus::DAT_STATUS::E_COMMITING) {
        std::cout << "CLIENT_GENERAL: applying locales " << info.percentage << "% "
        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
        return;
    }
    if (info.status == DatStatus::DAT_STATUS::E_EXTRACTING) {
        std::cout << "CLIENT_GENERAL: extracting data " << info.percentage << "% "
        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
        return;
    }
    if (info.status == DatStatus::DAT_STATUS::E_GATHERING_INFO) {
        std::cout << "CLIENT_GENERAL: gathering info " << info.percentage << "% "
        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
        return;
    }
    if (info.status == DatStatus::DAT_STATUS::E_INITIALISING) {
        std::cout << "CLIENT_GENERAL: initialising " << info.percentage << "% "
        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
        return;
    }
    if (info.status == DatStatus::DAT_STATUS::E_PATCHING) {
        std::cout << "CLIENT_GENERAL: applying patch " << info.percentage << "% "
        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
        return;
    }
}

LotroDatManager::LotroDatManager(QObject *parent) :
    QObject(parent) {

    qRegisterMetaType<Status>("Status");
    qRegisterMetaType<LOTRO_DAT::DatLocaleManager::LOCALE>("LOCALE");
    client_local_file_.GetStatusModule().AddStatusChangedCallbackFunction(client_local_status_updated_callback_);
    client_general_file_.GetStatusModule().AddStatusChangedCallbackFunction(client_general_status_updated_callback_);
}

LotroDatManager::~LotroDatManager()
{
    deinitializeManager();
}

bool LotroDatManager::Initialised()
{
    return client_general_file_.Initialized() && client_local_file_.Initialized();
}

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

bool LotroDatManager::DatPathIsRelevant()
{
    QString game_folder = Settings::getValue("Lotro/game_path").toString();
    QString locale_prefix = Settings::getValue("Lotro/original_locale").toString();

    QString client_local_filepath = game_folder + "/client_local_" + locale_prefix + ".dat";
    QString client_general_filepath = game_folder + "/client_general.dat";

    QString client_local_current_path = QString::fromStdString(client_local_file_.GetIO().GetFilename().value);
    QString client_general_current_path = QString::fromStdString(client_general_file_.GetIO().GetFilename().value);

    return QFileInfo(client_local_filepath) != QFileInfo(client_local_current_path)
            || QFileInfo(client_general_filepath) != QFileInfo(client_general_current_path);
}

void LotroDatManager::initializeManager()
{
    emit operationStarted("initializeManager");
    qDebug() << __FUNCTION__ << "Starting initialisation of LotroDatManager";

    QString game_folder = Settings::getValue("Lotro/game_path").toString();
    QString locale_prefix = Settings::getValue("Lotro/original_locale").toString();

    QString client_local_filepath = game_folder + "/client_local_" + locale_prefix + ".dat";
    QString client_general_filepath = game_folder + "/client_general.dat";

    if (game_folder == "none") {
        qDebug() << __FUNCTION__ << "Finished initialisation LotroDatManager - error: .dat files not found!";
        emit errorOccured("initializeManager", {}, "FolderNotDefined");
        emit operationFinished("initializeManager", {}, false);
        return;
    }

    if (!FileSystem::fileExists(client_local_filepath) || !FileSystem::fileExists(client_general_filepath)) {
        emit errorOccured("initializeManager", {}, "DatFilesNotFound");
        emit operationFinished("initializeManager", {}, false);
        return;
    }

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

    if (!QFile::setPermissions(client_local_filepath, QFileDevice::Permission(0x6666))) {
        qDebug() << __FUNCTION__ << "Unable to update permissions on client_local_* file!";
    }

    if (!QFile::setPermissions(client_general_filepath, QFileDevice::Permission(0x6666))) {
        qDebug() << __FUNCTION__ << "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(client_local_filepath.toStdString(), 0);
    auto client_general_init_res = client_general_file_.Initialise(client_general_filepath.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() << __FUNCTION__ << "Finished LotroDatManager initialisation - error: DatFile initialisation error!";

        emit errorOccured("initializeManager", {}, "DatInitError");
        emit operationFinished("initializeManager", {}, false);
        return;
    }

    qDebug() << "LotroDatManager initialisation successfull! Dat files: "
             << QString::fromStdString(client_general_file_.GetIO().GetFilename().value)
             << QString::fromStdString(client_local_file_.GetIO().GetFilename().value);

    emit operationFinished("initializeManager", {}, true);
}

void LotroDatManager::deinitializeManager()
{
    emit operationStarted("deinitializeManager");
    client_local_file_.Deinitialize();
    client_general_file_.Deinitialize();
    emit operationFinished("deinitializeManager");
}

void LotroDatManager::startGame(LOTRO_DAT::DatLocaleManager::LOCALE locale)
{
    emit operationStarted("startGame", {locale});

    client_general_file_.GetLocaleManager().SetLocale(locale);
    client_local_file_.GetLocaleManager().SetLocale(locale);

    QString game_folder = Settings::getValue("Lotro/game_path").toString();

    if (game_folder == "none") {
        qDebug() << __FUNCTION__ << "Starting game FAILED - game folder wasnt set!";
        emit errorOccured("startGame", {locale}, "GameFolderNotSet");
        emit operationFinished("startGame", {locale}, false);
        return;
    }

    if (!FileSystem::fileExists(QApplication::applicationDirPath() + "/Launcher.exe")) {
        qDebug() << __FUNCTION__ << "Starting game FAILED - no game launcher in legacy directory found!";
        emit errorOccured("startGame", {locale}, "NoGameLauncherInLegacyDir");
        emit operationFinished("startGame", {locale}, false);
        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")) {
            qDebug() << __FUNCTION__ << "Starting game FAILED - cannot copy LotroLauncher to lotro_ru.exe!!";
            emit errorOccured("startGame", {locale}, "LauncherCopyFailed");
            emit operationFinished("startGame", {locale}, false);
            return;
        }

        QFile::remove(game_folder + "/LotroLauncher.exe");
        if (!QFile::copy(QApplication::applicationDirPath() + "/Launcher.exe", game_folder + "/LotroLauncher.exe")) {
            qDebug() << __FUNCTION__ << "Starting game FAILED - cannot copy GameLauncher to LotroLauncher!!";
            emit errorOccured("startGame", {locale}, "NoAccessToGameLauncher");
            emit operationFinished("startGame", {locale}, false);
            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() << __FUNCTION__ << "Starting game FAILED - cannot copy LotroLauncher from working dir to LotroLauncher in lotro dir!!";
            emit errorOccured("startGame", {locale}, "NoAccessToGameLauncher");
            emit operationFinished("startGame", {locale}, false);
            return;
        }
    }

    if (!startLotroLauncherWithParameters(locale)) {
        emit errorOccured("startGame", {locale}, "StartLotroLauncherWithParametersFailed");
        emit operationFinished("startGame", {locale}, false);
        return;
    }

    emit operationFinished("startGame", {locale}, true);
}

void LotroDatManager::installPatch(QString patch_name, QString database_path)
{
    emit operationStarted("installPatch", {patch_name, database_path});
    LOTRO_DAT::Database db;

    if (!db.InitDatabase(database_path.toStdString())) {
        emit errorOccured("installPatch", {patch_name, database_path}, "ErrorInitDatabase");
        emit operationFinished("installPatch", {patch_name, database_path}, false);
        return;
    }

    if (client_local_file_.GetPatcher().PatchAllDatabase(&db).result != LOTRO_DAT::SUCCESS) {
        db.CloseDatabase();
        emit errorOccured("installPatch", {patch_name, database_path}, "ErrorPatchClientLocal");
        emit operationFinished("installPatch", {patch_name, database_path}, false);
        return;
    }

    if (client_general_file_.GetPatcher().PatchAllDatabase(&db).result != LOTRO_DAT::SUCCESS) {
        db.CloseDatabase();
        emit errorOccured("installPatch", {patch_name, database_path});
        emit operationFinished("installPatch", {patch_name, database_path}, false);
        return;
    }

    db.CloseDatabase();
    emit operationFinished("installPatch", {patch_name, database_path}, true);
}

void LotroDatManager::enableCategory(QString patch_name, LotroDatManager::Category category)
{
    emit operationStarted("enableCategory", {patch_name, category});

    if (client_local_file_.GetLocaleManager().EnableCategory(category).result != LOTRO_DAT::SUCCESS) {
        emit errorOccured("enableCategory", {patch_name, category}, "ErrorEnableCategoryClientLocal");
        emit operationFinished("enableCategory", {patch_name, category}, false);
        return;
    }

    if (client_general_file_.GetLocaleManager().EnableCategory(category).result != LOTRO_DAT::SUCCESS) {
        emit errorOccured("enableCategory", {patch_name, category}, "ErrorEnableCategoryClientGeneral");
        emit operationFinished("enableCategory", {patch_name, category}, false);
        return;
    }

    emit operationFinished("enableCategory", {patch_name, category});
}

void LotroDatManager::disableCategory(QString patch_name, LotroDatManager::Category category)
{
    emit operationStarted("disableCategory", {patch_name, category});

    if (client_local_file_.GetLocaleManager().DisableCategory(category).result != LOTRO_DAT::SUCCESS) {
        emit errorOccured("disableCategory", {patch_name, category}, "ErrorDisableCategoryClientLocal");
        emit operationFinished("disableCategory", {patch_name, category}, false);
        return;
    }

    if (client_general_file_.GetLocaleManager().DisableCategory(category).result != LOTRO_DAT::SUCCESS) {
        emit errorOccured("disableCategory", {patch_name, category}, "ErrorDisableCategoryClientGeneral");
        emit operationFinished("disableCategory", {patch_name, category}, false);
        return;
    }

    emit operationFinished("disableCategory", {patch_name, category}, true);
}

void LotroDatManager::createBackup()
{
    emit operationStarted("createBackup");
    QString locale_prefix = Settings::getValue("Lotro/original_locale").toString();
    QString client_local_backup_path = QApplication::applicationDirPath() + Settings::getValue("Backup/path").toString() + "client_local_" + locale_prefix + ".dat";
    QString client_general_backup_path = QApplication::applicationDirPath() + Settings::getValue("Backup/path").toString() + "client_general.dat";
    auto loc_res = client_local_file_.GetBackupManager().CreateBackup(client_local_backup_path.toStdString());
    auto gen_res = client_general_file_.GetBackupManager().CreateBackup(client_general_backup_path.toStdString());
    bool operations_result = (loc_res.result == LOTRO_DAT::SUCCESS && gen_res.result == LOTRO_DAT::SUCCESS);

    emit operationFinished("createBackup", {client_local_backup_path, client_general_backup_path}, operations_result);
}

void LotroDatManager::restoreFromBackup()
{
    emit operationStarted("restoreFromBackup");
    QString locale_prefix = Settings::getValue("Lotro/original_locale").toString();
    QString client_local_backup_path = QApplication::applicationDirPath() + Settings::getValue("Backup/path").toString() + "client_local_" + locale_prefix + ".dat";
    QString client_general_backup_path = QApplication::applicationDirPath() + Settings::getValue("Backup/path").toString() + "client_general.dat";
    auto loc_res = client_local_file_.GetBackupManager().RestoreFromBackup(client_local_backup_path.toStdString());
    auto gen_res = client_general_file_.GetBackupManager().RestoreFromBackup(client_general_backup_path.toStdString());
    bool operations_result = (loc_res.result == LOTRO_DAT::SUCCESS && gen_res.result == LOTRO_DAT::SUCCESS);
    emit operationFinished("restoreFromBackup", {}, operations_result);
}

void LotroDatManager::removeBackup()
{
    emit operationStarted("removeBackup");
    QString locale_prefix = Settings::getValue("Lotro/original_locale").toString();
    QString client_local_backup_path = QApplication::applicationDirPath() + Settings::getValue("Backup/path").toString() + "client_local_" + locale_prefix + ".dat";
    QString client_general_backup_path = QApplication::applicationDirPath() + Settings::getValue("Backup/path").toString() + "client_general.dat";
    auto loc_res = client_local_file_.GetBackupManager().RemoveBackup(client_local_backup_path.toStdString());
    auto gen_res = client_general_file_.GetBackupManager().RemoveBackup(client_general_backup_path.toStdString());
    bool operations_result = (loc_res.result == LOTRO_DAT::SUCCESS && gen_res.result == LOTRO_DAT::SUCCESS);
    emit operationFinished("removeBackup", {}, operations_result);
}

bool LotroDatManager::startLotroLauncherWithParameters(LOTRO_DAT::DatLocaleManager::LOCALE locale)
{
    QStringList args;
    if (Settings::getValue("Lotro/skip_raw_download").toBool()) {
        args << "-skiprawdownload";
    }

    if (Settings::getValue("Lotro/no_splash_screen").toBool()) {
        args << "-nosplashscreen";
    }

    if (locale == LOTRO_DAT::DatLocaleManager::PATCHED) {
        args << "gamelaunch" << "-disablePatch";
    }

    client_general_file_.Deinitialize();
    client_local_file_.Deinitialize();

    QString username = Settings::getValue("Account/username").toString();
    QString password = Settings::getValue("Account/password").toString();
    if (!username.isEmpty() && !password.isEmpty()) {
        args << "-username" << username << "-password" << password;
    }

    qDebug() << __FUNCTION__ << "Starting game with arguments: " << args;

    QFile f(Settings::getValue("Lotro/game_path").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 {
        return false;
    }
}
