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

#include <QUrlQuery>

Patch::Patch(QString patch_name, LotroDatManager* mgr, QObject *parent)
    : QObject(parent)
    , lotro_mgr_(mgr)
    , patch_name_(patch_name)
{
    qRegisterMetaType<Operation>("Operation");
    qRegisterMetaType<QList<Patch::Operation>>("QList<Patch::Operation>");
    qRegisterMetaType<OperationProgress>("OperationProgress");


    connect(this, &Patch::operationStarted, this, &Patch::onOperationStarted);
    connect(this, &Patch::operationFinished, this, &Patch::onOperationFinished);
}

bool Patch::needDownloadDatabase(QString db_name)
{
    QString patch_filename = Settings::getValue("PatchDatabases/" + db_name + "/path").toString();
    QString current_file_hash = FileSystem::fileHash(patch_filename);
    QString required_file_hash = Settings::getValue("PatchDatabases/" + db_name + "/hashsum").toString();
    return isDatabaseDownloadEnabled(db_name) && (current_file_hash != required_file_hash);
}

bool Patch::isDatabaseDownloadEnabled(QString db_name)
{
    if (db_name == "text") {
        return Settings::getValue("Components/texts_main").toBool() ||
               Settings::getValue("Components/texts_items").toBool() ||
               Settings::getValue("Components/texts_emotes").toBool();
    }

    if (db_name == "font") {
        return Settings::getValue("Components/fonts").toBool();
    }

    if (db_name == "image") {
        return Settings::getValue("Components/maps").toBool();
    }

    if (db_name == "texture") {
        return Settings::getValue("Components/logos").toBool();
    }

    if (db_name == "loadscreen") {
        return Settings::getValue("Components/loadscreens").toBool();
    }

    if (db_name == "sound") {
        return Settings::getValue("Components/sounds").toBool();
    }

    if (db_name == "video") {
        return Settings::getValue("Components/video").toBool();
    }

    return false;
}

void Patch::execOperation(Patch::Operation operation)
{
    switch (operation) {
    case E_CHECKFORUPDATES:
        checkForUpdates();
        return;
    case E_DOWNLOAD:
        download();
        return;
    case E_INSTALL:
        install();
        return;
    case E_ACTIVATE:
        activate();
        return;
    default:
        qCritical() << "Unknown operation on patchset " << patch_name_ << "!";
    }
}

QString Patch::getPatchName() const
{
    return patch_name_;
}

void Patch::enqueue(const Patch::Operation &operation)
{
    qDebug() << "Enqueuing operation " << operation << " in patchset" << *this;
    operations_queue_.append(operation);
    if (!running_operation_) {
        processQueue();
    }
}

void Patch::enqueue(const QList<Patch::Operation> &operations)
{
    qDebug() << "Enqueuing operations " << operations << " in patchset" << *this;
    operations_queue_.append(operations);
    if (!running_operation_) {
        processQueue();
    }
}

void Patch::processQueue()
{
    if (operations_queue_.empty() || running_operation_) {
        return;
    }

    Operation current_operation = operations_queue_.dequeue();
    qDebug() << "Processing patch queue: " << *this << ", starting operation " << current_operation;
    emit operationStarted(current_operation, this);
    execOperation(current_operation);
}

void Patch::onOperationStarted(Patch::Operation, Patch*)
{
    running_operation_ = true;
}

void Patch::onOperationFinished(Patch::Operation, Patch*)
{
    running_operation_ = false;
    processQueue();
}

Patch::OperationProgress::OperationProgress(Downloader::Status status)
{
    download_finished_bytes = status.downloaded_bytes;
    download_total_bytes = status.total_bytes;
    download_speed = status.current_speed;
    download_elapsed_time = status.elapsed_time;
}

Patch::OperationProgress::OperationProgress(LotroDatManager::Status status) {
    install_finished_parts = status.finished_parts;
    install_total_parts = status.total_parts;
}

Patch::OperationProgress Patch::OperationProgress::operator+(const Patch::OperationProgress &other) const
{
    Patch::OperationProgress result;

    result.download_elapsed_time = download_elapsed_time + other.download_elapsed_time;
    result.download_finished_bytes = download_finished_bytes + other.download_finished_bytes;
    result.download_speed = download_speed + other.download_speed;
    result.download_total_bytes = download_total_bytes + other.download_total_bytes;

    result.install_finished_parts = install_finished_parts + other.install_finished_parts;
    result.install_total_parts = install_total_parts + other.install_total_parts;

    return result;
}

double Patch::OperationProgress::getDownloadPercent() const
{
    return double(download_finished_bytes) * 100.0/ double(download_total_bytes);
}

double Patch::OperationProgress::getInstallPercent() const
{
    return double(install_finished_parts) * 100.0 / double(install_total_parts);
}

QDebug operator<<(QDebug dbg, const Patch::Operation& operation) {
    switch (operation) {
    case Patch::Operation::E_CHECKFORUPDATES:
        dbg << "Patch::Operation::E_CHECKFORUPDATES";
        return dbg;
    case Patch::Operation::E_DOWNLOAD:
        dbg << "Patch::Operation::E_DOWNLOAD";
        return dbg;
    case Patch::Operation::E_INSTALL:
        dbg << "Patch::Operation::E_INSTALL";
        return dbg;
    case Patch::Operation::E_ACTIVATE:
        dbg << "Patch::Operation::E_ACTIVATE";
        return dbg;
    default:
        dbg << "{Unknown operation!}";
    }
    return dbg;
}

QDebug operator<<(QDebug dbg, const Patch::OperationProgress& operation_progress) {
    dbg.nospace().noquote()
        << "Downloaded: " << Downloader::getSizeFormatted(operation_progress.download_finished_bytes)
        << " of " << Downloader::getSizeFormatted(operation_progress.download_total_bytes)
        << " with speed " << Downloader::getSpeedFormatted(operation_progress.download_speed)
        << " Elapsed time " << Downloader::getElapsedTimeFormatted(operation_progress.download_elapsed_time)
        << " Installed: " << operation_progress.install_finished_parts << " of " << operation_progress.install_total_parts << ".";
    return dbg;
}

QDebug operator<<(QDebug dbg, const Patch& patch) {
    dbg.nospace().noquote() << "Patch::" << patch.getPatchName();
    return dbg;
}

