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

#include "QskPlotCorridorSkinlet.h"
#include "QskPlotCorridor.h"
#include "QskPlotCorridorData.h"

#include <QskSGNode.h>
#include <QskVertex.h>

#include <qvector.h>
#include <qsgvertexcolormaterial.h>

namespace
{
    class GeometryNode : public QSGGeometryNode
    {
      protected:
        GeometryNode()
            : m_geometry( QSGGeometry::defaultAttributes_ColoredPoint2D(), 0 )
        {
            setGeometry( &m_geometry );
            setMaterial( &m_material );
        }

      private:
        QSGGeometry m_geometry;
        QSGVertexColorMaterial m_material;
    };

    class CorridorNode : public GeometryNode
    {
      public:
        void updateCorridor( const QskPlotCorridorData* data,
            const QskPlotCorridorSample& sample1, int index1,
            const QskPlotCorridorSample& sample2, int index2,
            const QColor& color )
        {
            using namespace QskVertex;

            const Color vertexColor( color );

            geometry()->setDrawingMode( QSGGeometry::DrawTriangleStrip );

            auto line = allocateLines< ColoredLine >( *geometry(), index2 - index1 + 1 );

            line++->setLine( sample1.value, sample1.boundary.lowerBound(),
                sample1.value, sample1.boundary.upperBound(), vertexColor );

            for ( int i = index1 + 1; i < index2; i++ )
            {
                const auto sample = data->sampleAt( i );
                line++->setLine( sample.value, sample.boundary.lowerBound(),
                    sample.value, sample.boundary.upperBound(), vertexColor );
            }

            line++->setLine( sample2.value, sample2.boundary.lowerBound(),
                sample2.value, sample2.boundary.upperBound(), vertexColor );

            markDirty( QSGNode::DirtyGeometry );
        }
    };

    class BorderNode : public GeometryNode
    {
      public:
        void updateBorder( const QskPlotCorridorData* data,
            const QskPlotCorridorSample& sample1, int index1,
            const QskPlotCorridorSample& sample2, int index2,
            quint8 nodeRole, const QColor& color, qreal lineWidth )
        {
            auto& geometry = *this->geometry();

            geometry.setDrawingMode( QSGGeometry::DrawLineStrip );

            const float lineWidthF = lineWidth;
            if( lineWidthF != geometry.lineWidth() )
                geometry.setLineWidth( lineWidthF );

            const QskVertex::Color c( color );

            geometry.allocate( index2 - index1 + 1 );

            auto p = geometry.vertexDataAsColoredPoint2D();

            if( nodeRole == QskPlotCorridorSkinlet::LowerBoundRole )
            {
                p++->set( sample1.value, sample1.boundary.lowerBound(),
                    c.r, c.g, c.b, c.a );

                for ( int i = index1 + 1; i < index2; i++ )
                {
                    const auto sample = data->sampleAt( i );
                    p++->set( sample.value, sample.boundary.lowerBound(),
                        c.r, c.g, c.b, c.a );
                }

                p++->set( sample2.value, sample2.boundary.lowerBound(),
                    c.r, c.g, c.b, c.a );
            }
            else
            {
                p++->set( sample1.value, sample1.boundary.upperBound(),
                    c.r, c.g, c.b, c.a );

                for ( int i = index1 + 1; i < index2; i++ )
                {
                    const auto sample = data->sampleAt( i );
                    p++->set( sample.value, sample.boundary.upperBound(),
                        c.r, c.g, c.b, c.a );
                }

                p++->set( sample2.value, sample2.boundary.upperBound(),
                    c.r, c.g, c.b, c.a );
            }

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

class QskPlotCorridorSkinlet::PrivateData
{
  public:
    int index1, index2;
    QskPlotCorridorSample sample1, sample2;
};

QskPlotCorridorSkinlet::QskPlotCorridorSkinlet( QskSkin* skin )
    : Inherited( skin )
    , m_data( new PrivateData )
{
    setNodeRoles( { CorridorRole, LowerBoundRole, UpperBoundRole } );
}

QskPlotCorridorSkinlet::~QskPlotCorridorSkinlet()
{
}

void QskPlotCorridorSkinlet::updateNode(
    QskSkinnable* skinnable, QSGNode* parent ) const
{
    /*
        As clipping is the same for borders and corridor
        we do it only once here
     */

    auto corridor = static_cast< const QskPlotCorridor* >( skinnable );
    const auto data = corridor->data();

    m_data->index1 = m_data->index2 = -1;

    const auto n = data->count();
    if ( data && n > 0 )
    {
        auto& s1 = m_data->sample1;
        auto& s2 = m_data->sample2;

        s1 = data->sampleAt( 0 );
        s2 = data->sampleAt( n - 1 );

        const auto scaleRect = corridor->scaleRect();

        const qreal x1 = scaleRect.left();
        const qreal x2 = scaleRect.right();

        if ( !( x1 > s2.value || x2 < s1.value ) )
        {
            m_data->index1 = 0;
            m_data->index2 = n - 1;

            const int index1 = data->upperIndex( x1 );
            if ( index1 > 0 )
            {
                m_data->index1 = index1 - 1;
                s1 = data->interpolatedSample( x1 );
            }

            const int index2 = data->upperIndex( x2 );
            if ( index2 > 0 )
            {
                m_data->index2 = index2;
                s2 = data->interpolatedSample( x2 );
            }
        }
    }

    Inherited::updateNode( skinnable, parent );
}

QSGNode* QskPlotCorridorSkinlet::updateSubNode(
    const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const
{
    if ( m_data->index2 < 0 )
        return nullptr;

    if ( nodeRole == CorridorRole )
        return updateCorridorNode( skinnable, node );
    else
        return updateBorderNode( skinnable, nodeRole, node );
}

QSGNode* QskPlotCorridorSkinlet::updateCorridorNode(
    const QskSkinnable* skinnable, QSGNode* node ) const
{
    using Q = QskPlotCorridor;

    auto corridor = static_cast< const QskPlotCorridor* >( skinnable );

    const auto color = corridor->color( Q::Corridor );
    if ( !color.isValid() || color.alpha() == 0 )
        return nullptr;

    const auto corridorData = corridor->data();
    if ( corridorData->count() == 0 )
        return nullptr;

    auto corridorNode = QskSGNode::ensureNode< CorridorNode >( node );

    corridorNode->updateCorridor( corridorData,
        m_data->sample1, m_data->index1, m_data->sample2, m_data->index2, color );

    return corridorNode;
}

QSGNode* QskPlotCorridorSkinlet::updateBorderNode(
    const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const
{
    using Q = QskPlotCorridor;
    using A = QskAspect;

    auto corridor = static_cast< const QskPlotCorridor* >( skinnable );

    const auto corridorData = corridor->data();
    if ( corridorData->count() == 0 )
        return nullptr;

    QColor color;
    if( nodeRole == QskPlotCorridorSkinlet::LowerBoundRole )
        color = corridor->color( Q::Border | A::Lower );
    else
        color = corridor->color( Q::Border | A::Upper );

    if ( !color.isValid() || color.alpha() == 0 )
        return nullptr;

    auto lineWidth = corridor->metric( Q::Border | A::Size );
    lineWidth = qMax( lineWidth, 0.0 );

    auto borderNode = QskSGNode::ensureNode< BorderNode >( node );

    borderNode->updateBorder( corridorData,
        m_data->sample1, m_data->index1, m_data->sample2, m_data->index2,
        nodeRole, color, lineWidth );

    return borderNode;
}

#include "moc_QskPlotCorridorSkinlet.cpp"
