/*
 * SPDX-FileCopyrightText: 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved.
 *
 * This file is part of the KD Chart library.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "KChartRadarDiagram.h"
#include "KChartRadarDiagram_p.h"

#include "KChartPaintContext.h"
#include "KChartPainterSaver_p.h"
#include "KChartMath_p.h"

#include <QPainter>

using namespace KChart;

RadarDiagram::Private::Private() :
    closeDatasets( false ),
    reverseData( false ),
    fillAlpha( 0.0 )
{
}

RadarDiagram::Private::~Private() {}

#define d d_func()

RadarDiagram::RadarDiagram( QWidget* parent, RadarCoordinatePlane* plane ) :
    AbstractPolarDiagram( new Private( ), parent, plane )
{
    //init();
}

RadarDiagram::~RadarDiagram()
{

}

void RadarDiagram::init()
{
}


RadarDiagram * RadarDiagram::clone() const
{
    RadarDiagram* newDiagram = new RadarDiagram( new Private( *d ) );
    // This needs to be copied after the fact
    newDiagram->d->closeDatasets = d->closeDatasets;
    return newDiagram;
}

const QPair<QPointF, QPointF> RadarDiagram::calculateDataBoundaries () const
{
    if ( !checkInvariants(true) ) return QPair<QPointF, QPointF>( QPointF( 0, 0 ), QPointF( 0, 0 ) );
    const int rowCount = model()->rowCount(rootIndex());
    const int colCount = model()->columnCount(rootIndex());
    qreal xMin = 0.0;
    qreal xMax = colCount;
    qreal yMin = 0, yMax = 0;
    for ( int iCol=0; iCol<colCount; ++iCol ) {
        for ( int iRow=0; iRow< rowCount; ++iRow ) {
            qreal value = model()->data( model()->index( iRow, iCol, rootIndex() ) ).toReal(); // checked
            yMax = qMax( yMax, value );
            yMin = qMin( yMin, value );
        }
    }
    QPointF bottomLeft ( QPointF( xMin, yMin ) );
    QPointF topRight ( QPointF( xMax, yMax ) );
    return QPair<QPointF, QPointF> ( bottomLeft,  topRight );
}



void RadarDiagram::paintEvent ( QPaintEvent*)
{
    QPainter painter ( viewport() );
    PaintContext ctx;
    ctx.setPainter ( &painter );
    ctx.setRectangle( QRectF ( 0, 0, width(), height() ) );
    paint ( &ctx );
}

void RadarDiagram::paint( PaintContext* ctx )
{
    qreal dummy1, dummy2;
    paint( ctx, true,  dummy1, dummy2 );
    paint( ctx, false, dummy1, dummy2 );
}

static qreal fitFontSizeToGeometry( const QString& text, const QFont& font, const QRectF& geometry, const TextAttributes& ta )
{
    QFont f = font;
    const qreal origResult = f.pointSizeF();
    qreal result = origResult;
    const QSizeF mySize = geometry.size();
    if ( mySize.isNull() )
        return result;

    const QString t = text;
    QFontMetrics fm( f );
    while ( true )
    {
        const QSizeF textSize = rotatedRect( fm.boundingRect( t ), ta.rotation() ).normalized().size();

        if ( textSize.height() <= mySize.height() && textSize.width() <= mySize.width() )
            return result;

        result -= 0.5;
        if ( result <= 0.0 )
            return origResult;
        f.setPointSizeF( result );
        fm = QFontMetrics( f );
    }
}

static QPointF scaleToRealPosition( const QPointF& origin, const QRectF& sourceRect, const QRectF& destRect, const AbstractCoordinatePlane& plane )
{
    QPointF result = plane.translate( origin );
    result -= sourceRect.topLeft();
    result.setX( result.x() / sourceRect.width() * destRect.width() );
    result.setY( result.y() / sourceRect.height() * destRect.height() );
    result += destRect.topLeft();
    return result;
}

void RadarDiagram::setReverseData( bool val )
{
    d->reverseData = val;
}
bool RadarDiagram::reverseData()
{
    return d->reverseData;
}

// local structure to remember the settings of a polygon inclusive the used color and pen.
struct Polygon {
    QPolygonF polygon;
    QBrush brush;
    QPen pen;
    Polygon(const QPolygonF &polygon, const QBrush &brush, const QPen &pen) : polygon(polygon), brush(brush), pen(pen) {}
};

void RadarDiagram::paint( PaintContext* ctx,
                          bool calculateListAndReturnScale,
                          qreal& newZoomX, qreal& newZoomY )
{
    // note: Not having any data model assigned is no bug
    //       but we can not draw a diagram then either.
    if ( !checkInvariants(true) )
        return;
    d->reverseMapper.clear();

    const int rowCount = model()->rowCount( rootIndex() );
    const int colCount = model()->columnCount( rootIndex() );

    int iRow, iCol;

    const qreal min = dataBoundaries().first.y();
    const qreal r = qAbs( min ) + dataBoundaries().second.y();
    const qreal step = ( r - qAbs( min ) ) / ( numberOfGridRings() );
    
    RadarCoordinatePlane* plane = dynamic_cast<RadarCoordinatePlane*>(ctx->coordinatePlane());
    TextAttributes ta = plane->textAttributes();
    QRectF fontRect = ctx->rectangle();    
    fontRect.setSize( QSizeF( fontRect.width(), step / 2.0 ) );
    const qreal labelFontSize = fitFontSizeToGeometry( QString::fromLatin1( "TestXYWQgqy" ), ta.font(), fontRect, ta );
    QFont labelFont = ta.font();
    ctx->painter()->setPen( ta.pen() );
    labelFont.setPointSizeF( labelFontSize );
    const QFontMetricsF metric( labelFont );
    const qreal labelHeight = metric.height();
    QRectF destRect = ctx->rectangle();    
    if ( ta.isVisible() )
    {        
        destRect.setY( destRect.y() + 2 * labelHeight );
        destRect.setHeight( destRect.height() - 4 * labelHeight );
    }
    
    if ( calculateListAndReturnScale ) {
        ctx->painter()->save();
        // Check if all of the data value texts / data comments will fit
        // into the available space:
        d->labelPaintCache.clear();
        ctx->painter()->save();
        for ( iCol=0; iCol < colCount; ++iCol ) {
            for ( iRow=0; iRow < rowCount; ++iRow ) {
                QModelIndex index = model()->index( iRow, iCol, rootIndex() ); // checked
                const qreal value = model()->data( index ).toReal();
                QPointF point = scaleToRealPosition( QPointF( value, iRow ), ctx->rectangle(), destRect, *ctx->coordinatePlane() );
                d->addLabel( &d->labelPaintCache, index, nullptr, PositionPoints( point ),
                             Position::Center, Position::Center, value );
            }
        }
        ctx->painter()->restore();
        const qreal oldZoomX = coordinatePlane()->zoomFactorX();
        const qreal oldZoomY = coordinatePlane()->zoomFactorY();
        newZoomX = oldZoomX;
        newZoomY = oldZoomY;
        if ( d->labelPaintCache.paintReplay.count() ) {
            QRectF txtRectF;
            d->paintDataValueTextsAndMarkers( ctx, d->labelPaintCache, true, true, &txtRectF );
            const QRect txtRect = txtRectF.toRect();
            const QRect curRect = coordinatePlane()->geometry();
            const qreal gapX = qMin( txtRect.left() - curRect.left(), curRect.right()  - txtRect.right() );
            const qreal gapY = qMin( txtRect.top()  - curRect.top(),  curRect.bottom() - txtRect.bottom() );
            newZoomX = oldZoomX;
            newZoomY = oldZoomY;
            if ( gapX < 0.0 )
                newZoomX *= 1.0 + (gapX-1.0) / curRect.width();
            if ( gapY < 0.0 )
                newZoomY *= 1.0 + (gapY-1.0) / curRect.height();
        }
        ctx->painter()->restore();

    } else {
        // Iterate through data sets and create a list of polygons out of them.
        QList<Polygon> polygons;
        for ( iCol=0; iCol < colCount; ++iCol ) {
            //TODO(khz): As of yet RadarDiagram can not show per-segment line attributes
            //           but it draws every polyline in one go - using one color.
            //           This needs to be enhanced to allow for cell-specific settings
            //           in the same way as LineDiagram does it.
            QPolygonF polygon;
            QPointF point0;
            for ( iRow=0; iRow < rowCount; ++iRow ) {
                QModelIndex index = model()->index( iRow, iCol, rootIndex() ); // checked
                const qreal value = model()->data( index ).toReal();
                QPointF point = scaleToRealPosition( QPointF( value, d->reverseData ? ( rowCount - iRow ) : iRow ), ctx->rectangle(), destRect, *ctx->coordinatePlane() );
                polygon.append( point );
                if ( ! iRow )
                    point0= point;
            }
            if ( closeDatasets() && rowCount )
                polygon.append( point0 );

            QBrush brush = d->datasetAttrs( iCol, KChart::DatasetBrushRole ).value<QBrush>();
            QPen p = d->datasetAttrs( iCol, KChart::DatasetPenRole ).value< QPen >();
            if ( p.style() != Qt::NoPen )
            {
                polygons.append( Polygon( polygon, brush, PrintingParameters::scalePen( p ) ) );
            }
        }

        // first fill the areas with the brush-color and the defined alpha-value.
        if (d->fillAlpha > 0.0) {
            for (const Polygon& p : qAsConst(polygons)) {
                PainterSaver painterSaver( ctx->painter() );
                ctx->painter()->setRenderHint ( QPainter::Antialiasing );
                QBrush br = p.brush;
                QColor c = br.color();
                c.setAlphaF(d->fillAlpha);
                br.setColor(c);
                ctx->painter()->setBrush( br );
                ctx->painter()->setPen( p.pen );
                ctx->painter()->drawPolygon( p.polygon );
            }
        }

        // then draw the poly-lines.
        for (const Polygon& p : qAsConst(polygons)) {
            PainterSaver painterSaver( ctx->painter() );
            ctx->painter()->setRenderHint ( QPainter::Antialiasing );
            ctx->painter()->setBrush( p.brush );
            ctx->painter()->setPen( p.pen );
            ctx->painter()->drawPolyline( p.polygon );
        }

        d->paintDataValueTextsAndMarkers( ctx, d->labelPaintCache, true );
    }
}

void RadarDiagram::resize ( const QSizeF& size )
{
    AbstractPolarDiagram::resize( size );
}

/*virtual*/
qreal RadarDiagram::valueTotals () const
{
    return model()->rowCount(rootIndex());
}

/*virtual*/
qreal RadarDiagram::numberOfValuesPerDataset() const
{
    return model() ? model()->rowCount(rootIndex()) : 0.0;
}

/*virtual*/
qreal RadarDiagram::numberOfGridRings() const
{
    return 5; // FIXME
}

void RadarDiagram::setCloseDatasets( bool closeDatasets )
{
    d->closeDatasets = closeDatasets;
}

bool RadarDiagram::closeDatasets() const
{
    return d->closeDatasets;
}

qreal RadarDiagram::fillAlpha() const
{
    return d->fillAlpha;
}

void RadarDiagram::setFillAlpha(qreal alphaF)
{
    d->fillAlpha = alphaF;
}

void RadarDiagram::resizeEvent ( QResizeEvent*)
{
}
