/***************************************************************************
    File                 : ArrowMarker.cpp
    Project              : SciDAVis
    --------------------------------------------------------------------
    Copyright            : (C) 2006 by Ion Vasilief
    Email (use @ for *)  : ion_vasilief*yahoo.fr
    Description          : Arrow marker (extension to QwtPlotMarker)

 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *  This program is free software; you can redistribute it and/or modify   *
 *  it under the terms of the GNU General Public License as published by   *
 *  the Free Software Foundation; either version 2 of the License, or      *
 *  (at your option) any later version.                                    *
 *                                                                         *
 *  This program 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 General Public License for more details.                           *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the Free Software           *
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor,                    *
 *   Boston, MA  02110-1301  USA                                           *
 *                                                                         *
 ***************************************************************************/
#include "ArrowMarker.h"
#include "LineDialog.h"

#include <QPainter>
#include <QMouseEvent>
#include <QApplication>

#include <qwt_plot.h>
#include <qwt_plot_canvas.h>
#include <qwt_painter.h>

#ifndef M_PI
#define M_PI 3.141592653589793238462643;
#endif

ArrowMarker::ArrowMarker()
    : d_end_arrow(true),
      d_fill_head(true),
      d_head_angle(45),
      d_head_length(4),
      d_rect(0, 0, 1, 1),
      d_editable(false),
      d_op(None)
{
}

void ArrowMarker::draw(QPainter *p, const QwtScaleMap &xMap, const QwtScaleMap &yMap,
                       const QRect &) const
{
    const int x0 = xMap.transform(d_rect.left());
    const int y0 = yMap.transform(d_rect.top());
    const int x1 = xMap.transform(d_rect.right());
    const int y1 = yMap.transform(d_rect.bottom());

    p->save();
    QPen pen = linePen();
    p->setPen(pen);

    QBrush brush = QBrush(pen.color(), Qt::SolidPattern);
    QwtPainter::drawLine(p, x0, y0, x1, y1);
    p->restore();

    if (d_end_arrow) {
        p->save();
        p->translate(x1, y1);
        const double t = theta(x0, y0, x1, y1);
        p->rotate(-t);

        QPolygon endArray(3);
        endArray[0] = QPoint(0, 0);

        int d = (int)floor(d_head_length * tan(M_PI * d_head_angle / 180.0) + 0.5);
        endArray[1] = QPoint(-d_head_length, d);
        endArray[2] = QPoint(-d_head_length, -d);

        p->setPen(QPen(pen.color(), pen.width(), Qt::SolidLine));
        if (d_fill_head)
            p->setBrush(brush);

        QwtPainter::drawPolygon(p, endArray);
        p->restore();
    }

    if (d_start_arrow) {
        p->save();
        p->translate(x0, y0);
        const double t = theta(x0, y0, x1, y1);
        p->rotate(-t);

        QPolygon startArray(3);
        startArray[0] = QPoint(0, 0);

        int d = (int)floor(d_head_length * tan(M_PI * d_head_angle / 180.0) + 0.5);
        startArray[1] = QPoint(d_head_length, d);
        startArray[2] = QPoint(d_head_length, -d);

        p->setPen(QPen(pen.color(), pen.width(), Qt::SolidLine));
        if (d_fill_head)
            p->setBrush(brush);
        QwtPainter::drawPolygon(p, startArray);
        p->restore();
    }

    if (d_editable) {
        p->save();
        p->setPen(QPen(Qt::black, 1, Qt::SolidLine));
        QRect handler(QPoint(0, 0), QSize(10, 10));
        handler.moveCenter(startPoint());
        p->fillRect(handler, QBrush(Qt::black));
        handler.moveCenter(endPoint());
        p->fillRect(handler, QBrush(Qt::black));
        p->restore();
    }
}

double ArrowMarker::theta(int xs, int ys, int xe, int ye) const
{
    double t;
    if (xe == xs) {
        if (ys > ye)
            t = 90;
        else
            t = 270;
    } else {
        t = atan2((ye - ys) * 1.0, (xe - xs) * 1.0) * 45 / atan(-1.0);
        if (t < 0)
            t = 360 + t;
    }
    return t;
}

double ArrowMarker::length()
{
    if (!plot())
        return -1.0;

    const QwtScaleMap &xMap = plot()->canvasMap(xAxis());
    const QwtScaleMap &yMap = plot()->canvasMap(yAxis());

    const int x0 = xMap.transform(d_rect.left());
    const int y0 = yMap.transform(d_rect.top());
    const int x1 = xMap.transform(d_rect.right());
    const int y1 = yMap.transform(d_rect.bottom());

    double l = sqrt(double((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)));
    return fabs(l);
}

double ArrowMarker::dist(int x, int y)
{
    if (!plot())
        return -1.0;

    const QwtScaleMap &xMap = plot()->canvasMap(xAxis());
    const QwtScaleMap &yMap = plot()->canvasMap(yAxis());

    const int x0 = xMap.transform(d_rect.left());
    const int y0 = yMap.transform(d_rect.top());
    const int x1 = xMap.transform(d_rect.right());
    const int y1 = yMap.transform(d_rect.bottom());

    int xmin = qMin(x0, x1);
    int xmax = qMax(x0, x1);
    int ymin = qMin(y0, y1);
    int ymax = qMax(y0, y1);

    if ((x > xmax || x < xmin || xmin == xmax) && (ymax < y || ymin > y || ymin == ymax))
        // return the shortest distance to one of the ends
        return qMin(sqrt(double((x - x0) * (x - x0) + (y - y0) * (y - y0))),
                    sqrt(double((x - x1) * (x - x1) + (y - y1) * (y - y1))));

    double d;
    if (x0 == x1)
        d = abs(x - x0);
    else {
        double a = (double)(y1 - y0) / (double)(x1 - x0);
        double b = y0 - a * x0;
        d = (a * x - y + b) / sqrt(a * a + 1);
    }
    return fabs(d);
}

void ArrowMarker::setColor(const QColor &c)
{
    if (linePen().color() == c)
        return;

    QPen pen = linePen();
    pen.setColor(c);
    setLinePen(pen);
}

void ArrowMarker::setWidth(int w)
{
    if (linePen().width() == w)
        return;

    QPen pen = linePen();
    pen.setWidth(w);
    setLinePen(pen);
}

void ArrowMarker::setStyle(Qt::PenStyle style)
{
    if (linePen().style() == style)
        return;

    QPen pen = linePen();
    pen.setStyle(style);
    setLinePen(pen);
}

void ArrowMarker::setCapStyle(Qt::PenCapStyle style)
{
    if (linePen().capStyle() == style)
        return;

    QPen pen = linePen();
    pen.setCapStyle(style);
    setLinePen(pen);
}

void ArrowMarker::setJoinStyle(Qt::PenJoinStyle style)
{
    if (linePen().joinStyle() == style)
        return;

    QPen pen = linePen();
    pen.setJoinStyle(style);
    setLinePen(pen);
}

void ArrowMarker::setHeadLength(int l)
{
    d_head_length = l;
}

void ArrowMarker::setHeadAngle(int a)
{
    d_head_angle = a;
}

void ArrowMarker::fillArrowHead(bool fill)
{
    d_fill_head = fill;
}

void ArrowMarker::setStartPoint(const QPoint &p)
{
    d_start = p;

    if (!plot())
        return;

    d_rect.setLeft(plot()->invTransform(xAxis(), p.x()));
    d_rect.setTop(plot()->invTransform(yAxis(), p.y()));
}

void ArrowMarker::setEndPoint(const QPoint &p)
{
    d_end = p;

    if (!plot())
        return;

    d_rect.setRight(plot()->invTransform(xAxis(), p.x()));
    d_rect.setBottom(plot()->invTransform(yAxis(), p.y()));
}

QPoint ArrowMarker::startPoint() const
{
    if (!plot())
        return QPoint();

    return QPoint(plot()->transform(xAxis(), d_rect.left()),
                  plot()->transform(yAxis(), d_rect.top()));
}

QwtDoublePoint ArrowMarker::startPointCoord()
{
    return QwtDoublePoint(d_rect.left(), d_rect.top());
}

void ArrowMarker::setStartPoint(double x, double y)
{
    if (d_rect.left() == x && d_rect.top() == y)
        return;

    d_rect.setLeft(x);
    d_rect.setTop(y);

    if (!plot())
        return;

    plot()->updateLayout();
    d_start = QPoint(plot()->transform(xAxis(), x), plot()->transform(yAxis(), y));
}

QPoint ArrowMarker::endPoint() const
{
    if (!plot())
        return QPoint();

    return QPoint(plot()->transform(xAxis(), d_rect.right()),
                  plot()->transform(yAxis(), d_rect.bottom()));
}

void ArrowMarker::setEndPoint(double x, double y)
{
    if (d_rect.right() == x && d_rect.bottom() == y)
        return;

    d_rect.setRight(x);
    d_rect.setBottom(y);

    if (!plot())
        return;

    plot()->updateLayout();
    d_end = QPoint(plot()->transform(xAxis(), x), plot()->transform(yAxis(), y));
}

QwtDoublePoint ArrowMarker::endPointCoord()
{
    return QwtDoublePoint(d_rect.right(), d_rect.bottom());
}

void ArrowMarker::setBoundingRect(double xs, double ys, double xe, double ye)
{
    if (d_rect.left() == xs && d_rect.top() == ys && d_rect.right() == xe && d_rect.bottom() == ye)
        return;

    d_rect.setLeft(xs);
    d_rect.setTop(ys);
    d_rect.setRight(xe);
    d_rect.setBottom(ye);

    if (!plot())
        return;

    plot()->updateLayout();
    d_start = QPoint(plot()->transform(xAxis(), xs), plot()->transform(yAxis(), ys));
    d_end = QPoint(plot()->transform(xAxis(), xe), plot()->transform(yAxis(), ye));
}

QwtDoubleRect ArrowMarker::boundingRect() const
{
    const QwtScaleMap &xMap = plot()->canvasMap(xAxis());
    const QwtScaleMap &yMap = plot()->canvasMap(yAxis());

    const int x0 = xMap.transform(d_rect.left());
    const int y0 = yMap.transform(d_rect.top());
    const int x1 = xMap.transform(d_rect.right());
    const int y1 = yMap.transform(d_rect.bottom());

    return QwtDoubleRect(
            x0 < x1 ? d_rect.left() : d_rect.right(), y0 < y1 ? d_rect.top() : d_rect.bottom(),
            qAbs(d_rect.left() - d_rect.right()), qAbs(d_rect.top() - d_rect.bottom()));
}

void ArrowMarker::updateBoundingRect()
{
    const QwtScaleMap &xMap = plot()->canvasMap(xAxis());
    const QwtScaleMap &yMap = plot()->canvasMap(yAxis());

    d_rect.setLeft(xMap.invTransform(d_start.x()));
    d_rect.setTop(yMap.invTransform(d_start.y()));
    d_rect.setRight(xMap.invTransform(d_end.x()));
    d_rect.setBottom(yMap.invTransform(d_end.y()));
}

void ArrowMarker::setEditable(bool yes)
{
    if (yes == d_editable)
        return;
    if (yes) {
        d_editable = true;
        plot()->canvas()->installEventFilter(this);
    } else {
        d_editable = false;
        plot()->canvas()->removeEventFilter(this);
    }
    plot()->replot();
}

bool ArrowMarker::eventFilter(QObject *, QEvent *e)
{
    switch (e->type()) {
    case QEvent::MouseButtonPress: {
        const QMouseEvent *me = (const QMouseEvent *)e;
        if (me->button() != Qt::LeftButton)
            return false;
        QRect handler = QRect(QPoint(0, 0), QSize(10, 10));
        handler.moveCenter(startPoint());
        if (handler.contains(me->pos())) {
            QApplication::changeOverrideCursor(QCursor(Qt::SizeAllCursor));
            d_op = MoveStart;
            return true;
        }
        handler.moveCenter(endPoint());
        if (handler.contains(me->pos())) {
            QApplication::changeOverrideCursor(QCursor(Qt::SizeAllCursor));
            d_op = MoveEnd;
            return true;
        }
        int d = width() + (int)floor(headLength() * tan(M_PI * headAngle() / 180.0) + 0.5);
        if (dist(me->pos().x(), me->pos().y()) <= d) {
            QApplication::changeOverrideCursor(QCursor(Qt::SizeAllCursor));
            d_op = MoveBoth;
            d_op_startat = me->pos() - startPoint();
            return true;
        }
        return false;
    }
    case QEvent::MouseMove: {
        const QMouseEvent *me = (const QMouseEvent *)e;
        switch (d_op) {
        case MoveStart:
            setStartPoint(me->pos());
            plot()->replot();
            return true;
        case MoveEnd:
            setEndPoint(me->pos());
            plot()->replot();
            return true;
        case MoveBoth:
            setEndPoint(endPoint() + me->pos() - d_op_startat - startPoint());
            setStartPoint(me->pos() - d_op_startat);
            plot()->replot();
            return true;
        default:
            return false;
        }
    }
    case QEvent::MouseButtonRelease: {
        const QMouseEvent *me = (const QMouseEvent *)e;
        switch (d_op) {
        case MoveStart:
            setStartPoint(me->pos());
            plot()->replot();
            d_op = None;
            QApplication::restoreOverrideCursor();
            return true;
        case MoveEnd:
            setEndPoint(me->pos());
            plot()->replot();
            d_op = None;
            QApplication::restoreOverrideCursor();
            return true;
        case MoveBoth:
            setXValue(plot()->invTransform(xAxis(), me->pos().x() - d_op_startat.x()));
            setYValue(plot()->invTransform(yAxis(), me->pos().y() - d_op_startat.y()));
            plot()->replot();
            d_op = None;
            QApplication::restoreOverrideCursor();
            return true;
        default:
            d_op = None;
            QApplication::restoreOverrideCursor();
            return false;
        }
    }
    case QEvent::MouseButtonDblClick: {
        const QMouseEvent *me = (const QMouseEvent *)e;
        if (me->button() != Qt::LeftButton)
            return false;
        LineDialog *ld = new LineDialog(this, plot()->window());
        ld->setAttribute(Qt::WA_DeleteOnClose);
        ld->exec();
        return true;
    }
    default:
        return false;
    }
}
