// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#include <QtCore/qregularexpression.h>
#include <QtGui/qbrush.h>
#if QT_CONFIG(opengl)
#include <QtGui/qoffscreensurface.h>
#endif

#include "private/qquickrectangle_p.h"
#include "utils_p.h"

#include <rhi/qrhi.h>

QT_BEGIN_NAMESPACE

static qreal s_maxTextureSize = 0.;

Utils::ParamType Utils::preParseFormat(
    const QString &format, QString &preStr, QString &postStr, int &precision, char &formatSpec)
{
    static QRegularExpression formatMatcher(
        QStringLiteral("^([^%]*)%([\\-\\+#\\s\\d\\.lhjztL]*)([dicuoxfegXFEG])(.*)$"));
    static QRegularExpression precisionMatcher(QStringLiteral("\\.(\\d+)"));

    Utils::ParamType retVal;

    QRegularExpressionMatch formatMatch = formatMatcher.match(format, 0);

    if (formatMatch.hasMatch()) {
        preStr = formatMatch.captured(1);
        // Six and 'g' are defaults in Qt API
        precision = 6;
        if (!formatMatch.captured(2).isEmpty()) {
            QRegularExpressionMatch precisionMatch = precisionMatcher.match(formatMatch.captured(2),
                                                                            0);
            if (precisionMatch.hasMatch())
                precision = precisionMatch.captured(1).toInt();
        }
        if (formatMatch.captured(3).isEmpty())
            formatSpec = 'g';
        else
            formatSpec = formatMatch.captured(3).at(0).toLatin1();
        postStr = formatMatch.captured(4);
        retVal = mapFormatCharToParamType(formatSpec);
    } else {
        retVal = ParamType::Unknown;
        // The out parameters are irrelevant in unknown case
    }

    return retVal;
}

Utils::ParamType Utils::mapFormatCharToParamType(char formatSpec)
{
    ParamType retVal = ParamType::Unknown;
    if (formatSpec == 'd' || formatSpec == 'i' || formatSpec == 'c') {
        retVal = ParamType::Int;
    } else if (formatSpec == 'u' || formatSpec == 'o' || formatSpec == 'x' || formatSpec == 'X') {
        retVal = ParamType::UInt;
    } else if (formatSpec == 'f' || formatSpec == 'F' || formatSpec == 'e' || formatSpec == 'E'
               || formatSpec == 'g' || formatSpec == 'G') {
        retVal = ParamType::Real;
    }

    return retVal;
}

QString Utils::formatLabelSprintf(const QByteArray &format, Utils::ParamType paramType, qreal value)
{
    switch (paramType) {
    case ParamType::Int:
        return QString::asprintf(format.constData(), qint64(value));
    case ParamType::UInt:
        return QString::asprintf(format.constData(), quint64(value));
    case ParamType::Real:
        return QString::asprintf(format.constData(), value);
    default:
        // Return format string to detect errors. Bars selection label logic also
        // depends on this.
        return QString::fromUtf8(format);
    }
}

QString Utils::formatLabelLocalized(Utils::ParamType paramType,
                                    qreal value,
                                    const QLocale &locale,
                                    const QString &preStr,
                                    const QString &postStr,
                                    int precision,
                                    char formatSpec,
                                    const QByteArray &format)
{
    switch (paramType) {
    case ParamType::Int:
    case ParamType::UInt:
        return preStr + locale.toString(qint64(value)) + postStr;
    case ParamType::Real:
        return preStr + locale.toString(value, formatSpec, precision) + postStr;
    default:
        // Return format string to detect errors. Bars selection label logic also
        // depends on this.
        return QString::fromUtf8(format);
    }
}

QString Utils::defaultLabelFormat()
{
    static const QString defaultFormat(QStringLiteral("%.2f"));
    return defaultFormat;
}

float Utils::wrapValue(float value, float min, float max)
{
    if (value > max) {
        value = min + (value - max);

        // In case single wrap fails, jump to opposite end.
        if (value > max)
            value = min;
    }

    if (value < min) {
        value = max + (value - min);

        // In case single wrap fails, jump to opposite end.
        if (value < min)
            value = max;
    }

    return value;
}

QQuaternion Utils::calculateRotation(QVector3D xyzRotations)
{
    QQuaternion rotQuatX = QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, xyzRotations.x());
    QQuaternion rotQuatY = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, xyzRotations.y());
    QQuaternion rotQuatZ = QQuaternion::fromAxisAndAngle(0.0f, 0.0f, 1.0f, xyzRotations.z());
    QQuaternion totalRotation = rotQuatY * rotQuatZ * rotQuatX;
    return totalRotation;
}

void Utils::verifyGradientCompleteness(QLinearGradient &gradient)
{
    // Fix the start and end stops of the gradient, to make sure it's complete
    // (0...1)
    auto stops = gradient.stops();
    if (stops.first().first != 0.) {
        const QColor firstColor = stops.first().second;
        gradient.setColorAt(0., firstColor);
    }
    if (stops.last().first != 1.) {
        const QColor lastColor = stops.last().second;
        gradient.setColorAt(1., lastColor);
    }
}

void Utils::setSeriesGradient(QAbstract3DSeries *series, QJSValue gradient, GradientType type)
{
    auto newGradient = qobject_cast<QQuickGradient *>(gradient.toQObject());
    QLinearGradient linearGradient;
    linearGradient.setStops(newGradient->gradientStops());

    switch (type) {
    case GradientType::Base:
        series->setBaseGradient(linearGradient);
        break;
    case GradientType::Single:
        series->setSingleHighlightGradient(linearGradient);
        break;
    case GradientType::Multi:
        series->setMultiHighlightGradient(linearGradient);
        break;
    default: // Never goes here
        break;
    }
}

void Utils::setSeriesGradient(QAbstract3DSeries *series, QQuickGradient *gradient, GradientType type)
{
    QLinearGradient lg;
    lg.setStops(gradient->gradientStops());
    switch (type) {
    case GradientType::Base:
        series->setBaseGradient(lg);
        break;
    case GradientType::Single:
        series->setSingleHighlightGradient(lg);
        break;
    case GradientType::Multi:
        series->setMultiHighlightGradient(lg);
        break;
    default: // Never goes here
        break;
    }
}

void Utils::connectSeriesGradient(QAbstract3DSeries *series,
                                  QJSValue newGradient,
                                  GradientType type,
                                  QJSValue &memberGradient)
{
    // connect new / disconnect old
    if (newGradient.isQObject() && !newGradient.equals(memberGradient)) {
        auto quickGradient = qobject_cast<QQuickGradient *>(memberGradient.toQObject());
        if (quickGradient)
            QObject::disconnect(quickGradient, 0, series, 0);

        memberGradient = newGradient;
        quickGradient = qobject_cast<QQuickGradient *>(memberGradient.toQObject());

        const int updatedIndex = QMetaMethod::fromSignal(&QQuickGradient::updated).methodIndex();

        int handleIndex = -1;
        switch (type) {
        case GradientType::Base:
            handleIndex = series->metaObject()->indexOfSlot("handleBaseGradientUpdate()");
            break;
        case GradientType::Single:
            handleIndex = series->metaObject()->indexOfSlot(
                "handleSingleHighlightGradientUpdate()");
            break;
        case GradientType::Multi:
            handleIndex = series->metaObject()->indexOfSlot("handleMultiHighlightGradientUpdate()");
            break;
        default: // Never goes here
            break;
        }

        if (quickGradient)
            QMetaObject::connect(quickGradient, updatedIndex, series, handleIndex);
    }

    if (!memberGradient.isNull())
        setSeriesGradient(series, memberGradient, type);
}

qreal Utils::maxTextureSize()
{
    // Query maximum texture size only once
    if (!s_maxTextureSize) {
        std::unique_ptr<QRhi> rhi;
#if defined(Q_OS_WIN)
        QRhiD3D12InitParams params;
        rhi.reset(QRhi::create(QRhi::D3D12, &params));
#elif defined(Q_OS_MACOS) || defined(Q_OS_IOS)
        QRhiMetalInitParams params;
        rhi.reset(QRhi::create(QRhi::Metal, &params));
#elif QT_CONFIG(opengl)
        QRhiGles2InitParams params;
        params.fallbackSurface = QRhiGles2InitParams::newFallbackSurface();
        rhi.reset(QRhi::create(QRhi::OpenGLES2, &params));
#elif QT_CONFIG(vulkan)
        if (!qEnvironmentVariable("QSG_RHI_BACKEND").compare("vulkan")) {
            QVulkanInstance inst;
            inst.setExtensions(QRhiVulkanInitParams::preferredInstanceExtensions());
            if (inst.create()) {
                QRhiVulkanInitParams params;
                params.inst = &inst;
                rhi.reset(QRhi::create(QRhi::Vulkan, &params));
            } else {
                qWarning("Failed to create Vulkan instance");
            }
        }
#endif
        if (rhi)
            s_maxTextureSize = qreal(rhi->resourceLimit(QRhi::TextureSizeMax));
        else
            s_maxTextureSize = gradientTextureWidth;
    }

    return s_maxTextureSize;
}

QT_END_NAMESPACE
