// SPDX-License-Identifier: GPL-3.0-or-later

#include "test_utils.h"
#include "tests/tests_common.h"

#include <QRegularExpression>
#include <QTemporaryFile>

TemporaryFile::TemporaryFile()
{
    QTemporaryFile tmp;
    tmp.setAutoRemove(false);
    QVERIFY(tmp.open());
    m_fileName = tmp.fileName();
    tmp.close();
}

TemporaryFile::~TemporaryFile()
{
    QFile::remove(m_fileName);
}

bool testStderr(const QByteArray &stderrData, TestInterface::ReadStderrFlag flag)
{
    static const QRegularExpression reFailure("(Warning:|ERROR:|ASSERT|ScriptError:).*", QRegularExpression::CaseInsensitiveOption);
    const QLatin1String scriptError("ScriptError:");

    const auto plain = [](const char *str){
        return QRegularExpression(QRegularExpression::escape(QLatin1String(str)));
    };
    const auto regex = [](const char *str){
        return QRegularExpression(QLatin1String(str));
    };
    // Ignore exceptions and errors from clients in application log
    // (these are expected in some tests).
    static const std::array ignoreList{
        regex(R"(\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}\] Note <Client-[^\n]*)"),

        plain("Event handler maximum recursion reached"),

        plain("Warning: CopyQ server is already running"),

        // X11 (Linux)
        plain("QXcbXSettings::QXcbXSettings(QXcbScreen*) Failed to get selection owner for XSETTINGS_S atom"),
        plain("QXcbConnection: XCB error:"),
        plain("QXcbClipboard: SelectionRequest too old"),
        plain("libpng warning: iCCP: known incorrect sRGB profile"),
        plain("QMime::convertToMime: unhandled mimetype: text/plain"),
        plain("[kf.notifications] Failed to notify \"Created too many similar notifications in quick succession\""),

        // Wayland (Linux)
        plain("Wayland does not support QWindow::requestActivate()"),
        plain("Unexpected wl_keyboard.enter event"),
        plain("The compositor sent a wl_pointer.enter"),
        plain("QObject::connect: No such signal QPlatformNativeInterface::systemTrayWindowChanged(QScreen*)"),

        // Windows
        plain("QWindowsWindow::setGeometry: Unable to set geometry"),
        plain("QWinEventNotifier: no event dispatcher, application shutting down? Cannot deliver event."),
        plain("setGeometry: Unable to set geometry"),

        plain("ERROR: QWindowsPipeWriter::write failed. (The pipe is being closed.)"),
        plain("ERROR: QWindowsPipeWriter: asynchronous write failed. (The pipe has been ended.)"),

        plain("[kf.notifications] Received a response for an unknown notification."),
        // KStatusNotifierItem
        plain("[kf.windowsystem] Could not find any platform plugin"),

        regex("QTemporaryDir: Unable to remove .* most likely due to the presence of read-only files."),

        // Windows Qt 5.15.2
        plain("[qt.qpa.mime] Retrying to obtain clipboard."),
        plain("[default] QSystemTrayIcon::setVisible: No Icon set"),

        // macOS
        plain("Failed to get QCocoaScreen for NSObject(0x0)"),
        plain("ERROR: Failed to open session mutex: QSystemSemaphore::handle:: ftok failed"),
        plain("Warning: [qt.tlsbackend.ossl] Failed to load libssl/libcrypto."),
        regex(R"(Window position.* outside any known screen.*)"),
        regex(R"(Populating font family aliases took .* ms. Replace uses of missing font family "Font Awesome.*" with one that exists to avoid this cost.)"),

        // New in Qt 5.15.0
        regex(R"(Populating font family aliases took .* ms. Replace uses of missing font family "Monospace" with one that exists to avoid this cost.)"),

        // New in Qt 6.5
        regex("Error in contacting registry"),

        // KNotification bug
        plain(R"(QLayout: Attempting to add QLayout "" to QWidget "", which already has a layout)"),

        // Warnings from itemsync plugin, not sure what it causes
        regex(R"(Could not remove our own lock file .* maybe permissions changed meanwhile)"),
    };
    static QHash<QString, bool> ignoreLog;

    const QString output = QString::fromUtf8(stderrData);
    QRegularExpressionMatchIterator it = reFailure.globalMatch(output);
    while ( it.hasNext() ) {
        const auto match = it.next();

        const QString log = match.captured();

        if ( flag == TestInterface::ReadErrorsWithoutScriptException
             && log.contains(scriptError) )
        {
            return false;
        }

        if ( ignoreLog.contains(log) )
            return ignoreLog[log];

        const bool ignore = std::any_of(
            std::begin(ignoreList), std::end(ignoreList),
                [&output](const QRegularExpression &reIgnore){
                    return output.contains(reIgnore);
                });

        ignoreLog[log] = ignore;
        if (!ignore)
            return false;
    }

    return true;
}

int count(const QStringList &items, const QString &pattern)
{
    int from = -1;
    int count = 0;
    const QRegularExpression re(pattern);
    while ( (from = items.indexOf(re, from + 1)) != -1 )
        ++count;
    return count;
}

QStringList splitLines(const QByteArray &nativeText)
{
    return QString::fromUtf8(nativeText).split(QRegularExpression("\r\n|\n|\r"));
}

QByteArray generateData()
{
    static int i = 0;
    const QByteArray id = "tests_"
            + QByteArray::number(QDateTime::currentMSecsSinceEpoch() % 1000);
    return id + '_' + QByteArray::number(++i);
}

QString appWindowTitle(const QString &text)
{
#ifdef Q_OS_MAC
    return QStringLiteral("CopyQ - %1\n").arg(text);
#elif defined(Q_OS_WIN)
    return QStringLiteral("%1 - CopyQ-%2\n").arg(text, sessionName);
#else
    return QStringLiteral("%1 — CopyQ-%2\n").arg(text, sessionName);
#endif
}
