/*
 *
 * Copyright 2013 Canonical Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
*/

#include "file-transfer.h"
#include <plugins/cordova-plugin-file/file.h>
#include <cassert>

static void SetHeaders(QNetworkRequest &request, const QVariantMap &headers) {
    for (const QString &key: headers.keys()) {
        QVariant val = *headers.find(key);
        QString value = val.toString();
        if (val.userType() == QMetaType::QVariantList || val.userType() == QMetaType::QStringList) {
            QList<QVariant> list = val.toList();
            for (QVariant v: list) {
                if (value.size())
                    value += ", ";
                value += v.toString();
            }
        }
        request.setRawHeader(key.toUtf8(), value.toUtf8());
    }
}

void FileTransfer::download(int scId, int ecId, const QString& url, const QString &target, bool /*trustAllHost*/, int id, const QVariantMap &headers) {
    QSharedPointer<FileTransferRequest> request(new FileTransferRequest(_manager, scId, ecId, id, this));

    assert(_id2request.find(id) == _id2request.end());

    _id2request.insert(id, request);

    request->connect(request.data(), &FileTransferRequest::done, [&]() {
        auto it = _id2request.find(id);
        while (it != _id2request.end() && it.key() == id) {
            if (it.value().data() == request.data()) {
                _id2request.erase(it);
                break;
            }
            it++;
        }
    });
    request->download(url, target, headers);
}

void FileTransfer::upload(int scId, int ecId, const QString &fileURI, const QString& url, const QString& fileKey, const QString& fileName, const QString& mimeType,
                          const QVariantMap & params, bool /*trustAllHosts*/, bool /*chunkedMode*/, const QVariantMap &headers, int id, const QString &/*httpMethod*/) {
    QSharedPointer<FileTransferRequest> request(new FileTransferRequest(_manager, scId, ecId, id, this));

    assert(_id2request.find(id) == _id2request.end());

    _id2request.insert(id, request);

    request->connect(request.data(), &FileTransferRequest::done, [&]() {
        auto it = _id2request.find(id);
        while (it != _id2request.end() && it.key() == id) {
            if (it.value().data() == request.data()) {
                _id2request.erase(it);
                break;
            }
            it++;
        }
    });
    request->upload(url, fileURI, fileKey, fileName, mimeType, params, headers);
}

void FileTransfer::abort(int scId, int ecId, int id) {
    Q_UNUSED(scId)
    Q_UNUSED(ecId)

    auto it = _id2request.find(id);
    while (it != _id2request.end() && it.key() == id) {
        (*it)->abort();
        it++;
    }
}

void FileTransferRequest::download(const QString& uri, const QString &targetURI, const QVariantMap &headers) {
    QUrl url(uri);
    QNetworkRequest request;

    QSharedPointer<CPlugin> filePlugin(_plugin->cordova()->getPlugin<File>());

    if (!filePlugin.data())
        return;

    if (!url.isValid()) {
        QVariantMap map;
        map.insert("code", INVALID_URL_ERR);
        map.insert("source", uri);
        map.insert("target", targetURI);
        _plugin->cb(_ecId, map);
        emit done();
        return;
    }

    request.setUrl(url);
    if (url.password().size() || url.userName().size()) {
        QString headerData = "Basic " + (url.userName() + ":" + url.password()).toLocal8Bit().toBase64();
        request.setRawHeader("Authorization", headerData.toLocal8Bit());
    }
    SetHeaders(request, headers);
    _reply = QSharedPointer<QNetworkReply>(_manager.get(request));

    _reply->connect(_reply.data(), &QNetworkReply::finished, [this, targetURI, uri, filePlugin]() {
        if (!_scId || _reply->error() != QNetworkReply::NoError)
            return;

        QPair<bool, QFileInfo> f1(dynamic_cast<File*>(filePlugin.data())->resolveURI(targetURI));

        QFile res(f1.second.absoluteFilePath());
        if (!f1.first || !res.open(QIODevice::WriteOnly)) {
            QVariantMap map;
            map.insert("code", INVALID_URL_ERR);
            map.insert("source", uri);
            map.insert("target", targetURI);
            _plugin->cb(_ecId, map);
            emit done();
            return;
        }
        res.write(_reply->readAll());

        _plugin->cb(_scId, dynamic_cast<File*>(filePlugin.data())->file2map(f1.second));

        emit done();
    });
    _reply->connect(_reply.data(), SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(error(QNetworkReply::NetworkError)));
    _reply->connect(_reply.data(), SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(progress(qint64, qint64)));
}

void FileTransferRequest::upload(const QString& _url, const QString& fileURI, QString fileKey, QString fileName, QString mimeType, const QVariantMap &params, const QVariantMap &headers) {
    QUrl url(_url);
    QNetworkRequest request;

    QSharedPointer<CPlugin> filePlugin(_plugin->cordova()->getPlugin<File>());

    if (!filePlugin.data())
        return;

    if (!url.isValid()) {
        QVariantMap map;
        map.insert("code", INVALID_URL_ERR);
        map.insert("source", fileURI);
        map.insert("target", _url);
        _plugin->cb(_ecId, map);
        emit done();
        return;
    }

    QPair<bool, QFileInfo> f1(dynamic_cast<File*>(filePlugin.data())->resolveURI(fileURI));
    QFile file(f1.second.absoluteFilePath());
    if (!f1.first || !file.open(QIODevice::ReadOnly)) {
        QVariantMap map;
        map.insert("code", FILE_NOT_FOUND_ERR);
        map.insert("source", fileURI);
        map.insert("target", _url);
        _plugin->cb(_ecId, map);
        emit done();
        return;
    }
    QString content{file.readAll()};

    request.setUrl(url);
    if (url.password().size() || url.userName().size()) {
        QString headerData = "Basic " + (url.userName() + ":" + url.password()).toLocal8Bit().toBase64();
        request.setRawHeader("Authorization", headerData.toLocal8Bit());
    }
    SetHeaders(request, headers);

    QString boundary = QString("CORDOVA-QT-%1A").arg(qrand());
    while (content.contains(boundary)) {
        boundary += QString("B%1A").arg(qrand());
    }

    request.setHeader(QNetworkRequest::ContentTypeHeader, QString("multipart/form-data; boundary=") + boundary);

    fileKey.replace("\"", "");
    fileName.replace("\"", "");
    mimeType.replace("\"", "");
    QString part = "--" + boundary + "\r\n";

    part += "Content-Disposition: form-data; name=\"" + fileKey +"\"; filename=\"" + fileName + "\"\r\n";
    part += "Content-Type: " + mimeType + "\r\n\r\n";
    part += content + "\r\n";

    for (QString key: params.keys()) {
        part += "--" + boundary + "\r\n";
        part += "Content-Disposition: form-data; name=\"" +  key + "\";\r\n\r\n";
        part += params.find(key)->toString();
        part += "\r\n";
    }

    part += QString("--") + boundary + "--" + "\r\n";

    _reply = QSharedPointer<QNetworkReply>(_manager.post(request, QByteArray(part.toUtf8())));

    _reply->connect(_reply.data(), &QNetworkReply::finished, [this, content]() {
        if (_reply->error() != QNetworkReply::NoError)
            return;
        int status = 200;
        QVariant statusCode = _reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);

        if (statusCode.isValid()) {
            status = statusCode.toInt();
        }

        QVariantMap map;
        map.insert("responseCode", status);
        map.insert("response", QString(_reply->readAll()));
        map.insert("bytesSent", content.size());
        _plugin->cb(_scId, map);
        emit done();
    });
    _reply->connect(_reply.data(), SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(error(QNetworkReply::NetworkError)));
    _reply->connect(_reply.data(), SIGNAL(uploadProgress(qint64, qint64)), this, SLOT(progress(qint64, qint64)));
}

void FileTransferRequest::abort() {
    QVariantMap map;
    map.insert("code", ABORT_ERR);
    _plugin->cb(_ecId, map);
    _scId = 0;
    emit done();
}

void FileTransferRequest::error(QNetworkReply::NetworkError code) {
    Q_UNUSED(code);

    int status = 404;
    QVariant statusCode = _reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
    if (statusCode.isValid()) {
        status = statusCode.toInt();
    }

    QVariantMap map;
    map.insert("http_status", status);
    map.insert("body", QString(_reply->readAll()));
    map.insert("code", CONNECTION_ERR);
    _plugin->cb(_ecId, map);
    emit done();
}

void FileTransferRequest::progress(qint64 bytesReceived, qint64 bytesTotal) {
    QVariantMap map;
    map.insert("lengthComputable", true);
    map.insert("total", bytesTotal);
    map.insert("loaded", bytesReceived);

    if (bytesReceived && bytesTotal && _scId)
        _plugin->callbackWithoutRemove(_scId, CordovaInternal::format(map));
}