// Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB).
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include "qframegraphnode.h"
#include "qframegraphnode_p.h"
#include <Qt3DRender/qfilterkey.h>
#include <Qt3DRender/qtechniquefilter.h>
#include <Qt3DRender/qrenderpassfilter.h>

#include <Qt3DCore/QNode>
#include <QList>
#include <QQueue>

using namespace Qt3DCore;

QT_BEGIN_NAMESPACE

namespace {

QString dumpNode(const Qt3DRender::QFrameGraphNode *n) {
    QString res = QLatin1String(n->metaObject()->className());
    if (!n->objectName().isEmpty())
        res += QString(QLatin1String(" (%1)")).arg(n->objectName());
    if (!n->isEnabled())
        res += QLatin1String(" [D]");
    return res;
}

QString dumpNodeFilters(const Qt3DRender::QFrameGraphNode *n, const QList<Qt3DRender::QFilterKey *> &filters) {
    QString res = QLatin1String(n->metaObject()->className());
    if (!n->objectName().isEmpty())
        res += QString(QLatin1String(" (%1)")).arg(n->objectName());

    QStringList kv;
    for (auto filter: filters)
        kv.push_back(QString(QLatin1String("%1: %2")).arg(filter->name(), filter->value().toString()));
    if (kv.size())
        res += QString(QLatin1String(" <%1>")).arg(kv.join(QLatin1String(", ")));

    return res;
}

QStringList dumpFG(const Qt3DCore::QNode *n, int level = 0)
{
    QStringList reply;

    const Qt3DRender::QFrameGraphNode *fgNode = qobject_cast<const Qt3DRender::QFrameGraphNode *>(n);
    if (fgNode) {
        QString res = dumpNode(fgNode);
        reply += res.rightJustified(res.size() + level * 2, QLatin1Char(' '));
    }

    const auto children = n->childNodes();
    const int inc = fgNode ? 1 : 0;
    for (auto *child: children) {
        auto *childFGNode = qobject_cast<Qt3DCore::QNode *>(child);
        if (childFGNode != nullptr)
            reply += dumpFG(childFGNode, level + inc);
    }

    return reply;
}

struct HierarchyFGNode
{
    const Qt3DRender::QFrameGraphNode *root;
    QList<QSharedPointer<HierarchyFGNode>> children;
};
using HierarchyFGNodePtr = QSharedPointer<HierarchyFGNode>;

HierarchyFGNodePtr buildFGHierarchy(const Qt3DCore::QNode *n, HierarchyFGNodePtr lastFGParent = HierarchyFGNodePtr())
{
    const Qt3DRender::QFrameGraphNode *fgNode = qobject_cast<const Qt3DRender::QFrameGraphNode *>(n);

    // Only happens for the root case
    if (!lastFGParent) {
        lastFGParent = HierarchyFGNodePtr::create();
        lastFGParent->root = fgNode;
    } else {
        if (fgNode != nullptr) {
            HierarchyFGNodePtr hN = HierarchyFGNodePtr::create();
            hN->root = fgNode;
            if (lastFGParent)
                lastFGParent->children.push_back(hN);
            lastFGParent = hN;
        }
    }

    const auto children = n->childNodes();
    for (auto *child: children)
        buildFGHierarchy(child, lastFGParent);

    return lastFGParent;
}

void findFGLeaves(const HierarchyFGNodePtr root, QList<const Qt3DRender::QFrameGraphNode *> &fgLeaves)
{
    const auto children = root->children;
    for (const auto &child : children)
        findFGLeaves(child, fgLeaves);

    if (children.empty())
        fgLeaves.push_back(root->root);
}

void dumpFGPaths(const Qt3DRender::QFrameGraphNode *n, QStringList &result)
{
    // Build FG node hierarchy
    const HierarchyFGNodePtr rootHFg = buildFGHierarchy(n);

    // Gather FG leaves
    QList<const Qt3DRender::QFrameGraphNode *> fgLeaves;
    findFGLeaves(rootHFg, fgLeaves);

    // Traverse back to root
    int rv = 1;
    for (const Qt3DRender::QFrameGraphNode *fgNode : fgLeaves) {
        QStringList parents;
        while (fgNode != nullptr) {
            parents.prepend(dumpNode(fgNode));
            fgNode = fgNode->parentFrameGraphNode();
        }
        if (parents.size()) {
            result << QString(QLatin1String("%1 [ %2 ]")).arg(QString::number(rv), parents.join(QLatin1String(", ")));
            ++rv;
        }
    }
}

void dumpFGFilterState(const Qt3DRender::QFrameGraphNode *n, QStringList &result)
{
    // Build FG node hierarchy
    const HierarchyFGNodePtr rootHFg = buildFGHierarchy(n);

    // Gather FG leaves
    QList<const Qt3DRender::QFrameGraphNode *> fgLeaves;
    findFGLeaves(rootHFg, fgLeaves);

    // Traverse back to root
    int rv = 1;
    for (const Qt3DRender::QFrameGraphNode *fgNode : fgLeaves) {
        int parents = 0;
        QStringList filters;
        while (fgNode != nullptr) {
            ++parents;
            if (fgNode->isEnabled()) {
                auto techniqueFilter = qobject_cast<const Qt3DRender::QTechniqueFilter *>(fgNode);
                if (techniqueFilter && techniqueFilter->matchAll().size())
                    filters.prepend(dumpNodeFilters(techniqueFilter, techniqueFilter->matchAll()));
                auto renderPassFilter = qobject_cast<const Qt3DRender::QRenderPassFilter *>(fgNode);
                if (renderPassFilter)
                    filters.prepend(dumpNodeFilters(renderPassFilter, renderPassFilter->matchAny()));
            }
            fgNode = fgNode->parentFrameGraphNode();
        }
        if (parents) {
            if (filters.size())
                result << QString(QLatin1String("%1 [ %2 ]")).arg(QString::number(rv), filters.join(QLatin1String(", ")));
            else
                result << QString(QObject::tr("%1 [ No Filters ]")).arg(rv);
            ++rv;
        }
    }
}

}

namespace Qt3DRender {

QFrameGraphNodePrivate::QFrameGraphNodePrivate()
    : QNodePrivate()
{
}

/*!
    \class Qt3DRender::QFrameGraphNode
    \inmodule Qt3DRender
    \since 5.5

    \brief Base class of all FrameGraph configuration nodes.

    This class is rarely instanced directly since it doesn't provide
    any frame graph specific behavior, although it can be convenient
    to use for grouping other nodes together in dynamic frame graphs.
    The actual behavior comes from the subclasses.

    The subclasses are:
    \table
    \header
        \li class
        \li description
    \row
        \li Qt3DRender::QCameraSelector
        \li Select camera from all available cameras in the scene
    \row
        \li Qt3DRender::QClearBuffers
        \li Specify which buffers to clear and to what values
    \row
        \li Qt3DRender::QDispatchCompute
        \li Specify Compute operation kernels
    \row
        \li Qt3DRender::QFrustumCulling
        \li Enable frustum culling
    \row
        \li Qt3DRender::QLayerFilter
        \li Select which layers to draw
    \row
        \li Qt3DRender::QNoDraw
        \li Disable drawing
    \row
        \li Qt3DRender::QRenderPassFilter
        \li Select which render passes to draw
    \row
        \li Qt3DRender::QRenderStateSet
        \li Set render states
    \row
        \li Qt3DRender::QRenderSurfaceSelector
        \li Select which surface to draw to
    \row
        \li Qt3DRender::QRenderTargetSelector
        \li Select which QRenderTarget to draw to
    \row
        \li Qt3DRender::QSortPolicy
        \li Specify how entities are sorted to determine draw order
    \row
        \li Qt3DRender::QTechniqueFilter
        \li Select which techniques to draw
    \row
        \li Qt3DRender::QViewport
        \li Specify viewport
    \row
        \li Qt3DRender::QMemoryBarrier
        \li Places a memory barrier
    \endtable

 */

/*!
    \qmltype FrameGraphNode
    \inqmlmodule Qt3D.Render
    \instantiates Qt3DRender::QFrameGraphNode
    \inherits Node
    \since 5.5
    \brief Base class of all FrameGraph configuration nodes.

    This class is rarely instanced directly since it doesn't provide
    any frame graph specific behavior, although it can be convenient
    to use for grouping other nodes together in dynamic frame graphs.
    The actual behavior comes from the subclasses.

    The subclasses are:
    \table
    \header
        \li class
        \li description
    \row
        \li CameraSelector
        \li Select camera from all available cameras in the scene
    \row
        \li ClearBuffers
        \li Specify which buffers to clear and to what values
    \row
        \li DispatchCompute
        \li Specify compute operation kernels
    \row
        \li FrustumCulling
        \li Enable frustum culling
    \row
        \li LayerFilter
        \li Select which layers to draw
    \row
        \li NoDraw
        \li Disable drawing
    \row
        \li RenderPassFilter
        \li Select which render passes to draw
    \row
        \li RenderStateSet
        \li Set render states
    \row
        \li RenderSurfaceSelector
        \li Select which surface to draw to
    \row
        \li RenderTargetSelector
        \li Select which RenderTarget to draw to
    \row
        \li SortPolicy
        \li Specify how entities are sorted to determine draw order
    \row
        \li TechniqueFilter
        \li Select which techniques to draw
    \row
        \li Viewport
        \li Specify viewport
    \row
        \li MemoryBarrier
        \li Places a memory barrier
    \endtable
*/

/*!
    The constructor creates an instance with the specified \a parent.
 */
QFrameGraphNode::QFrameGraphNode(QNode *parent)
    : QNode(*new QFrameGraphNodePrivate, parent)
{
}

/*! \internal */
QFrameGraphNode::~QFrameGraphNode()
{
}

/*!
    Returns a pointer to the parent frame graph node.

    If the parent of this node is not a frame graph node,
    this method will recursively look for a parent node that is a frame graph node.
 */
QFrameGraphNode *QFrameGraphNode::parentFrameGraphNode() const
{
    QFrameGraphNode *parentFGNode = nullptr;
    QNode *parentN = parentNode();

    while (parentN) {
        if ((parentFGNode = qobject_cast<QFrameGraphNode *>(parentN)) != nullptr)
            break;
        parentN = parentN->parentNode();
    }
    return parentFGNode;
}

/*!
    \internal
 * Returns a list of the children that are frame graph nodes.
 * If this function encounters a child node that is not a frame graph node,
 * it will go through the children of the child node and look for frame graph nodes.
 * If any of these are not frame graph nodes, they will be further searched as
 * if they were direct children of this node.
 */
QList<QFrameGraphNode *> QFrameGraphNodePrivate::childFrameGraphNodes() const
{
    Q_Q(const QFrameGraphNode);
    QList<QFrameGraphNode *> result;
    QQueue<QNode *> queue;
    const auto childNodes = q->childNodes();
    for (auto c: childNodes)
        queue.append(c);
    result.reserve(queue.size());
    while (!queue.isEmpty()) {
        auto *child = queue.dequeue();
        auto *childFGNode = qobject_cast<QFrameGraphNode *>(child);
        if (childFGNode != nullptr)
            result.push_back(childFGNode);
        else {
            const auto childNodes = child->childNodes();
            for (auto c: childNodes)
                queue.append(c);
        }
    }
    return result;
}

QString QFrameGraphNodePrivate::dumpFrameGraph() const
{
    Q_Q(const QFrameGraphNode);
    return dumpFG(q).join(QLatin1Char('\n'));
}

QStringList QFrameGraphNodePrivate::dumpFrameGraphPaths() const
{
    Q_Q(const QFrameGraphNode);
    QStringList result;
    dumpFGPaths(q, result);
    return result;
}

QStringList QFrameGraphNodePrivate::dumpFrameGraphFilterState() const
{
    Q_Q(const QFrameGraphNode);
    QStringList result;
    dumpFGFilterState(q, result);
    return result;
}

/*! \internal */
QFrameGraphNode::QFrameGraphNode(QFrameGraphNodePrivate &dd, QNode *parent)
    : QNode(dd, parent)
{
}

void QFrameGraphNode::onParentChanged(QObject *)
{
    // Direct sync update request
    Q_D(QFrameGraphNode);
    d->update();
}

} // namespace Qt3DRender

QT_END_NAMESPACE

#include "moc_qframegraphnode.cpp"
