/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtWebEngine module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QScopedPointer>
#include <QtQml/QQmlEngine>
#include <QtTest/QtTest>
#include <QQuickWebEngineProfile>
#include <QtWebEngine/private/qquickwebengineview_p.h>

#define INSPECTOR_SERVER_PORT "23654"
static const QUrl s_inspectorServerHttpBaseUrl("http://localhost:" INSPECTOR_SERVER_PORT);

class tst_InspectorServer : public QObject {
    Q_OBJECT
public:
    tst_InspectorServer();

private Q_SLOTS:
    void init();
    void cleanup();

    void testPageList();
    void testRemoteDebuggingMessage();
    void openRemoteDebuggingSession();
private:
    void prepareWebViewComponent();
    inline QQuickWebEngineView* newWebView();
    inline QQuickWebEngineView* webView() const;
    QJsonArray fetchPageList() const;
    QScopedPointer<QQuickWebEngineView> m_webView;
    QScopedPointer<QQmlComponent> m_component;
};

tst_InspectorServer::tst_InspectorServer()
{
    qputenv("QTWEBENGINE_REMOTE_DEBUGGING", INSPECTOR_SERVER_PORT);
    QtWebEngine::initialize();
    QQuickWebEngineProfile::defaultProfile()->setOffTheRecord(true);
    prepareWebViewComponent();
}

void tst_InspectorServer::prepareWebViewComponent()
{
    static QQmlEngine* engine = new QQmlEngine(this);
    engine->addImportPath(QString::fromUtf8(IMPORT_DIR));

    m_component.reset(new QQmlComponent(engine, this));

    m_component->setData(QByteArrayLiteral("import QtQuick 2.0\n"
                                           "import QtWebEngine 1.2\n"
                                           "WebEngineView { }")
                        , QUrl());
}

QQuickWebEngineView* tst_InspectorServer::newWebView()
{
    QObject* viewInstance = m_component->create();

    return qobject_cast<QQuickWebEngineView*>(viewInstance);
}

void tst_InspectorServer::init()
{
    m_webView.reset(newWebView());
}

void tst_InspectorServer::cleanup()
{
    m_webView.reset();
}

inline QQuickWebEngineView* tst_InspectorServer::webView() const
{
    return m_webView.data();
}

QJsonArray tst_InspectorServer::fetchPageList() const
{
    QNetworkAccessManager qnam;
    QScopedPointer<QNetworkReply> reply(qnam.get(QNetworkRequest(s_inspectorServerHttpBaseUrl.resolved(QUrl("json/list")))));
    QSignalSpy(reply.data(), SIGNAL(finished())).wait();
    return QJsonDocument::fromJson(reply->readAll()).array();
}

void tst_InspectorServer::testPageList()
{
    const QUrl testPageUrl = QUrl::fromLocalFile(QLatin1String(TESTS_SOURCE_DIR "/html/basic_page.html"));
    QSignalSpy loadSpy(webView(), SIGNAL(loadingChanged(QQuickWebEngineLoadRequest*)));
    webView()->setUrl(testPageUrl);
    QTRY_VERIFY(loadSpy.size() && !webView()->isLoading());

    // Our page has developerExtrasEnabled and should be the only one in the list.
    QJsonArray pageList = fetchPageList();
    QCOMPARE(pageList.size(), 1);
    QCOMPARE(testPageUrl.toString(), pageList.at(0).toObject().value("url").toString());
}

void tst_InspectorServer::testRemoteDebuggingMessage()
{
    const QUrl testPageUrl = QUrl::fromLocalFile(QLatin1String(TESTS_SOURCE_DIR "/html/basic_page.html"));
    QSignalSpy loadSpy(webView(), SIGNAL(loadingChanged(QQuickWebEngineLoadRequest*)));
    webView()->setUrl(testPageUrl);
    QTRY_VERIFY(loadSpy.size() && !webView()->isLoading());

    QJsonArray pageList = fetchPageList();
    QCOMPARE(pageList.size(), 1);
    QVERIFY(pageList.at(0).toObject().contains("webSocketDebuggerUrl"));

    // Test sending a raw remote debugging message through our web socket server.
    // For this specific message see: http://code.google.com/chrome/devtools/docs/protocol/tot/runtime.html#command-evaluate
    QLatin1String jsExpression("2 + 2");
    QLatin1String jsExpressionResult("4");
    QScopedPointer<QQuickWebEngineView> webSocketQueryWebView(newWebView());
    webSocketQueryWebView->loadHtml(QString(
        "<script type=\"text/javascript\">\n"
        "var socket = new WebSocket('%1');\n"
        "socket.onmessage = function(message) {\n"
            "var response = JSON.parse(message.data);\n"
            "if (response.id === 1)\n"
                "document.title = response.result.result.value;\n"
        "}\n"
        "socket.onopen = function() {\n"
            "socket.send('{\"id\": 1, \"method\": \"Runtime.evaluate\", \"params\": {\"expression\": \"%2\" } }');\n"
        "}\n"
        "</script>")
        .arg(pageList.at(0).toObject().value("webSocketDebuggerUrl").toString())
        .arg(jsExpression));

    QTRY_COMPARE(webSocketQueryWebView->title(), jsExpressionResult);
}

void tst_InspectorServer::openRemoteDebuggingSession()
{
    const QUrl testPageUrl = QUrl::fromLocalFile(QLatin1String(TESTS_SOURCE_DIR "/html/basic_page.html"));
    QSignalSpy loadSpy(webView(), SIGNAL(loadingChanged(QQuickWebEngineLoadRequest*)));
    webView()->setUrl(testPageUrl);
    QTRY_VERIFY(loadSpy.size() && !webView()->isLoading());

    QJsonArray pageList = fetchPageList();
    QCOMPARE(pageList.size(), 1);
    QVERIFY(pageList.at(0).toObject().contains("devtoolsFrontendUrl"));

    QScopedPointer<QQuickWebEngineView> inspectorWebView(newWebView());
    inspectorWebView->setUrl(s_inspectorServerHttpBaseUrl.resolved(QUrl(pageList.at(0).toObject().value("devtoolsFrontendUrl").toString())));

    // To test the whole pipeline this exploits a behavior of the inspector front-end which won't provide any title unless the
    // debugging session was established correctly through web socket.
    // So this test case will fail if:
    // - The page list didn't return a valid inspector URL
    // - Or the front-end couldn't be loaded through the inspector HTTP server
    // - Or the web socket connection couldn't be established between the front-end and the page through the inspector server
    QTRY_VERIFY_WITH_TIMEOUT(inspectorWebView->title().startsWith("DevTools -"), 30000);
}

QTEST_MAIN(tst_InspectorServer)

#include "tst_inspectorserver.moc"
