#include "patchdownloader.h"
#include "models/filesystem.h"
#include <QApplication>
#include <QDir>
#include <QStringList>
#include <QUrlQuery>
#include <QVariant>
#include <QDebug>

PatchDownloader::PatchDownloader(QSettings *settings, QObject *parent) : QObject(parent), patch_download_dir(QApplication::applicationDirPath() + "/data")
{
    app_settings = settings;
    active_downloads_number = 0;

    connect(&update_check_timer, &QTimer::timeout, this, &PatchDownloader::checkForUpdates);
    update_check_timer.setInterval(1000 * 60 * 5); // 5 minutes
    update_check_timer.start();
}

PatchDownloader::~PatchDownloader()
{
    foreach (Downloader* downloader, downloads_list) {
        downloader->waitForDownloaded();
        downloader->deleteLater();
    }
}

QString PatchDownloader::getPredictedDownloadSizeFormatted()
{
    double mbytes = 0;

    if (app_settings->value("patch_database/sound", "Disabled").toString() == "Enabled") {
        mbytes += 650;
    }
    if (app_settings->value("patch_database/text", "Disabled").toString() == "Enabled") {
        mbytes += 80;
    }
    if (app_settings->value("patch_database/image", "Disabled").toString() == "Enabled") {
        mbytes += 120;
    }
    if (app_settings->value("patch_database/loadscreen", "Disabled").toString() == "Enabled") {
        mbytes += 3;
    }
    if (app_settings->value("patch_database/texture", "Disabled").toString() == "Enabled") {
        mbytes += 4;
    }
    if (app_settings->value("patch_database/font", "Disabled").toString() == "Enabled") {
        mbytes += 1;
    }
    if (app_settings->value("patch_database/video", "Disabled").toString() == "Enabled") {
        mbytes += 2100;
    }

    QString unit = "Мб";
    if (mbytes > 1024) {
        mbytes /= 1024;
        unit = "Гб";
    }

    return QString::number(mbytes, 'f', 1) + " " + unit;
}

QString PatchDownloader::getDatabasePathByPatchName(QString name)
{
    return patch_download_dir.absolutePath() + "/" + patch_data[name].url.fileName();
}

void PatchDownloader::checkForUpdates()
{
    foreach (QString patch_name, all_patch_names) {
        if (downloads_list.contains(patch_name))
            continue;

        downloads_list[patch_name] = new Downloader();
        connect(downloads_list[patch_name], &Downloader::progressChanged, this, &PatchDownloader::onDownloaderProgressChanged);
        connect(downloads_list[patch_name], &Downloader::downloadFinished, this, &PatchDownloader::onDownloaderCompleted);
    }

    if (active_downloads_number > 0) {
        qDebug() << "PatchDownloader: downloads are not ready yet, passing checkForUpdates";
        return;
    }

    emit checkForUpdatesStarted();
    if (!updatePatchList()) {
        emit checkForUpdatesFinished();
        return;
    }
    removeOldPatchesFromDirecrory();
    addMissingPatchesToDownloadList();
    emit checkForUpdatesFinished();
}

void PatchDownloader::onDownloaderCompleted(Downloader *downloader_ptr)
{
    if (downloader_ptr->targetFile) {
        downloader_ptr->targetFile->close();
        downloader_ptr->targetFile->deleteLater();
    }

    active_downloads_number--;

    if (active_downloads_number == 0) {
        emit downloadCompleted();
    }
}

void PatchDownloader::onDownloaderProgressChanged(Downloader*)
{
    quint64 totalSize = 0;
    quint64 downloadedSize = 0;
    quint64 summary_speed = 0;
    quint64 time_elapsed = 0;

    foreach (Downloader* downloader, downloads_list) {
        totalSize += downloader->getBytesTotal();
        downloadedSize += downloader->getBytesDownloaded();

        if (downloader->getBytesTotal() != downloader->getBytesDownloaded()) {
            summary_speed += downloader->getSpeed();
        }
    }

    time_elapsed = (totalSize - downloadedSize) / qMax(quint64(1), summary_speed);

    emit progressChanged(downloadedSize, totalSize,
                         Downloader::getSpeedFormatted(summary_speed),
                         Downloader::getElapsedTimeFormatted(time_elapsed));
}

int PatchDownloader::versionFromPatchFilename(QString filename)
{
    int version = 0;
    for (int i = filename.indexOf("_v") + 2; i < filename.indexOf("_v") + 7; i += 2) {
        version = version * 10 + (filename.at(i).toLatin1() - '0');
    }
    return version;
}

bool PatchDownloader::updatePatchList()
{
    QUrlQuery query; // query for building GET-request aka patch-version

    foreach (QString patch_name, all_patch_names) {
        query.addQueryItem(patch_name, "100");
    }

    QUrl target_url;
    target_url.setUrl("http://translate.lotros.ru/groupware/check_updates");
    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 getPatchListError();
        return false;
    }

    QStringList entry_list = QString(target_array).split('|');

    if (entry_list.size() != all_patch_names.size()) {
        qDebug() << __FUNCTION__ << "Entry list size is not equal to patch names size!" << QString(target_array);
        emit getPatchListError();
        return false;
    }

    for (int i = 0; i < entry_list.size(); ++i) {
        QStringList current_patch_data = entry_list[i].split(":::");
        if (current_patch_data.size() != 3) {
            qDebug() << __FUNCTION__ << "Incorrect patch entry size! Entry: " << entry_list[i];
            emit getPatchListError();
            return false;
        }
        patch_data[all_patch_names[i]] = {current_patch_data[0], current_patch_data[1], current_patch_data[2], all_patch_names[i]};
    }
    return true;
}

bool PatchDownloader::removeOldPatchesFromDirecrory()
{
    QStringList actual_hash_list;
    foreach (Patch patch, patch_data) {
        actual_hash_list.append(patch.md5_hash);
    }

    qDebug() << "Removing old patches. Current hash list " << actual_hash_list;

    QStringList paths = patch_download_dir.entryList(QDir::Files);

    foreach (QString filename, paths) {
        QString hash = FileSystem::fileHash(patch_download_dir.absolutePath() + "/" + filename, QCryptographicHash::Md5);
        if (!actual_hash_list.contains(hash)) {
            qDebug() << "File " << filename << " with hash " << hash << "seems outdated, deleting!";
            if (!QFile::remove(patch_download_dir.absolutePath() + "/" +filename)) {
                qDebug() << __FUNCTION__ << "Unable to remove file " << filename;
                emit removeFileError(patch_download_dir.absolutePath() + "/" + filename);
            }
        }
    }
    return true;
}

bool PatchDownloader::addMissingPatchesToDownloadList()
{
    QDir dir(patch_download_dir);
    if (!dir.exists())
        QDir().mkdir(patch_download_dir.absolutePath());

    bool download_started = false;

    foreach (Patch patch, patch_data) {
        QString patch_filepath = patch_download_dir.absolutePath() + "/" + patch.url.fileName();

        qDebug() << "Patch" << patch.name << "is marked as" << app_settings->value("patch_databases/" + patch.name, "Disabled");
        if (app_settings->value("patch_databases/" + patch.name, "Disabled").toString() != "Enabled")
            continue;

        if (FileSystem::fileExists(patch_filepath)) {
            if (FileSystem::fileHash(patch_filepath, QCryptographicHash::Md5) == patch.md5_hash)
                continue;

            if (!QFile::remove(patch_filepath)) {
                qDebug() << __FUNCTION__ << "Unable to remove file " << patch_filepath;
                emit removeFileError(patch_filepath);
                continue;
            }
        }

        if (!download_started) {
            download_started = true;
            qDebug() << "Started downloads of PatchDownloader!";
            emit downloadStarted();
        }

        qDebug() << "Starting download of file " << patch_filepath << " from url " << patch.url;

        downloads_list[patch.name]->setUrl(patch.url);
        downloads_list[patch.name]->targetFile = new QFile(patch_filepath, downloads_list[patch.name]);
        downloads_list[patch.name]->targetFile->open(QIODevice::ReadWrite);
        downloads_list[patch.name]->start();
        active_downloads_number++;
    }
    return true;
}
