/******************************************************************************
 * QSkinny - Copyright (C) The authors
 *           SPDX-License-Identifier: BSD-3-Clause
 *****************************************************************************/

#include "QskArcRenderNode.h"
#include "QskGradient.h"
#include "QskArcRenderer.h"
#include "QskArcMetrics.h"
#include "QskGradient.h"
#include "QskSGNode.h"
#include "QskFillNodePrivate.h"

static inline bool qskHasBorder( qreal width, const QColor& color )
{
    return ( width > 0.0 ) && color.isValid() && ( color.alpha() > 0 );
}

class QskArcRenderNodePrivate final : public QskFillNodePrivate
{
  public:
    inline void resetNode( QskArcRenderNode* node )
    {
        m_metricsHash = m_colorsHash = 0;
        node->resetGeometry();
    }

    inline bool updateMetrics( const QRectF& rect,
        const QskArcMetrics& metrics, bool radial, qreal borderWidth )
    {
        QskHashValue hash = 13000;

        hash = qHashBits( &rect, sizeof( rect ), hash );
        hash = metrics.hash( hash );
        hash = qHash( radial, hash );
        hash = qHash( borderWidth, hash );

        return updateHash( m_metricsHash, hash );
    }

    inline bool updateColors( const QColor& borderColor, const QskGradient& gradient )
    {
        QskHashValue hash = 13000;

        if ( borderColor.isValid() && ( borderColor.alpha() > 0 ) )
            hash = qHashBits( &borderColor, sizeof( borderColor ), hash );

        if ( gradient.isVisible() )
            hash = gradient.hash( hash );

        return updateHash( m_colorsHash, hash );
    }

  private:
    inline bool updateHash( QskHashValue& value, const QskHashValue newValue ) const
    {
        if ( newValue != value )
        {
            value = newValue;
            return true;
        }

        return false;
    }

  public:
    QskHashValue m_metricsHash = 0;
    QskHashValue m_colorsHash = 0;
};

QskArcRenderNode::QskArcRenderNode()
    : QskFillNode( *new QskArcRenderNodePrivate )
{
}

QskArcRenderNode::~QskArcRenderNode()
{
}

void QskArcRenderNode::updateFilling( const QRectF& rect,
    const QskArcMetrics& metrics, const QskGradient& gradient )
{
    updateFilling( rect, metrics, false, 0.0, gradient );
}

void QskArcRenderNode::updateFilling( const QRectF& rect,
    const QskArcMetrics& arcMetrics, bool radial,
    qreal borderWidth, const QskGradient& gradient )
{
    Q_D( QskArcRenderNode );

    if ( rect.isEmpty() || arcMetrics.isNull() || !gradient.isVisible() )
    {
        d->resetNode( this );
        return;
    }

    const auto metrics = arcMetrics.toAbsolute( rect.size() );

    if ( borderWidth >= 0.5 * metrics.thickness() )
    {
        d->resetNode( this );
        return;
    }

    const bool coloredGeometry = hasHint( PreferColoredGeometry )
        && QskArcRenderer::isGradientSupported( rect, metrics, gradient );

    bool dirtyGeometry = d->updateMetrics( rect, metrics, radial, borderWidth );
    bool dirtyMaterial = d->updateColors( QColor(), gradient );

    if ( coloredGeometry != isGeometryColored() )
        dirtyGeometry = dirtyMaterial = true;

    if ( dirtyGeometry || dirtyMaterial )
    {
        if ( coloredGeometry )
        {
            setColoring( QskFillNode::Polychrome );

            QskArcRenderer::setColoredFillLines( rect, metrics, radial,
                borderWidth, gradient, *geometry() );

            markDirty( QSGNode::DirtyGeometry );
        }
        else
        {
            setColoring( rect, gradient );

            if ( dirtyGeometry )
            {
                QskArcRenderer::setFillLines( rect, metrics,
                    radial, borderWidth, *geometry() );

                markDirty( QSGNode::DirtyGeometry );
            }
        }
    }
}

void QskArcRenderNode::updateBorder( const QRectF& rect,
    const QskArcMetrics& arcMetrics, bool radial,
    qreal borderWidth, const QColor& borderColor )
{
    Q_D( QskArcRenderNode );

    if ( rect.isEmpty() || arcMetrics.isNull()
        || !qskHasBorder( borderWidth, borderColor ) )
    {
        d->resetNode( this );
        return;
    }

    const bool coloredGeometry = hasHint( PreferColoredGeometry );

    bool dirtyGeometry = d->updateMetrics( rect, arcMetrics, radial, borderWidth );
    bool dirtyMaterial = d->updateColors( borderColor, QskGradient() );

    if ( coloredGeometry != isGeometryColored() )
        dirtyGeometry = dirtyMaterial = true;

    if ( dirtyGeometry || dirtyMaterial )
    {
        const auto metrics = arcMetrics.toAbsolute( rect.size() );
        borderWidth = qMin( borderWidth, 0.5 * metrics.thickness() );

        if ( coloredGeometry )
        {
            setColoring( QskFillNode::Polychrome );

            QskArcRenderer::setColoredBorderLines( rect, metrics, radial,
                borderWidth, borderColor, *geometry() );

            markDirty( QSGNode::DirtyGeometry );
        }
        else
        {
            setColoring( borderColor );

            if ( dirtyGeometry )
            {
                QskArcRenderer::setBorderLines( rect, metrics, radial,
                    borderWidth, *geometry() );

                markDirty( QSGNode::DirtyGeometry );
            }
        }
    }
}

void QskArcRenderNode::updateArc(
    const QRectF& rect, const QskArcMetrics& arcMetrics, bool radial,
    qreal borderWidth, const QColor& borderColor, const QskGradient& gradient )
{
    Q_D( QskArcRenderNode );

    const auto metrics = arcMetrics.toAbsolute( rect.size() );
    if ( rect.isEmpty() || metrics.isNull() )
    {
        d->resetNode( this );
        return;
    }

    const auto borderMax = 0.5 * metrics.thickness();

    const bool hasFill = gradient.isVisible() && ( borderWidth < borderMax );
    const bool hasBorder = qskHasBorder( borderWidth, borderColor );

    if ( hasBorder && hasFill )
    {
        /*
            When having a monochrome gradient that has the same color as the
            border we might be able to use QskFillNode::Monochrome. For the
            moment this is not implemented
         */
        borderWidth = qMin( borderWidth, borderMax );

        const bool isDirty = d->updateMetrics( rect, arcMetrics, radial, borderWidth )
            || d->updateColors( borderColor, gradient ) || !isGeometryColored();

        if ( isDirty )
        {
            setColoring( QskFillNode::Polychrome );

            QskArcRenderer::setColoredBorderAndFillLines( rect, metrics, radial,
                borderWidth, borderColor, gradient, *geometry() );

            markDirty( QSGNode::DirtyGeometry );
        }
    }
    else if ( hasBorder )
    {
        updateBorder( rect, arcMetrics, radial, borderWidth, borderColor );
    }
    else if ( hasFill )
    {
        updateFilling( rect, arcMetrics, radial, borderWidth, gradient );
    }
    else
    {
        d->resetNode( this );
        return;
    }
}
