/*
  Copyright (C) 2006 Enrico Ros <enrico.ros@m31engineering.it>
  Copyright (C) 2007 Trolltech ASA
  Copyright (C) 2007 Staikos Computing Services Inc.  <info@staikos.net>

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Library General Public
  License as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Library General Public License for more details.

  You should have received a copy of the GNU Library General Public License
  along with this library; see the file COPYING.LIB.  If not, write to
  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  Boston, MA 02110-1301, USA.

  This class provides all functionality needed for loading images, style sheets and html
  pages from the web. It has a memory cache for these objects.
*/
#include <qglobal.h>
#if QT_VERSION < 0x040400
#include "qwebframe.h"
#include "qwebnetworkinterface.h"
#include "qwebnetworkinterface_p.h"
#include "qwebobjectpluginconnector.h"
#include "qwebpage.h"
#include "qcookiejar.h"
#include <qdebug.h>
#include <qfile.h>
#include <qnetworkproxy.h>
#include <qurl.h>
#include <QAuthenticator>
#include <QCoreApplication>
#include <QSslError>

#include "ResourceHandle.h"
#include "ResourceHandleClient.h"
#include "ResourceHandleInternal.h"
#include "MIMETypeRegistry.h"
#include "CookieJar.h"

#if 0
#define DEBUG qDebug
#else
#define DEBUG if (1) {} else qDebug
#endif

static QWebNetworkInterface *s_default_interface = 0;
static QWebNetworkManager *s_manager = 0;

using namespace WebCore;

uint qHash(const HostInfo &info)
{
    return qHash(info.host) + info.port;
}

static bool operator==(const HostInfo &i1, const HostInfo &i2)
{
    return i1.port == i2.port && i1.host == i2.host;
}

enum ParserState {
    State_Begin,
    State_FirstChar,
    State_SecondChar
};

/*
 * Decode URLs without doing any charset conversion.
 *
 * Most simple approach to do it without any lookahead.
 */
static QByteArray decodePercentEncoding(const QByteArray& input)
{
    int actualLength = 0;
    QByteArray tmpVal;
    QByteArray output;
    ParserState state = State_Begin;

    output.resize(input.length());
    tmpVal.resize(2);

    for (int i = 0; i < input.length(); ++i)
        if (state == State_Begin) {
            if (input.at(i) == '%') {
                state = State_FirstChar;
            } else {
                output[actualLength++] = input[i];
            }
        } else if (state == State_FirstChar) {
            state = State_SecondChar;
            tmpVal[0] = input[i];
        } else if (state == State_SecondChar) {
            state = State_Begin;
            tmpVal[1] = input[i];
            output[actualLength++] = tmpVal.toShort(0, 16);
        }

    output.resize(actualLength);
    return output;
}

void QWebNetworkRequestPrivate::init(const WebCore::ResourceRequest &resourceRequest)
{
    KURL url = resourceRequest.url();
    QUrl qurl = QString(url.string());
    init(resourceRequest.httpMethod(), qurl, &resourceRequest);
}

void QWebNetworkRequestPrivate::init(const QString &method, const QUrl &url, const WebCore::ResourceRequest *resourceRequest)
{
    httpHeader = QHttpRequestHeader(method, url.toString(QUrl::RemoveScheme|QUrl::RemoveAuthority));
    httpHeader.setValue(QLatin1String("Connection"), QLatin1String("Keep-Alive"));
    setURL(url);

    if (resourceRequest) {
        httpHeader.setValue(QLatin1String("User-Agent"), resourceRequest->httpUserAgent());
        const QString scheme = url.scheme().toLower();
        if (scheme == QLatin1String("http") || scheme == QLatin1String("https")) {
            QString cookies = QCookieJar::cookieJar()->cookies(resourceRequest->url());
            if (!cookies.isEmpty())
                httpHeader.setValue(QLatin1String("Cookie"), cookies);
        }


        const HTTPHeaderMap& loaderHeaders = resourceRequest->httpHeaderFields();
        HTTPHeaderMap::const_iterator end = loaderHeaders.end();
        for (HTTPHeaderMap::const_iterator it = loaderHeaders.begin(); it != end; ++it)
            httpHeader.setValue(it->first, it->second);

        // handle and perform a 'POST' request
        if (method == "POST") {
            Vector<char> data;
            resourceRequest->httpBody()->flatten(data);
            postData = QByteArray(data.data(), data.size());
            httpHeader.setValue(QLatin1String("content-length"), QString::number(postData.size()));
        }
    }
}

void QWebNetworkRequestPrivate::setURL(const QUrl &u)
{
    url = u;
    int port = url.port();
    const QString scheme = u.scheme();
    if (port > 0 && (port != 80 || scheme != "http") && (port != 443 || scheme != "https"))
        httpHeader.setValue(QLatin1String("Host"), url.host() + QLatin1Char(':') + QString::number(port));
    else
        httpHeader.setValue(QLatin1String("Host"), url.host());
}

/*!
  \class QWebNetworkRequest

  The QWebNetworkRequest class represents a request for data from the network with all the
  necessary information needed for retrieval. This includes the url, extra HTTP header fields
  as well as data for a HTTP POST request.
*/

QWebNetworkRequest::QWebNetworkRequest()
    : d(new QWebNetworkRequestPrivate)
{
}

QWebNetworkRequest::QWebNetworkRequest(const QUrl &url, Method method, const QByteArray &postData)
    : d(new QWebNetworkRequestPrivate)
{
    d->init(method == Get ? "GET" : "POST", url);
    d->postData = postData;
}

QWebNetworkRequest::QWebNetworkRequest(const QWebNetworkRequest &other)
    : d(new QWebNetworkRequestPrivate(*other.d))
{
}

QWebNetworkRequest &QWebNetworkRequest::operator=(const QWebNetworkRequest &other)
{
    *d = *other.d;
    return *this;
}

/*!
  \internal
*/
QWebNetworkRequest::QWebNetworkRequest(const QWebNetworkRequestPrivate &priv)
    : d(new QWebNetworkRequestPrivate(priv))
{
}

/*!
  \internal
*/
QWebNetworkRequest::QWebNetworkRequest(const WebCore::ResourceRequest &request)
    : d(new QWebNetworkRequestPrivate)
{
    d->init(request);
}

QWebNetworkRequest::~QWebNetworkRequest()
{
    delete d;
}

/*!
  The requested URL
*/
QUrl QWebNetworkRequest::url() const
{
    return d->url;
}

/*!
   Sets the URL to request.

   Note that setting the URL also sets the "Host" field in the HTTP header.
*/
void QWebNetworkRequest::setUrl(const QUrl &url)
{
    d->setURL(url);
}

/*!
   The http request header information.
*/
QHttpRequestHeader QWebNetworkRequest::httpHeader() const
{
    return d->httpHeader;
}

void QWebNetworkRequest::setHttpHeader(const QHttpRequestHeader &header) const
{
    d->httpHeader = header;
}

QString QWebNetworkRequest::httpHeaderField(const QString &key) const
{
    return d->httpHeader.value(key);
}

void QWebNetworkRequest::setHttpHeaderField(const QString &key, const QString &value)
{
    d->httpHeader.setValue(key, value);
}

/*!
    Post data sent with HTTP POST requests.
*/
QByteArray QWebNetworkRequest::postData() const
{
    return d->postData;
}

void QWebNetworkRequest::setPostData(const QByteArray &data)
{
    d->postData = data;
}

/*!
  \class QWebNetworkJob

  The QWebNetworkJob class represents a network job, that needs to be
  processed by the QWebNetworkInterface.

  This class is only required when implementing a new network layer (or
  support for a special protocol) using QWebNetworkInterface.

  QWebNetworkJob objects are created and owned by the QtWebKit library.
  Most of it's properties are read-only.

  The job is reference counted. This can be used to ensure that the job doesn't
  get deleted while it's still stored in some data structure.
*/

/*!
  \internal
*/
QWebNetworkJob::QWebNetworkJob()
    : d(new QWebNetworkJobPrivate)
{
}

/*!
  \internal
*/
QWebNetworkJob::~QWebNetworkJob()
{
    delete d;
    d = 0;
}

/*!
  The requested URL
*/
QUrl QWebNetworkJob::url() const
{
    return d->request.url;
}

/*!
  Post data associated with the job
*/
QByteArray QWebNetworkJob::postData() const
{
    return d->request.postData;
}

/*!
  The HTTP request header that should be used to download the job.
*/
QHttpRequestHeader QWebNetworkJob::httpHeader() const
{
    return d->request.httpHeader;
}

/*!
  The complete network request that should be used to download the job.
*/
QWebNetworkRequest QWebNetworkJob::request() const
{
    return QWebNetworkRequest(d->request);
}

/*!
  The HTTP response header received from the network.
*/
QHttpResponseHeader QWebNetworkJob::response() const
{
    return d->response;
}

/*!
  The last error of the Job.
*/
QString QWebNetworkJob::errorString() const
{
    return d->errorString;
}

/*!
  Sets the HTTP reponse header. The response header has to be called before
  emitting QWebNetworkInterface::started.
*/
void QWebNetworkJob::setResponse(const QHttpResponseHeader &response)
{
    d->response = response;
}

void QWebNetworkJob::setErrorString(const QString& errorString)
{
    d->errorString = errorString;
}

/*!
  returns true if the job has been cancelled by the WebKit framework
*/
bool QWebNetworkJob::cancelled() const
{
    return !d->resourceHandle && !d->connector;
}

/*!
  reference the job.
*/
void QWebNetworkJob::ref()
{
    ++d->ref;
}

/*!
  derefence the job.

  If the reference count drops to 0 this method also deletes the job.

  Returns false if the reference count has dropped to 0.
*/
bool QWebNetworkJob::deref()
{
    if (!--d->ref) {
        delete this;
        return false;
    }
    return true;
}

/*!
   Returns the network interface that is associated with this job.
*/
QWebNetworkInterface *QWebNetworkJob::networkInterface() const
{
    return d->interface;
}

/*!
   Returns the network interface that is associated with this job.
*/
QWebFrame *QWebNetworkJob::frame() const
{
    if (d->resourceHandle) {
        ResourceHandleInternal *rhi = d->resourceHandle->getInternal();
        if (rhi) {
            return rhi->m_frame;
        }
    }
    return 0;
}

QWebNetworkJob::JobStatus QWebNetworkJob::status() const
{
    return d->jobStatus;
}

void QWebNetworkJob::setStatus(const JobStatus& status)
{
    d->jobStatus = status;
}

/*!
  \class QWebNetworkManager
  \internal
*/
QWebNetworkManager::QWebNetworkManager()
    : QObject(0)
    , m_scheduledWork(false)
{
    connect(this, SIGNAL(scheduleWork()), SLOT(doWork()), Qt::QueuedConnection);
}

QWebNetworkManager *QWebNetworkManager::self()
{
    // ensure everything's constructed and connected
    QWebNetworkInterface::defaultInterface();

    return s_manager;
}

bool QWebNetworkManager::add(ResourceHandle *handle, QWebNetworkInterface *interface, JobMode jobMode)
{
    if (!interface)
        interface = s_default_interface;

    ASSERT(interface);

    QWebNetworkJob *job = new QWebNetworkJob();
    handle->getInternal()->m_job = job;
    job->d->resourceHandle = handle;
    job->d->interface = interface;
    job->d->connector = 0;

    job->d->request.init(handle->request());

    const QString method = handle->getInternal()->m_request.httpMethod();
    if (method != "POST" && method != "GET" && method != "HEAD") {
        qWarning("REQUEST: [%s]\n", qPrintable(job->d->request.httpHeader.toString()));
        return false;
    }

    DEBUG() << "QWebNetworkManager::add:" <<  job->d->request.httpHeader.toString();

    if (jobMode == SynchronousJob) {
        Q_ASSERT(!m_synchronousJobs.contains(job));
        m_synchronousJobs[job] = 1;
    }

    interface->addJob(job);

    return true;
}

void QWebNetworkManager::cancel(ResourceHandle *handle)
{
    QWebNetworkJob *job = handle->getInternal()->m_job;
    if (!job)
        return;
    DEBUG() << "QWebNetworkManager::cancel:" <<  job->d->request.httpHeader.toString();
    job->d->resourceHandle = 0;
    job->d->connector = 0;
    job->d->interface->cancelJob(job);
    handle->getInternal()->m_job = 0;
}

void QWebNetworkManager::started(QWebNetworkJob *job)
{
    Q_ASSERT(job->d);
    Q_ASSERT(job->status() == QWebNetworkJob::JobCreated ||
             job->status() == QWebNetworkJob::JobRecreated);

    job->setStatus(QWebNetworkJob::JobStarted);
    ResourceHandleClient* client = 0;
    if (job->d->resourceHandle) {
        client = job->d->resourceHandle->client();
        if (!client)
            return;
    } else if (!job->d->connector) {
        return;
    }

    DEBUG() << "ResourceHandleManager::receivedResponse:";
    DEBUG() << job->d->response.toString();

    QStringList cookies = job->d->response.allValues("Set-Cookie");
    KURL url(job->url());
    foreach (QString c, cookies) {
        QCookieJar::cookieJar()->setCookies(url, url, c);
    }
    QString contentType = job->d->response.value("Content-Type");
    QString encoding;
    int idx = contentType.indexOf(QLatin1Char(';'));
    if (idx > 0) {
        QString remainder = contentType.mid(idx + 1).toLower();
        contentType = contentType.left(idx).trimmed();

        idx = remainder.indexOf("charset");
        if (idx >= 0) {
            idx = remainder.indexOf(QLatin1Char('='), idx);
            if (idx >= 0)
                encoding = remainder.mid(idx + 1).trimmed();
        }
    }
    if (contentType.isEmpty()) {
        // let's try to guess from the extension
        QString extension = job->d->request.url.path();
        int index = extension.lastIndexOf(QLatin1Char('.'));
        if (index > 0) {
            extension = extension.mid(index + 1);
            contentType = MIMETypeRegistry::getMIMETypeForExtension(extension);
        }
    }
//     qDebug() << "Content-Type=" << contentType;
//     qDebug() << "Encoding=" << encoding;


    ResourceResponse response(url, contentType,
                              0 /* FIXME */,
                              encoding,
                              String() /* FIXME */);

    int statusCode = job->d->response.statusCode();
    if (job->url().scheme() != QLatin1String("file"))
        response.setHTTPStatusCode(statusCode);
    else if (statusCode == 404)
        response.setHTTPStatusCode(statusCode);

    /* Fill in the other fields */

    if (statusCode >= 300 && statusCode < 400) {
        // we're on a redirect page! if the 'Location:' field is valid, we redirect
        QString location = job->d->response.value("location");
        DEBUG() << "Redirection";
        if (!location.isEmpty()) {
            QUrl newUrl = job->d->request.url.resolved(location);
            if (job->d->resourceHandle) {
                ResourceRequest newRequest = job->d->resourceHandle->request();
                newRequest.setURL(KURL(newUrl));
                if (client)
                    client->willSendRequest(job->d->resourceHandle, newRequest, response);
            }

            job->d->request.httpHeader.setRequest(job->d->request.httpHeader.method(),
                                                  newUrl.toString(QUrl::RemoveScheme|QUrl::RemoveAuthority));
            job->d->request.setURL(newUrl);
            job->d->redirected = true;
            return;
        }
    }

    if (client)
        client->didReceiveResponse(job->d->resourceHandle, response);
    if (job->d->connector)
        emit job->d->connector->started(job);

}

void QWebNetworkManager::data(QWebNetworkJob *job, const QByteArray &data)
{
    Q_ASSERT(job->status() == QWebNetworkJob::JobStarted ||
             job->status() == QWebNetworkJob::JobReceivingData);

    job->setStatus(QWebNetworkJob::JobReceivingData);
    ResourceHandleClient* client = 0;
    if (job->d->resourceHandle) {
        client = job->d->resourceHandle->client();
        if (!client)
            return;
    } else if (!job->d->connector) {
        return;
    }

    if (job->d->redirected)
        return; // don't emit the "Document has moved here" type of HTML

    DEBUG() << "receivedData" << job->d->request.url.path();
    if (client)
        client->didReceiveData(job->d->resourceHandle, data.constData(), data.length(), data.length() /*FixMe*/);
    if (job->d->connector)
        emit job->d->connector->data(job, data);

}

void QWebNetworkManager::finished(QWebNetworkJob *job, int errorCode)
{
    Q_ASSERT(errorCode == 1 ||
             job->status() == QWebNetworkJob::JobStarted ||
             job->status() == QWebNetworkJob::JobReceivingData);

    if (m_synchronousJobs.contains(job))
        m_synchronousJobs.remove(job);

    job->setStatus(QWebNetworkJob::JobFinished);
    ResourceHandleClient* client = 0;
    if (job->d->resourceHandle) {
        client = job->d->resourceHandle->client();
        if (!client)
            return;
    } else if (!job->d->connector) {
        job->deref();
        return;
    }

    DEBUG() << "receivedFinished" << errorCode << job->url();

    if (job->d->redirected) {
        job->d->redirected = false;
        job->setStatus(QWebNetworkJob::JobRecreated);
        job->d->interface->addJob(job);
        return;
    }

    if (job->d->resourceHandle)
        job->d->resourceHandle->getInternal()->m_job = 0;

    if (client) {
        if (errorCode) {
            //FIXME: error setting error was removed from ResourceHandle
            client->didFail(job->d->resourceHandle,
                            ResourceError(job->d->request.url.host(), job->d->response.statusCode(),
                                          job->d->request.url.toString(), job->d->errorString));
        } else {
            client->didFinishLoading(job->d->resourceHandle);
        }
    }

    if (job->d->connector)
        emit job->d->connector->finished(job, errorCode);

    DEBUG() << "receivedFinished done" << job->d->request.url;

    job->deref();
}

void QWebNetworkManager::addHttpJob(QWebNetworkJob *job)
{
    HostInfo hostInfo(job->url());
    WebCoreHttp *httpConnection = m_hostMapping.value(hostInfo);
    if (!httpConnection) {
        // #### fix custom ports
        DEBUG() << "   new connection to" << hostInfo.host << hostInfo.port;
        httpConnection = new WebCoreHttp(this, hostInfo);
        QObject::connect(httpConnection, SIGNAL(connectionClosed(const WebCore::HostInfo&)),
                         this, SLOT(httpConnectionClosed(const WebCore::HostInfo&)));

        m_hostMapping[hostInfo] = httpConnection;
    }
    httpConnection->request(job);
}

void QWebNetworkManager::cancelHttpJob(QWebNetworkJob *job)
{
    WebCoreHttp *httpConnection = m_hostMapping.value(job->url());
    if (httpConnection)
        httpConnection->cancel(job);
}

void QWebNetworkManager::httpConnectionClosed(const WebCore::HostInfo &info)
{
    WebCoreHttp *connection = m_hostMapping.take(info);
    connection->deleteLater();
}

void QWebNetworkInterfacePrivate::sendFileData(QWebNetworkJob* job, int statusCode, const QByteArray &data)
{
    int error = statusCode >= 400 ? 1 : 0;
    if (!job->cancelled()) {
        QHttpResponseHeader response;
        response.setStatusLine(statusCode);
        response.setContentLength(data.length());
        job->setResponse(response);
        q->started(job);
        if (!data.isEmpty())
            q->data(job, data);
    }
    q->finished(job, error);
}

void QWebNetworkInterfacePrivate::parseDataUrl(QWebNetworkJob* job)
{
    QByteArray data = job->url().toString().toLatin1();
    //qDebug() << "handling data url:" << data;

    ASSERT(data.startsWith("data:"));

    // Here's the syntax of data URLs:
    // dataurl    := "data:" [ mediatype ] [ ";base64" ] "," data
    // mediatype  := [ type "/" subtype ] *( ";" parameter )
    // data       := *urlchar
    // parameter  := attribute "=" value
    QByteArray header;
    bool base64 = false;

    int index = data.indexOf(',');
    if (index != -1) {
        header = data.mid(5, index - 5).toLower();
        //qDebug() << "header=" << header;
        data = data.mid(index+1);
        //qDebug() << "data=" << data;

        if (header.endsWith(";base64")) {
            //qDebug() << "base64";
            base64 = true;
            header = header.left(header.length() - 7);
            //qDebug() << "mime=" << header;
        }
    } else {
        data = QByteArray();
    }

    if (base64) {
        data = QByteArray::fromBase64(data);
    } else {
        data = decodePercentEncoding(data);
    }

    if (header.isEmpty())
        header = "text/plain;charset=US-ASCII";
    int statusCode = 200;
    QHttpResponseHeader response;
    response.setContentType(header);
    response.setContentLength(data.size());
    job->setResponse(response);

    int error = statusCode >= 400 ? 1 : 0;
    q->started(job);
    if (!data.isEmpty())
        q->data(job, data);
    q->finished(job, error);
}

void QWebNetworkManager::queueStart(QWebNetworkJob* job)
{
    Q_ASSERT(job->d);

    QMutexLocker locker(&m_queueMutex);
    job->ref();
    m_pendingWork.append(new JobWork(job));
    doScheduleWork();
}

void QWebNetworkManager::queueData(QWebNetworkJob* job, const QByteArray& data)
{
    ASSERT(job->d);

    QMutexLocker locker(&m_queueMutex);
    job->ref();
    m_pendingWork.append(new JobWork(job, data));
    doScheduleWork();
}

void QWebNetworkManager::queueFinished(QWebNetworkJob* job, int errorCode)
{
    Q_ASSERT(job->d);

    QMutexLocker locker(&m_queueMutex);
    job->ref();
    m_pendingWork.append(new JobWork(job, errorCode));
    doScheduleWork();
}

void QWebNetworkManager::doScheduleWork()
{
    if (!m_scheduledWork) {
        m_scheduledWork = true;
        emit scheduleWork();
    }
}


/*
 * We will work on a copy of m_pendingWork. While dispatching m_pendingWork
 * new work will be added that will be handled at a later doWork call. doWork
 * will be called we set m_scheduledWork to false early in this method.
 */
void QWebNetworkManager::doWork()
{
    m_queueMutex.lock();
    m_scheduledWork = false;
    bool hasSyncJobs = m_synchronousJobs.size();
    const QHash<QWebNetworkJob*, int> syncJobs = m_synchronousJobs;
    m_queueMutex.unlock();

    foreach (JobWork* work, m_pendingWork) {
        if (hasSyncJobs && !syncJobs.contains(work->job))
            continue;

        if (work->workType == JobWork::JobStarted)
            started(work->job);
        else if (work->workType == JobWork::JobData) {
            // This job was not yet started
            if (static_cast<int>(work->job->status()) < QWebNetworkJob::JobStarted)
                continue;

            data(work->job, work->data);
        } else if (work->workType == JobWork::JobFinished) {
            // This job was not yet started... we have no idea if data comes by...
            // and it won't start in case of errors
            if (static_cast<int>(work->job->status()) < QWebNetworkJob::JobStarted && work->errorCode != 1)
                continue;

            finished(work->job, work->errorCode);
        }

        m_queueMutex.lock();
        m_pendingWork.removeAll(work);
        m_queueMutex.unlock();

        work->job->deref();
        delete work;
    }

    m_queueMutex.lock();
    if (hasSyncJobs && m_synchronousJobs.size() == 0)
        doScheduleWork();
    m_queueMutex.unlock();
}

/*!
  \class QWebNetworkInterface

  The QWebNetworkInterface class provides an abstraction layer for
  WebKit's network interface.  It allows to completely replace or
  extend the builtin network layer.

  QWebNetworkInterface contains two virtual methods, addJob and
  cancelJob that have to be reimplemented when implementing your own
  networking layer.

  QWebNetworkInterface can by default handle the http, https, file and
  data URI protocols.

*/

static bool gRoutineAdded = false;

static void gCleanupInterface()
{
    delete s_default_interface;
    s_default_interface = 0;
}

/*!
  Sets a new default interface that will be used by all of WebKit
  for downloading data from the internet.
*/
void QWebNetworkInterface::setDefaultInterface(QWebNetworkInterface *defaultInterface)
{
    if (s_default_interface == defaultInterface)
        return;
    if (s_default_interface)
        delete s_default_interface;
    s_default_interface = defaultInterface;
    if (!gRoutineAdded) {
        qAddPostRoutine(gCleanupInterface);
        gRoutineAdded = true;
    }
}

/*!
  Returns the default interface that will be used by WebKit. If no
  default interface has been set, QtWebkit will create an instance of
  QWebNetworkInterface to do the work.
*/
QWebNetworkInterface *QWebNetworkInterface::defaultInterface()
{
    if (!s_default_interface) {
        setDefaultInterface(new QWebNetworkInterface);
    }
    return s_default_interface;
}


/*!
  Constructs a QWebNetworkInterface object.
*/
QWebNetworkInterface::QWebNetworkInterface(QObject *parent)
    : QObject(parent)
{
    d = new QWebNetworkInterfacePrivate;
    d->q = this;

    if (!s_manager)
        s_manager = new QWebNetworkManager;
}

/*!
  Destructs the QWebNetworkInterface object.
*/
QWebNetworkInterface::~QWebNetworkInterface()
{
    delete d;
}

/*!
  This virtual method gets called whenever QtWebkit needs to add a
  new job to download.

  The QWebNetworkInterface should process this job, by first emitting
  the started signal, then emitting data repeatedly as new data for
  the Job is available, and finally ending the job with emitting a
  finished signal.

  After the finished signal has been emitted, the QWebNetworkInterface
  is not allowed to access the job anymore.
*/
void QWebNetworkInterface::addJob(QWebNetworkJob *job)
{
    QString protocol = job->url().scheme();
    if (protocol == QLatin1String("http") || protocol == QLatin1String("https")) {
        QWebNetworkManager::self()->addHttpJob(job);
        return;
    }

    // "file", "data" and all unhandled stuff go through here
    //DEBUG() << "fileRequest";
    DEBUG() << "FileLoader::request" << job->url();

    if (job->cancelled()) {
        d->sendFileData(job, 400, QByteArray());
        return;
    }

    QUrl url = job->url();
    if (protocol == QLatin1String("data")) {
        d->parseDataUrl(job);
        return;
    }

    int statusCode = 200;
    QByteArray data;
    QString path = url.path();
    if (protocol == QLatin1String("qrc")) {
        protocol = "file";
        path.prepend(QLatin1Char(':'));
    }

    if (!(protocol.isEmpty() || protocol == QLatin1String("file"))) {
        statusCode = 404;
    } else {
        // we simply ignore post data here.
        QFile f(path);
        DEBUG() << "opening" << QString(url.path());

        if (f.open(QIODevice::ReadOnly)) {
            QHttpResponseHeader response;
            response.setStatusLine(200);
            job->setResponse(response);
            data = f.readAll();
        } else {
            statusCode = 404;
        }
    }

    if (statusCode == 404) {
        QHttpResponseHeader response;
        response.setStatusLine(404);
        job->setResponse(response);
    }

    d->sendFileData(job, statusCode, data);
}

/*!
  This virtual method gets called whenever QtWebkit needs to cancel a
  new job.

  The QWebNetworkInterface acknowledge the canceling of the job, by
  emitting the finished signal with an error code of 1. After emitting
  the finished signal, the interface should not access the job
  anymore.
*/
void QWebNetworkInterface::cancelJob(QWebNetworkJob *job)
{
    QString protocol = job->url().scheme();
    if (protocol == QLatin1String("http") || protocol == QLatin1String("https"))
        QWebNetworkManager::self()->cancelHttpJob(job);
}

void QWebNetworkInterface::started(QWebNetworkJob* job)
{
    Q_ASSERT(s_manager);
    s_manager->queueStart(job);
}

void QWebNetworkInterface::data(QWebNetworkJob* job, const QByteArray& data)
{
    Q_ASSERT(s_manager);
    s_manager->queueData(job, data);
}

void QWebNetworkInterface::finished(QWebNetworkJob* job, int errorCode)
{
    Q_ASSERT(s_manager);
    s_manager->queueFinished(job, errorCode);
}

/////////////////////////////////////////////////////////////////////////////
WebCoreHttp::WebCoreHttp(QObject* parent, const HostInfo &hi)
    : QObject(parent), info(hi),
      m_inCancel(false)
{
    for (int i = 0; i < 2; ++i) {
        connection[i].http = new QHttp(info.host, (hi.protocol == QLatin1String("https")) ? QHttp::ConnectionModeHttps : QHttp::ConnectionModeHttp, info.port);
        connect(connection[i].http, SIGNAL(responseHeaderReceived(const QHttpResponseHeader&)),
                this, SLOT(onResponseHeaderReceived(const QHttpResponseHeader&)));
        connect(connection[i].http, SIGNAL(readyRead(const QHttpResponseHeader&)),
                this, SLOT(onReadyRead()));
        connect(connection[i].http, SIGNAL(requestFinished(int, bool)),
                this, SLOT(onRequestFinished(int, bool)));
        connect(connection[i].http, SIGNAL(done(bool)),
                this, SLOT(onDone(bool)));
        connect(connection[i].http, SIGNAL(stateChanged(int)),
                this, SLOT(onStateChanged(int)));
        connect(connection[i].http, SIGNAL(authenticationRequired(const QString&, quint16, QAuthenticator*)),
                this, SLOT(onAuthenticationRequired(const QString&, quint16, QAuthenticator*)));
        connect(connection[i].http, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy&, QAuthenticator*)),
                this, SLOT(onProxyAuthenticationRequired(const QNetworkProxy&, QAuthenticator*)));
        connect(connection[i].http, SIGNAL(sslErrors(const QList<QSslError>&)),
                this, SLOT(onSslErrors(const QList<QSslError>&)));
    }
}

WebCoreHttp::~WebCoreHttp()
{
    connection[0].http->deleteLater();
    connection[1].http->deleteLater();
}

void WebCoreHttp::request(QWebNetworkJob *job)
{
    m_pendingRequests.append(job);
    scheduleNextRequest();
}

void WebCoreHttp::scheduleNextRequest()
{
    int c = 0;
    for (; c < 2; ++c) {
        if (!connection[c].current)
            break;
    }
    if (c >= 2)
        return;

    QWebNetworkJob *job = 0;
    while (!job && !m_pendingRequests.isEmpty()) {
        job = m_pendingRequests.takeFirst();
        if (job->cancelled()) {
            job->networkInterface()->finished(job, 1);
            job = 0;
        }
    }
    if (!job)
        return;

    QHttp *http = connection[c].http;

    connection[c].current = job;
    connection[c].id = -1;
#ifndef QT_NO_NETWORKPROXY
    int proxyId = http->setProxy(job->frame()->page()->networkProxy());
#endif

    QByteArray postData = job->postData();
    if (!postData.isEmpty())
        connection[c].id = http->request(job->httpHeader(), postData);
    else
        connection[c].id = http->request(job->httpHeader());

    DEBUG() << "WebCoreHttp::scheduleNextRequest: using connection" << c;
    DEBUG() << job->httpHeader().toString();
//     DEBUG() << job->request.toString();
}

int WebCoreHttp::getConnection()
{
    QObject *o = sender();
    int c;
    if (o == connection[0].http) {
        c = 0;
    } else {
        Q_ASSERT(o == connection[1].http);
        c = 1;
    }
    Q_ASSERT(connection[c].current);
    return c;
}

void WebCoreHttp::onResponseHeaderReceived(const QHttpResponseHeader &resp)
{
    QHttp *http = qobject_cast<QHttp*>(sender());
    if (http->currentId() == 0) {
        qDebug() << "ERROR!  Invalid job id.  Why?"; // foxnews.com triggers this
        return;
    }
    int c = getConnection();
    QWebNetworkJob *job = connection[c].current;
    DEBUG() << "WebCoreHttp::slotResponseHeaderReceived connection=" << c;
    DEBUG() << resp.toString();

    job->setResponse(resp);

    job->networkInterface()->started(job);
}

void WebCoreHttp::onReadyRead()
{
    QHttp *http = qobject_cast<QHttp*>(sender());
    if (http->currentId() == 0) {
        qDebug() << "ERROR!  Invalid job id.  Why?"; // foxnews.com triggers this
        return;
    }
    int c = getConnection();
    QWebNetworkJob *job = connection[c].current;
    Q_ASSERT(http == connection[c].http);
    //DEBUG() << "WebCoreHttp::slotReadyRead connection=" << c;

    QByteArray data;
    data.resize(http->bytesAvailable());
    http->read(data.data(), data.length());
    job->networkInterface()->data(job, data);
}

void WebCoreHttp::onRequestFinished(int id, bool error)
{
    int c = getConnection();
    if (connection[c].id != id) {
        return;
    }

    QWebNetworkJob *job = connection[c].current;
    if (!job) {
        scheduleNextRequest();
        return;
    }

    QHttp *http = connection[c].http;
    DEBUG() << "WebCoreHttp::slotFinished connection=" << c << error << job;
    if (error) {
        DEBUG() << "   error: " << http->errorString();
        job->setErrorString(http->errorString());
    }

    if (!error && http->bytesAvailable()) {
        QByteArray data;
        data.resize(http->bytesAvailable());
        http->read(data.data(), data.length());
        job->networkInterface()->data(job, data);
    }

    job->networkInterface()->finished(job, error ? 1 : 0);
    connection[c].current = 0;
    connection[c].id = -1;
    scheduleNextRequest();
}

void WebCoreHttp::onDone(bool error)
{
    DEBUG() << "WebCoreHttp::onDone" << error;
}

void WebCoreHttp::onStateChanged(int state)
{
    DEBUG() << "State changed to" << state << "and connections are" << connection[0].current << connection[1].current;
    if (state == QHttp::Closing || state == QHttp::Unconnected) {
        if (!m_inCancel && m_pendingRequests.isEmpty()
            && !connection[0].current && !connection[1].current)
            emit connectionClosed(info);
    }
}

void WebCoreHttp::cancel(QWebNetworkJob* request)
{
    bool doEmit = true;
    m_inCancel = true;
    for (int i = 0; i < 2; ++i) {
        if (request == connection[i].current) {
            connection[i].http->abort();
            doEmit = false;
        }
    }
    if (!m_pendingRequests.removeAll(request))
        doEmit = false;
    m_inCancel = false;

    if (doEmit)
        request->networkInterface()->finished(request, 1);

    if (m_pendingRequests.isEmpty()
        && !connection[0].current && !connection[1].current)
        emit connectionClosed(info);
}

void WebCoreHttp::onSslErrors(const QList<QSslError>& errors)
{
    int c = getConnection();
    QWebNetworkJob *job = connection[c].current;
    if (job) {
        bool continueAnyway = false;
        emit job->networkInterface()->sslErrors(job->frame(), job->url(), errors, &continueAnyway);
#ifndef QT_NO_OPENSSL
        if (continueAnyway)
            connection[c].http->ignoreSslErrors();
#endif
    }
}

void WebCoreHttp::onAuthenticationRequired(const QString& hostname, quint16 port, QAuthenticator *auth)
{
    int c = getConnection();
    QWebNetworkJob *job = connection[c].current;
    if (job) {
        emit job->networkInterface()->authenticate(job->frame(), job->url(), hostname, port, auth);
        if (auth->isNull())
            connection[c].http->abort();
    }
}

void WebCoreHttp::onProxyAuthenticationRequired(const QNetworkProxy& proxy, QAuthenticator *auth)
{
    int c = getConnection();
    QWebNetworkJob *job = connection[c].current;
    if (job) {
        emit job->networkInterface()->authenticateProxy(job->frame(), job->url(), proxy, auth);
        if (auth->isNull())
            connection[c].http->abort();
    }
}

HostInfo::HostInfo(const QUrl& url)
    : protocol(url.scheme())
    , host(url.host())
    , port(url.port())
{
    if (port < 0) {
        if (protocol == QLatin1String("http"))
            port = 80;
        else if (protocol == QLatin1String("https"))
            port = 443;
    }
}

#endif
