/*  -*- C++ -*-
    This file is part of the KDE libraries
    SPDX-FileCopyrightText: 2003 Jason Harris <kstars@30doradus.org>

    SPDX-License-Identifier: LGPL-2.0-or-later
*/

#include "kplotobject.h"

#include <QDebug>
#include <QPainter>
#include <QtAlgorithms>

#include "kplotpoint.h"
#include "kplotwidget.h"

class KPlotObject::Private
{
public:
    Private(KPlotObject *qq)
        : q(qq)
    {
    }

    ~Private()
    {
        qDeleteAll(pList);
    }

    KPlotObject *q;

    QList<KPlotPoint *> pList;
    PlotTypes type;
    PointStyle pointStyle;
    double size;
    QPen pen, linePen, barPen, labelPen;
    QBrush brush, barBrush;
};

KPlotObject::KPlotObject(const QColor &c, PlotType t, double size, PointStyle ps)
    : d(new Private(this))
{
    // By default, all pens and brushes are set to the given color
    setBrush(c);
    setBarBrush(c);
    setPen(QPen(brush(), 1));
    setLinePen(pen());
    setBarPen(pen());
    setLabelPen(pen());

    d->type |= t;
    setSize(size);
    setPointStyle(ps);
}

KPlotObject::~KPlotObject()
{
    delete d;
}

KPlotObject::PlotTypes KPlotObject::plotTypes() const
{
    return d->type;
}

void KPlotObject::setShowPoints(bool b)
{
    if (b) {
        d->type |= KPlotObject::Points;
    } else {
        d->type &= ~KPlotObject::Points;
    }
}

void KPlotObject::setShowLines(bool b)
{
    if (b) {
        d->type |= KPlotObject::Lines;
    } else {
        d->type &= ~KPlotObject::Lines;
    }
}

void KPlotObject::setShowBars(bool b)
{
    if (b) {
        d->type |= KPlotObject::Bars;
    } else {
        d->type &= ~KPlotObject::Bars;
    }
}

double KPlotObject::size() const
{
    return d->size;
}

void KPlotObject::setSize(double s)
{
    d->size = s;
}

KPlotObject::PointStyle KPlotObject::pointStyle() const
{
    return d->pointStyle;
}

void KPlotObject::setPointStyle(PointStyle p)
{
    d->pointStyle = p;
}

const QPen &KPlotObject::pen() const
{
    return d->pen;
}

void KPlotObject::setPen(const QPen &p)
{
    d->pen = p;
}

const QPen &KPlotObject::linePen() const
{
    return d->linePen;
}

void KPlotObject::setLinePen(const QPen &p)
{
    d->linePen = p;
}

const QPen &KPlotObject::barPen() const
{
    return d->barPen;
}

void KPlotObject::setBarPen(const QPen &p)
{
    d->barPen = p;
}

const QPen &KPlotObject::labelPen() const
{
    return d->labelPen;
}

void KPlotObject::setLabelPen(const QPen &p)
{
    d->labelPen = p;
}

const QBrush KPlotObject::brush() const
{
    return d->brush;
}

void KPlotObject::setBrush(const QBrush &b)
{
    d->brush = b;
}

const QBrush KPlotObject::barBrush() const
{
    return d->barBrush;
}

void KPlotObject::setBarBrush(const QBrush &b)
{
    d->barBrush = b;
}

QList<KPlotPoint *> KPlotObject::points() const
{
    return d->pList;
}

void KPlotObject::addPoint(const QPointF &p, const QString &label, double barWidth)
{
    addPoint(new KPlotPoint(p.x(), p.y(), label, barWidth));
}

void KPlotObject::addPoint(KPlotPoint *p)
{
    if (!p) {
        return;
    }
    d->pList.append(p);
}

void KPlotObject::addPoint(double x, double y, const QString &label, double barWidth)
{
    addPoint(new KPlotPoint(x, y, label, barWidth));
}

void KPlotObject::removePoint(int index)
{
    if ((index < 0) || (index >= d->pList.count())) {
        // qWarning() << "KPlotObject::removePoint(): index " << index << " out of range!";
        return;
    }

    d->pList.removeAt(index);
}

void KPlotObject::clearPoints()
{
    qDeleteAll(d->pList);
    d->pList.clear();
}

void KPlotObject::draw(QPainter *painter, KPlotWidget *pw)
{
    // Order of drawing determines z-distance: Bars in the back, then lines,
    // then points, then labels.

    if (d->type & Bars) {
        painter->setPen(barPen());
        painter->setBrush(barBrush());

        double w = 0;
        for (int i = 0; i < d->pList.size(); ++i) {
            if (d->pList[i]->barWidth() == 0.0) {
                if (i < d->pList.size() - 1) {
                    w = d->pList[i + 1]->x() - d->pList[i]->x();
                }
                // For the last bin, we'll just keep the previous width

            } else {
                w = d->pList[i]->barWidth();
            }

            QPointF pp = d->pList[i]->position();
            QPointF p1(pp.x() - 0.5 * w, 0.0);
            QPointF p2(pp.x() + 0.5 * w, pp.y());
            QPointF sp1 = pw->mapToWidget(p1);
            QPointF sp2 = pw->mapToWidget(p2);

            QRectF barRect = QRectF(sp1.x(), sp1.y(), sp2.x() - sp1.x(), sp2.y() - sp1.y()).normalized();
            painter->drawRect(barRect);
            pw->maskRect(barRect, 0.25);
        }
    }

    // Draw lines:
    if (d->type & Lines) {
        painter->setPen(linePen());

        QPointF Previous = QPointF(); // Initialize to null

        for (const KPlotPoint *pp : std::as_const(d->pList)) {
            // q is the position of the point in screen pixel coordinates
            QPointF q = pw->mapToWidget(pp->position());

            if (!Previous.isNull()) {
                painter->drawLine(Previous, q);
                pw->maskAlongLine(Previous, q);
            }

            Previous = q;
        }
    }

    // Draw points:
    if (d->type & Points) {
        for (const KPlotPoint *pp : std::as_const(d->pList)) {
            // q is the position of the point in screen pixel coordinates
            QPointF q = pw->mapToWidget(pp->position());
            if (pw->pixRect().contains(q.toPoint(), false)) {
                double x1 = q.x() - size();
                double y1 = q.y() - size();
                QRectF qr = QRectF(x1, y1, 2 * size(), 2 * size());

                // Mask out this rect in the plot for label avoidance
                pw->maskRect(qr, 2.0);

                painter->setPen(pen());
                painter->setBrush(brush());

                switch (pointStyle()) {
                case Circle:
                    painter->drawEllipse(qr);
                    break;

                case Letter:
                    painter->drawText(qr, Qt::AlignCenter, pp->label().left(1));
                    break;

                case Triangle: {
                    QPolygonF tri;
                    /* clang-format off */
                    tri << QPointF(q.x() - size(), q.y() + size())
                        << QPointF(q.x(), q.y() - size())
                        << QPointF(q.x() + size(), q.y() + size());
                    /* clang-format on */
                    painter->drawPolygon(tri);
                    break;
                }

                case Square:
                    painter->drawRect(qr);
                    break;

                case Pentagon: {
                    QPolygonF pent;
                    /* clang-format off */
                    pent << QPointF(q.x(), q.y() - size())
                         << QPointF(q.x() + size(), q.y() - 0.309 * size())
                         << QPointF(q.x() + 0.588 * size(), q.y() + size())
                         << QPointF(q.x() - 0.588 * size(), q.y() + size())
                         << QPointF(q.x() - size(), q.y() - 0.309 * size());
                    /* clang-format on */
                    painter->drawPolygon(pent);
                    break;
                }

                case Hexagon: {
                    QPolygonF hex;
                    /* clang-format off */
                    hex << QPointF(q.x(), q.y() + size())
                        << QPointF(q.x() + size(), q.y() + 0.5 * size())
                        << QPointF(q.x() + size(), q.y() - 0.5 * size())
                        << QPointF(q.x(), q.y() - size())
                        << QPointF(q.x() - size(), q.y() + 0.5 * size())
                        << QPointF(q.x() - size(), q.y() - 0.5 * size());
                    /* clang-format on */
                    painter->drawPolygon(hex);
                    break;
                }

                case Asterisk:
                    painter->drawLine(q, QPointF(q.x(), q.y() + size()));
                    painter->drawLine(q, QPointF(q.x() + size(), q.y() + 0.5 * size()));
                    painter->drawLine(q, QPointF(q.x() + size(), q.y() - 0.5 * size()));
                    painter->drawLine(q, QPointF(q.x(), q.y() - size()));
                    painter->drawLine(q, QPointF(q.x() - size(), q.y() + 0.5 * size()));
                    painter->drawLine(q, QPointF(q.x() - size(), q.y() - 0.5 * size()));
                    break;

                case Star: {
                    QPolygonF star;
                    /* clang-format off */
                    star << QPointF(q.x(), q.y() - size())
                         << QPointF(q.x() + 0.2245 * size(), q.y() - 0.309 * size())
                         << QPointF(q.x() + size(), q.y() - 0.309 * size()) << QPointF(q.x() + 0.363 * size(), q.y() + 0.118 * size())
                         << QPointF(q.x() + 0.588 * size(), q.y() + size()) << QPointF(q.x(), q.y() + 0.382 * size())
                         << QPointF(q.x() - 0.588 * size(), q.y() + size()) << QPointF(q.x() - 0.363 * size(), q.y() + 0.118 * size())
                         << QPointF(q.x() - size(), q.y() - 0.309 * size()) << QPointF(q.x() - 0.2245 * size(), q.y() - 0.309 * size());
                    /* clang-format on */
                    painter->drawPolygon(star);
                    break;
                }

                default:
                    break;
                }
            }
        }
    }

    // Draw labels
    painter->setPen(labelPen());

    for (KPlotPoint *pp : std::as_const(d->pList)) {
        QPoint q = pw->mapToWidget(pp->position()).toPoint();
        if (pw->pixRect().contains(q, false) && !pp->label().isEmpty()) {
            pw->placeLabel(painter, pp);
        }
    }
}
