/****************************************************************************
**
** Copyright (C) 2012 Róbert Márki
**
** This file is part of Web Feeds.
**
** Web Feeds 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 3 of the License, or
** (at your option) any later version.
**
** Web Feeds 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 General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with Web Feeds.  If not, see <http://www.gnu.org/licenses/>.
****************************************************************************/

#include "webserviceclient.h"

WebServiceClient::WebServiceClient(QObject *parent) :
    QObject(parent),
    m_networkAccessManger(new QNetworkAccessManager(this))
{
    connect(m_networkAccessManger, SIGNAL(finished(QNetworkReply*)),
            this, SLOT(onNetworkRequestFinished(QNetworkReply*)));
}

/*!
  Sends a new network \a request.
  A pointer to the related reply object is returned.
*/
QNetworkReply *WebServiceClient::sendRequest(const WebServiceClient::NetworkRequestPtr request)
{
    QNetworkReply* reply = 0;
    QNetworkRequest networkRequest(QUrl::fromEncoded(request->url.toUtf8(), QUrl::StrictMode));
    for(int i = 0; i < request->additionalHeaders.count(); ++i)
        networkRequest.setRawHeader(request->additionalHeaders.at(i).first.toAscii(),
                                    request->additionalHeaders.at(i).second.toAscii());
    if(request->operationType == QNetworkAccessManager::PostOperation)
    {
        networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded;charset=UTF-8");
        networkRequest.setHeader(QNetworkRequest::ContentLengthHeader, request->baData.size());
        reply = m_networkAccessManger->post(networkRequest, request->baData);
    }
    else if(request->operationType == QNetworkAccessManager::GetOperation)
    {
        reply = m_networkAccessManger->get(networkRequest);
    }
#ifdef DEBUG_REQUESTS
    qDebug() << "REQUEST:" << reply->url().toString() << request->baData;
#endif
#ifdef DEBUG_REQUEST_HEADERS
    qDebug() << "HEADERS:";
    foreach(const QByteArray& headerName, reply->request().rawHeaderList())
        qDebug() << headerName << reply->request().rawHeader(headerName);
#endif
    return reply;
}

void WebServiceClient::onNetworkRequestFinished(QNetworkReply *reply)
{
    QNetworkReply* networkReply = reply;
    //get the request object related to the network reply
    NetworkRequestPtr request = m_requests.take(networkReply);
    //get the reply object related to the network reply
    WebServiceReplyPtr webServiceReply = m_webServiceReplies.value(networkReply);
    //initialize the pointer to a new reply to 0, since it's not known yet
    //if a new request will be sent or not
    QNetworkReply* newReply = 0;

    //if the network errors type is TemporaryNetworkFailureError
    //based on Qt's docs, the request should be resent
    if(request->reply->error() == QNetworkReply::TemporaryNetworkFailureError)
    {
        newReply = sendRequest(request);
        request->reply = newReply;
        m_requests.insert(newReply, request);
    }
    //the request has been aborted so abort the entire operation
    else if(request->reply->error() == QNetworkReply::OperationCanceledError)
    {
        onRequestAborted(webServiceReply);
    }
    else
    {
        newReply = networkRequestFinished(reply, webServiceReply, request->type);
    }
    //if the operation is completed remove it
    if(webServiceReply && (webServiceReply->completedState() != WebServiceReply::CsNotCompleted))
    {
        m_webServiceReplies.remove(networkReply);
    }
    else if(newReply != 0) //if a new network request was sent, save the reply related to it
    {
        m_webServiceReplies.remove(networkReply);
        m_webServiceReplies.insert(newReply, webServiceReply);
    }
    if(networkReply)
        networkReply->deleteLater();
}

/*!
  Sets the state of the \a reply to WebServiceReply::CsFinished.
*/
void WebServiceClient::onRequestFinished(WebServiceReplyPtr reply)
{
    reply->setCompletedState(WebServiceReply::CsFinished);
    emit requestFinished(reply);
    emit requestCompleted(reply, reply->completedState());
}

/*!
  Sets the state of the \a reply to WebServiceReply::CsFailed.
*/
void WebServiceClient::onRequestFailed(WebServiceReplyPtr reply, int error)
{
    reply->setError(error);
    reply->setCompletedState(WebServiceReply::CsFailed);
    emit requestFailed(reply);
    emit requestCompleted(reply, reply->completedState());
}

/*!
  Sets the state of the \a reply to WebServiceReply::CsAborted.
*/
void WebServiceClient::onRequestAborted(WebServiceReplyPtr reply)
{
    reply->setCompletedState(WebServiceReply::CsAborted);
    emit requestAborted(reply);
    emit requestCompleted(reply, reply->completedState());
}

/*!
  Aborts the given \a reply.
*/
void WebServiceClient::onAbortRequested(WebServiceReplyPtr reply)
{
    int replyIndex = m_webServiceReplies.values().indexOf(reply);
    if(replyIndex >= 0)
    {
        QNetworkReply* networkReply = m_webServiceReplies.keys().at(replyIndex);
        networkReply->abort();
    }
}

QNetworkReply *WebServiceClient::networkRequestFinished(QNetworkReply *networkReply,
                                                        WebServiceReplyPtr webServiceReply,
                                                        int networkRequestType)
{
    Q_UNUSED(networkReply);
    Q_UNUSED(webServiceReply);
    Q_UNUSED(networkRequestType);
    return 0;
}

void WebServiceClient::addReply(QNetworkReply *networkReply, const WebServiceReplyPtr &reply)
{
    m_webServiceReplies.insert(networkReply, reply);
    connect(reply.data(), SIGNAL(abortRequested(WebServiceReplyPtr)),
            this, SLOT(onAbortRequested(WebServiceReplyPtr)));
}

void WebServiceClient::addNetworkRequestType(int requestTypeId, const QString &url,
                                             QNetworkAccessManager::Operation operationType)
{
    m_requestProperties.insert(requestTypeId,
                               (RequestProperties){
                               url,
                               operationType});
}

/*!
  Sends a new network request to the url specified for the given \a type to which the \a path is appended,
  with the specified list of request \a parameters.
  A pointer to the related reply object is returned.
*/
QNetworkReply *WebServiceClient::sendRequest(int requestTypeId, const QString &path,
                                             const StringPairList &parameters,
                                             const StringPairList &additionalHeaders)
{
    //set the properties of the request type
    RequestProperties requestProperties = m_requestProperties.value(requestTypeId);
    QNetworkAccessManager::Operation operationType = requestProperties.operationType;
    QString url = requestProperties.url + path;
    QByteArray requestData;
    QUrl urlParameters;
    //in case of a GET reqest append the parameters to the url
    if((operationType == QNetworkAccessManager::GetOperation) && parameters.count())
    {
        for(int i = 0; i < parameters.count(); ++i)
            urlParameters.addQueryItem(parameters.at(i).first, parameters.at(i).second);
        url += "?" +urlParameters.encodedQuery();
    }
    //in case of a post request the parameters should be added as the content of the request
    else if(operationType == QNetworkAccessManager::PostOperation)
    {
        StringPairList prepairedParameters = parameters;
        for(int i = 0; i < parameters.count(); ++i)
        {
            prepairedParameters[i].first.replace(" ", "+");
            prepairedParameters[i].second.replace(" ", "+");
            urlParameters.addEncodedQueryItem(QUrl::toPercentEncoding(parameters.at(i).first),
                                              QUrl::toPercentEncoding(parameters.at(i).second));
        }
        requestData = urlParameters.encodedQuery();
    }
    NetworkRequestPtr request(new NetworkRequest);
    request->type = requestTypeId;
    request->url = url;
    request->operationType = operationType;
    request->baData = requestData;
    request->additionalHeaders = additionalHeaders;
    QNetworkReply* reply = sendRequest(request);
    request->reply = reply;
    m_requests.insert(reply, request);
    return reply;
}

QNetworkAccessManager *WebServiceClient::networkAccessManager() const
{
    return m_networkAccessManger;
}
