/*
    uploadmanger.cpp

    Copyright (c) 2011 by Evgeny Khryukin

 ***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************
*/
#include <QBuffer>
#include <QFile>
#include <QFileInfo>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QRegExp>

#include "authmanager.h"
#include "common.h"
#include "options.h"
#include "uploadmanager.h"

static const QString boundary = "AaB03x";

//---------------------------------
//-------HttpDevice----------------
//---------------------------------
class HttpDevice : public QIODevice {
public:
    HttpDevice(const QString &fileName, QObject *p = 0) :
        QIODevice(p), totalSize(0), ioIndex(0), lastIndex(0), fileName_(fileName)
    {
        QFileInfo  fi(fileName_);
        QByteArray mpData;
        mpData.append("--" + boundary + "\r\n");
        mpData.append("Content-Disposition: form-data; name=\"file\"; filename=\"" + fi.fileName().toUtf8() + "\"\r\n");
        mpData.append("Content-Transfer-Encoding: binary\r\n");
        mpData.append("\r\n");

        appendData(mpData);

        QFile *device = new QFile(fileName_, this);

        Range r;
        r.start = totalSize;
        r.end   = totalSize + device->size() - 1;

        ioDevices.append(QPair<Range, QIODevice *>(r, device));
        totalSize += device->size();

        appendData("\r\n--" + boundary.toLatin1() + "--\r\n");
    }

    ~HttpDevice() { }

    virtual qint64 size() const { return totalSize; }

    virtual bool seek(qint64 pos)
    {
        if (pos >= totalSize)
            return false;
        ioIndex   = pos;
        lastIndex = 0;
        return QIODevice::seek(pos);
    }

    virtual bool open(QIODevice::OpenMode mode)
    {
        if (mode != QIODevice::ReadOnly)
            return false;

        for (int i = 0; i < ioDevices.size(); i++) {
            if (!ioDevices.at(i).second->open(mode))
                return false;
        }

        return QIODevice::open(mode);
    }

protected:
    virtual qint64 readData(char *data, qint64 len)
    {
        if ((len = qMin(len, qint64(totalSize) - ioIndex)) <= 0)
            return qint64(0);

        qint64 totalRead = 0;

        while (len > 0) {
            if (ioIndex >= ioDevices[lastIndex].first.start && ioIndex <= ioDevices[lastIndex].first.end) {

            } else {
                for (int i = 0; i < ioDevices.count(); ++i) {
                    if (ioIndex >= ioDevices[i].first.start && ioIndex <= ioDevices[i].first.end) {
                        lastIndex = i;
                    }
                }
            }

            QIODevice *chunk = ioDevices[lastIndex].second;

            if (!ioDevices[lastIndex].second->seek(ioIndex - ioDevices[lastIndex].first.start)) {
                qDebug("HttpDevice: Failed to seek inner device");
                break;
            }

            qint64 bytesLeftInThisChunk     = chunk->size() - chunk->pos();
            qint64 bytesToReadInThisRequest = qMin(bytesLeftInThisChunk, len);

            qint64 readLen = chunk->read(data, bytesToReadInThisRequest);
            if (readLen != bytesToReadInThisRequest) {
                qDebug("HttpDevice: Failed to read requested amount of data");
                break;
            }

            data += bytesToReadInThisRequest;
            len -= bytesToReadInThisRequest;
            totalRead += bytesToReadInThisRequest;
            ioIndex += bytesToReadInThisRequest;
        }

        return totalRead;
    }

    virtual qint64 writeData(const char * /*data*/, qint64 /*len*/) { return -1; }

private:
    struct Range {
        int start;
        int end;
    };

    void appendData(const QByteArray &data)
    {
        QBuffer *buffer = new QBuffer(this);
        buffer->setData(data);

        Range r;
        r.start = totalSize;
        r.end   = totalSize + data.size() - 1;

        ioDevices.append(QPair<Range, QIODevice *>(r, buffer));
        totalSize += data.size();
    }

private:
    QVector<QPair<Range, QIODevice *>> ioDevices;
    int                                totalSize;
    qint64                             ioIndex;
    int                                lastIndex;
    QString                            fileName_;
};

//-----------------------------------------
//-------UploadManager---------------------
//-----------------------------------------
UploadManager::UploadManager(QObject *p) : QObject(p), success_(false), hd_(0) { manager_ = newManager(this); }

UploadManager::~UploadManager() { }

void UploadManager::go(const QString &file)
{
    if (file.isEmpty()) {
        emit statusText(O_M(MCancel));
        emit uploaded();
        return;
    }

    // manager_->cookieJar()->setCookiesFromUrl(Options::instance()->loadCookies(), mainUrl);

    if (manager_->cookieJar()->cookiesForUrl(mainUrl).isEmpty()) {
        AuthManager am;
        emit        statusText(O_M(MAuthStart));
        bool        auth = am.go(Options::instance()->getOption(CONST_LOGIN, "").toString(),
                                 Options::decodePassword(Options::instance()->getOption(CONST_PASS, "").toString()));
        if (auth) {
            setCookies(am.cookies());
            Options::instance()->saveCookies(am.cookies());
            emit statusText(O_M(MAuthOk));
        } else {
            emit statusText(O_M(MAuthError));
            emit uploaded();
            return;
        }
    }

    fileName_          = file;
    QNetworkRequest nr = newRequest();
    nr.setUrl(QUrl("http://narod.yandex.ru/disk/getstorage/"));
    emit           statusText(tr("Getting storage..."));
    QNetworkReply *reply = manager_->get(nr);
    connect(reply, SIGNAL(finished()), SLOT(getStorageFinished()));
}

void UploadManager::setCookies(const QList<QNetworkCookie> &cookies)
{
    manager_->cookieJar()->setCookiesFromUrl(cookies, mainUrl);
}

void UploadManager::getStorageFinished()
{
    QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
    if (reply->error() == QNetworkReply::NoError) {
        QString page = reply->readAll();
        QRegExp rx("\"url\":\"(\\S+)\".+\"hash\":\"(\\S+)\".+\"purl\":\"(\\S+)\"");
        if (rx.indexIn(page) > -1) {
            doUpload(QUrl(rx.cap(1) + "?tid=" + rx.cap(2)));
        } else {
            emit statusText(tr("Can't get storage"));
            emit uploaded();
        }
    } else {
        emit statusText(O_M(MError).arg(reply->errorString()));
        emit uploaded();
    }

    reply->deleteLater();
}

void UploadManager::doUpload(const QUrl &url)
{
    emit statusText(tr("Starting upload..."));

    hd_ = new HttpDevice(fileName_, this);
    if (!hd_->open(QIODevice::ReadOnly)) {
        emit statusText(tr("Error opening file!"));
        emit uploaded();
        return;
    }

    QNetworkRequest nr = newRequest();
    nr.setUrl(url);
    nr.setHeader(QNetworkRequest::ContentTypeHeader, "multipart/form-data, boundary=" + boundary.toLatin1());
    nr.setHeader(QNetworkRequest::ContentLengthHeader, hd_->size());

    QNetworkReply *netrp = manager_->post(nr, hd_);
    connect(netrp, SIGNAL(uploadProgress(qint64, qint64)), this, SIGNAL(transferProgress(qint64, qint64)));
    connect(netrp, SIGNAL(finished()), this, SLOT(uploadFinished()));
}

void UploadManager::uploadFinished()
{
    QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
    if (reply->error() == QNetworkReply::NoError) {
        emit            statusText(tr("Verifying..."));
        QNetworkRequest nr = newRequest();
        nr.setUrl(QUrl("http://narod.yandex.ru/disk/last/"));
        QNetworkReply *netrp = manager_->get(nr);
        connect(netrp, SIGNAL(finished()), SLOT(verifyingFinished()));
    } else {
        emit statusText(O_M(MError).arg(reply->errorString()));
        emit uploaded();
    }

    hd_->deleteLater();
    hd_ = 0;
    reply->deleteLater();
}

void UploadManager::verifyingFinished()
{
    QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
    if (reply->error() == QNetworkReply::NoError) {
        QString page = reply->readAll();
        QRegExp rx("<span class='b-fname'><a href=\"(http://narod.ru/disk/\\S+html)\">[^<]+</a></span><br/>");
        if (rx.indexIn(page) != -1) {
            success_ = true;
            emit    statusText(tr("Uploaded successfully"));
            QString url = rx.cap(1);
            emit    uploadFileURL(url);
        } else {
            emit statusText(tr("Verifying failed"));
        }
    } else {
        emit statusText(O_M(MError).arg(reply->errorString()));
    }

    emit uploaded();

    reply->deleteLater();
}

// #include "uploadmanager.moc"
