#include "patchlist.h"

#include <QDebug>
#include <QDirIterator>

PatchList::PatchList(LotroDatManager *mgr, QObject *parent) : QObject(parent)
{
    lotro_mgr_ = mgr;

    qInfo() << "PatchList: Initialising patchsets";
    texts_patch_ = new TextsPatch(mgr);
    graphics_patch_ = new GraphicsPatch(mgr);
    sounds_patch_ = new SoundsPatch(mgr);
    videos_patch_ = new VideosPatch(mgr);

    qInfo() << "PatchList: Patchsets were initialized, initializing threads";
    texts_patch_thread_ = new QThread(this);
    graphics_patch_thread_ = new QThread(this);
    sounds_patch_thread_ = new QThread(this);
    videos_patch_thread_ = new QThread(this);

    texts_patch_->moveToThread(texts_patch_thread_);
    graphics_patch_->moveToThread(graphics_patch_thread_);
    sounds_patch_->moveToThread(sounds_patch_thread_);
    videos_patch_->moveToThread(videos_patch_thread_);

    connect(texts_patch_, &TextsPatch::operationStarted, this, &PatchList::onPatchOperationStarted, Qt::QueuedConnection);
    connect(graphics_patch_, &GraphicsPatch::operationStarted, this, &PatchList::onPatchOperationStarted, Qt::QueuedConnection);
    connect(sounds_patch_, &SoundsPatch::operationStarted, this, &PatchList::onPatchOperationStarted, Qt::QueuedConnection);
    connect(videos_patch_, &VideosPatch::operationStarted, this, &PatchList::onPatchOperationStarted, Qt::QueuedConnection);

    connect(texts_patch_, &TextsPatch::operationFinished, this, &PatchList::onPatchOperationFinished, Qt::QueuedConnection);
    connect(graphics_patch_, &GraphicsPatch::operationFinished, this, &PatchList::onPatchOperationFinished, Qt::QueuedConnection);
    connect(sounds_patch_, &SoundsPatch::operationFinished, this, &PatchList::onPatchOperationFinished, Qt::QueuedConnection);
    connect(videos_patch_, &VideosPatch::operationFinished, this, &PatchList::onPatchOperationFinished, Qt::QueuedConnection);

    connect(texts_patch_, &TextsPatch::progressChanged, this, &PatchList::onPatchOperationProgressChanged, Qt::QueuedConnection);
    connect(graphics_patch_, &GraphicsPatch::progressChanged, this, &PatchList::onPatchOperationProgressChanged, Qt::QueuedConnection);
    connect(sounds_patch_, &SoundsPatch::progressChanged, this, &PatchList::onPatchOperationProgressChanged, Qt::QueuedConnection);
    connect(videos_patch_, &VideosPatch::progressChanged, this, &PatchList::onPatchOperationProgressChanged, Qt::QueuedConnection);

    connect(lotro_mgr_, &LotroDatManager::operationStarted, this, &PatchList::onLotroManagerOperationStarted, Qt::QueuedConnection);
    connect(lotro_mgr_, &LotroDatManager::operationFinished, this, &PatchList::onLotroManagerOperationFinished, Qt::QueuedConnection);

    qInfo() << "PatchList: Patchset threads initialized, starting workers";
    texts_patch_thread_->start();
    graphics_patch_thread_->start();
    sounds_patch_thread_->start();
    videos_patch_thread_->start();

    patch_update_timer.setInterval(5 * 60 * 1000); // Once in 5 minutes check for updates
    connect(&patch_update_timer, &QTimer::timeout, this, &PatchList::update);
}

PatchList::~PatchList()
{
    qDebug() << "PatchList: finishing work";
    texts_patch_thread_->quit();
    graphics_patch_thread_->quit();
    sounds_patch_thread_->quit();
    videos_patch_thread_->quit();

    if (!texts_patch_thread_->wait(500)) {
        qDebug() << "PatchList: ERROR, TEXTS PATCHSET DIDNT STOP, FORCEFULLY TERMINATING!";
        texts_patch_thread_->terminate();
        texts_patch_thread_->wait();
    }

    if (!graphics_patch_thread_->wait(500)) {
        qDebug() << "PatchList: ERROR, GRAPHICS PATCHSET DIDNT STOP, FORCEFULLY TERMINATING!";
        graphics_patch_thread_->terminate();
        graphics_patch_thread_->wait();
    }

    if (!sounds_patch_thread_->wait(500)) {
        qDebug() << "PatchList: ERROR, SOUNDS PATCHSET DIDNT STOP, FORCEFULLY TERMINATING!";
        sounds_patch_thread_->terminate();
        sounds_patch_thread_->wait();
    }

    if (!videos_patch_thread_->wait(500)) {
        qDebug() << "PatchList: ERROR, VIDEOS PATCHSET DIDNT STOP, FORCEFULLY TERMINATING!";
        videos_patch_thread_->terminate();
        videos_patch_thread_->wait();
    }

    qDebug() << "Patchlist: all jobs stopped, destroying";

    delete texts_patch_;
    delete graphics_patch_;
    delete sounds_patch_;
    delete videos_patch_;
}

QList<Patch *> PatchList::getPatchList()
{
    return {texts_patch_, graphics_patch_, sounds_patch_, videos_patch_};
}

LotroDatManager *PatchList::getManager()
{
    return lotro_mgr_;
}

void PatchList::onPatchOperationProgressChanged(Patch::OperationProgress operation_progress, Patch *patch)
{
    patch_operations_status_[patch] = operation_progress;

    Patch::OperationProgress total_status;
    for (const Patch::OperationProgress &st : patch_operations_status_) {
        total_status = total_status + st;
    }
    emit progressChanged(total_status);
}

void PatchList::onLotroManagerOperationFinished(QString operation_name, QVector<QVariant>, bool result)
{
    qDebug() << "LotroManager: operation finished " << operation_name;
    if (operation_name == "initializeManager") {
        --active_operations_num_;

        if (active_operations_num_ == 0) {
            emit patchOperationsFinished();
        }

        if (result) {
            startAutoUpdate();
            update();
        } else {
            qCritical() << "DatManager initialisation error!!!";
        }
    }
    if (operation_name == "createBackup" || operation_name == "restoreFromBackup" || operation_name == "removeBackup") {
        if (operation_name == "createBackup" && result) {
            Settings::setValue("Backup/installed", true);
            Settings::setValue("Backup/creation_time", QDateTime::currentDateTime().toString("dd-MM-yyyy HH:mm:ss"));
        }

        if (operation_name == "removeBackup" && result) {
            Settings::setValue("Backup/installed", false);
            Settings::setValue("Backup/creation_time", "none");
        }

        emit backupSettingsInfoChanged();

        --active_operations_num_;
        if (active_operations_num_ == 0) {
            emit patchOperationsFinished();
        }
    }
}

void PatchList::onLotroManagerOperationStarted(QString operation_name, QVector<QVariant>)
{
    qDebug() << "LotroManager: operation started " << operation_name;
}

void PatchList::startAutoUpdate()
{
    auto_updates_enabled_ = true;
    patch_update_timer.start();
}

void PatchList::stopAutoUpdate()
{
    patch_update_timer.stop();
    auto_updates_enabled_ = false;
}

void PatchList::initialize() {
    ++active_operations_num_;
    emit patchOperationsStarted();
    QMetaObject::invokeMethod(lotro_mgr_, &LotroDatManager::initializeManager, Qt::QueuedConnection);
}

void PatchList::createBackup()
{
    if (active_operations_num_ > 0) {
        qWarning() << "Tried to start create backup operation, while others are still running!";
        return;
    }

    ++active_operations_num_;
    emit patchOperationsStarted();
    QMetaObject::invokeMethod(lotro_mgr_, "createBackup", Qt::QueuedConnection);
}

void PatchList::restoreFromBackup()
{
    if (active_operations_num_ > 0) {
        qWarning() << "Tried to start restore from backup operation, while others are still running!";
        return;
    }

    ++active_operations_num_;
    emit patchOperationsStarted();
    QMetaObject::invokeMethod(lotro_mgr_, "restoreFromBackup", Qt::QueuedConnection);
}

void PatchList::removeBackup()
{
    if (active_operations_num_ > 0) {
        qWarning() << "Tried to start remove backup operation, while others are still running!";
        return;
    }

    ++active_operations_num_;
    emit patchOperationsStarted();
    QMetaObject::invokeMethod(lotro_mgr_, "removeBackup", Qt::QueuedConnection);
}

void PatchList::onPatchOperationStarted(Patch::Operation, Patch*)
{
    if (active_operations_num_ == 0) {
        emit patchOperationsStarted();
    }
    ++active_operations_num_;
}

void PatchList::onPatchOperationFinished(Patch::Operation, Patch*)
{
    --active_operations_num_;
    if (active_operations_num_ == 0) {
        cleanUpPatchDirectory();
        emit patchOperationsFinished();
    }
}

void PatchList::update()
{
    if (active_operations_num_ > 0) {
        qWarning() << "Tried to start patch update chain, while there are already some operations on them!";
        return;
    }
    qInfo() << "PatchList: Starting update chain!";
    for (Patch* patch : getPatchList()) {
        QMetaObject::invokeMethod(patch, "enqueue",
                                  Q_ARG(QList<Patch::Operation>,
                                        QList<Patch::Operation>({Patch::E_CHECKFORUPDATES,
                                                                 Patch::E_DOWNLOAD,
                                                                 Patch::E_INSTALL,
                                                                 Patch::E_ACTIVATE})));
    }
}

void PatchList::forceInstallPatches()
{
    if (active_operations_num_ > 0) {
        qWarning() << "Trying to start force patch installation, while there are already some operations running!";
    	return;
    }

    auto is_database_enabled = [](QString db_name) -> bool {
        return Settings::getValue("DatabaseDownload/" + db_name).toBool();
    };

    for (const QString& db_name : QStringList(
            {"text", "font", "image", "loadscreen", "texture", "sound", "video", "micro"})) {
        Settings::setValue("DatabaseNeedInstall/" + db_name, is_database_enabled(db_name));
    }

    update();
}

void PatchList::cleanUpPatchDirectory()
{
    QStringList databases_filenames;

    QSettings settings;
    settings.beginGroup("PatchDatabases/");
    qDebug() << "cleanUpPatchDirectory: Iterating through groups: " << settings.childGroups();

    for (const QString& key : settings.childGroups()) {
        settings.beginGroup(key);
        QString filename = settings.value("path").toString();
        if (!filename.isEmpty()) {
            databases_filenames << filename;
        }
        settings.endGroup();
    }
    settings.endGroup();

    QDirIterator patch_dir_iterator(
                    Settings::getValue("General/PatchDownloadDir").toString(),
                    QDir::NoDotAndDotDot | QDir::Files,
                    QDirIterator::Subdirectories
                );

    while (patch_dir_iterator.hasNext()) {
        qDebug() << "cleanUpPatchDirectory: found " << patch_dir_iterator.next();
        bool is_file_actual = false;
        for (const QString& file_path : databases_filenames) {
            if (QFileInfo(file_path) == patch_dir_iterator.fileInfo()) {
                is_file_actual = true;
                break;
            }
        }

        if (!is_file_actual) {
            qDebug() << "cleanUpPatchDirectory: file" << patch_dir_iterator.filePath()
                     << "is not in patch list. Removing";

            QFile::remove(patch_dir_iterator.filePath());
        }
    }
}
