#include "videospatch.h"
#include "LotroDat/LotroDat.h"
#include "LotroDat/Database.h"
#include "models/lotrodatmanagerobserver.h"

#include <QUrlQuery>
#include <QSet>
#include <QThread>

VideosPatch::VideosPatch(LotroDatManager *mgr, QObject *parent) : Patch("VideosPatch", mgr, parent)
{
    connect(lotro_mgr_, &LotroDatManager::operationStarted, this, &VideosPatch::onLotroDatManagerOperationStarted);
    connect(lotro_mgr_, &LotroDatManager::operationFinished, this, &VideosPatch::onLotroDatManagerOperationFinished);
    connect(lotro_mgr_->getObserver(), &LotroDatManagerObserver::statusChanged, this, &VideosPatch::onLotroDatManagerStatusChanged);
}

void VideosPatch::checkForUpdates()
{
    emit operationStarted("checkForUpdates", this);
    QUrlQuery query; // query for building GET-request aka patch-version

    foreach (QString db_name, databases_names) {
        query.addQueryItem(db_name, "100");
    }

    QUrl target_url;
    target_url.setUrl(Settings::getValue("Network/patch_updates_url").toString());
    target_url.setQuery(query);

    QByteArray target_array;
    Downloader downloader;
    downloader.setUrl(target_url);
    downloader.targetBytearray = &target_array;
    downloader.start();
    downloader.waitForDownloaded();

    if (target_array.isEmpty()) {
        qDebug() << __FUNCTION__ << "Cannot download, target_array is empty!";
        emit errorOccured("checkForUpdates", this, "QueryDownloadFailed");
        emit operationFinished("checkForUpdates", this, false);
        return;
    }

    QStringList patch_info = QString(target_array).split('|');
    if (patch_info.size() != databases_names.size()) {
        qDebug() << __FUNCTION__ << "Incorrect patches number! Data: " << patch_info;
        emit errorOccured("checkForUpdates", this, "IncorrectQueryResult");
        emit operationFinished("checkForUpdates", this, false);
        return;
    }

    for (int i = 0; i < databases_names.size(); ++i) {
        QStringList patch_data = patch_info[i].split(":::");
        if (patch_data.size() != 3) {
            qDebug() << __FUNCTION__ << "Incorrect patch entry size! Entry: " << patch_data;
            emit errorOccured("checkForUpdates", this, "IncorrectDbInfo");
            emit operationFinished("checkForUpdates", this, false);
            return;
        }

        QString patch_filename = Settings::getValue("General/PatchDownloadDir").toString() + "/" + QUrl(patch_data[0]).fileName();

        Settings::setValue("PatchDatabases/" + databases_names[i] + "/url", patch_data[0]);
        Settings::setValue("PatchDatabases/" + databases_names[i] + "/hashsum", patch_data[1]);
        Settings::setValue("PatchDatabases/" + databases_names[i] + "/datetime", patch_data[2]);
        Settings::setValue("PatchDatabases/" + databases_names[i] + "/path", patch_filename);
    }
    emit operationFinished("checkForUpdates", this, true);
}

void VideosPatch::download()
{
    if (elapsed_patches_to_download_ != 0) {
        qDebug() << "Trying to start download of patch set " << patch_name_ << " while download is still active!";
        return;
    }

    emit operationStarted("download", this);

    bool download_started = false;

    foreach (QString db_name, databases_names) {
        QString settings_prefix = "PatchDatabases/" + db_name;

        QString target_filename = QApplication::applicationDirPath() + "/" + Settings::getValue(settings_prefix + "/path").toString();
        qDebug() << "VideosPatch: Checking if there's no need to download file " << target_filename;

        if (FileSystem::fileHash(target_filename) == Settings::getValue(settings_prefix + "/hashsum").toString()) {
            qDebug() << "VideosPatch: file " << target_filename << " is fresh, no need to download";
            continue;
        }

        FileSystem::createFilePath(target_filename);
        QFile* target_file = new QFile(target_filename);
        if (!target_file->open(QIODevice::ReadWrite | QIODevice::Truncate)) {
            emit errorOccured("download", this, "CantOpenDbFile");
            continue;
        }

        qDebug() << "VideosPatch: beginning download of file " << target_filename;
        download_started = true;

        ++elapsed_patches_to_download_;
        Downloader* downloader = new Downloader();
        downloader->setUrl(Settings::getValue(settings_prefix + "/url").toUrl());
        downloader->targetFile = target_file;
        connect(downloader, &Downloader::progressChanged, this, &VideosPatch::onDownloaderProgressChanged);
        connect(downloader, &Downloader::downloadFinished, this, &VideosPatch::onDownloaderFinished);
        downloaders_.insert(downloader);
        downloader->start();
    }

    if (!download_started) {
        emit operationFinished("download", this, true);
    } // otherwise will be emitted on the last onDownloaderFinished signal
}

void VideosPatch::install()
{
    emit operationStarted("install", this);
    QThread::sleep(5);
    emit operationFinished("install", this, true);
}

void VideosPatch::activate()
{
    emit operationStarted("activate", this);
    QThread::sleep(5);
    emit operationFinished("activate", this, true);
}

void VideosPatch::onDownloaderProgressChanged(Downloader *, Downloader::Status)
{
    Downloader::Status all_downloads_status;
    foreach (Downloader* downloader, downloaders_) {
        all_downloads_status = all_downloads_status + downloader->getDownloadStatus();
    }

    emit downloadStatusChanged(this, all_downloads_status);
}

void VideosPatch::onDownloaderFinished(Downloader *ptr)
{
    Downloader::Status all_downloads_status;
    foreach (Downloader* downloader, downloaders_) {
        all_downloads_status = all_downloads_status + downloader->getDownloadStatus();
    }

    ptr->targetFile->close();
    ptr->targetFile->deleteLater();

    if (elapsed_patches_to_download_ == 1) {
        foreach (Downloader* downloader, downloaders_) {
            downloader->deleteLater();
        }
        downloaders_.clear();

        emit downloadStatusChanged(this, all_downloads_status);
        emit operationFinished("download", this, true);
    }

    --elapsed_patches_to_download_;
}

void VideosPatch::onLotroDatManagerOperationFinished(QString operation_name, QVector<QVariant> args, bool successful)
{
    if (operation_name == "installPatch") {
        if (!args[0].toString().contains("SoundsPatch_")) {
            return;
        }

        status_.process = CurrentProcess::E_AWAITING_INSTALL;
        status_.percent = 100;
        emit installStatusChanged(this, status_);

        --elapsed_databases_to_install_;

        if (!successful) {
            emit errorOccured("install", this, "Database " + args[0].toString() + " (" + args[1].toString() + ") was not installed due to Legacy core error.");
        }

        if (elapsed_databases_to_install_ == 0) {
            status_.process = CurrentProcess::E_FINISHED;
            emit operationFinished("install", this, true);
        }
    }

    if (operation_name == "enableCategory" || operation_name == "disableCategory") {
        if (!args[0].toString().contains("SoundsPatch_")) {
            return;
        }

        status_.process = CurrentProcess::E_AWAITING_APPLY;
        status_.percent = 100;
        emit installStatusChanged(this, status_);

        --elapsed_categories_to_activate_;
        if (!successful) {
            emit errorOccured("activate", this, "Error in patch " + args[0].toString() + ": activating category " + QString::number(args[1].toInt()) + " failed.");
        }

        if (elapsed_categories_to_activate_ == 0) {
            status_.process = CurrentProcess::E_FINISHED;
            emit operationFinished("activate", this, true);
        }
    }
}

void VideosPatch::onLotroDatManagerOperationStarted(QString operation_name, QVector<QVariant> args)
{
    if (operation_name == "installPatch") {
        if (!args[0].toString().contains("SoundsPatch_")) {
            return;
        }
        status_.process = CurrentProcess::E_INSTALL;
        status_.current_part++;
        status_.percent = 0;
        emit installStatusChanged(this, status_);
    }

    if (operation_name == "enableCategory" || operation_name == "disableCategory") {
        if (!args[0].toString().contains("SoundsPatch_")) {
            return;
        }
        status_.process = CurrentProcess::E_APPLY;
        status_.current_part++;
        status_.percent = 0;
        emit installStatusChanged(this, status_);
    }
}

void VideosPatch::onLotroDatManagerStatusChanged(LotroDatManager::Status status)
{
    if (status_.process != CurrentProcess::E_INSTALL || status_.process != CurrentProcess::E_APPLY) {
        return;
    }

    status_.percent = status.percent;
    emit installStatusChanged(this, status_);
}
