// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default

#include "qsgrhiinternaltextnode_p.h"

#include <private/qquadpath_p.h>
#include <private/qsgcurvefillnode_p.h>
#include <private/qsgcurvestrokenode_p.h>
#include <private/qsgcurveprocessor_p.h>

#include <QtCore/qmath.h>

QT_BEGIN_NAMESPACE

QSGRhiInternalTextNode::QSGRhiInternalTextNode(QSGRenderContext *renderContext)
    : QSGInternalTextNode(renderContext)
{
}

// Dash pattern arrays {dash, gap, ...} in units of pen width.
static constexpr qreal dashPattern[] = { 4, 2 };
static constexpr qreal dotPattern[] = { 1, 2 };
static constexpr qreal dashDotPattern[] = { 4, 2, 1, 2 };
static constexpr qreal dashDotDotPattern[] = { 4, 2, 1, 2, 1, 2 };

// Returns the dash pattern for a given underline style.
static QSpan<const qreal> dashPatternForStyle(QTextCharFormat::UnderlineStyle style)
{
    switch (style) {
    case QTextCharFormat::DashUnderline:
        return dashPattern;
    case QTextCharFormat::DotLine:
        return dotPattern;
    case QTextCharFormat::DashDotLine:
        return dashDotPattern;
    case QTextCharFormat::DashDotDotLine:
        return dashDotDotPattern;
    default:
        return {};
    }
}

// Builds a QQuadPath of dashed line segments along y = cy, from x0 to x1.
static QQuadPath buildDashedPath(qreal x0, qreal x1, qreal cy, QSpan<const qreal> pattern,
                                 qreal penWidth)
{
    Q_ASSERT(!pattern.isEmpty());

    QQuadPath path;
    qreal x = x0;
    int idx = 0;
    bool drawing = true; // pattern starts with a dash (drawn segment)

    while (x < x1) {
        // Round segment length to nearest pixel to avoid uneven dot sizes
        const qreal rawLen = pattern[idx % pattern.size()] * penWidth;
        const qreal segLen = qMax(qreal(1), qreal(qRound(rawLen)));
        const qreal segEnd = qMin(x + segLen, x1);

        if (drawing && segEnd > x) {
            path.moveTo(QVector2D(qRound(x), cy));
            path.lineTo(QVector2D(qRound(segEnd), cy));
        }

        x += segLen;
        drawing = !drawing;
        ++idx;
    }

    return path;
}

static constexpr qreal goldenRatio = 1.6180339887498949;

// Builds a QQuadPath of a wavy line along y = cy, from x0 to x1.
// Uses quadratic Bezier curves, adapted from generateWavyPixmap() in QPainter.
static QQuadPath buildWavyPath(qreal x0, qreal x1, qreal cy, qreal penWidth)
{
    const qreal radius = qMax(qreal(1), penWidth);
    const qreal halfPeriod = qMax(qreal(2), radius * goldenRatio);

    QQuadPath path;
    path.moveTo(QVector2D(x0, cy));

    qreal x = x0;
    qreal dir = 1.0; // alternates between +1 and -1

    while (x < x1) {
        const qreal nextX = qMin(x + halfPeriod, x1);
        const qreal fraction = (nextX - x) / halfPeriod;
        const qreal peakY = cy + dir * radius * fraction;
        const qreal ctrlX = (x + nextX) / 2.0;

        path.quadTo(QVector2D(ctrlX, peakY), QVector2D(nextX, cy));

        x = nextX;
        dir = -dir;
    }

    return path;
}

// Helper: creates a QSGCurveStrokeNode from a QQuadPath and appends it.
static void strokePathToNode(QSGNode *parent, const QQuadPath &path, const QColor &color,
                             qreal penWidth)
{
    QSGCurveStrokeNode *node = new QSGCurveStrokeNode;
    node->setColor(color);
    node->setStrokeWidth(penWidth);

    QSGCurveProcessor::processStroke(
            path, 2, penWidth, false, Qt::MiterJoin, Qt::FlatCap,
            [&node](const std::array<QVector2D, 3> &s, const std::array<QVector2D, 3> &p,
                    const std::array<QVector2D, 3> &n, const std::array<float, 3> &ex,
                    QSGCurveStrokeNode::TriangleFlags /*f*/) {
                node->appendTriangle(s, std::array<QVector2D, 2>{ p.at(0), p.at(2) }, n, ex);
            });
    node->cookGeometry();
    parent->appendChildNode(node);
}

void QSGRhiInternalTextNode::addDecorationNode(const QRectF &rect, const QColor &color,
                                               QTextCharFormat::UnderlineStyle style)
{
    if (rect.width() <= 0)
        return;

    const qreal penWidth = rect.height();
    const QPointF c = rect.center();

    if (style == QTextCharFormat::WaveUnderline) {
        QQuadPath path = buildWavyPath(rect.left(), rect.right(), c.y(), penWidth);
        // Limit pen width so the wave doesn't get too fat
        const qreal radius = qMax(qreal(1), penWidth);
        const qreal maxPenWidth = 0.8 * radius;
        strokePathToNode(this, path, color, qMin(penWidth, maxPenWidth));
        return;
    }

    if (style != QTextCharFormat::SingleUnderline) {
        // Dashed styles
        QSpan<const qreal> pattern = dashPatternForStyle(style);
        if (!pattern.isEmpty()) {
            QQuadPath path = buildDashedPath(rect.left(), rect.right(), c.y(), pattern, penWidth);
            strokePathToNode(this, path, color, penWidth);
            return;
        }
    }

    // SingleUnderline: solid line
    QQuadPath path;
    path.moveTo(QVector2D(rect.left(), c.y()));
    path.lineTo(QVector2D(rect.right(), c.y()));
    strokePathToNode(this, path, color, penWidth);
}

QT_END_NAMESPACE
