
#pragma once

#include <QtGlobal>
#include <QLocale>
#include <QRegularExpression>
#include <QTimeZone>
#include <QString>

#include <type_traits>

//-----------------------------------------------------------------------------

template<unsigned major, unsigned minor, class T = void>
struct qt_atleast : public std::enable_if<QT_VERSION>=QT_VERSION_CHECK(major, minor, 0), T>
{
};

template<unsigned major, unsigned minor, class T = void>
struct qt_before : public std::enable_if<QT_VERSION<QT_VERSION_CHECK(major, minor, 0), T>
{
};

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

inline QStringList QLocale_uiLanguages()
{
#if QT_VERSION>=QT_VERSION_CHECK(6, 8, 0)
    return QLocale::system().uiLanguages(QLocale::TagSeparator::Underscore);
#else
    auto languages = QLocale::system().uiLanguages();
    for (auto& language: languages) {
        language.replace(QLatin1Char('-'), QLatin1Char('_'));
    }
    return languages;
#endif
}

//-----------------------------------------------------------------------------

template <typename T>
inline QRegularExpressionMatch
matchRegularExpression(const QRegularExpression& re, const T& s)
{
#if QT_VERSION>=QT_VERSION_CHECK(6, 5, 0)
    return re.matchView(s);
#else
    return re.match(s);
#endif
}

//-----------------------------------------------------------------------------

#if QT_VERSION<QT_VERSION_CHECK(6, 5, 0)
template <typename Char, typename... Args>
inline QDebug& operator<<(QDebug& d, const std::basic_string<Char, Args...>& s)
{
    return d << s.c_str();
}
#endif

//-----------------------------------------------------------------------------

#if QT_VERSION<QT_VERSION_CHECK(6, 6, 0)

#include <chrono>

template <typename Rep, typename Period>
inline QDebug& operator<<(QDebug& d, const std::chrono::duration<Rep, Period> dur)
{
    return d << dur.count() << " " << Period::num << "/" << Period::den;
}
#endif

//-----------------------------------------------------------------------------

#if QT_VERSION>=QT_VERSION_CHECK(6, 5, 0)
const auto QTimeZoneUTC = QTimeZone::UTC;
#else
const auto QTimeZoneUTC = Qt::UTC;
#endif

//-----------------------------------------------------------------------------

#if QT_VERSION<QT_VERSION_CHECK(6, 4, 0)
#define QCOMPARE_EQ(computed, baseline) QCOMPARE(computed==baseline, true)
#define QCOMPARE_GT(computed, baseline) QCOMPARE(computed>baseline, true)
#endif

//-----------------------------------------------------------------------------

#if QT_VERSION<QT_VERSION_CHECK(6, 3, 0)
#define QLatin1StringOrView QLatin1String
#else
#define QLatin1StringOrView QLatin1StringView
#endif


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

#if QT_VERSION<QT_VERSION_CHECK(6, 4, 0)
namespace Qt { namespace StringLiterals {

inline QLatin1StringOrView operator""_L1(const char *str, size_t size)
{
    return QLatin1StringOrView(str, size);
}

inline QLatin1Char operator""_L1(char c)
{
    return QLatin1Char(c);
}

inline QString operator""_s(const char16_t *str, size_t size)
{
    return QString::fromUtf16(str, static_cast<qsizetype>(size));
}

inline QByteArray operator""_ba(const char *str, size_t size)
{
    return QByteArray(str, size);
}

} }
#endif

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

#if QT_VERSION<QT_VERSION_CHECK(6, 5, 0)
#include <QDebug>
#define qCFatal(category) QDebug(QtFatalMsg)
#endif

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

#if QT_VERSION<QT_VERSION_CHECK(6, 6, 0)
#define QPromiseEmplaceResult(promise, type, ...) promise->addResult(type(__VA_ARGS__))
#else
#define QPromiseEmplaceResult(promise, type, ...) promise->emplaceResult(__VA_ARGS__)
#endif

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

#ifdef QT_WIDGETS_LIB

//-----------------------------------------------------------------------------

#include <QCheckBox>

//-----------------------------------------------------------------------------

template <typename Functor>
inline QMetaObject::Connection connectCheckBoxStateChanged(
    const QCheckBox* sender, const QObject* context, Functor functor)
{
    return QObject::connect(sender,
#if QT_VERSION>=QT_VERSION_CHECK(6, 8, 0)
                            &QCheckBox::checkStateChanged,
#else
                            &QCheckBox::stateChanged,
#endif
                            context, functor);
}

//-----------------------------------------------------------------------------

#endif // QT_WIDGETS_LIB

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

#ifdef QT_WEBSOCKETS_LIB

//-----------------------------------------------------------------------------

#include <QWebSocket>

//-----------------------------------------------------------------------------

#if QT_VERSION>=QT_VERSION_CHECK(6, 5, 0)
#define QWebSocketErrorOccurred &QWebSocket::errorOccurred
#else
#define QWebSocketErrorOccurred &QWebSocket::error
#endif

//-----------------------------------------------------------------------------

#endif // QT_WEBSOCKETS_LIB

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

#ifdef QT_XML_LIB

//-----------------------------------------------------------------------------

#include <QDomDocument>

//-----------------------------------------------------------------------------

#if QT_VERSION>=QT_VERSION_CHECK(6, 5, 0)

inline QDomDocument::ParseResult
QDomDocumentSetContentsUseNamespaceProcessing(QDomDocument& doc, QIODevice* device)
{
    return doc.setContent(device, QDomDocument::ParseOption::UseNamespaceProcessing);
}

#else // QT_VERSION>=QT_VERSION_CHECK(6, 5, 0)

struct QDomDocumentParseResult {
    bool retval = false;

    QString errorMessage;

    int errorLine = -1;

    int errorColumn = -1;

    inline operator bool() const {
        return retval;
    }
};

inline QDomDocumentParseResult
QDomDocumentSetContentsUseNamespaceProcessing(QDomDocument& doc, QIODevice* device)
{
    QDomDocumentParseResult result;
    result.retval = doc.setContent(device, true, &result.errorMessage,
                                   &result.errorLine, &result.errorColumn);
    return result;
}

#endif // QT_VERSION>=QT_VERSION_CHECK(6, 5, 0)

//-----------------------------------------------------------------------------

#endif // QT_XML_LIB

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

#ifdef QT_GUI_LIB

//-----------------------------------------------------------------------------

#include <QGuiApplication>
#include <QStyleHints>

//-----------------------------------------------------------------------------

inline double getColorDarkness(const QColor &color)
{
    // account for different sensitivity of the human eye to certain colors
    const double threshold = 1.0 - (0.299 * color.red() + 0.587 * color.green() + 0.114 * color.blue()) / 255.0;
    return threshold;
}

//-----------------------------------------------------------------------------

inline bool isDarkColor(const QColor& color)
{
    return getColorDarkness(color) > 0.5;
}

//-----------------------------------------------------------------------------

template <class Target, typename Functor>
inline void connectQStyleHintsColorSchemeChanged(QGuiApplication* app,
                                                 const Target* context,
                                                 Functor functor)
{
#if QT_VERSION>=QT_VERSION_CHECK(6, 5, 0)
    QObject::connect(app->styleHints(), &QStyleHints::colorSchemeChanged,
                     context, functor, Qt::UniqueConnection);
#else
    QObject::connect(app, &QGuiApplication::paletteChanged, context, functor);
#endif
}

//-----------------------------------------------------------------------------

inline bool isDarkMode() {
#if QT_VERSION>=QT_VERSION_CHECK(6, 5, 0)
    switch (qGuiApp->styleHints()->colorScheme())
    {
      case Qt::ColorScheme::Dark:
        return true;
      case Qt::ColorScheme::Light:
        return false;
      case Qt::ColorScheme::Unknown:
        return isDarkColor(QGuiApplication::palette().window().color());
    }

    return false;
#else // QT_VERSION>=QT_VERSION_CHECK(6, 5, 0)
    return isDarkColor(QGuiApplication::palette().window().color());
#endif // QT_VERSION>=QT_VERSION_CHECK(6, 5, 0)
}

//-----------------------------------------------------------------------------

#endif // QT_GUI_LIB

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

#ifdef QT_NETWORK_LIB

//-----------------------------------------------------------------------------

#include <QNetworkRequest>

//-----------------------------------------------------------------------------

#if QT_VERSION<QT_VERSION_CHECK(6, 8, 0)
inline void removeAuthorizationHeaders(QNetworkRequest& request)
{
    request.setRawHeader("Authorization", QByteArray());
}
#else
inline void removeAuthorizationHeaders(QNetworkRequest& request)
{
    auto headers = request.headers();
    headers.removeAll(QHttpHeaders::WellKnownHeader::Authorization);
    request.setHeaders(headers);
}
#endif

//-----------------------------------------------------------------------------

#if QT_VERSION<QT_VERSION_CHECK(6, 7, 0)
#include <QNetworkReply>
inline bool QNetworkReplyHasRawHeader(QNetworkReply *reply, const char *header)
{
    return reply->hasRawHeader(QByteArray(header));
}
#else
#include <QNetworkReply>
inline bool QNetworkReplyHasRawHeader(QNetworkReply *reply, const char *header)
{
    return reply->hasRawHeader(header);
}
#endif

//-----------------------------------------------------------------------------

#endif // QT_NETWORK_LIB

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

#ifdef QT_CORE_LIB

//-----------------------------------------------------------------------------

#include <QJsonValue>

//-----------------------------------------------------------------------------

#if QT_VERSION<QT_VERSION_CHECK(6, 8, 0)
inline bool operator==(const QString &lhs, const QJsonValue &rhs)
{
    return lhs == rhs.toString();
}

inline bool operator==(const QJsonValue &lhs, const QString &rhs)
{
    return lhs.toString() == rhs;
}

inline bool operator!=(const QString &lhs, const QJsonValue &rhs)
{
    return lhs != rhs.toString();
}

inline bool operator!=(const QJsonValue &lhs, const QString &rhs)
{
    return lhs.toString() != rhs;
}

// QJsonValueRef is a non-const reference to a QJsonValue inside QJsonObject/QJsonArray
// It can be implicitly converted to QJsonValue, but we need explicit operators
// to avoid ambiguity when comparing with QString
inline bool operator==(const QString &lhs, const QJsonValueRef &rhs)
{
    return lhs == rhs.toString();
}

inline bool operator==(const QJsonValueRef &lhs, const QString &rhs)
{
    return lhs.toString() == rhs;
}

inline bool operator!=(const QString &lhs, const QJsonValueRef &rhs)
{
    return lhs != rhs.toString();
}

inline bool operator!=(const QJsonValueRef &lhs, const QString &rhs)
{
    return lhs.toString() != rhs;
}
#endif

//-----------------------------------------------------------------------------

#endif // QT_CORE_LIB

//-----------------------------------------------------------------------------

#ifdef QT_QML_LIB

//-----------------------------------------------------------------------------

#include <QQmlComponent>
#include <QQmlEngine>
#include <QUrl>

//-----------------------------------------------------------------------------

#if QT_VERSION<QT_VERSION_CHECK(6, 5, 0)
inline QQmlComponent *QmlComponentFromUri(QQmlEngine *engine, const QString &uri, const QString &typeName)
{
    // Convert uri like "com.nextcloud.desktopclient.search" to qrc path
    // like "qrc:/com/nextcloud/desktopclient/search/SearchWindow.qml"
    QString qrcPath = QStringLiteral("qrc:/") + uri + u'/' + typeName + QStringLiteral(".qml");
    return new QQmlComponent(engine, QUrl::fromUserInput(qrcPath));
}
#else
inline QQmlComponent *QmlComponentFromUri(QQmlEngine *engine, const QString &uri, const QString &typeName)
{
    return new QQmlComponent(engine, uri, typeName);
}
#endif

//-----------------------------------------------------------------------------

#endif // QT_QML_LIB

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
