/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite 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 <qlogging.h>
#include <qloggingcategory.h>
#include <QtTest/QTest>

Q_LOGGING_CATEGORY(debugTestCategory, "debug", QtDebugMsg)
Q_LOGGING_CATEGORY(infoTestCategory, "info", QtInfoMsg)
Q_LOGGING_CATEGORY(warningTestCategory, "warning", QtWarningMsg)
Q_LOGGING_CATEGORY(criticalTestCategory, "critical", QtCriticalMsg)

struct LoggerMessageInfo
{
    QtMsgType messageType { QtFatalMsg };
    QString message;
    const char *file { nullptr };
    int line { 0 };
    const char *function { nullptr };
    const char *category { nullptr };
};

LoggerMessageInfo messageInfo;

static void customMessageHandler(QtMsgType type, const QMessageLogContext &context,
                                 const QString &message)
{
    messageInfo.messageType = type;
    messageInfo.message = message;
    messageInfo.file = context.file;
    messageInfo.line = context.line;
    messageInfo.function = context.function;
    messageInfo.category = context.category;
}

class tst_QMessageLogger : public QObject
{
    Q_OBJECT
private slots:
    void initTestCase_data();

    void init();
    void cleanup();

    void logMessage();
    void logMessageWithLoggingCategory();
    void logMessageWithLoggingCategoryDisabled();
    void logMessageWithCategoryFunction();
    void logMessageWithNoDebug();

private:
    void logWithLoggingCategoryHelper(bool messageTypeEnabled);
};

void tst_QMessageLogger::initTestCase_data()
{
    QTest::addColumn<QtMsgType>("messageType");
    QTest::addColumn<QByteArray>("categoryName");
    QTest::addColumn<QByteArray>("messageText");
    QTest::addColumn<bool>("useDebugStream");

    // not testing QtFatalMsg, as it terminates the application
    QTest::newRow("debug") << QtDebugMsg << QByteArray("categoryDebug")
                           << QByteArray("debug message") << false;
    QTest::newRow("info") << QtInfoMsg << QByteArray("categoryInfo") << QByteArray("info message")
                          << false;
    QTest::newRow("warning") << QtWarningMsg << QByteArray("categoryWarning")
                             << QByteArray("warning message") << false;
    QTest::newRow("critical") << QtCriticalMsg << QByteArray("categoryCritical")
                              << QByteArray("critical message") << false;

#ifndef QT_NO_DEBUG_STREAM
    QTest::newRow("stream debug") << QtDebugMsg << QByteArray("categoryDebug")
                                  << QByteArray("debug message") << true;
    QTest::newRow("stream info") << QtInfoMsg << QByteArray("categoryInfo")
                                 << QByteArray("info message") << true;
    QTest::newRow("stream warning") << QtWarningMsg << QByteArray("categoryWarning")
                                    << QByteArray("warning message") << true;
    QTest::newRow("stream critical") << QtCriticalMsg << QByteArray("categoryCritical")
                                     << QByteArray("critical message") << true;
#endif
}

void tst_QMessageLogger::init()
{
    qInstallMessageHandler(customMessageHandler);
}

void tst_QMessageLogger::cleanup()
{
    qInstallMessageHandler((QtMessageHandler)0);
    messageInfo.messageType = QtFatalMsg;
    messageInfo.message.clear();
    messageInfo.file = nullptr;
    messageInfo.line = 0;
    messageInfo.function = nullptr;
    messageInfo.category = nullptr;
}

void tst_QMessageLogger::logMessage()
{
    const int line = QT_MESSAGELOG_LINE;
    QMessageLogger logger(QT_MESSAGELOG_FILE, line, QT_MESSAGELOG_FUNC);

    QFETCH_GLOBAL(QtMsgType, messageType);
    QFETCH_GLOBAL(QByteArray, messageText);
    QFETCH_GLOBAL(bool, useDebugStream);
    if (useDebugStream) {
#ifndef QT_NO_DEBUG_STREAM
        switch (messageType) {
        case QtDebugMsg:
            logger.debug().noquote() << messageText;
            break;
        case QtInfoMsg:
            logger.info().noquote() << messageText;
            break;
        case QtWarningMsg:
            logger.warning().noquote() << messageText;
            break;
        case QtCriticalMsg:
            logger.critical().noquote() << messageText;
            break;
        default:
            QFAIL("Invalid message type");
            break;
        }
#else
        QSKIP("Qt debug stream disabled");
#endif
    } else {
        switch (messageType) {
        case QtDebugMsg:
            logger.debug("%s", messageText.constData());
            break;
        case QtInfoMsg:
            logger.info("%s", messageText.constData());
            break;
        case QtWarningMsg:
            logger.warning("%s", messageText.constData());
            break;
        case QtCriticalMsg:
            logger.critical("%s", messageText.constData());
            break;
        default:
            QFAIL("Invalid message type");
            break;
        }
    }

    QCOMPARE(messageInfo.messageType, messageType);
    QCOMPARE(messageInfo.message, messageText);
    QCOMPARE(messageInfo.file, __FILE__);
    QCOMPARE(messageInfo.line, line);
    QCOMPARE(messageInfo.function, Q_FUNC_INFO);
}

void tst_QMessageLogger::logMessageWithLoggingCategory()
{
    logWithLoggingCategoryHelper(true);
}

void tst_QMessageLogger::logMessageWithLoggingCategoryDisabled()
{
    logWithLoggingCategoryHelper(false);
}

void tst_QMessageLogger::logMessageWithCategoryFunction()
{
    const int line = QT_MESSAGELOG_LINE;
    QMessageLogger logger(QT_MESSAGELOG_FILE, line, QT_MESSAGELOG_FUNC);

    const QLoggingCategory *category = nullptr;
    QFETCH_GLOBAL(QtMsgType, messageType);
    QFETCH_GLOBAL(QByteArray, messageText);
    QFETCH_GLOBAL(bool, useDebugStream);
    if (useDebugStream) {
#ifndef QT_NO_DEBUG_STREAM
        switch (messageType) {
        case QtDebugMsg:
            logger.debug(debugTestCategory()).noquote() << messageText;
            category = &debugTestCategory();
            break;
        case QtInfoMsg:
            logger.info(infoTestCategory()).noquote() << messageText;
            category = &infoTestCategory();
            break;
        case QtWarningMsg:
            logger.warning(warningTestCategory()).noquote() << messageText;
            category = &warningTestCategory();
            break;
        case QtCriticalMsg:
            logger.critical(criticalTestCategory()).noquote() << messageText;
            category = &criticalTestCategory();
            break;
        default:
            QFAIL("Invalid message type");
            break;
        }
#else
        QSKIP("Qt debug stream disabled");
#endif
    } else {
        switch (messageType) {
        case QtDebugMsg:
            logger.debug(debugTestCategory(), "%s", messageText.constData());
            category = &debugTestCategory();
            break;
        case QtInfoMsg:
            logger.info(infoTestCategory(), "%s", messageText.constData());
            category = &infoTestCategory();
            break;
        case QtWarningMsg:
            logger.warning(warningTestCategory(), "%s", messageText.constData());
            category = &warningTestCategory();
            break;
        case QtCriticalMsg:
            logger.critical(criticalTestCategory(), "%s", messageText.constData());
            category = &criticalTestCategory();
            break;
        default:
            QFAIL("Invalid message type");
            break;
        }
    }

    QCOMPARE(messageInfo.messageType, messageType);
    QCOMPARE(messageInfo.message, messageText);
    QCOMPARE(messageInfo.file, __FILE__);
    QCOMPARE(messageInfo.line, line);
    QCOMPARE(messageInfo.function, Q_FUNC_INFO);
    QCOMPARE(messageInfo.category, category->categoryName());
}

void tst_QMessageLogger::logMessageWithNoDebug()
{
    const int line = QT_MESSAGELOG_LINE;
    QMessageLogger logger(QT_MESSAGELOG_FILE, line, QT_MESSAGELOG_FUNC);

    QFETCH_GLOBAL(QByteArray, messageText);
    QFETCH_GLOBAL(bool, useDebugStream);
    if (useDebugStream) {
#ifndef QT_NO_DEBUG_STREAM
        logger.noDebug().noquote() << messageText;
#else
        QSKIP("Qt debug stream disabled");
#endif
    } else {
        logger.noDebug("%s", messageText.constData());
    }

    // the callback was not called
    QVERIFY(messageInfo.messageType == QtFatalMsg);
    QVERIFY(messageInfo.message.isEmpty());
    QVERIFY(messageInfo.file == nullptr);
    QVERIFY(messageInfo.line == 0);
    QVERIFY(messageInfo.function == nullptr);
    QVERIFY(messageInfo.category == nullptr);
}

void tst_QMessageLogger::logWithLoggingCategoryHelper(bool messageTypeEnabled)
{
    QFETCH_GLOBAL(QtMsgType, messageType);
    QFETCH_GLOBAL(QByteArray, categoryName);
    QLoggingCategory category(categoryName.constData(), messageType);
    if (!messageTypeEnabled)
        category.setEnabled(messageType, false);

    const int line = QT_MESSAGELOG_LINE;
    QMessageLogger logger(QT_MESSAGELOG_FILE, line, QT_MESSAGELOG_FUNC);

    QFETCH_GLOBAL(QByteArray, messageText);
    QFETCH_GLOBAL(bool, useDebugStream);
    if (useDebugStream) {
#ifndef QT_NO_DEBUG_STREAM
        switch (messageType) {
        case QtDebugMsg:
            logger.debug(category).noquote() << messageText;
            break;
        case QtInfoMsg:
            logger.info(category).noquote() << messageText;
            break;
        case QtWarningMsg:
            logger.warning(category).noquote() << messageText;
            break;
        case QtCriticalMsg:
            logger.critical(category).noquote() << messageText;
            break;
        default:
            QFAIL("Invalid message type");
            break;
        }
#else
        QSKIP("Qt debug stream disabled");
#endif
    } else {
        switch (messageType) {
        case QtDebugMsg:
            logger.debug(category, "%s", messageText.constData());
            break;
        case QtInfoMsg:
            logger.info(category, "%s", messageText.constData());
            break;
        case QtWarningMsg:
            logger.warning(category, "%s", messageText.constData());
            break;
        case QtCriticalMsg:
            logger.critical(category, "%s", messageText.constData());
            break;
        default:
            QFAIL("Invalid message type");
            break;
        }
    }

    if (messageTypeEnabled) {
        QCOMPARE(messageInfo.messageType, messageType);
        QCOMPARE(messageInfo.message, messageText);
        QCOMPARE(messageInfo.file, __FILE__);
        QCOMPARE(messageInfo.line, line);
        QCOMPARE(messageInfo.function, Q_FUNC_INFO);
        QCOMPARE(messageInfo.category, categoryName);
    } else {
        // the callback was not called
        QVERIFY(messageInfo.messageType == QtFatalMsg);
        QVERIFY(messageInfo.message.isEmpty());
        QVERIFY(messageInfo.file == nullptr);
        QVERIFY(messageInfo.line == 0);
        QVERIFY(messageInfo.function == nullptr);
        QVERIFY(messageInfo.category == nullptr);
    }
}

QTEST_MAIN(tst_QMessageLogger)
#include "tst_qmessagelogger.moc"
