/**************************************************************************
 * This is only a part of the WS Request for demonstrating the bug !
 * Do not use in productive code!
 *************************************************************************/

#include <QTextStream>
#include <QDebug>
#include <QtWebSockets>

#include "qwebsockethandshakerequestpart.h"

QWebSocketHandshakeRequest::QWebSocketHandshakeRequest(int port, bool isSecure) :
    m_port(port),
    m_isSecure(isSecure),
    m_isValid(false),
    m_headers(),
    //m_versions(),
    m_key(),
    m_origin(),
    m_protocols(),
    m_extensions(),
    m_requestUrl()
{
}

QWebSocketHandshakeRequest::~QWebSocketHandshakeRequest()
{
}

void QWebSocketHandshakeRequest::clear()
{
    m_isValid = false;
    m_headers.clear();
    //m_versions.clear();
    m_key.clear();
    m_origin.clear();
    m_protocols.clear();
    m_extensions.clear();
    m_requestUrl.clear();
}

int QWebSocketHandshakeRequest::port() const
{
    return m_requestUrl.port(m_port);
}

bool QWebSocketHandshakeRequest::isSecure() const
{
    return m_isSecure;
}

bool QWebSocketHandshakeRequest::isValid() const
{
    return m_isValid;
}

QMap<QString, QString> QWebSocketHandshakeRequest::headers() const
{
    return m_headers;
}

QString QWebSocketHandshakeRequest::resourceName() const
{
    return m_requestUrl.path();
}

QString QWebSocketHandshakeRequest::key() const
{
    return m_key;
}

QString QWebSocketHandshakeRequest::host() const
{
    return m_requestUrl.host();
}

QString QWebSocketHandshakeRequest::origin() const
{
    return m_origin;
}

QList<QString> QWebSocketHandshakeRequest::protocols() const
{
    return m_protocols;
}

QList<QString> QWebSocketHandshakeRequest::extensions() const
{
    return m_extensions;
}

QUrl QWebSocketHandshakeRequest::requestUrl() const
{
    return m_requestUrl;
}

void QWebSocketHandshakeRequest::readHandshake(QTextStream &textStream)
{
    m_isValid = false;
    clear();
    if (Q_UNLIKELY(textStream.status() != QTextStream::Ok))
        return;
    const QString requestLine = textStream.readLine();

    qDebug() << "*** Request Line 1: " << requestLine;

    const QStringList tokens = requestLine.split(' ', QString::SkipEmptyParts);
    if (Q_UNLIKELY(tokens.length() < 3)) {
        m_isValid = false;
        clear();
        return;
    }
    const QString verb(tokens.at(0));
    const QString resourceName(tokens.at(1));
    const QString httpProtocol(tokens.at(2));
    bool conversionOk = false;
    const float httpVersion = httpProtocol.midRef(5).toFloat(&conversionOk);

    if (Q_UNLIKELY(!conversionOk)) {
        clear();
        m_isValid = false;
        return;
    }
    QString headerLine = textStream.readLine();
    qDebug() << "*** Header  Line 1: " << headerLine;
    m_headers.clear();
    while (!headerLine.isEmpty()) {
        const QStringList headerField = headerLine.split(QStringLiteral(": "),
                                                         QString::SkipEmptyParts);
        if (Q_UNLIKELY(headerField.length() < 2)) {
            clear();
            return;
        }
        m_headers.insertMulti(headerField.at(0), headerField.at(1));
        headerLine = textStream.readLine();
        qDebug() << "*** Header  Line n: " << headerLine;
    }

    const QString host = m_headers.value(QStringLiteral("Host"), QString());
    m_requestUrl = QUrl::fromEncoded(resourceName.toLatin1());

    qDebug() << "Res: "  << resourceName;
    qDebug() << "Host: " << host;
    if (m_requestUrl.isRelative())
    {

// ********************************************
// * Wrong host calculation,
// no not use port in host string
// set port in second step?
// Missing test for set host
// Missing test for error string
// ********************************************
        qDebug() << "Must set host";
        qDebug() << "ErrorString: " << m_requestUrl.errorString();
        m_requestUrl.setHost(host);
        qDebug() << "new ErrorString: " << m_requestUrl.errorString();
        qDebug() << "New Host of url: " << m_requestUrl.host();
    }

    if (m_requestUrl.scheme().isEmpty()) {
        const QString scheme =  isSecure() ? QStringLiteral("wss") : QStringLiteral("ws");
        m_requestUrl.setScheme(scheme);
    }

    qDebug() << "New Request Url: " << m_requestUrl.toDisplayString(QUrl::PrettyDecoded);

    /*
    const QStringList versionLines = m_headers.values(QStringLiteral("Sec-WebSocket-Version"));
    for (QStringList::const_iterator v = versionLines.begin(); v != versionLines.end(); ++v) {
        const QStringList versions = (*v).split(QStringLiteral(","), QString::SkipEmptyParts);
        for (QStringList::const_iterator i = versions.begin(); i != versions.end(); ++i) {
            bool ok = false;
            (void)(*i).toUInt(&ok);
            if (!ok) {
                clear();
                return;
            }
            const QWebSocketProtocol::Version ver =
                    QWebSocketProtocol::versionFromString((*i).trimmed());
            m_versions << ver;
        }
    }

    //sort in descending order
    std::sort(m_versions.begin(), m_versions.end(), std::greater<QWebSocketProtocol::Version>());
    */

    m_key = m_headers.value(QStringLiteral("Sec-WebSocket-Key"), QString());
    //must contain "Upgrade", case-insensitive
    const QString upgrade = m_headers.value(QStringLiteral("Upgrade"), QString());
    //must be equal to "websocket", case-insensitive
    const QString connection = m_headers.value(QStringLiteral("Connection"), QString());
    const QStringList connectionLine = connection.split(QStringLiteral(","),
                                                        QString::SkipEmptyParts);
    QStringList connectionValues;
    for (QStringList::const_iterator c = connectionLine.begin(); c != connectionLine.end(); ++c)
        connectionValues << (*c).trimmed();

    //optional headers
    m_origin = m_headers.value(QStringLiteral("Sec-WebSocket-Origin"), QString());
    const QStringList protocolLines = m_headers.values(QStringLiteral("Sec-WebSocket-Protocol"));
    for (QStringList::const_iterator pl = protocolLines.begin(); pl != protocolLines.end(); ++pl) {
        QStringList protocols = (*pl).split(QStringLiteral(","), QString::SkipEmptyParts);
        for (QStringList::const_iterator p = protocols.begin(); p != protocols.end(); ++p)
            m_protocols << (*p).trimmed();
    }
    const QStringList extensionLines = m_headers.values(QStringLiteral("Sec-WebSocket-Extensions"));
    for (QStringList::const_iterator el = extensionLines.begin();
         el != extensionLines.end(); ++el) {
        QStringList extensions = (*el).split(QStringLiteral(","), QString::SkipEmptyParts);
        for (QStringList::const_iterator e = extensions.begin(); e != extensions.end(); ++e)
            m_extensions << (*e).trimmed();
    }

    //TODO: authentication field

// ********************************************
// * Wrong calculation...
// test for
//    m_requestUrl.host()
//    m_requestUrl.toDisplayString() ...
//    instead of host/resourceName ...
// ********************************************
    m_isValid = !(host.isEmpty() ||
                  resourceName.isEmpty() ||
//                  m_versions.isEmpty() ||
                  m_key.isEmpty() ||
                  (verb != QStringLiteral("GET")) ||
                  (!conversionOk || (httpVersion < 1.1f)) ||
                  (upgrade.toLower() != QStringLiteral("websocket")) ||
                  (!connectionValues.contains(QStringLiteral("upgrade"), Qt::CaseInsensitive)));
    if (Q_UNLIKELY(!m_isValid))
        clear();
}
