/***************************************************************************
    File                 : SelectionMoveResizer.cpp
    Project              : SciDAVis
    --------------------------------------------------------------------
    Copyright            : (C) 2007 by Knut Franke
    Email (use @ for *)  : knut.franke*gmx.de
    Description          : Selection of Widgets and QwtPlotMarkers

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

/***************************************************************************
 *                                                                         *
 *  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 "SelectionMoveResizer.h"

#include <QPainter>
#include <QMouseEvent>

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

#include "Legend.h"
#include "ArrowMarker.h"
#include "ImageMarker.h"
#include "PlotEnrichement.h"

SelectionMoveResizer::SelectionMoveResizer(Legend *target) : QWidget(target->plot()->canvas())
{
    init();
    add(target);
}
SelectionMoveResizer::SelectionMoveResizer(ArrowMarker *target) : QWidget(target->plot()->canvas())
{
    init();
    add(target);
}
SelectionMoveResizer::SelectionMoveResizer(ImageMarker *target) : QWidget(target->plot()->canvas())
{
    init();
    add(target);
}

SelectionMoveResizer::SelectionMoveResizer(QWidget *target) : QWidget(target->parentWidget())
{
    init();
    add(target);
}

void SelectionMoveResizer::init()
{
    d_bounding_rect = QRect(0, 0, -1, -1);
    d_op = None;
    d_op_start = QPoint(0, 0);
    d_op_dp = QPoint(0, 0);

    setGeometry(0, 0, parentWidget()->width(), parentWidget()->height());
    setMouseTracking(true);
    parentWidget()->installEventFilter(this);
    show();
    setFocus();
}

SelectionMoveResizer::~SelectionMoveResizer()
{
    parentWidget()->removeEventFilter(this);
}

void SelectionMoveResizer::add(Legend *target)
{
    if ((QWidget *)target->plot()->canvas() != parent())
        return;
    d_legend_markers << target;

    if (d_bounding_rect.isValid())
        d_bounding_rect |= boundingRectOf(target);
    else
        d_bounding_rect = boundingRectOf(target);

    update();
}
void SelectionMoveResizer::add(ArrowMarker *target)
{
    if ((QWidget *)target->plot()->canvas() != parent())
        return;
    d_line_markers << target;

    if (d_bounding_rect.isValid())
        d_bounding_rect |= boundingRectOf(target);
    else
        d_bounding_rect = boundingRectOf(target);

    update();
}
void SelectionMoveResizer::add(ImageMarker *target)
{
    if ((QWidget *)target->plot()->canvas() != parent())
        return;
    d_image_markers << target;

    if (d_bounding_rect.isValid())
        d_bounding_rect |= boundingRectOf(target);
    else
        d_bounding_rect = boundingRectOf(target);

    update();
}

void SelectionMoveResizer::add(QWidget *target)
{
    if (target->parentWidget() != parent())
        return;
    d_widgets << target;
    target->installEventFilter(this);
    connect(target, SIGNAL(destroyed(QObject *)), this, SLOT(removeWidget(QObject *)));

    if (d_bounding_rect.isValid())
        d_bounding_rect |= target->frameGeometry();
    else
        d_bounding_rect = target->frameGeometry();

    update();
}

QRect SelectionMoveResizer::boundingRectOf(QwtPlotMarker *target) const
{
    return ((PlotEnrichement *)target)->rect();
}

int SelectionMoveResizer::removeAll(Legend *target)
{
    int result = d_legend_markers.removeAll(target);
    if (d_legend_markers.isEmpty() && d_line_markers.isEmpty() && d_image_markers.isEmpty()
        && d_widgets.isEmpty())
        delete this;
    else
        recalcBoundingRect();
    return result;
}
int SelectionMoveResizer::removeAll(ArrowMarker *target)
{
    int result = d_line_markers.removeAll(target);
    if (d_legend_markers.isEmpty() && d_line_markers.isEmpty() && d_image_markers.isEmpty()
        && d_widgets.isEmpty())
        delete this;
    else
        recalcBoundingRect();
    return result;
}
int SelectionMoveResizer::removeAll(ImageMarker *target)
{
    int result = d_image_markers.removeAll(target);
    if (d_legend_markers.isEmpty() && d_line_markers.isEmpty() && d_image_markers.isEmpty()
        && d_widgets.isEmpty())
        delete this;
    else
        recalcBoundingRect();
    return result;
}

int SelectionMoveResizer::removeAll(QWidget *target)
{
    int result = d_widgets.removeAll(target);
    if (d_legend_markers.isEmpty() && d_line_markers.isEmpty() && d_image_markers.isEmpty()
        && d_widgets.isEmpty())
        delete this;
    else
        recalcBoundingRect();
    return result;
}

void SelectionMoveResizer::recalcBoundingRect()
{
    d_bounding_rect = QRect(0, 0, -1, -1);

    foreach (Legend *i, d_legend_markers) {
        if (d_bounding_rect.isValid())
            d_bounding_rect |= boundingRectOf(i);
        else
            d_bounding_rect = boundingRectOf(i);
    }
    foreach (ArrowMarker *i, d_line_markers) {
        if (d_bounding_rect.isValid())
            d_bounding_rect |= boundingRectOf(i);
        else
            d_bounding_rect = boundingRectOf(i);
    }
    foreach (ImageMarker *i, d_image_markers) {
        if (d_bounding_rect.isValid())
            d_bounding_rect |= boundingRectOf(i);
        else
            d_bounding_rect = boundingRectOf(i);
    }
    foreach (QWidget *i, d_widgets) {
        if (d_bounding_rect.isValid())
            d_bounding_rect |= i->frameGeometry();
        else
            d_bounding_rect = i->frameGeometry();
    }

    update();
}

const QRect SelectionMoveResizer::handlerRect(QRect rect, Operation op)
{
    switch (op) {
    case Resize_N:
        return QRect(QPoint(rect.center().x() - handler_size / 2, rect.top() + 1),
                     QSize(handler_size, handler_size));
    case Resize_NE:
        return QRect(QPoint(rect.right() - handler_size + 1, rect.top() + 1),
                     QSize(handler_size, handler_size));
    case Resize_E:
        return QRect(
                QPoint(rect.right() - handler_size + 1, rect.center().y() - handler_size / 2 + 1),
                QSize(handler_size, handler_size));
    case Resize_SE:
        return QRect(QPoint(rect.right() - handler_size + 1, rect.bottom() - handler_size + 1),
                     QSize(handler_size, handler_size));
    case Resize_S:
        return QRect(QPoint(rect.center().x() - handler_size / 2, rect.bottom() - handler_size + 1),
                     QSize(handler_size, handler_size));
    case Resize_SW:
        return QRect(QPoint(rect.left(), rect.bottom() - handler_size + 1),
                     QSize(handler_size, handler_size));
    case Resize_W:
        return QRect(QPoint(rect.left(), rect.center().y() - handler_size / 2 + 1),
                     QSize(handler_size, handler_size));
    case Resize_NW:
        return QRect(rect.topLeft() + QPoint(0, 1), QSize(handler_size, handler_size));
    default:
        return QRect(rect.topLeft(), QSize(0, 0));
    }
}

QRect SelectionMoveResizer::operateOn(const QRect in)
{
    QRect boundary_out = d_bounding_rect;
    switch (d_op) {
    case Move:
        boundary_out.translate(d_op_dp);
        break;
    case Resize_N:
        boundary_out.setTop(boundary_out.top() + d_op_dp.y());
        break;
    case Resize_E:
        boundary_out.setRight(boundary_out.right() + d_op_dp.x());
        break;
    case Resize_S:
        boundary_out.setBottom(boundary_out.bottom() + d_op_dp.y());
        break;
    case Resize_W:
        boundary_out.setLeft(boundary_out.left() + d_op_dp.x());
        break;
    case Resize_NE:
        boundary_out.setTop(boundary_out.top() + d_op_dp.y());
        boundary_out.setRight(boundary_out.right() + d_op_dp.x());
        break;
    case Resize_SE:
        boundary_out.setBottom(boundary_out.bottom() + d_op_dp.y());
        boundary_out.setRight(boundary_out.right() + d_op_dp.x());
        break;
    case Resize_SW:
        boundary_out.setBottom(boundary_out.bottom() + d_op_dp.y());
        boundary_out.setLeft(boundary_out.left() + d_op_dp.x());
        break;
    case Resize_NW:
        boundary_out.setTop(boundary_out.top() + d_op_dp.y());
        boundary_out.setLeft(boundary_out.left() + d_op_dp.x());
        break;
    default:
        return in;
    }
    boundary_out = boundary_out.normalized();
    if (in == d_bounding_rect)
        return boundary_out;
    double scale_x = ((double)boundary_out.width()) / d_bounding_rect.width();
    double scale_y = ((double)boundary_out.height()) / d_bounding_rect.height();
    int offset_x = qRound(boundary_out.left() - d_bounding_rect.left() * scale_x);
    int offset_y = qRound(boundary_out.top() - d_bounding_rect.top() * scale_y);
    return QRect(
            QPoint(qRound(in.left() * scale_x) + offset_x, qRound(in.top() * scale_y) + offset_y),
            QSize(qRound(in.width() * scale_x), qRound(in.height() * scale_y)));
}

void SelectionMoveResizer::operateOnTargets()
{
    foreach (Legend *i, d_legend_markers) {
        QRect new_rect = operateOn(i->rect());
        i->setOrigin(new_rect.topLeft());
        if (!i->text().isEmpty()) {
            QFont f = i->font();
            f.setPointSize(f.pointSize() * new_rect.width() * new_rect.height()
                           / (i->rect().width() * i->rect().height()));
            i->setFont(f);
        }
    }
    foreach (ArrowMarker *i, d_line_markers) {
        QPoint p1 = i->startPoint();
        QPoint p2 = i->endPoint();
        QRect new_rect = operateOn(i->rect());
        i->setStartPoint(QPoint(p1.x() < p2.x() ? new_rect.left() : new_rect.right(),
                                p1.y() < p2.y() ? new_rect.top() : new_rect.bottom()));
        i->setEndPoint(QPoint(p2.x() < p1.x() ? new_rect.left() : new_rect.right(),
                              p2.y() < p1.y() ? new_rect.top() : new_rect.bottom()));
    }
    foreach (ImageMarker *i, d_image_markers) {
        QRect new_rect = operateOn(i->rect());
        i->setOrigin(new_rect.topLeft());
        i->setSize(new_rect.size());
    }
    foreach (QWidget *i, d_widgets) {
        i->setGeometry(operateOn(i->geometry()));
        // ugly hack
        // see Graph::setIgnoreResizeEvents() and Graph::resizeEvent()
        Graph *g = qobject_cast<Graph *>(i);
        if (g)
            g->plotWidget()->resize(i->size());
    }
    recalcBoundingRect();

    d_op_start = d_op_dp = QPoint(0, 0);

    update();
    emit targetsChanged();
}

void SelectionMoveResizer::paintEvent(QPaintEvent *e)
{
    QPainter p(this);
    QRect drawn_rect = operateOn(d_bounding_rect);

    QPen white_pen(Qt::white, 3, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin);
    p.setPen(white_pen);
    p.drawRect(QRect(QPoint(drawn_rect.left(), drawn_rect.top()), drawn_rect.size()));
    white_pen.setWidth(2);
    p.setPen(white_pen);
    for (int i = 0; i < 8; i++)
        p.drawRect(handlerRect(drawn_rect, (Operation)i));
    p.setPen(QPen(Qt::black, 1, Qt::SolidLine));
    p.drawRect(QRect(QPoint(drawn_rect.left(), drawn_rect.top()), drawn_rect.size()));
    for (int i = 0; i < 8; i++)
        p.fillRect(handlerRect(drawn_rect, (Operation)i), QBrush(Qt::black));

    e->accept();
}

void SelectionMoveResizer::mousePressEvent(QMouseEvent *me)
{
    if (me->button() == Qt::RightButton) {
        // If one of the parents' event handlers deletes me, Qt crashes while trying to send the
        // QContextMenuEvent.
        me->accept();
        return;
    }
    if (me->button() != Qt::LeftButton || !d_bounding_rect.contains(me->pos())) {
        me->ignore();
        return;
    }
    d_op_start = me->pos();
    d_op = Move;
    for (int i = 0; i < 8; i++)
        if (handlerRect(d_bounding_rect, (Operation)i).contains(d_op_start)) {
            d_op = (Operation)i;
            break;
        }

    me->accept();
}

void SelectionMoveResizer::mouseMoveEvent(QMouseEvent *me)
{
    if (d_op == None && d_bounding_rect.contains(me->pos()) && me->button() == Qt::LeftButton) {
        d_op = Move;
        d_op_start = me->pos();
    }
    if (d_op == None || d_op_start == QPoint(-1, -1)) {
        if (d_bounding_rect.contains(me->pos())) {
            setCursor(Qt::SizeAllCursor);
            if (handlerRect(d_bounding_rect, Resize_N).contains(me->pos())
                || handlerRect(d_bounding_rect, Resize_S).contains(me->pos()))
                setCursor(Qt::SizeVerCursor);
            else if (handlerRect(d_bounding_rect, Resize_E).contains(me->pos())
                     || handlerRect(d_bounding_rect, Resize_W).contains(me->pos()))
                setCursor(Qt::SizeHorCursor);
            else if (handlerRect(d_bounding_rect, Resize_NE).contains(me->pos())
                     || handlerRect(d_bounding_rect, Resize_SW).contains(me->pos()))
                setCursor(Qt::SizeBDiagCursor);
            else if (handlerRect(d_bounding_rect, Resize_NW).contains(me->pos())
                     || handlerRect(d_bounding_rect, Resize_SE).contains(me->pos()))
                setCursor(Qt::SizeFDiagCursor);
        } else
            unsetCursor();
        return QWidget::mouseMoveEvent(me);
    }
    d_op_dp = me->pos() - d_op_start;
    repaint();
    me->accept();
}

void SelectionMoveResizer::mouseDoubleClickEvent(QMouseEvent *e)
{
    e->ignore();
}

void SelectionMoveResizer::mouseReleaseEvent(QMouseEvent *me)
{
    if (me->button() != Qt::LeftButton || d_op == None || d_op_start == QPoint(-1, -1))
        return QWidget::mouseReleaseEvent(me);

    d_op_dp = me->pos() - d_op_start;
    operateOnTargets();
    d_op = None;
    me->accept();
}

void SelectionMoveResizer::keyPressEvent(QKeyEvent *ke)
{
    switch (ke->key()) {
    case Qt::Key_Enter:
    case Qt::Key_Return:
        if (d_op == None) {
            ke->ignore();
            break;
        }
        operateOnTargets();
        d_op = None;
        ke->accept();
        break;
    case Qt::Key_Escape:
        delete this;
        ke->accept();
        return;
    case Qt::Key_Left:
        if (d_op == None) {
            d_op = Move;
            d_op_start = QPoint(-1, -1);
        }
        d_op_dp += QPoint(-1, 0);
        repaint();
        ke->accept();
        break;
    case Qt::Key_Right:
        if (d_op == None) {
            d_op = Move;
            d_op_start = QPoint(-1, -1);
        }
        d_op_dp += QPoint(1, 0);
        repaint();
        ke->accept();
        break;
    case Qt::Key_Up:
        if (d_op == None) {
            d_op = Move;
            d_op_start = QPoint(-1, -1);
        }
        d_op_dp += QPoint(0, -1);
        repaint();
        ke->accept();
        break;
    case Qt::Key_Down:
        if (d_op == None) {
            d_op = Move;
            d_op_start = QPoint(-1, -1);
        }
        d_op_dp += QPoint(0, 1);
        repaint();
        ke->accept();
        break;
    default:
        ke->ignore();
    }
}

bool SelectionMoveResizer::eventFilter(QObject *o, QEvent *e)
{
    switch (e->type()) {
    case QEvent::Resize:
        if ((QWidget *)o == parentWidget())
            setGeometry(0, 0, parentWidget()->width(), parentWidget()->height());
        recalcBoundingRect();
        return false;
    case QEvent::Move:
        if ((QWidget *)o != parentWidget())
            recalcBoundingRect();
        return false;
    default:
        return false;
    }
}
