/*LICENSE_START*/
/*
 *  Copyright (C) 2014  Washington University School of Medicine
 *
 *  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.
 *
 *  This program 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 this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
/*LICENSE_END*/

#include "CaretHttpManager.h"
#include "CaretAssert.h"
#include "NetworkException.h"
#include "CaretLogger.h"
#include <QNetworkRequest>

using namespace caret;
using namespace std;

CaretHttpManager* CaretHttpManager::m_singleton = NULL;

CaretHttpManager::CaretHttpManager() : QObject()
{
    connect(&m_netMgr,
        SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError> & )),
        this,
        SLOT(handleSslErrors(QNetworkReply*, const QList<QSslError> & )));
}

CaretHttpManager* CaretHttpManager::getHttpManager()
{
    if (m_singleton == NULL)
    {
        m_singleton = new CaretHttpManager();
    }
    return m_singleton;
}

void CaretHttpManager::deleteHttpManager()
{
    if (m_singleton != NULL)
    {
        delete m_singleton;
    }
}

QNetworkAccessManager* CaretHttpManager::getQNetManager()
{
    return &(getHttpManager()->m_netMgr);
}

void CaretHttpManager::httpRequest(const CaretHttpRequest &request, CaretHttpResponse &response)
{
    QEventLoop myLoop;
    QNetworkRequest myRequest;
    myRequest.setSslConfiguration(QSslConfiguration::defaultConfiguration());
    CaretHttpManager* myCaretMgr = getHttpManager();
    AString myServerString = getServerString(request.m_url);
    bool have_auth = false;
    for (int i = 0; i < (int)myCaretMgr->m_authList.size(); ++i)
    {
        if (myServerString == myCaretMgr->m_authList[i].m_serverString)
        {
            QString unencoded = myCaretMgr->m_authList[i].m_user + ":" + myCaretMgr->m_authList[i].m_pass;
            myRequest.setRawHeader("Authorization", "Basic " + unencoded.toLocal8Bit().toBase64());
            CaretLogInfo("Found auth for URL " + request.m_url);
            have_auth = true;
            break;
        }
    }
    if (!have_auth)
    {
        CaretLogInfo("NO AUTH FOUND for URL " + request.m_url);
    }
    QNetworkReply* myReply = NULL;
    QUrl myUrl = QUrl::fromUserInput(request.m_url);
    for (int32_t i = 0; i < (int32_t)request.m_queries.size(); ++i)
    {
        myUrl.addQueryItem(request.m_queries[i].first, request.m_queries[i].second);
    }
    QNetworkAccessManager* myQNetMgr = &(myCaretMgr->m_netMgr);
    bool first = true;
    QByteArray postData;
    switch (request.m_method)
    {
    case POST:
        for (int32_t i = 0; i < (int32_t)request.m_arguments.size(); ++i)
        {
            if (!first) postData += "&";
            if (request.m_arguments[i].second == "")
            {
                postData += request.m_arguments[i].first;
            } else {
                postData += request.m_arguments[i].first + "=" + request.m_arguments[i].second;
            }
            first = false;
        }
        myRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
        myRequest.setUrl(myUrl);
        CaretLogInfo("POST URL: " + myUrl.toString());
        myReply = myQNetMgr->post(myRequest, postData);
        break;
    case GET:
        for (int32_t i = 0; i < (int32_t)request.m_arguments.size(); ++i)
        {
            myUrl.addQueryItem(request.m_arguments[i].first, request.m_arguments[i].second);
        }
        myRequest.setUrl(myUrl);
        CaretLogInfo("GET URL: " + myUrl.toString());
        myReply = myQNetMgr->get(myRequest);
        break;
    case HEAD:
        for (int32_t i = 0; i < (int32_t)request.m_arguments.size(); ++i)
        {
            myUrl.addQueryItem(request.m_arguments[i].first, request.m_arguments[i].second);
        }
        myRequest.setUrl(myUrl);
        CaretLogInfo("HEAD URL: " + myUrl.toString());
        myReply = myQNetMgr->head(myRequest);
        break;
    default:
        CaretAssertMessage(false, "Unrecognized http request method");
    };
    //QObject::connect(myReply, SIGNAL(sslErrors(QList<QSslError>)), &myLoop, SLOT(quit()));
    //QObject::connect(myQNetMgr, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), myCaretMgr, SLOT(authenticationCallback(QNetworkReply*,QAuthenticator*)));
    QObject::connect(myReply, SIGNAL(finished()), &myLoop, SLOT(quit()));//this is safe, because nothing will hand this thread events except queued through this thread's event mechanism
    /*QObject::connect(myReply,
        SIGNAL(sslErrors(const QList<QSslError> & )),
        CaretHttpManager::getHttpManager(),
        SLOT(handleSslErrors(const QList<QSslError> & )));//*/
    myLoop.exec();//so, they can only be delivered after myLoop.exec() starts
    response.m_method = request.m_method;
    response.m_ok = false;
    response.m_responseCode = -1;
    response.m_responseCode = myReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
    if (response.m_responseCode == 200)
    {
        response.m_ok = true;
    }
    QByteArray myBody = myReply->readAll();
    int64_t mySize = myBody.size();
    response.m_body.reserve(mySize + 1);//make room for the null terminator that will sometimes be added to the end
    response.m_body.resize(mySize);//but don't set size to include it
    for (int64_t i = 0; i < mySize; ++i)
    {
        response.m_body[i] = myBody[(int)i];//because QByteArray apparently just uses int - hope we won't need to transfer 2GB on a system that uses int32 for this
    }
    delete myReply;
}

void CaretHttpManager::setAuthentication(const AString& url, const AString& user, const AString& password)
{
    CaretHttpManager* myCaretMgr = getHttpManager();
    AString myServerString = getServerString(url);
    CaretLogInfo("Setting auth for server " + myServerString);
    for (int i = 0; i < (int)myCaretMgr->m_authList.size(); ++i)
    {
        if (myServerString == myCaretMgr->m_authList[i].m_serverString)
        {//for the moment, only allow one auth token per server in one instance of caret, so replace
            myCaretMgr->m_authList[i].m_user = user;
            myCaretMgr->m_authList[i].m_pass = password;
            return;
        }
    }//not found, need to add
    AuthEntry myAuth;
    myAuth.m_serverString = myServerString;
    myAuth.m_user = user;
    myAuth.m_pass = password;
    myCaretMgr->m_authList.push_back(myAuth);
}

void CaretHttpManager::handleSslErrors(QNetworkReply* reply, const QList<QSslError> &/*errors*/)
{
    /*qDebug() << "handleSslErrors: ";
    foreach (QSslError e, errors)
    {
        qDebug() << "ssl error: " << e;
    }*/

    reply->ignoreSslErrors();
}

/*void CaretHttpManager::authenticationCallback(QNetworkReply* reply, QAuthenticator* authenticator)
{
    if (reply->url() != QUrl::fromUserInput(m_authURL))//note: a redirect will cause this to break, this is ON PURPOSE so that auth isn't sent to a redirect
    {
        throw NetworkException("Authentication requested from different URL than authentication set for");
    }
    authenticator->setUser(m_authUser);
    authenticator->setPassword(m_authPass);
    m_authURL = "";
    m_authUser = "";
    m_authPass = "";
}//*/ //currently not used, because callback doesn't work for the way xnat is set up, and doesn't fit well with synchronous requests

AString CaretHttpManager::getServerString(const AString& url)
{
    QUrl fullURL = QUrl::fromUserInput(url);
    AString ret = fullURL.toEncoded(QUrl::RemovePath | QUrl::StripTrailingSlash | QUrl::RemoveQuery | QUrl::RemoveUserInfo);
    return ret;
}
