// Copyright (C) 2016 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

#include "qsgsoftwarerenderablenode_p.h"

#include "qsgsoftwareinternalimagenode_p.h"
#include "qsgsoftwareinternalrectanglenode_p.h"
#include "qsgsoftwareglyphnode_p.h"
#include "qsgsoftwarepublicnodes_p.h"
#include "qsgsoftwarepainternode_p.h"
#include "qsgsoftwarepixmaptexture_p.h"
#if QT_CONFIG(quick_sprite)
#include "qsgsoftwarespritenode_p.h"
#endif

#include <qsgsimplerectnode.h>
#include <qsgsimpletexturenode.h>
#include <private/qsgrendernode_p.h>
#include <private/qsgplaintexture_p.h>

#include <qmath.h>

Q_STATIC_LOGGING_CATEGORY(lcRenderable, "qt.scenegraph.softwarecontext.renderable")

QT_BEGIN_NAMESPACE

// Largest subrectangle with integer coordinates
inline QRect toRectMin(const QRectF & r)
{
    int x1 = qCeil(r.left());
    int x2 = qFloor(r.right());
    int y1 = qCeil(r.top());
    int y2 = qFloor(r.bottom());
    return QRect(x1, y1, x2 - x1, y2 - y1);
}

// Smallest superrectangle with integer coordinates
inline QRect toRectMax(const QRectF & r)
{
    return r.toAlignedRect();
}

QSGSoftwareRenderableNode::QSGSoftwareRenderableNode(NodeType type, QSGNode *node)
    : m_nodeType(type)
    , m_isOpaque(true)
    , m_isDirty(true)
    , m_hasClipRegion(false)
    , m_opacity(1.0f)
{
    switch (m_nodeType) {
    case QSGSoftwareRenderableNode::SimpleRect:
        m_handle.simpleRectNode = static_cast<QSGSimpleRectNode*>(node);
        break;
    case QSGSoftwareRenderableNode::SimpleTexture:
        m_handle.simpleTextureNode = static_cast<QSGSimpleTextureNode*>(node);
        break;
    case QSGSoftwareRenderableNode::Image:
        m_handle.imageNode = static_cast<QSGSoftwareInternalImageNode*>(node);
        break;
    case QSGSoftwareRenderableNode::Painter:
        m_handle.painterNode = static_cast<QSGSoftwarePainterNode*>(node);
        break;
    case QSGSoftwareRenderableNode::Rectangle:
        m_handle.rectangleNode = static_cast<QSGSoftwareInternalRectangleNode*>(node);
        break;
    case QSGSoftwareRenderableNode::Glyph:
        m_handle.glpyhNode = static_cast<QSGSoftwareGlyphNode*>(node);
        break;
    case QSGSoftwareRenderableNode::NinePatch:
        m_handle.ninePatchNode = static_cast<QSGSoftwareNinePatchNode*>(node);
        break;
    case QSGSoftwareRenderableNode::SimpleRectangle:
        m_handle.simpleRectangleNode = static_cast<QSGRectangleNode*>(node);
        break;
    case QSGSoftwareRenderableNode::SimpleImage:
        m_handle.simpleImageNode = static_cast<QSGImageNode*>(node);
        break;
#if QT_CONFIG(quick_sprite)
    case QSGSoftwareRenderableNode::SpriteNode:
        m_handle.spriteNode = static_cast<QSGSoftwareSpriteNode*>(node);
        break;
#endif
    case QSGSoftwareRenderableNode::RenderNode:
        m_handle.renderNode = static_cast<QSGRenderNode*>(node);
        break;
    case QSGSoftwareRenderableNode::Invalid:
        m_handle.simpleRectNode = nullptr;
        break;
    }
}

QSGSoftwareRenderableNode::~QSGSoftwareRenderableNode()
{

}

void QSGSoftwareRenderableNode::update()
{
    // Update the Node properties
    m_isDirty = true;
    m_isOpaque = false;

    QRectF boundingRect;

    switch (m_nodeType) {
    case QSGSoftwareRenderableNode::SimpleRect:
        if (m_handle.simpleRectNode->color().alpha() == 255)
            m_isOpaque = true;

        boundingRect = m_handle.simpleRectNode->rect();
        break;
    case QSGSoftwareRenderableNode::SimpleTexture:
        if (!m_handle.simpleTextureNode->texture()->hasAlphaChannel())
            m_isOpaque = true;

        boundingRect = m_handle.simpleTextureNode->rect();
        break;
    case QSGSoftwareRenderableNode::Image:
        m_isOpaque = !m_handle.imageNode->pixmap().hasAlphaChannel();

        boundingRect = m_handle.imageNode->rect().toRect();
        break;
    case QSGSoftwareRenderableNode::Painter:
        if (m_handle.painterNode->opaquePainting())
            m_isOpaque = true;

        boundingRect = QRectF(0, 0, m_handle.painterNode->size().width(), m_handle.painterNode->size().height());
        break;
    case QSGSoftwareRenderableNode::Rectangle:
        if (m_handle.rectangleNode->isOpaque())
            m_isOpaque = true;

        boundingRect = m_handle.rectangleNode->rect();
        break;
    case QSGSoftwareRenderableNode::Glyph:
        // Always has alpha
        boundingRect = m_handle.glpyhNode->boundingRect();
        break;
    case QSGSoftwareRenderableNode::NinePatch:
        m_isOpaque = m_handle.ninePatchNode->isOpaque();

        boundingRect = m_handle.ninePatchNode->bounds();
        break;
    case QSGSoftwareRenderableNode::SimpleRectangle:
        if (m_handle.simpleRectangleNode->color().alpha() == 255)
            m_isOpaque = true;

        boundingRect = m_handle.simpleRectangleNode->rect();
        break;
    case QSGSoftwareRenderableNode::SimpleImage:
        if (!m_handle.simpleImageNode->texture()->hasAlphaChannel())
            m_isOpaque = true;

        boundingRect = m_handle.simpleImageNode->rect();
        break;
#if QT_CONFIG(quick_sprite)
    case QSGSoftwareRenderableNode::SpriteNode:
        m_isOpaque = m_handle.spriteNode->isOpaque();
        boundingRect = m_handle.spriteNode->rect();
        break;
#endif
    case QSGSoftwareRenderableNode::RenderNode:
        if (m_handle.renderNode->flags().testFlag(QSGRenderNode::OpaqueRendering))
            m_isOpaque = true;

        boundingRect = m_handle.renderNode->rect();
        break;
    default:
        break;
    }

    if (m_transform.isRotating())
        m_isOpaque = false;

    const QRectF transformedRect = m_transform.mapRect(boundingRect);
    m_boundingRectMin = toRectMin(transformedRect);
    m_boundingRectMax = toRectMax(transformedRect);

    if (m_hasClipRegion && m_clipRegion.rectCount() <= 1) {
        // If there is a clipRegion, and it is empty, the item wont be rendered
        if (m_clipRegion.isEmpty()) {
            m_boundingRectMin = QRect();
            m_boundingRectMax = QRect();
        } else {
            const auto rects = m_clipRegion.begin();
            m_boundingRectMin = m_boundingRectMin.intersected(rects[0]);
            m_boundingRectMax = m_boundingRectMax.intersected(rects[0]);
        }
    }

    // Overrides
    if (m_opacity < 1.0f)
        m_isOpaque = false;

    m_dirtyRegion = QRegion(m_boundingRectMax);
}

struct RenderNodeState : public QSGRenderNode::RenderState
{
    const QMatrix4x4 *projectionMatrix() const override { return &ident; }
    QRect scissorRect() const override { return QRect(); }
    bool scissorEnabled() const override { return false; }
    int stencilValue() const override { return 0; }
    bool stencilEnabled() const override { return false; }
    const QRegion *clipRegion() const override { return &cr; }
    QMatrix4x4 ident;
    QRegion cr;
};

QRegion QSGSoftwareRenderableNode::renderNode(QPainter *painter, bool forceOpaquePainting)
{
    Q_ASSERT(painter);

    // Check for don't paint conditions
    if (m_nodeType != RenderNode) {
        if (!m_isDirty || qFuzzyIsNull(m_opacity) || m_dirtyRegion.isEmpty()) {
            m_isDirty = false;
            m_dirtyRegion = QRegion();
            return QRegion();
        }
    } else {
        if (!m_isDirty || qFuzzyIsNull(m_opacity)) {
            m_isDirty = false;
            m_dirtyRegion = QRegion();
            return QRegion();
        } else {
            QSGRenderNodePrivate *rd = QSGRenderNodePrivate::get(m_handle.renderNode);
            rd->m_localMatrix = m_transform;
            rd->m_matrix = &rd->m_localMatrix;
            rd->m_opacity = m_opacity;

            // all the clip region below is in world coordinates, taking m_transform into account already
            QRegion cr = m_dirtyRegion;
            if (m_clipRegion.rectCount() > 1)
                cr &= m_clipRegion;

            painter->save();
            RenderNodeState rs;
            rs.cr = cr;
            m_handle.renderNode->render(&rs);
            painter->restore();

            const QRect br = m_handle.renderNode->flags().testFlag(QSGRenderNode::BoundedRectRendering)
                ? m_boundingRectMax // already mapped to world
                : QRect(0, 0, painter->device()->width(), painter->device()->height());
            m_previousDirtyRegion = QRegion(br);
            m_isDirty = false;
            m_dirtyRegion = QRegion();
            return br;
        }
    }

    painter->save();
    painter->setOpacity(m_opacity);

    // Set clipRegion to m_dirtyRegion (in world coordinates, so must be done before the setTransform below)
    // as m_dirtyRegion already accounts for clipRegion
    painter->setClipRegion(m_dirtyRegion, Qt::ReplaceClip);
    if (m_clipRegion.rectCount() > 1)
        painter->setClipRegion(m_clipRegion, Qt::IntersectClip);

    painter->setTransform(m_transform, false); //precalculated worldTransform
    if (forceOpaquePainting || m_isOpaque)
        painter->setCompositionMode(QPainter::CompositionMode_Source);

    switch (m_nodeType) {
    case QSGSoftwareRenderableNode::SimpleRect:
        painter->fillRect(m_handle.simpleRectNode->rect(), m_handle.simpleRectNode->color());
        break;
    case QSGSoftwareRenderableNode::SimpleTexture:
    {
        QSGTexture *texture = m_handle.simpleTextureNode->texture();
        if (QSGSoftwarePixmapTexture *pt = qobject_cast<QSGSoftwarePixmapTexture *>(texture)) {
            const QPixmap &pm = pt->pixmap();
            painter->drawPixmap(m_handle.simpleTextureNode->rect(), pm, m_handle.simpleTextureNode->sourceRect());
        } else if (QSGPlainTexture *pt = qobject_cast<QSGPlainTexture *>(texture)) {
            const QImage &im = pt->image();
            painter->drawImage(m_handle.simpleTextureNode->rect(), im, m_handle.simpleTextureNode->sourceRect());
        }
    }
        break;
    case QSGSoftwareRenderableNode::Image:
        m_handle.imageNode->paint(painter);
        break;
    case QSGSoftwareRenderableNode::Painter:
        m_handle.painterNode->paint(painter);
        break;
    case QSGSoftwareRenderableNode::Rectangle:
        m_handle.rectangleNode->paint(painter);
        break;
    case QSGSoftwareRenderableNode::Glyph:
        m_handle.glpyhNode->paint(painter);
        break;
    case QSGSoftwareRenderableNode::NinePatch:
        m_handle.ninePatchNode->paint(painter);
        break;
    case QSGSoftwareRenderableNode::SimpleRectangle:
        static_cast<QSGSoftwareRectangleNode *>(m_handle.simpleRectangleNode)->paint(painter);
        break;
    case QSGSoftwareRenderableNode::SimpleImage:
        static_cast<QSGSoftwareImageNode *>(m_handle.simpleImageNode)->paint(painter);
        break;
#if QT_CONFIG(quick_sprite)
    case QSGSoftwareRenderableNode::SpriteNode:
        static_cast<QSGSoftwareSpriteNode *>(m_handle.spriteNode)->paint(painter);
        break;
#endif
    default:
        break;
    }

    painter->restore();

    QRegion areaToBeFlushed = m_dirtyRegion;
    m_previousDirtyRegion = QRegion(m_boundingRectMax);
    m_isDirty = false;
    m_dirtyRegion = QRegion();

    return areaToBeFlushed;
}

bool QSGSoftwareRenderableNode::isDirtyRegionEmpty() const
{
    return m_dirtyRegion.isEmpty();
}

void QSGSoftwareRenderableNode::setTransform(const QTransform &transform)
{
    if (m_transform == transform)
        return;
    m_transform = transform;
    update();
}

void QSGSoftwareRenderableNode::setClipRegion(const QRegion &clipRect, bool hasClipRegion)
{
    if (m_clipRegion == clipRect && m_hasClipRegion == hasClipRegion)
        return;

    m_clipRegion = clipRect;
    m_hasClipRegion = hasClipRegion;
    update();
}

void QSGSoftwareRenderableNode::setOpacity(float opacity)
{
    if (qFuzzyCompare(m_opacity, opacity))
        return;

    m_opacity = opacity;
    update();
}

void QSGSoftwareRenderableNode::markGeometryDirty()
{
    update();
}

void QSGSoftwareRenderableNode::markMaterialDirty()
{
    update();
}

void QSGSoftwareRenderableNode::addDirtyRegion(const QRegion &dirtyRegion, bool forceDirty)
{
    // Check if the dirty region applies to this node
    QRegion prev = m_dirtyRegion;
    if (dirtyRegion.intersects(m_boundingRectMax)) {
        if (forceDirty)
            m_isDirty = true;
        m_dirtyRegion += dirtyRegion.intersected(m_boundingRectMax);
    }
    qCDebug(lcRenderable) << "addDirtyRegion: " << dirtyRegion << "old dirtyRegion: " << prev << "new dirtyRegion: " << m_dirtyRegion;
}

void QSGSoftwareRenderableNode::subtractDirtyRegion(const QRegion &dirtyRegion)
{
    QRegion prev = m_dirtyRegion;
    if (m_isDirty) {
        // Check if this rect concerns us
        if (dirtyRegion.intersects(m_boundingRectMax)) {
            m_dirtyRegion -= dirtyRegion;
            if (m_dirtyRegion.isEmpty())
                m_isDirty = false;
        }
    }
    qCDebug(lcRenderable) << "subtractDirtyRegion: " << dirtyRegion << "old dirtyRegion" << prev << "new dirtyRegion: " << m_dirtyRegion;
}

QRegion QSGSoftwareRenderableNode::previousDirtyRegion(bool wasRemoved) const
{
    // When removing a node, the boundingRect shouldn't be subtracted
    // because a deleted node has no valid boundingRect
    if (wasRemoved)
        return m_previousDirtyRegion;

    return m_previousDirtyRegion.subtracted(QRegion(m_boundingRectMax));
}

QRegion QSGSoftwareRenderableNode::dirtyRegion() const
{
    return m_dirtyRegion;
}

QT_END_NAMESPACE
