/******************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtMqtt module.
**
** $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 "broker_connection.h"

#include <QtCore/QString>
#include <QtTest/QtTest>
#include <QtMqtt/QMqttClient>
#include <QtMqtt/QMqttConnectionProperties>

class tst_QMqttLastWillProperties : public QObject
{
    Q_OBJECT

public:
    tst_QMqttLastWillProperties();

private Q_SLOTS:
    void initTestCase();
    void cleanupTestCase();
    void getSet();
    void payloadFormat();
    void willDelay_data();
    void willDelay();

private:
    QProcess m_brokerProcess;
    QString m_testBroker;
    quint16 m_port{1883};
};

tst_QMqttLastWillProperties::tst_QMqttLastWillProperties()
{
}

void tst_QMqttLastWillProperties::initTestCase()
{
    m_testBroker = invokeOrInitializeBroker(&m_brokerProcess);
    if (m_testBroker.isEmpty())
        qFatal("No MQTT broker present to test against.");
}

void tst_QMqttLastWillProperties::cleanupTestCase()
{
}

void tst_QMqttLastWillProperties::getSet()
{
    QMqttLastWillProperties properties;

    QCOMPARE(properties.willDelayInterval(), 0u);
    properties.setWillDelayInterval(50);
    QCOMPARE(properties.willDelayInterval(), 50u);

    QCOMPARE(properties.payloadFormatIndicator(), QMqtt::PayloadFormatIndicator::Unspecified);
    properties.setPayloadFormatIndicator(QMqtt::PayloadFormatIndicator::UTF8Encoded);
    QCOMPARE(properties.payloadFormatIndicator(), QMqtt::PayloadFormatIndicator::UTF8Encoded);

    QCOMPARE(properties.messageExpiryInterval(), 0u);
    properties.setMessageExpiryInterval(50);
    QCOMPARE(properties.messageExpiryInterval(), 50u);

    const QString content = QLatin1String("contentType");
    const QString response = QLatin1String("Qt/some/responseTopic");
    const QByteArray correlation(500, char('a'));

    QCOMPARE(properties.contentType(), QString());
    properties.setContentType(content);
    QCOMPARE(properties.contentType(), content);

    QCOMPARE(properties.responseTopic(), QString());
    properties.setResponseTopic(response);
    QCOMPARE(properties.responseTopic(), response);

    QCOMPARE(properties.correlationData(), QByteArray());
    properties.setCorrelationData(correlation);
    QCOMPARE(properties.correlationData(), correlation);

    QVERIFY(properties.userProperties().isEmpty());
    QMqttUserProperties user;
    user.append(QMqttStringPair(QLatin1String("SomeName"), QLatin1String("SomeValue")));
    user.append(QMqttStringPair(QLatin1String("SomeName2"), QLatin1String("SomeValue2")));
    properties.setUserProperties(user);
    QCOMPARE(properties.userProperties(), user);
}

void tst_QMqttLastWillProperties::payloadFormat()
{
    QMqttClient client;
    client.setProtocolVersion(QMqttClient::MQTT_5_0);
    client.setHostname(m_testBroker);
    client.setPort(m_port);

    client.setWillMessage(QString::fromLatin1("willmessage to utf8").toUtf8());
    client.setWillTopic(QLatin1String("Qt/LastWillProperties/willtopic"));
    client.setWillQoS(1);

    QMqttLastWillProperties lastWill;
    lastWill.setPayloadFormatIndicator(QMqtt::PayloadFormatIndicator::UTF8Encoded);

    // The broker MAY verify the content is utf8
    client.connectToHost();
    QTRY_VERIFY2(client.state() == QMqttClient::Connected, "Could not connect to broker.");
}

void tst_QMqttLastWillProperties::willDelay_data()
{
    QTest::addColumn<int>("delay");
    QTest::addColumn<int>("expiry");
    QTest::newRow("delay == expiry") << 5 << 5;
    QTest::newRow("delay < expiry") << 3 << 10; // will delay is send first
    QTest::newRow("delay > expiry") << 10 << 3; // will is send at expiry
    QTest::newRow("delay > expiry(0)") << 5 << 0; // No expiry, hence will is send immediately
}

void tst_QMqttLastWillProperties::willDelay()
{
    QFETCH(int, delay);
    QFETCH(int, expiry);

    QMqttClient client;
    client.setProtocolVersion(QMqttClient::MQTT_5_0);
    client.setHostname(m_testBroker);
    client.setPort(m_port);

    const QString wTopic{"Qt/lastwillproperties/willdelay"};
    const QByteArray wMessage{"client got lost"};

    client.setWillMessage(wMessage);
    client.setWillQoS(2);
    client.setWillTopic(wTopic);

    if (expiry > 0) {
        // If a session expires first, then last will is send immediately
        QMqttConnectionProperties connectionProperties;
        connectionProperties.setSessionExpiryInterval(quint32(expiry));
        client.setConnectionProperties(connectionProperties);
    }

    QMqttLastWillProperties lastWillProperties;
    lastWillProperties.setWillDelayInterval(quint32(delay));
    client.setLastWillProperties(lastWillProperties);

    client.connectToHost();
    QTRY_VERIFY2(client.state() == QMqttClient::Connected, "Could not connect to broker");

    QMqttClient recipient;
    recipient.setProtocolVersion(QMqttClient::MQTT_5_0);
    recipient.setHostname(m_testBroker);
    recipient.setPort(m_port);
    recipient.connectToHost();
    QTRY_VERIFY2(recipient.state() == QMqttClient::Connected, "Could not connect to broker");

    QTime delayTime;
    bool receivedWill = false;
    auto sub = recipient.subscribe(wTopic, 1);
    connect(sub, &QMqttSubscription::messageReceived, this, [wMessage, &receivedWill](QMqttMessage m) {
        if (m.payload() == wMessage)
            receivedWill = true;
    });
    QTRY_VERIFY2(sub->state() == QMqttSubscription::Subscribed, "Could not subscribe.");

    auto transport = client.transport();
    transport->close(); // closing transport does not send DISCONNECT
    delayTime = QTime::currentTime();
    delayTime.start();

    const int minimalWait = qMin(delay, expiry) * 1000;
    const int maximumWait = 2 * (minimalWait == 0 ? 1000 : minimalWait);
    QTRY_VERIFY2_WITH_TIMEOUT(receivedWill, "Did not receive a will message", delay * 1000 * 3);
    const int elapsed = delayTime.elapsed();
    QVERIFY(elapsed > minimalWait);
    QVERIFY(elapsed < maximumWait);
}

QTEST_MAIN(tst_QMqttLastWillProperties)

#include "tst_qmqttlastwillproperties.moc"
