#include "lotromanager.h"
#include "app.h"
#include "filesystem.h"

#include <QStringList>
#include <QDateTime>
#include <QDate>
#include <QTime>

LotroManager::LotroManager(QObject *parent) : QObject(parent),
                                              dat_files_(std::vector<LOTRO_DAT::DatFile>(5)),
                                              database_(LOTRO_DAT::Database()) {
    busy_ = false;

    connect(this, SIGNAL(dat_operation_started(QString)), this, SLOT(on_dat_operation_started(QString)));
    connect(this, SIGNAL(dat_operation_finished(QString,QString)), this, SLOT(on_dat_operation_finished(QString,QString)));
}

bool LotroManager::execute(QString command, QString args, QString name) {
    qInfo("%s:%i: %s%s", __FILE__, __LINE__, "Вызываем execute() команды ", command.toUtf8().data());

    if (busy_) {
        qWarning("%s:%i: %s", __FILE__, __LINE__, "Ошибка! Уже выполняется другой процесс с данными!");
        emit dat_operation_finished(command, args, name, "error");
        return false;
    }

    if (std::find(available_operations_.begin(), available_operations_.end(), command)
            == available_operations_.end()) {

        qWarning("%s:%i: %s%s%s", __FILE__, __LINE__, "Ошибка! Операция ", command.toUtf8().data(), " не найдена в списке доступных!");
        emit dat_operation_finished(command, args, name, "error");
        return false;
    }

    App* app = &App::getInstance();
    QString filename = app->config->getValue("Local", "folder") + "/" + app->config->getValue("Local", "file");
    QString execpath = app->config->getValue("Local", "folder") + "/" + "TurbineLauncher.exe";

    if (!FileSystem::fileExists(filename) && FileSystem::fileExists(execpath) && command != "StartGame") {
        qWarning("%s:%i: %s\n", __FILE__, __LINE__, "Файл данных не найден, но папка верная");
        QString text = "Не найден файл ресурсов";
        QString msg = "Файла ресурсов " + filename + ", похоже, не существует. Запустить лаунчер игры для "
                                                     "начала автоматического скачивания?<p>При запуске "
                                                     "лаунчера игры проверьте, что язык, выбранный в лаунчере и язык, "
                                                     "выбранный в Наследии, совпадают.<br>Язык в Наследии: <b>"
                + app->config->getValue("Local", "lang") + "</b></p>";
        app->helper->myDialogBox(text, msg, "OK", "Отмена", "gandalf.png", "loaddatfile", "", 420, 205, true, true, true);
        emit dat_operation_finished(command, args, name, "error");
        return false;
    }

    if (!FileSystem::fileExists(execpath)) {
        QString text = "Не найдены файлы игры!";
        QString msg = "Похоже, папка с игрой указана неверно. Укажите, пожалуйста, путь к папке с игрой. "
                      "Опознать папку с игрой можно по наличию папок 3rdparty, backup, bindat, de, en, forward и т.п.";
        app->helper->myDialogBox(text, msg, "OK", "Выход", "gandalf.png", "choosedatpath", "exit", 400, 170, true, true, true);
        emit dat_operation_finished(command, args, name, "error");
        return false;
    }

    if (command == "StartGame") {
        QtConcurrent::run([=]() {
            emit dat_operation_started(command, args, name);
            bool result = startGame();
            emit dat_operation_finished(command, args, name, result ? "" : "error");
        });
        return true;
    }

    if (command == "SetLocale") {
        QtConcurrent::run([=]() {
            emit dat_operation_started(command, args, name);
            setGameLocale(args);
            emit dat_operation_finished(command, args, name);
        });
        return true;
    }

    if (command == "ApplyPatch") {
        QtConcurrent::run([=]() {
            emit dat_operation_started(command, args, name);
            applyPatch(args);
            emit dat_operation_finished(command, args, name);
        });
        return true;
    }

    if (command == "ApplyMicroPatch") {
        QtConcurrent::run([=]() {
            emit dat_operation_started(command, args, name);
            applyMicroPatch();
            emit dat_operation_finished(command, args, name);
        });
        return true;
    }

    if (command == "ApplyLoadScreens") {
        QtConcurrent::run([=]() {
            emit dat_operation_started(command, args, name);
            applyLoadscreens();
            emit dat_operation_finished(command, args, name);
        });
        return true;
    }

    if (command == "ApplyPatchList") {
        QtConcurrent::run([=]() {
            emit dat_operation_started(command, args, name);
            applyPatch(args.split('|'));
            emit dat_operation_finished(command, args, name);
        });
        return true;
    }

    if (command == "ApplyGlobal") {
        QtConcurrent::run([=]() {
            emit dat_operation_started(command, args, name);
            applyPatch("fonts");
            applyPatch("sounds");
            applyPatch("texts");
            applyPatch("images");
            applyPatch("textures");
            applyLoadscreens();
            applyMicroPatch();
            emit dat_operation_finished(command, args, name);
        });
        return true;
    }

    if (command == "CheckDatFile") {
        QtConcurrent::run([=]() {
            emit dat_operation_started(command, args, name);
            int result = checkDatFile();
            emit dat_operation_finished(command, args, name, QString::number(result));
        });
        return true;
    }

    if (command == "SaveDatFiles") {
        QtConcurrent::run([=]() {
            emit dat_operation_started(command, args, name);
            saveDatFiles();
            emit dat_operation_finished(command, args, name);
        });
        return true;
    }
    return false;
}

bool LotroManager::isBusy() {
    return busy_;
}

void LotroManager::on_dat_operation_started(QString command, QString, QString name) {
    qInfo("%s:%i: %s%s %s %s", __FILE__, __LINE__, "Получен сигнал начала операции ", command.toUtf8().data()
          , "с именем", name.toUtf8().data());
    busy_ = true;
}

void LotroManager::on_dat_operation_finished(QString command, QString, QString name, QString result) {
    qInfo("%s:%i: %s%s %s %s %s %s", __FILE__, __LINE__
          , "Получен сигнал завершения операции ", command.toUtf8().data()
          , "с именем", name.toUtf8().data()
          , "с результатом", result.toUtf8().data());
    busy_ = false;
}

void LotroManager::saveDatFiles() {
    for (auto dat_file : dat_files_)
        dat_file.CloseDatFile();
}

bool LotroManager::startGame(){
    App *app = &App::getInstance();

    for (auto &dat_file : dat_files_)
        dat_file.CloseDatFile();

    QStringList args;
    args << "-skiprawdownload" << "-nosplash";
    if (dat_locale_ == PATCHED)
        args << "-disablePatch";


    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->config->getValue("Local", "folder") + "/TurbineLauncher.exe");
    QProcess process;

    if (FileSystem::fileExists(f.fileName())) {
        if(f.fileName().contains(" ")) f.setFileName("\"" + f.fileName() + "\"");
        process.startDetached(f.fileName(), args);
        process.waitForFinished(-1);
        return true;
    } else {
        QString text = "Запуск не удался";
        QString info = "Не удалось запустить игру. Не найден файл TurbineLauncher.exe или невозможно получить к нему доступ. "
                       "Проверьте в разделе 'Настройки', что у вас указан верный путь к игре, закрыт лаунчер игры и повторите попытку запуска.";
        app->helper->myDialogBox(text, info, "OK", "Отмена", "gandalf.png", "", "", 400, 160, true, true);
        return false;
    }
}

bool LotroManager::openDatFile(int id) {
    App *app = &App::getInstance();
    QStringList dat_files_name_list;

    dat_files_name_list << app->config->getValue("Local", "file")
                        << "client_general.dat"
                        << "client_sound.dat"
                        << "client_surface.dat"
                        << "client_highres.dat";

    if (dat_files_name_list[id] != nullptr) {

        qDebug() << dat_files_name_list[id];

        QString filename = app->config->getValue("Local", "folder") + "/" + dat_files_name_list[id];
        QString execpath = app->config->getValue("Local", "folder") + "/" + "TurbineLauncher.exe";

        qDebug("%s:%i: %s%s", __FILE__, __LINE__, "Инициализация dat-файла. Открываем файл ", filename.toUtf8().toStdString().c_str());
        int dat_state = dat_files_[id].InitDatFile(filename.toUtf8().toStdString(), id);
        qDebug("%s:%i: %s%d", __FILE__, __LINE__, "Состояние dat-файла: ", dat_state);

        if(dat_state > 0) {
            return true;
        } else {
            QString text = "Нет доступа к  файлам игры";
            QString info = "Возможно в данный момент запущены клиент или лаунчер игры либо попытка доступа блокируется настройкой прав, антивирусом или другими приложениями. Завершите все процессы, использующие файлы игры и повторите попытку.";
            app->helper->myDialogBox(text, info, "OK", "Отмена", "gandalf.png", "", "", 400, 200, true, false);
            return false;
        }
    }
    return true;
}

void LotroManager::applyPatch(QString name) {
    if (!correctPatchName(name))
        return;

    if (name == "loadscreens") {
        applyLoadscreens();
        return;
    }

    if (name == "videos") {
        return;
    }

    App *app = &App::getInstance();
    emit changeHint("Применение патчей", "Ожидайте. Идёт применение выбранных патчей");

    qDebug("%s:%i: %s%s", __FILE__, __LINE__, "Начинаем применение патча ", name.toUtf8().data());

    QDir dir(QApplication::applicationDirPath() + "/data");
    if (!dir.exists()) {
        emit changeHint("Ошибка применения патчей", "Не найдена папка data!");
        return;
    }

    QStringList paths = dir.entryList(QStringList(name + "*"));
    qInfo("%s:%i: %s%s%s%s", __FILE__, __LINE__, "Для патча ", name.toUtf8().data(), " найдены пути:", QString(paths.join(',')).toUtf8().data());

    if (paths.empty()) {
        emit changePatchStatus(name, "Не найден");
        emit changeHint("Патч " + patchTitleFromName(name) + " не найден", "Чтобы закачать его перейдите на вкладку \"Патчи\", когда все загрузки будут завершены.");
        return;
    }

    qDebug() << QString("data/" + paths.first()).toUtf8().data();
    qInfo("%s:%i: %s%s", __FILE__, __LINE__, "Инициализируем базу данных: ", QString("data/" + paths.first()).toUtf8().data());

    if (!database_.InitDatabase(QString("data/" + paths.first()).toUtf8().data())) {
        emit changePatchStatus(name, "Ошибка патча!");
        emit changeHint("Применение патча завершено", "Патч " + patchTitleFromName(name) + " НЕ установлен (ошибка чтения патча)");
        return;
    }

    int indb = database_.CountRows();
    qInfo("%s:%i: %s%d", __FILE__, __LINE__, "Файлов в обновлении: ", indb);

    last_second_files_count = 0;
    last_second_time = 0;
    start_time = QDateTime::currentDateTime().toMSecsSinceEpoch();

    for(int i = 0; i < indb; i++) {
        qint64 current_timestamp = QDateTime::currentDateTime().toMSecsSinceEpoch();

        if (current_timestamp - start_time < 1000)
            elapsed_time = " высчитывается...";
        else if (current_timestamp - last_second_time >= 1000) {
            last_second_time = current_timestamp;
            last_second_files_count = i;
            elapsed_time = app->helper->countFormattedElapsedTime(indb - i, double(i * 1000.0) / double(current_timestamp - start_time));
        }

        processFile();
        emit changePatchStatus(name, QString("Применение ... ")
                                     + QString::fromLatin1("%1").arg(i*100.0/indb, 3, 'f', 1)
                                     + QString("%"));
        emit changeHint("Применение патча " + patchTitleFromName(name)
                        , "Завершено " + QString::fromLatin1("%1").arg(i*100.0/indb, 3, 'f', 1)
                        + "% Оставшееся время: " + elapsed_time);

    }

    emit changePatchStatus(name, "Готово");

    app->config->setValue("Applied", name, paths.first());
    database_.CloseDatabase();

    emit changeHint("Применение патча завершено", "Патч " + patchTitleFromName(name) + " успешно установлен");
}

bool LotroManager::correctPatchName(QString patch_name) {
    return patch_name == "texts" ||
           patch_name == "fonts" ||
           patch_name == "sounds" ||
           patch_name == "images" ||
           patch_name == "textures" ||
           patch_name == "loadscreens" ||
           patch_name == "videos";
}

void LotroManager::applyPatch(QStringList patches_list) {
    for (QString patch : patches_list) {
        if (correctPatchName(patch))
            applyPatch(patch);
    }
}

int LotroManager::checkDatFile(){
    App* app = &App::getInstance();

    if (openDatFile(0) < 0)
        return 0;

    if(dat_files_[0].CheckIfUpdatedByGame())
        return 1;

    if(dat_files_[0].CheckIfNotPatched() && app->config->getValue("Local", "runfirst") == "1")
        return 2;

    if(dat_files_[0].CheckIfPatchedByOldLauncher())
        return 3;

    return 0;
}

void LotroManager::applyMicroPatch(){
    return;
    App *app = &App::getInstance();
    app->logSectionStart("Установка мгновенных обновлений");
    app->helper->setState("busy");
    app->window->ui->hintLabel->setText("Установка последних обновлений");
    app->window->ui->exthintLabel->setText("Подождите пока закончится применение обновлений");

    database_.InitDatabase(app->network->micropatch.toUtf8().data());
    int indb = database_.CountRows();
    qInfo("%s:%i: %s%d", __FILE__, __LINE__, "Файлов в обновлении: ", indb);

    for(int i = 0; i <= indb; i++ ) {
        processFile();
    }
    //for (int i = 0; i < 5; i++) app->datfiles[i].CommitChanges();
    database_.CloseDatabase();

    app->config->setValue("Updates", "update", QDate::currentDate().toString("yyyy-MM-dd"));

    app->helper->setState("free");
    qInfo("%s:%i: %s", __FILE__, __LINE__, "Выполнено.");
    app->logSectionEnd();
}

void LotroManager::applyLoadscreens(){
    App *app = &App::getInstance();
    app->logSectionStart("Установка заставочных экранов");

    QString datafolder = QApplication::applicationDirPath() + "/data";
    QDir dir(datafolder);
    if (!dir.exists()) {
        emit changePatchStatus("loadscreens", "Не найден!");
        return;
    }
    QStringList paths = dir.entryList(QStringList("loadscreens*"));

    if(paths.empty()){
        emit changePatchStatus("loadscreens", "Не найден!");
        return;
    }

    if (paths.first() == app->config->getValue("Applied", "screens")){
        return;
    }

    QString lang = app->config->getValue("Local", "lang");

    if (lang == "-1") {
        app->config->setValue("Local", "lang", "en");
        lang = "en";
    }

    QString folder = app->config->getValue("Local", "folder") + "raw/" + lang + "/logo/";
    SubfileData subfile;
    QStringList filenames;
    QString mainscreen = lang == "en" ? "lotro_ad_pregame.jpg" : "lotro_ad_pregame_" + lang + ".jpg";
    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 basename = datafolder + "/" + paths.first();
    database_.InitDatabase(basename.toStdString());
    int indb = database_.CountRows();
    qInfo("%s:%i: %s%d", __FILE__, __LINE__, "Файлы загрузочных экранов: ", indb);
    for(int i = 0; i<indb; i++){
        subfile = database_.GetNextFile();
        qDebug() << i;
        if (!subfile.Empty()){
            qInfo("%s:%i: %s", __FILE__, __LINE__, (folder + filenames[i]).toUtf8().data());
            QFile::remove(folder + filenames[i]);
            subfile.binary_data.WriteToFile((folder + filenames[i]).toStdString());
        }
    }
    database_.CloseDatabase();
    app->config->setValue("Applied", "screens", paths.first());
    emit changePatchStatus("loadscreens", "Готово");
    qInfo("%s:%i: %s", __FILE__, __LINE__, "Выполнено.");
    app->logSectionEnd();

}

void LotroManager::processFile(){
    SubfileData subfile;

    subfile = database_.GetNextFile();
    if (subfile.Empty()){
        qInfo("%s:%i: %s", __FILE__, __LINE__, "Достигнут конец файла.");
        return;
    }

    App* app = &App::getInstance();
    int dat_id = subfile.options["did"].as<int>();
    int dat_state = dat_files_[dat_id].InitDatFile(app->config->getDatPath(dat_id), dat_id);
    //qDebug("%s:%i: %s%d", __FILE__, __LINE__, "Состояние dat-файла: ", dat_state);
    if (dat_state > 0)
        dat_files_[dat_id].PatchFile(subfile);
}

void LotroManager::installMicroPatch(){
    return;
    App *app = &App::getInstance();
    if(app->config->getValue("Updates", "micro") != "true") return;

    app->helper->setState("busy");
    app->window->ui->hintLabel->setText("Установка последних обновлений");
    app->window->ui->exthintLabel->setText("Идет проверка и установка новых обновлений");
    QFuture<void> future = QtConcurrent::run([=]() {
        prepareMicroPatch();
        applyMicroPatch();
        QDir dir("/data/micro");
        FileSystem::clearFolder(dir);
    });
    app->window->ui->hintLabel->setText("Установка завершена");
    app->window->ui->exthintLabel->setText("Обновления успешно установлены");
    app->helper->setState("free");

}

void LotroManager::prepareMicroPatch(){
    return;
    App *app = &App::getInstance();
    app->logSectionStart("Автоматическая проверка обновлений");
    app->helper->setState("busy");
    app->window->ui->hintLabel->setText("Загрузка последних обновлений");
    app->window->ui->exthintLabel->setText("Подождите пока загрузятся последние изменения в системе переводов");

    QString date = app->config->getValue("Updates", "update");
    if (date == QDate::currentDate().toString("yyyy-MM-dd"))
        return;

    if (date == "false" || date == "-1")
        date = QDate::currentDate().toString("yyyy-MM-dd");

    //QTime time = QTime::currentTime();
    //int seconds = QTime(0, 0, 0).secsTo(time);
    //int timestamp = app->helper->dateToTimestamp(date + " " + time.toString("hh:mm:ss"), "yyyy-MM-dd hh:mm:ss") - seconds - 1209600;
    int timestamp = app->helper->dateToTimestamp(date, "yyyy-MM-dd") - 1209600;

    qInfo("%s:%i: %s", __FILE__, __LINE__, "Опция активна. Начинаем загрузку обновлений");
    app->network->micropatch = QApplication::applicationDirPath() + "/data/micro/" + QString::number(timestamp) + ".db";
    if(!FileSystem::fileExists(app->network->micropatch)){
        qInfo("%s:%i: %s%s", __FILE__, __LINE__, "Файл патча: ", app->network->micropatch.toUtf8().data());
        app->network->micropatch = app->network->getMicroPath(timestamp);
    }
    app->window->ui->hintLabel->setText("Загрузка завершена");
    app->window->ui->exthintLabel->setText("Все обновления были успешно загружены и доступны к установке");

    app->helper->setState("free");
    qInfo("%s:%i: %s", __FILE__, __LINE__, "Выполнено.");
    app->logSectionEnd();
}

bool LotroManager::setGameLocale(QString locale){
    if(openDatFile(0)){
        LOCALE loc = dat_files_[0].current_locale();
        dat_locale_ = loc;

        qDebug() << "Starting " + locale + " version. Current locale is " << loc;

        if (locale == "RU" && loc == ORIGINAL) {
            dat_files_[0].SetLocale(PATCHED);
            dat_locale_ = PATCHED;
        }

        if(locale == "Original" && loc == PATCHED){
            dat_files_[0].SetLocale(ORIGINAL);
            dat_locale_ = ORIGINAL;
        }
        saveDatFiles();

        qDebug() << "Successfully set locale " << locale;
    }
    return true;
}

//void LotroManager::handleFinisheddd(){
//   installMicroPatch();
//}

QString LotroManager::patchTitleFromName(QString name) {
    if (name == "sounds")
        return "\"Звуки\"";
    if (name == "texts")
        return "\"Тексты\"";
    if (name == "fonts")
        return "\"Шрифты\"";
    if (name == "images")
        return "\"Карты (1)\"";
    if (name == "textures")
        return "\"Карты (2)\"";
    if (name == "videos")
        return "\"Видеоролики\"";
    if (name == "loadscreens")
        return "\"Загрузочные экраны\"";
    return "\"Неизвестный патч\"";
}
