#include "downloader.h"
#include <QEventLoop>
#include <QApplication>
#include <QDebug>
#include <QTime>

Downloader::Downloader(QObject *parent) :QObject(parent), busy_(false)
{
    qRegisterMetaType<Downloader::Status>();
    connect(&m_WebCtrl, SIGNAL(finished(QNetworkReply*)), this, SLOT(onDownloadFinished(QNetworkReply*)));
}

Downloader::~Downloader() {
}

QUrl Downloader::getUrl()
{
    return url_;
}

void Downloader::setUrl(const QUrl &_url)
{
    url_ = _url;
}

void Downloader::waitForDownloaded()
{
    if (!busy_)
        return;

    QEventLoop loop;
    connect(this, &Downloader::downloadFinished, &loop, &QEventLoop::quit);
    loop.exec();
}

bool Downloader::isStarted()
{
    return busy_;
}

double Downloader::getPercent()
{
    return double(bytes_downloaded_) * 100.0 / double(bytes_total_);
}

quint64 Downloader::getBytesTotal()
{
    return bytes_total_;
}

quint64 Downloader::getBytesDownloaded()
{
    return bytes_downloaded_;
}

quint64 Downloader::getElapsedTime()
{
    quint64 delta_size = getBytesTotal() - getBytesDownloaded();
    quint64 avg_speed = qMax(quint64(1), getAverageSpeed());

    while (delta_size > 1024 && avg_speed > 1024) { // Making precision smaller to get smoother dynamics
        delta_size /= 1024;
        avg_speed /= 1024;
    }

    return delta_size / avg_speed;
}

quint64 Downloader::getCurrentSpeed()
{
    return download_speed_;
}

quint64 Downloader::getAverageSpeed()
{
    return average_speed_;
}

Downloader::Status Downloader::getDownloadStatus()
{
    return {isStarted(), getPercent(), getBytesTotal(), getBytesDownloaded(), getCurrentSpeed(), getAverageSpeed(), getElapsedTime()};
}

QString Downloader::getSpeedFormatted(quint64 speed_bytes_per_sec)
{
    float speed = speed_bytes_per_sec;

    QString unit;
    if (speed < 1024) {
        unit = "bytes/sec";
    } else if (speed < 1024*1024) {
        speed /= 1024;
        unit = "kB/s";
    } else {
        speed /= 1024*1024;
        unit = "MB/s";
    }
    return QString::number(speed, 'f', 1) + " " + unit;
}

QString Downloader::getSizeFormatted(quint64 bytes)
{
    float size = bytes;
    QString unit;
    if (size < 1024) {
        unit = "байт";
    } else if (size < 1024 * 1024) {
        size /= 1024;
        unit = "кб";
    } else if (size < 1024 * 1024 * 1024){
        size /= 1024*1024;
        unit = "мб";
    } else {
        size /= 1024 * 1024 * 1024;
        unit = "гб";
    }
    return QString::number(size, 'f', 1) + " " + unit;
}

QString Downloader::getElapsedTimeFormatted(quint64 elapsed_time_secs)
{
    qint64 secs = elapsed_time_secs;
    qint64 mins = 0;
    qint64 hours = 0;
    qint64 days = 0;

    if (secs > 60) {
        mins = secs / 60;
        secs %= 60;
    }

    if (mins > 60) {
        hours = mins / 60;
        mins %= 60;
    }

    if (hours > 24) {
        days = hours / 24;
        hours %= 24;
    }

    if (days > 0)
        return " очень много (низкая скорость)";

    QString result = "";

    if (hours > 0) {
        result += QString::number(hours);
        if (hours % 10 == 1 && hours / 10 != 1)
            result += " час ";
        else if (hours % 10 > 1 && hours % 10 < 5 && hours / 10 != 1)
            result += " часа ";
        else
            result += " часов ";
    }

    if (mins > 0) {
        result += QString::number(mins);
        if (mins % 10 == 1 && mins / 10 != 1)
            result += " минута ";
        else if (mins % 10 > 1 && mins % 10 < 5 && mins / 10 != 1)
            result += " минуты ";
        else
            result += " минут ";
    }

    if (secs > 0 && hours == 0) {
        result += QString::number(secs);
        if (secs % 10 == 1 && secs / 10 != 1)
            result += " секунда ";
        else if (secs % 10 > 1 && secs % 10 < 5 && secs / 10 != 1)
            result += " секунды ";
        else
            result += " секунд ";
    }

    if (result == "")
        result = "совсем чуть-чуть";

    return result;
}

void Downloader::start()
{
    if (busy_) {
        qDebug() << "Cannot download " << url_ << ", downloader is busy!";
        return;
    }
    qDebug() << "Starting download " << url_;

    last_tick_time_.restart();
    bytes_downloaded_ = 0;
    bytes_total_ = 0;
    download_speed_ = 0;
    bytes_downloaded_before_tick_ = 0;

    busy_ = true;
    QNetworkRequest request(url_);
    m_CurrentReply = m_WebCtrl.get(request);
    m_CurrentReply->setReadBufferSize(download_speed_limit);
    connect(m_CurrentReply, &QNetworkReply::readyRead, this, &Downloader::onReadyRead);
    connect(m_CurrentReply, &QNetworkReply::downloadProgress, this, &Downloader::onDownloadProgressChanged);
}

void Downloader::updateDownloadSpeedLimit(int bytes_per_sec)
{
    download_speed_limit = bytes_per_sec;
    if (m_CurrentReply)
        m_CurrentReply->setReadBufferSize(bytes_per_sec);
}

void Downloader::onDownloadProgressChanged(qint64 bytesReceived, qint64 bytesTotal)
{
    bytes_downloaded_ = bytesReceived;
    bytes_total_ = bytesTotal;

    if (last_tick_time_.elapsed() >= 300) {
        download_speed_ = (bytes_downloaded_ - bytes_downloaded_before_tick_) * 1000.0 / (last_tick_time_.elapsed());

        average_speed_ = (average_speed_ * update_ticks_counter_ + download_speed_) / (update_ticks_counter_ + 1);
        ++update_ticks_counter_;

        last_tick_time_.restart();
        bytes_downloaded_before_tick_ = bytes_downloaded_;
        emit progressChanged(this, getDownloadStatus());
    }
}

void Downloader::stop()
{
    if (m_CurrentReply) {
        m_CurrentReply->abort();
    }
    busy_ = false;
}

void Downloader::onDownloadFinished(QNetworkReply*) {
    if (m_CurrentReply)
        m_CurrentReply->deleteLater();

    download_speed_ = 0;
    busy_ = false;

    emit downloadFinished(this);
}

void Downloader::onReadyRead()
{
    QByteArray readdata = m_CurrentReply->readAll();
    if (targetFile && targetFile->isWritable())
        targetFile->write(readdata);
    if (targetBytearray)
        targetBytearray->append(readdata);
}
