#include "drawingarea.h"
#include "flyinglabels.h"
#include "constants.h"
#include "labelmodel.h"
#include "geomutils.h"

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

DrawingArea::DrawingArea(QWidget* parent) :
    QWidget(parent), _viewUpperLeft(0.0, 0.0), _zoomFactor(1.0), _zoomSteps(0.0), _currentlyDrawing(false)
{
    setAttribute(Qt::WA_StaticContents);
}

void DrawingArea::startDrawing()
{
    _currentlyDrawing = true;
    _image.fill(Qt::white);
}

void DrawingArea::drawImage(QRectF const& rect, QImage const& image)
{
    QPainter painter(&_image);
    applyTransformation(painter);
    painter.drawImage(rect, image, image.rect());
}

void DrawingArea::drawExistingVertex(QPointF const& position)
{
    drawVertex(position, true);
}
void DrawingArea::drawNewVertex(QPointF const& position)
{
    drawVertex(position, false);
}
void DrawingArea::drawVertex(QPointF const& position, bool preExisting)
{
    QPainter painter(&_image);
    applyTransformation(painter);
    painter.setBrush( QBrush(preExisting ? Qt::gray : Qt::red, Qt::SolidPattern) );
    painter.drawEllipse(position, vertexRadius, vertexRadius);
}
void DrawingArea::drawPoint(QPointF const& position, bool hasFreeLabel)
{
    QPainter painter(&_image);
    applyTransformation(painter);
    painter.setPen( QPen(hasFreeLabel ? Qt::darkBlue : Qt::darkRed) );
    painter.setBrush( QBrush(hasFreeLabel ? Qt::blue : Qt::red, Qt::SolidPattern) );
    painter.drawEllipse(position, vertexRadius, vertexRadius);
}

void DrawingArea::drawVertexSelection(QPointF const& position)
{
    QPainter painter(&_image);
    applyTransformation(painter);
    painter.setPen(QPen(Qt::red, 1, Qt::SolidLine, Qt::RoundCap,
                        Qt::RoundJoin));
    painter.drawEllipse(position, vertexProximityRadius, vertexProximityRadius);
}

void DrawingArea::drawExistingEdge(QPointF const& start, QPointF const& end)
{
    drawEdge(start, end, true);
}

void DrawingArea::drawNewEdge(QPointF const& start, QPointF const& end)
{
    drawEdge(start, end, false);
}

void DrawingArea::drawEdge(QPointF const& start, QPointF const& end, bool preExisting)
{
    QPainter painter(&_image);
    applyTransformation(painter);
    painter.setPen(QPen(preExisting ? Qt::gray : Qt::red, 1, Qt::SolidLine,
                        Qt::RoundCap, Qt::RoundJoin));
    painter.drawLine(start, end);
}

void DrawingArea::drawExistingRouteAnchor(QPointF const& position)
{
    drawRouteAnchor(position, true);
}

void DrawingArea::drawNewRouteAnchor(QPointF const& position)
{
    drawRouteAnchor(position, false);
}

void DrawingArea::drawRouteAnchor(QPointF const& position, bool preExisting)
{
    QPainter painter(&_image);
    applyTransformation(painter);
    painter.setBrush( QBrush(preExisting ? Qt::green : Qt::blue, Qt::SolidPattern) );
    painter.drawEllipse(position, vertexRadius, vertexRadius);
}

void DrawingArea::drawExistingRouteArrow(QPointF const& start, QPointF const& end)
{
    drawRouteArrow(start, end, true);
}

void DrawingArea::drawNewRouteArrow(QPointF const& start, QPointF const& end)
{
    drawRouteArrow(start, end, false);
}

void DrawingArea::drawRouteArrow(QPointF const& start, QPointF const& end, bool preExisting)
{
    QPainter painter(&_image);
    applyTransformation(painter);
    painter.setPen(QPen(preExisting ? Qt::darkGreen : Qt::blue,
                        1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));

    QPointF dir = end - start;
    qreal distance = sqrt(dir.x()*dir.x() + dir.y()*dir.y());
    dir /= distance;
    QPointF perp = perpendicular(dir);

    int numArrows = std::max(1, (int)floor((distance - 2*vertexRadius) / arrowSpacing));
    qreal spacing = distance / numArrows;
    QPointF arrowTip = start + (0.5*spacing + vertexRadius)*dir;
    for (int i = 0; i < numArrows; ++i) {
        QPointF leftEnd  = arrowTip - 0.5*arrowHeadWidth*perp - arrowHeadLength*dir;
        QPointF rightEnd = arrowTip + 0.5*arrowHeadWidth*perp - arrowHeadLength*dir;
        painter.drawLine(leftEnd,  arrowTip);
        painter.drawLine(rightEnd, arrowTip);
        arrowTip += spacing*dir;
    }
}

void DrawingArea::drawMissingRoute(QPointF const& start, QPointF const& end)
{
    QPainter painter(&_image);
    applyTransformation(painter);
    painter.setPen(QPen(Qt::blue, 1, Qt::DashLine,
                        Qt::RoundCap, Qt::RoundJoin));
    painter.drawLine(start, end);
}

void DrawingArea::drawLabelBBox(QPointF const& labelCenter, qreal labelFreeness)
{
    QPainter painter(&_image);
    applyTransformation(painter);

    if (labelFreeness < 0.0) { labelFreeness = 0.0; }
    if (labelFreeness > 1.0) { labelFreeness = 1.0; }
    QColor penColor = QColor::fromRgbF(1.0 - labelFreeness, 0, labelFreeness);
    QColor fillColor = penColor;
    fillColor.setAlphaF(0.3);

    int w = labelModel().labelWidth();
    int h = labelModel().labelHeight();
    QPointF delta(0.5*w, 0.5*h);
    QRectF bbox(labelCenter - delta, labelCenter + delta);

    painter.setBrush( QBrush(fillColor, Qt::SolidPattern) );
    painter.setPen( QPen(penColor) );
    painter.drawRect(bbox);
}

void DrawingArea::drawLabelText(QPointF const& labelCenter, QString const& text)
{
    QPainter painter(&_image);
    applyTransformation(painter);

    int w = labelModel().labelWidth();
    int h = labelModel().labelHeight();
    QPointF delta(0.5*w, 0.5*h);
    QRectF bbox(labelCenter - delta, labelCenter + delta);

    painter.setFont(labelFont());
    painter.drawText(bbox, Qt::AlignCenter, text);
}

void DrawingArea::drawLabelCenter(QPointF const& labelCenter)
{
    QPainter painter(&_image);
    applyTransformation(painter);

    painter.setPen(QPen(Qt::black, 2));
    qreal offset = 0.5*std::sqrt(2)*vertexRadius;
    painter.drawLine( labelCenter + QPointF(-offset, -offset),
                      labelCenter + QPointF(+offset, +offset) );
    painter.drawLine( labelCenter + QPointF(-offset, +offset),
                      labelCenter + QPointF(+offset, -offset) );
}

void DrawingArea::drawLabelCandidateSegment(QPointF const& start, QPointF const& end)
{
    QPainter painter(&_image);
    applyTransformation(painter);
    painter.setPen(QPen(Qt::darkGreen, 2, Qt::SolidLine));
    painter.drawLine(start, end);
}

void DrawingArea::endDrawing()
{
    _currentlyDrawing = false;
    update();
}

void DrawingArea::applyTransformation(QPainter& painter) const
{
    painter.scale(_zoomFactor, _zoomFactor);
    painter.translate(-_viewUpperLeft);
}

void DrawingArea::zoomIn(QPointF const& center, qreal steps)
{
    performZoomIn(untransform(center), center, steps);
}

void DrawingArea::zoomOut(QPointF const& center, qreal steps)
{
    zoomIn(center, -steps);
}

void DrawingArea::resetZoom()
{
    _zoomFactor = 1.0;
    _zoomSteps = 0;
}

void DrawingArea::performZoomIn(QPointF const& untransformedCenter, QPointF const& transformedCenter, qreal steps)
{
    _zoomSteps += steps;
    _zoomFactor = std::exp2(_zoomSteps / 5.0);

    QPointF centerToUpperLeft = -untransformedCenter / _zoomFactor;
    _viewUpperLeft = transformedCenter + centerToUpperLeft;
    centerToUpperLeft += QPoint(42, 42);
}

QPointF const& DrawingArea::translation() const
{
    return _viewUpperLeft;
}

void DrawingArea::addTranslation(QPointF const& v)
{
    _viewUpperLeft -= v / _zoomFactor;
}

void DrawingArea::resetTranslation()
{
    _viewUpperLeft = QPointF(0.0, 0.0);
}

FlyingLabels* DrawingArea::mainWindow() const
{
    return (FlyingLabels*)(parent()->parent());
}

LabelModel const& DrawingArea::labelModel() const
{
    return mainWindow()->problemInstance().labelModel();
}

QFont DrawingArea::labelFont() const
{
    return mainWindow()->problemInstance().labelFont();
}

QPointF DrawingArea::transform(QPointF const& pos) const
{
    QPointF result = pos;
    result /= _zoomFactor;
    result += _viewUpperLeft;
    return std::move(result);
}

QPointF DrawingArea::untransform(QPointF const& pos) const
{
    QPointF result = pos;
    result -= _viewUpperLeft;
    result *= _zoomFactor;
    return std::move(result);
}

void DrawingArea::mousePressEvent(QMouseEvent* event)
{
    mainWindow()->mousePressEvent(event, transform(event->posF()));
}

void DrawingArea::mouseMoveEvent(QMouseEvent* event)
{
    mainWindow()->mouseMoveEvent(event, transform(event->posF()));
}

void DrawingArea::mouseReleaseEvent(QMouseEvent* event)
{
    mainWindow()->mouseReleaseEvent(event, transform(event->posF()));
}

void DrawingArea::leaveEvent(QEvent*)
{
    // When the mouse leaves the DrawingArea, Qt issues a leaveEvent, but only
    // if no mouse buttons are pressed.  Otherwise, it issues a mouseMoveEvent.
    // For our purposes, it's most convenient if a mouseMoveEvent is issued in
    // both cases, so let's do just that.

    // Adjust the mouse position somewhat, so it's really outside the DrawingArea.
    QPoint globalPos = QCursor::pos() + QPoint(20, 20);
    QPoint localPos = mapFromGlobal(globalPos);

    QMouseEvent event(QEvent::MouseMove,
                      localPos,
                      globalPos,
                      Qt::NoButton,
                      QApplication::mouseButtons(),
                      QApplication::keyboardModifiers());
    mouseMoveEvent(&event);
}

void DrawingArea::wheelEvent(QWheelEvent* event)
{
    // delta() is in 8ths of a degree, and most mice work in steps of 15 degrees,
    // according to the documentation on QWheelEvent::delta()
    qreal rotationInDegrees = event->delta() / 8.0;
    qreal steps = rotationInDegrees / 15.0;
    performZoomIn(event->pos(), transform(event->pos()), steps);
}

void DrawingArea::paintEvent(QPaintEvent* event)
{
    QPainter painter(this);
    QRect dirtyRect = event->rect();
    painter.drawImage(dirtyRect, _image, dirtyRect);
}

void DrawingArea::resizeEvent(QResizeEvent* event)
{
    if (width() > _image.width() || height() > _image.height()) {
        int w = std::max(width() + 128, _image.width());
        int h = std::max(height() + 128, _image.height());
        QImage newImage(w, h, QImage::Format_RGB32);
        newImage.fill(Qt::white);
        QPainter painter(&newImage);
        painter.drawImage(QPoint(0,0), _image);
        _image = std::move(newImage);
        update();
    }
    QWidget::resizeEvent(event);
}
