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

#include <private/qgraphsglobal_p.h>
#if QT_CONFIG(graphs_2d_high_performance_backend)
#include <QtCanvasPainter/QCanvasPainter>
#include <QtGraphs/qcustomseriescanvasrenderer.h>
#endif
#include <QtGraphs/qcustomseries.h>
#include <private/qcustomseries_p.h>
#include <private/axisrenderer_p.h>
#include <private/charthelpers_p.h>
#include <private/customrenderer_p.h>
#include <private/qabstractseries_p.h>
#include <private/qcustomseriesdata_p.h>
#include <private/qgraphsview_p.h>
#include <QQmlIncubator>

#include <qtgraphs_tracepoints_p.h>

QT_BEGIN_NAMESPACE

Q_TRACE_PREFIX(qtgraphs,
               "QT_BEGIN_NAMESPACE" \
               "class CustomRenderer;" \
               "QT_END_NAMESPACE"
               )

Q_TRACE_POINT(qtgraphs, QGraphs2DCustomRendererHandlePolish_entry);
Q_TRACE_POINT(qtgraphs, QGraphs2DCustomRendererHandlePolish_exit);

Q_TRACE_POINT(qtgraphs, QGraphs2DCustomRendererAfterPolish_entry, int cleanupSeriesCount);
Q_TRACE_POINT(qtgraphs, QGraphs2DCustomRendererAfterPolish_exit);

class DataItemIncubator : public QQmlIncubator
{
public:
    DataItemIncubator(CustomRenderer *renderer) : m_renderer(renderer) {};

private:
    void setInitialState(QObject *object) override
    {
        auto item = qobject_cast<QQuickItem *>(object);
        item->setParent(m_renderer);
        item->setParentItem(m_renderer);
        recursivelySetParents(item);
    }

    void recursivelySetParents(QQuickItem *item)
    {
        for (const auto &child : item->children()) {
            auto childItem = qobject_cast<QQuickItem *>(child);
            if (childItem) {
                childItem->setParentItem(item);
                recursivelySetParents(childItem);
            }
        }
    }

    CustomRenderer *m_renderer = nullptr;
};

CustomRenderer::CustomRenderer(QGraphsView *graph, bool clipPlotArea)
    : QQuickItem(graph)
    , m_graph(graph)
{
    setFlag(QQuickItem::ItemHasContents);
    setClip(clipPlotArea);
}

CustomRenderer::~CustomRenderer()
{
    qDeleteAll(m_groups);
}

#if QT_CONFIG(graphs_2d_high_performance_backend)
CustomRenderer::PaintSnapshot CustomRenderer::paintSnapshot() const
{
    return m_paintSnapshot;
}
void CustomRenderer::paintSnapshot(const PaintSnapshot &snapshot, QCanvasPainter *p)
{
    for (auto csp : snapshot) {
        csp->canvasPrePaint(p);
        csp->canvasPaint(p);
    }
}

void CustomRenderer::synchronizeData(QCanvasPainterItem *item, QHash<QCustomSeries *, QCustomSeriesCanvasRenderer *> &painters)
{
    for (auto series : std::as_const(m_removedCustomSeries))
        delete painters.take(series);
    m_removedCustomSeries.clear();

    m_paintSnapshot.clear();
    m_paintSnapshot.reserve(m_groups.size());
    for (const auto &group : std::as_const(m_groups)) {
        auto *d = QCustomSeriesPrivate::get(group->series);
        if (d->m_customSeriesPainter) {
            // Adopt: the renderer now owns this instance exclusively; the
            // series has already relinquished it (see setCustomSeriesPainter()).
            // Delete any previously-adopted instance being replaced.
            delete painters.take(group->series);
            painters.insert(group->series, d->m_customSeriesPainter);
            d->m_customSeriesPainter = nullptr;
        }
        auto it = painters.find(group->series);
        if (it == painters.end())
            continue;

        (*it)->synchronizeData(group->series, item);
        m_paintSnapshot.append(*it);
    }
}
#endif

qreal CustomRenderer::mapX(AxisRenderer *axisRenderer, QCustomSeries *series, qreal x)
{
    auto &axisX = axisRenderer->getAxisX(series);

    if (axisX.isLogarithmic) {
        float logBase = log(axisX.logBase);
        x = log(x) / logBase;
    }

    auto flipX = axisX.maxValue < axisX.minValue ? -1 : 1;
    return m_areaWidth * flipX * x * m_maxHorizontal - m_horizontalOffset;
}

qreal CustomRenderer::mapY(AxisRenderer *axisRenderer, QCustomSeries *series, qreal y)
{
    auto &axisY = axisRenderer->getAxisY(series);

    if (axisY.isLogarithmic) {
        float logBase = log(axisY.logBase);
        y = log(y) / logBase;
    }

    if (m_graph->orientation() != Qt::Vertical)
        y = axisY.maxValue - y;

    auto flipY = axisY.maxValue < axisY.minValue ? -1 : 1;
    return m_areaHeight - m_areaHeight * flipY * y * m_maxVertical + m_verticalOffset;
}

void CustomRenderer::hideDelegates(QCustomSeries *series)
{
    auto group = m_groups.value(series);
    if (group && group->currentDataItem) {
        for (int i = 0; i < group->dataItems.size(); ++i) {
            auto *dataItem = group->dataItems[i];
            dataItem->setVisible(false);
        }
    }
}

void CustomRenderer::handlePolish(QCustomSeries *series)
{
    Q_TRACE(QGraphs2DCustomRendererHandlePolish_entry);

    auto theme = m_graph->theme();
    if (!theme) {
        qCCritical(lcCritical2D, "Theme not found");
        return;
    }

    if (!m_graph->m_axisRenderer) {
        qCCritical(lcCritical2D, "Axis renderer not found.");
        return;
    }

    if (series->dataItems().isEmpty()) {
        auto group = m_groups.value(series);

        if (group) {
            for (auto m : std::as_const(group->dataItems))
                m->deleteLater();

            group->dataItems.clear();
        }

        return;
    }

    if (width() <= 0 || height() <= 0)
        return;

    m_areaWidth = width();
    m_areaHeight = height();

    auto &axisX = m_graph->m_axisRenderer->getAxisX(series);
    auto &axisY = m_graph->m_axisRenderer->getAxisY(series);

    m_maxVertical = axisY.valueRange > 0 ? 1.0 / axisY.valueRange : 100.0;
    m_maxHorizontal = axisX.valueRange > 0 ? 1.0 / axisX.valueRange : 100.0;

    auto vmin = axisY.minValue > axisY.maxValue ? std::abs(axisY.minValue) : axisY.minValue;
    m_verticalOffset = (vmin / axisY.valueRange) * m_areaHeight;

    auto hmin = axisX.minValue > axisX.maxValue ? std::abs(axisX.minValue) : axisX.minValue;
    m_horizontalOffset = (hmin / axisX.valueRange) * m_areaWidth;

    if (!m_groups.contains(series)) {
        DataGroup *group = new DataGroup();
        group->series = series;
        m_groups.insert(series, group);
    }

    auto group = m_groups.value(series);

    qsizetype dataItemCount = series->dataItems().size();

    if (series->delegate())
        group->currentDataItem = series->delegate();

    if (group->currentDataItem != group->previousDataItem) {
        for (auto &&dataItem : group->dataItems)
            dataItem->deleteLater();
        group->dataItems.clear();
    }
    group->previousDataItem = group->currentDataItem;

    if (group->currentDataItem) {
        qsizetype dataItemVisualCount = group->dataItems.size();
        if (dataItemVisualCount < dataItemCount) {
            for (qsizetype i = dataItemVisualCount; i < dataItemCount; ++i) {
                DataItemIncubator incubator(this);
                group->currentDataItem->create(
                    incubator, group->currentDataItem->creationContext());
                incubator.forceCompletion();
                group->dataItems << qobject_cast<QQuickItem *>(incubator.object());
            }
        } else if (dataItemVisualCount > dataItemCount) {
            for (qsizetype i = dataItemCount; i < dataItemVisualCount; ++i)
                group->dataItems[i]->deleteLater();
            group->dataItems.resize(dataItemCount);
        }
    } else if (group->dataItems.size() > 0) {
        for (auto &&dataItem : group->dataItems)
            dataItem->deleteLater();
        group->dataItems.clear();
    }

    if (series->isVisible()) {
        for (int i = 0; i < group->dataItems.size(); ++i) {
            auto item = group->dataItems.at(i);
            series->updateDelegate(item, i);
            item->setZ(group->series->zValue());
        }
    } else {
        hideDelegates(series);
    }

    if (m_plotArea.width() != m_areaWidth || m_plotArea.height() != m_areaHeight) {
        m_plotArea.setWidth(m_areaWidth);
        m_plotArea.setHeight(m_areaHeight);
        emit plotAreaChanged();
    }

    Q_TRACE(QGraphs2DCustomRendererHandlePolish_exit);
}

void CustomRenderer::afterPolish(QList<QAbstractSeries *> &cleanupSeries)
{
    Q_TRACE(QGraphs2DCustomRendererAfterPolish_entry, static_cast<int>(cleanupSeries.count()));
    Q_UNUSED(cleanupSeries);
    Q_TRACE(QGraphs2DCustomRendererAfterPolish_exit);
}

void CustomRenderer::updateSeries(QCustomSeries *series)
{
    Q_UNUSED(series);
}

void CustomRenderer::seriesAboutToBeRemoved(QAbstractSeries *series)
{
    if (auto *customSeries = qobject_cast<QCustomSeries *>(series)) {
        auto iter = m_groups.find(customSeries);
        if (iter != m_groups.end()) {
            auto group = *iter;

            for (const auto &dataItem : std::as_const(group->dataItems))
                dataItem->deleteLater();

#if QT_CONFIG(graphs_2d_high_performance_backend)
            m_removedCustomSeries.insert(customSeries);
#endif
            delete group;
            m_groups.erase(iter);
        }
    }
}

void CustomRenderer::afterUpdate(QList<QAbstractSeries *> &cleanupSeries)
{
    Q_UNUSED(cleanupSeries);
}

QRectF CustomRenderer::plotArea() const
{
    return m_plotArea;
}

QT_END_NAMESPACE

#include "moc_customrenderer_p.cpp"
