/***************************************************************************
 *   Copyright (C) 2004-2009 by Pere Constans
 *   constans@molspaces.com
 *   cb2Bib version 1.3.2. Licensed under the GNU GPL version 3.
 *   See the LICENSE file that comes with this distribution.
 ***************************************************************************/
#include "network.h"

#include "settings.h"

#include <QNetworkCookie>
#include <QNetworkProxy>
#include <QNetworkReply>
#include <QTimer>


network::network(QObject* parento) : QObject(parento)
{
    _is_fetching = false;
    _fetcher = new QNetworkAccessManager(this);
    connect(_fetcher, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy&, QAuthenticator*)), this,
            SIGNAL(proxyAuthenticationRequired(const QNetworkProxy&, QAuthenticator*)));
    loadSettings();
    connect(settings::instance(), SIGNAL(newSettings()), this, SLOT(loadSettings()));
    // Set predefined cookies
    QNetworkCookieJar* ncj = _fetcher->cookieJar();
    QNetworkCookie nc("GSP", "ID=d093ce1ea042ad2b:IN=54afcd58e3b38df9:HIN=ff7e3a3ab3fbae0a+7e6cc990821af63:CF=4");
    ncj->setCookiesFromUrl(QList<QNetworkCookie>() << nc, QUrl("http://scholar.google.com"));
}

network::~network()
{}


/****************************************************************************

  PUBLIC PART

*****************************************************************************/

void network::getFile(const QString& orig, const QString& dest, const QString& action, QObject* receiver, const char* callback,
                      const bool overwrite)
{
    if (_is_fetching)
    {
        qDebug("[cb2bib] network::getFile: File requested while still fetching previous one. Returned.");
        return;
    }

    _is_fetching = true;
    disconnect(this, SIGNAL(getFileDone(bool, const QString&)), 0, 0);
    if (receiver)
        connect(this, SIGNAL(getFileDone(bool, const QString&)), receiver, callback);

    if (overwrite)
        if (QFileInfo(dest).exists())
            QFile::remove(dest);
    if (!getFilePrivate(orig, dest, action))
        emit_getFileDone(false, _getfile_error_string);
}

void network::cancelDownload()
{
    if (_is_fetching)
        _current_reply->abort();
}


/****************************************************************************

  PRIVATE PART

*****************************************************************************/

bool network::getFilePrivate(const QString& orig, const QString& dest, const QString& action)
{
    _getfile_error_string = "";
    _source_filename = orig;
    _destination_filename = dest;
    if (!checkDestination())
        return false;

    if (_source_filename.startsWith("<<post>>")) // cb2Bib keyword to use post http method
    {
        _source_filename.remove(QRegExp("^<<post>>"));
        return getFile_c2b(action, QNetworkAccessManager::PostOperation);
    }

    if (FmClient)
    {
        if (action == "copy" && !FmClientCopyBin.isEmpty())
            return getFile_client(action);
        else if (action == "move" && !FmClientMoveBin.isEmpty())
            return getFile_client(action);
    }
    return getFile_c2b(action);
}

void network::emit_getFileDone(bool status, const QString& error)
{
    _getfile_succeeded = status;
    _getfile_error_string = error;
    // Give some time to cleanup events and to return all network functions
    // before passing the control to the callback routine
    QTimer::singleShot(50, this, SLOT(emit_getFileDone()));
}

void network::emit_getFileDone()
{
    _is_fetching = false;
    // Assumed events are clean, all functions returned, then make the callback
    emit getFileDone(_getfile_succeeded, _getfile_error_string);
}

bool network::checkDestination()
{
    // Checks whether or not writing to dest is safe
    // Returns false if file exists
    QFile f(_destination_filename);
    if (f.exists())
    {
        _getfile_error_string = tr("Destination file '%1' already exists.").arg(_destination_filename);
        return false;
    }
    else
        return true;
}

void network::loadSettings()
{
    settings* s = settings::instance();
    FmClient = s->value("cb2Bib/FmClient").toBool();
    FmClientCopyBin = s->fileName("cb2Bib/FmClientCopyBin");
    FmClientMoveBin = s->fileName("cb2Bib/FmClientMoveBin");
    FmClientCopyArg = s->value("cb2Bib/FmClientCopyArg").toString();
    FmClientMoveArg = s->value("cb2Bib/FmClientMoveArg").toString();
    QNetworkProxy proxy;
    if (s->value("cb2Bib/UseProxy").toBool())
    {
        const QString hn(s->value("cb2Bib/ProxyHostName", QString()).toString());
        if (!hn.isEmpty())
        {
            if (s->value("cb2Bib/ProxyType").toInt() == 0)
                proxy = QNetworkProxy::HttpProxy;
            else
                proxy = QNetworkProxy::Socks5Proxy;
            proxy.setHostName(hn);
            proxy.setPort(quint16(s->value("cb2Bib/ProxyPort").toInt()));
        }
    }
    _fetcher->setProxy(proxy);
}


/****************************************************************************

  PRIVATE PART: FILEMANAGER CLIENT

*****************************************************************************/

bool network::getFile_client(const QString& action)
{
    // Getting NetworkFile through kfmclient
    QString c_action(action);
    // Only move local files
    QUrl u(_source_filename);
    if (!(u.scheme() == "file" || QFileInfo(_source_filename).exists()))
        if (c_action == "move")
            c_action = "copy"; // Copy network files

    QStringList arglist;
    QString fmclient_bin;
    if (c_action == "copy")
    {
        fmclient_bin = FmClientCopyBin;
        arglist = FmClientCopyArg.split(' ', QString::SkipEmptyParts);
    }
    else if (c_action == "move")
    {
        fmclient_bin = FmClientMoveBin;
        arglist = FmClientMoveArg.split(' ', QString::SkipEmptyParts);
    }
    arglist.append(_source_filename);
    arglist.append(_destination_filename);
    _fetcher_client = new QProcess(this);
    connect(_fetcher_client, SIGNAL(finished(int, QProcess::ExitStatus)),
            this, SLOT(fetcherClientEnded(int, QProcess::ExitStatus)));
    _fetcher_client->start(fmclient_bin, arglist);
    if (_fetcher_client->waitForStarted())
        return true;
    else
    {
        _getfile_error_string = tr("FM Client '%1' could not be launched.").arg(fmclient_bin);
        delete _fetcher_client;
        return false;
    }
}

void network::fetcherClientEnded(int exitCode, QProcess::ExitStatus exitStatus)
{
    if (exitStatus == QProcess::CrashExit)
        emit emit_getFileDone(false, tr("Client crashed."));
    else
    {
        QFile f(_destination_filename);
        if (f.exists())
            emit emit_getFileDone(true, "");
        else
            emit emit_getFileDone(false, tr("File '%1' has not been written. Exit code '%2'.").
                                  arg(_source_filename).arg(exitCode));
    }
    delete _fetcher_client;
}


/****************************************************************************

  PRIVATE PART: C2B FETCHER

*****************************************************************************/

bool network::getFile_c2b(const QString& action, const QNetworkAccessManager::Operation op)
{
    _operation = op;
    _url_query.clear();
    _redirection_count = 0;
    QString url_str;
    if (_operation == QNetworkAccessManager::PostOperation)
    {
        const int qmark(_source_filename.indexOf('?'));
        url_str = _source_filename.mid(0, qmark);
        if (qmark > -1)
        {
            url_str += '/';
            _url_query = _source_filename.mid(qmark + 1).toUtf8();
        }
    }
    else
        url_str = _source_filename;

    QUrl u(url_str, QUrl::TolerantMode);
    if (u.scheme() == "file" || QFileInfo(_source_filename).exists())
    {
        // Local File
        QString fn;
        if (u.scheme() == "file")
            fn = u.toLocalFile();
        else
            fn = _source_filename;
        QFile orig(fn);
        bool succeeded(false);
        if (action == "copy")
            succeeded = orig.copy(_destination_filename);
        else if (action == "move")
            succeeded = orig.rename(_destination_filename);
        if (succeeded)
            emit_getFileDone(true, "");
        else
            _getfile_error_string = orig.errorString();
        return succeeded;
    }
    else
    {
        // Network File
        return fetch(u);
    }
}

bool network::fetch(const QUrl& url)
{
    QNetworkRequest request;
    request.setUrl(url);
    request.setRawHeader("User-Agent", QString("cb2Bib/" + C2B_VERSION + " (Bibliographic Browser Tool)").toLatin1());
    if (_operation == QNetworkAccessManager::PostOperation)
    {
        request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
        _current_reply = _fetcher->post(request, _url_query);
    }
    else
        _current_reply = _fetcher->get(request);
    connect(_current_reply, SIGNAL(readyRead()), this, SLOT(fetcherReadyRead()));
    connect(_current_reply, SIGNAL(finished()), SLOT(fetcherEnded()));
    connect(_current_reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(logError()));
    connect(_current_reply, SIGNAL(downloadProgress(qint64, qint64)), this, SIGNAL(downloadProgress(qint64, qint64)));

    _destination_file.setFileName(_destination_filename);
    if (_destination_file.open(QIODevice::WriteOnly))
        return true;
    else
    {
        _getfile_error_string = tr("File '%1' has not been written. %2").
                                arg(_destination_filename).arg(_destination_file.errorString());
        return false;
    }
}

void network::fetcherEnded()
{
    _destination_file.close();
    if (_current_reply->error() == QNetworkReply::OperationCanceledError)
        _destination_file.remove(); // Delete file

    if (_redirection_count++ < 15)
    {
        QUrl redirection = _current_reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
        if (redirection.isValid())
        {
            QUrl newUrl = _current_reply->url().resolved(redirection);
            const int status(_current_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
            if ((status >= 301 && status <= 303) || status == 307)
                _operation = QNetworkAccessManager::GetOperation;
            _current_reply->deleteLater();
            fetch(newUrl);
            return;
        }
    }
    if (_current_reply->error() == QNetworkReply::NoError)
        emit_getFileDone(true, QString());
    else
        emit_getFileDone(false, _current_reply->errorString() + '.');
    _current_reply->deleteLater();
}

void network::fetcherReadyRead()
{
    _destination_file.write(_current_reply->readAll());
}

void network::logError()
{
    qDebug(tr("[cb2bib] network::QNetworkReply Log: %1.").arg(_current_reply->errorString()).toUtf8());
}
