#include "mousehandler.h"
#include "drawingarea.h"
#include "flyinglabels.h"
#include "probleminstance.h"
#include "routenetwork.h"
#include "constants.h"
#include "geomutils.h"
#include <QMouseEvent>

// BASE CLASS FOR MOUSE MODES
MouseMode::MouseMode(MouseHandler* parent) : _parent(parent) {}
MouseMode::~MouseMode() {}

void MouseMode::activate() {}
void MouseMode::deactivate() {}

void MouseMode::setMode(MouseModeType mode) { _parent->setMode(mode); }

RouteNetwork& MouseMode::routeNetwork() { return problemInstance().routeNetwork(); }
ProblemInstance& MouseMode::problemInstance() { return _parent->_parent.problemInstance(); }
MouseAnchor& MouseMode::currentAnchor() { return _parent->_currentAnchor; }

void MouseMode::makeAnchorIntoVertex(MouseAnchor& anchor)
{
    if (anchor.type == VERTEX) return;

    anchor.vertexId = routeNetwork().addVertex(anchor.position);
    if (anchor.type == INTERSECTION) {
        routeNetwork().rerouteEdgesWithin(anchor.vertexId, vertexProximityRadius);
    }
    anchor.type = VERTEX;
}

// TODO: rename as "snapToVertices" and add "snapToAnchor" instead of overloading updatePosition?
// TODO: might need to use some point location structure for speed
void MouseMode::updatePosition(QPointF const& position)
{
    VertexId vertexId; QPointF intersection;
    if (routeNetwork().closestVertexWithin(position, vertexProximityRadius, vertexId)) {
        currentAnchor().vertexId = vertexId;
        currentAnchor().position = routeNetwork().vertexPosition(vertexId);
        currentAnchor().type     = VERTEX;
    } else if (routeNetwork().closestIntersectionWithin(position, vertexProximityRadius, intersection)) {
        currentAnchor().position = intersection;
        currentAnchor().type     = INTERSECTION;
    } else {
        currentAnchor().position = position;
        currentAnchor().type     = POSITION;
    }
}

namespace {
// MOUSE MODE WHEN NOT PRESSING BUTTONS
class DefaultMode : public MouseMode
{
public:
    DefaultMode(MouseHandler* parent_)
        : MouseMode(parent_)
    {}

    void mousePressEvent(QMouseEvent* event, QPointF const& cursor)
    {
        updatePosition(cursor);

        if (event->button() == Qt::LeftButton) {
            setMode(NETWORK_CREATION_MODE);
        } else if (event->button() == Qt::RightButton && currentAnchor().type == VERTEX) {
            setMode(ROUTE_CREATION_MODE);
        } else if (event->button() == Qt::MidButton) {
            setMode(TRANSLATION_MODE);
        }
    }
    void mouseMoveEvent(QMouseEvent*, QPointF const& cursor)
    {
        updatePosition(cursor);
    }
    void mouseReleaseEvent(QMouseEvent*, QPointF const& cursor)
    {
        updatePosition(cursor);
    }

    void draw(DrawingArea* drawing)
    {
        drawing->drawVertex(currentAnchor().position, currentAnchor().type == VERTEX);
        if (currentAnchor().type != POSITION) {
            drawing->drawVertexSelection(currentAnchor().position);
        }
    };
};

// MOUSE MODE WHEN CREATING THE ROUTE NETWORK
class NetworkCreationMode : public MouseMode
{
public:
    NetworkCreationMode(MouseHandler* parent_)
        : MouseMode(parent_)
    {}

    void activate() {
        _startAnchor = currentAnchor();
    }

    void mousePressEvent(QMouseEvent*, QPointF const& cursor)
    {
        updatePosition(cursor);
    }
    void mouseMoveEvent(QMouseEvent*, QPointF const& cursor)
    {
        updatePosition(cursor);
    }
    void mouseReleaseEvent(QMouseEvent* event, QPointF const& cursor)
    {
        updatePosition(cursor);
        if (event->button() == Qt::LeftButton) {
            if (_startAnchor.position == currentAnchor().position) {
                // adding a new vertex
                makeAnchorIntoVertex(currentAnchor());
            } else {
                // adding a (possibly new) edge
                makeAnchorIntoVertex(_startAnchor);
                makeAnchorIntoVertex(currentAnchor());
                routeNetwork().addEdge(_startAnchor.vertexId, currentAnchor().vertexId);
            }
            updatePosition(event->posF());
            setMode(DEFAULT_MODE);
        }
    }

    void updatePosition(QPointF const& position)
    {
        if (distanceSquared(_startAnchor.position, position) <= vertexProximityRadiusSquared) {
            currentAnchor() = _startAnchor;
        } else {
            MouseMode::updatePosition(position);
        }
    }

    void draw(DrawingArea* drawing)
    {
        if (currentAnchor().position != _startAnchor.position) {
            drawing->drawNewEdge(_startAnchor.position, currentAnchor().position);
            drawing->drawVertex(_startAnchor.position, _startAnchor.type == VERTEX);
        }
        drawing->drawVertex(currentAnchor().position, currentAnchor().type == VERTEX);
        if (currentAnchor().type != POSITION || currentAnchor().position == _startAnchor.position) {
            drawing->drawVertexSelection(currentAnchor().position);
        }
    }

private:
    MouseAnchor _startAnchor;
};

// MOUSE MODE WHEN CREATING FLIGHT PATHS ON THE ROUTE NETWORK
class RouteCreationMode : public MouseMode
{
public:
    RouteCreationMode(MouseHandler* parent_)
        : MouseMode(parent_)
    {
    }

    void activate()
    {
        startVertex = currentAnchor().vertexId;
        routeNetwork().singleSourceShortestPaths(startVertex, predecessors, distances);
    }

    void mousePressEvent(QMouseEvent*, QPointF const& cursor)
    {
        updatePosition(cursor);
    }
    void mouseMoveEvent(QMouseEvent*, QPointF const& cursor)
    {
        updatePosition(cursor);
    }
    void mouseReleaseEvent(QMouseEvent* event, QPointF const& cursor)
    {
        updatePosition(cursor);
        if (event->button() == Qt::RightButton) {
            if (currentAnchor().type == VERTEX) {
                VertexId endVertex = currentAnchor().vertexId;
                bool reachable = predecessors[endVertex] != endVertex;
                if (reachable) {
                    problemInstance().addRoute(startVertex, endVertex, predecessors);
                }
            }
            setMode(DEFAULT_MODE);
        }
    }

    void draw(DrawingArea* drawing)
    {
        // if there is a path from the start vertex to the mouse vertex, draw it
        if (currentAnchor().type == VERTEX) {
            drawing->drawVertexSelection(currentAnchor().position);

            VertexId to = currentAnchor().vertexId;
            VertexId from = predecessors[to];
            while (from != to) {
                drawing->drawNewRouteArrow( routeNetwork().vertexPosition(from),
                                            routeNetwork().vertexPosition(to) );
                to = from;
                from = predecessors[to];
            }
            if (to == startVertex) {
                drawing->drawNewRouteAnchor( routeNetwork().vertexPosition(startVertex) );
                drawing->drawNewRouteAnchor( currentAnchor().position );
                return;
            }
        }

        // otherwise, draw a dotted line to the mouse cursor
        drawing->drawMissingRoute( routeNetwork().vertexPosition(startVertex), currentAnchor().position );
        drawing->drawNewRouteAnchor( routeNetwork().vertexPosition(startVertex) );
    }
private:
    PredecessorMap predecessors;
    DistanceMap distances;
    VertexId startVertex;
};

// MOUSE MODE WHEN TRANSLATING THE CANVAS
class TranslationMode : public MouseMode
{
public:
    TranslationMode(MouseHandler* parent_)
        : MouseMode(parent_)
    {
    }

    void activate()
    {
        // use actual mouse coordinates for translation, rather than the transformed
        // anchor, as the former changes more smoothly
        _startLocation = QCursor::pos();
        _lastTranslation = QPoint(0, 0);
    }

    void mousePressEvent(QMouseEvent*, QPointF const&)
    {
    }
    void mouseMoveEvent(QMouseEvent*, QPointF const&)
    {
    }
    void mouseReleaseEvent(QMouseEvent* event, QPointF const&)
    {
        if (event->button() == Qt::MidButton) {
            setMode(DEFAULT_MODE);
        }
    }
    void draw(DrawingArea* drawing)
    {
        QPoint newTranslation = QCursor::pos() - _startLocation;
        drawing->addTranslation( newTranslation - _lastTranslation );
        _lastTranslation = newTranslation;
    }

private:
    QPoint _startLocation, _lastTranslation;
};

} // unnamed namespace

MouseHandler::MouseHandler(FlyingLabels& parent)
    : _parent(parent)
{
    for (int i = 0; i < NUMBER_OF_MODES; ++i) {
        _modes[i] = nullptr;
    }
    _modes[DEFAULT_MODE]          = new DefaultMode(this);
    _modes[NETWORK_CREATION_MODE] = new NetworkCreationMode(this);
    _modes[ROUTE_CREATION_MODE]   = new RouteCreationMode(this);
    _modes[TRANSLATION_MODE]      = new TranslationMode(this);
    _currentMode = _modes[DEFAULT_MODE];
}

MouseHandler::~MouseHandler()
{
    _currentMode = nullptr;
    for (int i = 0; i < NUMBER_OF_MODES; ++i) {
        delete _modes[i];
    }
}

void MouseHandler::setMode(MouseModeType mode)
{
    _currentMode->deactivate();
    _currentMode = _modes[mode];
    _currentMode->activate();
}

void MouseHandler::draw(DrawingArea* drawing)
{
    _currentMode->draw(drawing);
}

void MouseHandler::mousePressEvent(QMouseEvent* event, QPointF const& cursor)
{
    return _currentMode->mousePressEvent(event, cursor);
}

void MouseHandler::mouseMoveEvent(QMouseEvent* event, QPointF const& cursor)
{
    return _currentMode->mouseMoveEvent(event, cursor);
}

void MouseHandler::mouseReleaseEvent(QMouseEvent* event, const QPointF &cursor)
{
    return _currentMode->mouseReleaseEvent(event, cursor);
}
