/*
 * 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 "KChartPolarDiagram.h"
#include "KChartPolarDiagram_p.h"

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

#include <QPainter>

using namespace KChart;

PolarDiagram::Private::Private()
    : rotateCircularLabels(false)
    , closeDatasets(false)
{
}

PolarDiagram::Private::~Private()
{
}

#define d d_func()

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

PolarDiagram::~PolarDiagram()
{
}

void PolarDiagram::init()
{
    setShowDelimitersAtPosition(Position::Unknown, false);
    setShowDelimitersAtPosition(Position::Center, false);
    setShowDelimitersAtPosition(Position::NorthWest, false);
    setShowDelimitersAtPosition(Position::North, true);
    setShowDelimitersAtPosition(Position::NorthEast, false);
    setShowDelimitersAtPosition(Position::West, false);
    setShowDelimitersAtPosition(Position::East, false);
    setShowDelimitersAtPosition(Position::SouthWest, false);
    setShowDelimitersAtPosition(Position::South, true);
    setShowDelimitersAtPosition(Position::SouthEast, false);
    setShowDelimitersAtPosition(Position::Floating, false);

    setShowLabelsAtPosition(Position::Unknown, false);
    setShowLabelsAtPosition(Position::Center, false);
    setShowLabelsAtPosition(Position::NorthWest, false);
    setShowLabelsAtPosition(Position::North, true);
    setShowLabelsAtPosition(Position::NorthEast, false);
    setShowLabelsAtPosition(Position::West, false);
    setShowLabelsAtPosition(Position::East, false);
    setShowLabelsAtPosition(Position::SouthWest, false);
    setShowLabelsAtPosition(Position::South, true);
    setShowLabelsAtPosition(Position::SouthEast, false);
    setShowLabelsAtPosition(Position::Floating, false);
}

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

const QPair<QPointF, QPointF> PolarDiagram::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 PolarDiagram::paintEvent(QPaintEvent *)
{
    QPainter painter(viewport());
    PaintContext ctx;
    ctx.setPainter(&painter);
    ctx.setRectangle(QRectF(0, 0, width(), height()));
    paint(&ctx);
}

void PolarDiagram::resizeEvent(QResizeEvent *)
{
}

void PolarDiagram::paintPolarMarkers(PaintContext *ctx, const QPolygonF &polygon)
{
    Q_UNUSED(ctx);
    Q_UNUSED(polygon);
    // obsolete, since we are using real markers now!
}

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

void PolarDiagram::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());

    if (calculateListAndReturnScale) {
        // Check if all of the data value texts / data comments fit into the available space...
        d->labelPaintCache.clear();

        for (int iCol = 0; iCol < colCount; ++iCol) {
            for (int iRow = 0; iRow < rowCount; ++iRow) {
                QModelIndex index = model()->index(iRow, iCol, rootIndex()); // checked
                const qreal value = model()->data(index).toReal();
                QPointF point = coordinatePlane()->translate(QPointF(value, iRow)) + ctx->rectangle().topLeft();
                // qDebug() << point;
                d->addLabel(&d->labelPaintCache, index, nullptr, PositionPoints(point), Position::Center, Position::Center, value);
            }
        }

        newZoomX = coordinatePlane()->zoomFactorX();
        newZoomY = coordinatePlane()->zoomFactorY();

        if (d->labelPaintCache.paintReplay.count()) {
            // ...and zoom out if necessary
            const qreal oldZoomX = newZoomX;
            const qreal oldZoomY = newZoomY;

            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());
            if (gapX < 0.0) {
                newZoomX = oldZoomX * (1.0 + (gapX - 1.0) / curRect.width());
            }
            if (gapY < 0.0) {
                newZoomY = oldZoomY * (1.0 + (gapY - 1.0) / curRect.height());
            }
        }
    } else {
        // Paint the data sets
        for (int iCol = 0; iCol < colCount; ++iCol) {
            // TODO(khz): As of yet PolarDiagram 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.
            QBrush brush = d->datasetAttrs(iCol, KChart::DatasetBrushRole).value<QBrush>();
            QPolygonF polygon;
            for (int iRow = 0; iRow < rowCount; ++iRow) {
                QModelIndex index = model()->index(iRow, iCol, rootIndex()); // checked
                const qreal value = model()->data(index).toReal();
                QPointF point = coordinatePlane()->translate(QPointF(value, iRow)) + ctx->rectangle().topLeft();
                polygon.append(point);
                // qDebug() << point;
            }
            if (closeDatasets() && !polygon.isEmpty()) {
                // close the circle by connecting the last data point to the first
                polygon.append(polygon.first());
            }

            PainterSaver painterSaver(ctx->painter());
            ctx->painter()->setRenderHint(QPainter::Antialiasing);
            ctx->painter()->setBrush(brush);
            QPen p = d->datasetAttrs(iCol, KChart::DatasetPenRole).value<QPen>();
            if (p.style() != Qt::NoPen) {
                ctx->painter()->setPen(PrintingParameters::scalePen(p));
                ctx->painter()->drawPolyline(polygon);
            }
        }
        d->paintDataValueTextsAndMarkers(ctx, d->labelPaintCache, true);
    }
}

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

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

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

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

void PolarDiagram::setZeroDegreePosition(int degrees)
{
    Q_UNUSED(degrees);
    qWarning() << "Deprecated PolarDiagram::setZeroDegreePosition() called, setting ignored.";
}

int PolarDiagram::zeroDegreePosition() const
{
    qWarning() << "Deprecated PolarDiagram::zeroDegreePosition() called.";
    return 0;
}

void PolarDiagram::setRotateCircularLabels(bool rotateCircularLabels)
{
    d->rotateCircularLabels = rotateCircularLabels;
}

bool PolarDiagram::rotateCircularLabels() const
{
    return d->rotateCircularLabels;
}

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

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

void PolarDiagram::setShowDelimitersAtPosition(Position position, bool showDelimiters)
{
    d->showDelimitersAtPosition[position.value()] = showDelimiters;
}

void PolarDiagram::setShowLabelsAtPosition(Position position, bool showLabels)
{
    d->showLabelsAtPosition[position.value()] = showLabels;
}

bool PolarDiagram::showDelimitersAtPosition(Position position) const
{
    return d->showDelimitersAtPosition[position.value()];
}

bool PolarDiagram::showLabelsAtPosition(Position position) const
{
    return d->showLabelsAtPosition[position.value()];
}
