#include <QCoreApplication>
#include <QThread>
 
#include "rangedownloader_p.hpp"
 
RangeDownloaderPrivate::RangeDownloaderPrivate(QNetworkAccessManager *manager, QObject *parent)
    : QObject(parent) {
    m_Manager = manager;
    m_Manager->clearAccessCache();
}
 
RangeDownloaderPrivate::~RangeDownloaderPrivate() {
    if(b_Running) {
        for(auto iter = m_ActiveRequests.begin(),
                end = m_ActiveRequests.end();
                iter != end;
                ++iter) {
            if(*iter) {
                (*iter)->disconnect();
                (*iter)->destroy();
            }
        }
    }
}
 
/// Public Slots.
 
void RangeDownloaderPrivate::setBlockSize(qint32 blockSize) {
    if(b_Running) {
        return;
    }
    n_BlockSize = blockSize;
}
 
void RangeDownloaderPrivate::setTargetFileUrl(const QUrl &url) {
    if(b_Running) {
        return;
    }
    m_Url = url;
}
 
void RangeDownloaderPrivate::setTargetFileLength(qint32 len) {
    if(b_Running) {
        return;
    }
    n_TotalSize = len;
}
 
void RangeDownloaderPrivate::setBytesWritten(qint64 len) {
    if(b_Running) {
        return;
    }
    n_BytesWritten = len;
}
 
void RangeDownloaderPrivate::setFullDownload(bool fullDownload) {
    if(b_Running) {
        return;
    }
    b_FullDownload = fullDownload;
}
 
void RangeDownloaderPrivate::appendRange(qint32 from, qint32 to) {
    if(b_Running) {
        return;
    }
 
 
    m_RequiredBlocks.append(qMakePair<qint32, qint32>(from,to));
}
 
void RangeDownloaderPrivate::start() {
    if(b_Running) {
        return;
    }
    b_Running = b_Finished = false;
    n_Active = -1;
 
    QNetworkRequest request;
 
    // Before starting the download we have to resolve the url such that it
    // does not have any redirections whatsoever.
    // For this we send a get request and abort it before it even begin.
    // We should not send a HEAD request since it may not be supported by some
    // hosts.
    request.setUrl(m_Url);
    request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
 
    auto reply = m_Manager->get(request);
    connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
            this, SLOT(handleUrlCheckError(QNetworkReply::NetworkError)));
    connect(reply, SIGNAL(downloadProgress(qint64, qint64)),
            this, SLOT(handleUrlCheck(qint64, qint64)));
    b_Running = true;
    emit started();
}
 
void RangeDownloaderPrivate::cancel() {
    if(!b_Running || b_CancelRequested) {
        return;
    }
    b_CancelRequested = true;
    for(auto iter = m_ActiveRequests.begin(),
            end = m_ActiveRequests.end();
            iter != end;
            ++iter) {
        if(*iter) {
            (*iter)->cancel();
        }
	QCoreApplication::processEvents();
    }
}
 
/// Private Slots
QNetworkRequest RangeDownloaderPrivate::makeRangeRequest(const QUrl &url, const QPair<qint32, qint32> &range) {
    QNetworkRequest request;
 
    request.setUrl(url);
    if(range.first || range.second) {
 
        auto fromRange = range.first * n_BlockSize;
        auto toRange = range.second * n_BlockSize;
 
 
        QByteArray rangeHeaderValue = "bytes=" + QByteArray::number(fromRange) + "-";
        rangeHeaderValue += QByteArray::number(toRange);
        request.setRawHeader("Range", rangeHeaderValue);
    }
    request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
    return request;
}
 
 
// Slots which does the url check routine
void RangeDownloaderPrivate::handleUrlCheckError(QNetworkReply::NetworkError code) {
    QNetworkReply *reply = qobject_cast<QNetworkReply*>(QObject::sender());
    if(!reply) {
        return;
    }
 
    reply->disconnect();
    reply->deleteLater();
 
    emit error(code);
}
 
void RangeDownloaderPrivate::handleUrlCheck(qint64 br, qint64 bt) {
    Q_UNUSED(br);
    Q_UNUSED(bt);
 
    auto reply = qobject_cast<QNetworkReply*>(QObject::sender());
    if(!reply) {
        return;
    }
 
    if(reply->error() != QNetworkReply::NoError) {
        return;
    }
 
    m_Url = reply->url();
 
    reply->disconnect();
    reply->abort();
    reply->deleteLater();
 
    /// Now we will start the actual download since we got
    //  the clean url to the target file.
 
    /// Amount of bytes downloaded
    n_RecievedBytes = 0;
    m_ElapsedTimer.start();
 
    /// If this flag is set then it means we can't use range request and
    //  we have to initiate a very simple download.
    if(b_FullDownload) {
        /// Full download just launch a single RangeReply object.
        ++n_Active;
        auto range = qMakePair<qint32,qint32>(0,0);
        auto rangeReply = new RangeReply(n_Active, m_Manager->get(makeRangeRequest(m_Url, range)), range);
 
        connect(rangeReply, SIGNAL(canceled(int)),
                this, SLOT(handleRangeReplyCancel(int)),
                Qt::QueuedConnection);
 
        connect(rangeReply, SIGNAL(restarted(int)),
                this, SLOT(handleRangeReplyRestart(int)),
                Qt::QueuedConnection);
 
        connect(rangeReply, SIGNAL(error(QNetworkReply::NetworkError, int,bool)),
                this, SLOT(handleRangeReplyError(QNetworkReply::NetworkError, int,bool)),
                Qt::QueuedConnection);
 
        connect(rangeReply, SIGNAL(finished(qint32,qint32, QByteArray*, int)),
                this, SLOT(handleRangeReplyFinished(qint32,qint32, QByteArray*, int)),
                Qt::QueuedConnection);
 
        connect(rangeReply, SIGNAL(progress(qint64, int)),
                this, SLOT(handleRangeReplyProgress(qint64, int)),
                Qt::QueuedConnection);
 
        connect(rangeReply, SIGNAL(data(QByteArray*, bool)),
                this, SIGNAL(data(QByteArray*, bool)),
                Qt::DirectConnection);
 
        m_ActiveRequests.append(rangeReply);
 
        return;
    }
 
    // Now we will determine the maximum no. of requests to be handled at
    // a time.
    int max_allowed = QThread::idealThreadCount() * 2;
    int i = n_Done;
 
    for(; i < m_RequiredBlocks.size(); ++i) {
        if(n_Active + 1 >= max_allowed) {
            break;
        }
 
        auto range = m_RequiredBlocks.at(i);
 
        QNetworkRequest request = makeRangeRequest(m_Url, range);
        ++n_Active;
 
        auto rangeReply = new RangeReply(n_Active, m_Manager->get(request), range);
 
        connect(rangeReply, SIGNAL(canceled(int)),
                this, SLOT(handleRangeReplyCancel(int)),
                Qt::QueuedConnection);
 
        connect(rangeReply, SIGNAL(restarted(int)),
                this, SLOT(handleRangeReplyRestart(int)),
                Qt::QueuedConnection);
 
        connect(rangeReply, SIGNAL(error(QNetworkReply::NetworkError, int,bool)),
                this, SLOT(handleRangeReplyError(QNetworkReply::NetworkError, int,bool)),
                Qt::QueuedConnection);
 
        connect(rangeReply, SIGNAL(finished(qint32,qint32, QByteArray*, int)),
                this, SLOT(handleRangeReplyFinished(qint32,qint32, QByteArray*, int)),
                Qt::QueuedConnection);
 
        connect(rangeReply, SIGNAL(progress(qint64, int)),
                this, SLOT(handleRangeReplyProgress(qint64, int)),
                Qt::QueuedConnection);
 
        m_ActiveRequests.append(rangeReply);
    }
    n_Done = i;
}
 
/// ----
 
/// Range Reply Handlers
void RangeDownloaderPrivate::handleRangeReplyCancel(int index) {
    (m_ActiveRequests.at(index))->destroy();
    m_ActiveRequests[index] = nullptr;
    --n_Active;
    if(n_Active == -1) {
        b_Running = b_Finished = b_CancelRequested = false;
        emit canceled();
    }
}
 
 
void RangeDownloaderPrivate::handleRangeReplyRestart(int index) {
    Q_UNUSED(index);
 
}
 
void RangeDownloaderPrivate::handleRangeReplyError(QNetworkReply::NetworkError code, int index, bool threshReached) {
    if(b_CancelRequested) {
        (m_ActiveRequests.at(index))->destroy();
        m_ActiveRequests[index] = nullptr;
        --n_Active;
        if(n_Active == -1) {
            b_Running = b_Finished = b_CancelRequested = false;
            emit canceled();
        }
        return;
    }
 
 
 
    /// Let's try to retry some type of errors.
    /// We don't try to retry a full download, if it
    /// fails then the update has to be started from the
    /// start. This is because even if we try to restart
    /// we have to download it from the begining and so
    /// It has some complications.
    if((code == QNetworkReply::RemoteHostClosedError ||
            code == QNetworkReply::HostNotFoundError ||
            code == QNetworkReply::TimeoutError ||
            code == QNetworkReply::TemporaryNetworkFailureError ||
            code == QNetworkReply::BackgroundRequestNotAllowedError ||
            code == QNetworkReply::ProxyConnectionClosedError ||
            code == QNetworkReply::ProxyTimeoutError ||
            code == QNetworkReply::ContentAccessDenied ||
            code == QNetworkReply::ContentReSendError ||
            code == QNetworkReply::InternalServerError ||
            code == QNetworkReply::ServiceUnavailableError) && !threshReached && !b_FullDownload) {
        (m_ActiveRequests.at(index))->retry();
        return;
    } else {
        n_Active = -1;
        for(auto iter = m_ActiveRequests.begin(),
                end = m_ActiveRequests.end();
                iter != end;
                ++iter) {
            if(*iter) {
                (*iter)->disconnect();
                (*iter)->destroy();
            }
        }
        m_ActiveRequests.clear();
        b_Running = b_Finished = b_CancelRequested = false;
        emit error(code);
    }
}
 
void RangeDownloaderPrivate::handleRangeReplyFinished(qint32 from, qint32 to, QByteArray *Data, int index) {
    (m_ActiveRequests.at(index))->destroy();
    m_ActiveRequests[index] = nullptr;
 
    if(b_CancelRequested) {
        --n_Active;
        if(n_Active == -1) {
            b_Running = b_Finished = b_CancelRequested = false;
            emit canceled();
        }
        return;
    }
 
    if(b_FullDownload) {
        emit data(Data, true);
        return;
    } else {
        bool isLast = (n_Done >= m_RequiredBlocks.size() && n_Active - 1 == -1);
        emit rangeData(from, to,  Data, isLast);
    }
 
    if(n_Done >= m_RequiredBlocks.size()) {
        --n_Active;
        if(n_Active == -1) {
            b_Running = false;
            b_Finished = true;
            emit finished();
        }
        return;
    }
 
    auto range = m_RequiredBlocks.at(n_Done++);
    QNetworkRequest request = makeRangeRequest(m_Url, range);
    auto rangeReply = new RangeReply(index, m_Manager->get(request), range);
    m_ActiveRequests[index] = rangeReply;
 
    connect(rangeReply, SIGNAL(canceled(int)),
            this, SLOT(handleRangeReplyCancel(int)),
            Qt::QueuedConnection);
 
    connect(rangeReply, SIGNAL(restarted(int)),
            this, SLOT(handleRangeReplyRestart(int)),
            Qt::QueuedConnection);
 
    connect(rangeReply, SIGNAL(error(QNetworkReply::NetworkError, int, bool)),
            this, SLOT(handleRangeReplyError(QNetworkReply::NetworkError, int, bool)),
            Qt::QueuedConnection);
 
    connect(rangeReply, SIGNAL(finished(qint32, qint32, QByteArray*, int)),
            this, SLOT(handleRangeReplyFinished(qint32, qint32, QByteArray*, int)),
            Qt::QueuedConnection);
 
    connect(rangeReply, SIGNAL(progress(qint64, int)),
            this, SLOT(handleRangeReplyProgress(qint64, int)),
            Qt::QueuedConnection);
 
 
}
 
void RangeDownloaderPrivate::handleRangeReplyProgress(qint64 bytesRc, int index) {
    Q_UNUSED(index);
 
    n_RecievedBytes += bytesRc;
    qint64 totalBytesRecieved = n_BytesWritten + n_RecievedBytes;
 
    if(totalBytesRecieved >= n_TotalSize) {
        totalBytesRecieved = n_TotalSize;
    }
 
    QString sUnit;
    int nPercentage = static_cast<int>(
                          (static_cast<float>
                           (totalBytesRecieved) * 100.0
                          ) / static_cast<float>
                          (n_TotalSize)
                      );
 
    double nSpeed =  (n_RecievedBytes) * 1000.0 / m_ElapsedTimer.elapsed();
    if (nSpeed < 1024) {
        sUnit = "bytes/sec";
    } else if (nSpeed < 1024 * 1024) {
        nSpeed /= 1024;
        sUnit = "kB/s";
    } else {
        nSpeed /= 1024 * 1024;
        sUnit = "MB/s";
    }
    emit progress(nPercentage, totalBytesRecieved, n_TotalSize, nSpeed, sUnit);
}

V1048 The 'b_Running' variable was assigned the same value.

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: n_RecievedBytes.