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

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#include "kplotobject.h"

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

#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

        Q_FOREACH (KPlotPoint *pp, 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) {

        Q_FOREACH (KPlotPoint *pp, 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;
                    tri << QPointF(q.x() - size(), q.y() + size())
                        << QPointF(q.x(), q.y() - size())
                        << QPointF(q.x() + size(), q.y() + size());
                    painter->drawPolygon(tri);
                    break;
                }

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

                case Pentagon: {
                    QPolygonF pent;
                    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());
                    painter->drawPolygon(pent);
                    break;
                }

                case Hexagon: {
                    QPolygonF hex;
                    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());
                    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;
                    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());
                    painter->drawPolygon(star);
                    break;
                }

                default:
                    break;
                }
            }
        }
    }

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

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

}
