/*
    TikZiT - a GUI diagram editor for TikZ
    Copyright (C) 2018 Aleks Kissinger

    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 3 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, see <https://www.gnu.org/licenses/>.
*/

#include "undocommands.h"
#include "nodeitem.h"
#include "edgeitem.h"

#include <QApplication>
#include <QGraphicsView>

GraphUpdateCommand::GraphUpdateCommand(TikzScene *scene, QUndoCommand *parent) : QUndoCommand(parent), _scene(scene)
{
}

void GraphUpdateCommand::undo()
{
    _scene->tikzDocument()->refreshTikz();
    _scene->refreshSceneBounds();
    _scene->invalidate();
}

void GraphUpdateCommand::redo()
{
    _scene->tikzDocument()->refreshTikz();
    _scene->refreshSceneBounds();
    _scene->invalidate();
}


MoveCommand::MoveCommand(TikzScene *scene,
                         QMap<Node*, QPointF> oldNodePositions,
                         QMap<Node*, QPointF> newNodePositions,
                         QUndoCommand *parent) :
    GraphUpdateCommand(scene, parent),
    _oldNodePositions(oldNodePositions),
    _newNodePositions(newNodePositions)
{}


void MoveCommand::undo()
{
    foreach (NodeItem *ni, _scene->nodeItems()) {
        if (_oldNodePositions.contains(ni->node())) {
            ni->node()->setPoint(_oldNodePositions.value(ni->node()));
            ni->readPos();
        }
    }

    _scene->refreshAdjacentEdges(_oldNodePositions.keys());
    GraphUpdateCommand::undo();
}

void MoveCommand::redo()
{
    foreach (NodeItem *ni, _scene->nodeItems()) {
        if (_newNodePositions.contains(ni->node())) {
            ni->node()->setPoint(_newNodePositions.value(ni->node()));
            ni->readPos();
        }
    }

    _scene->refreshAdjacentEdges(_newNodePositions.keys());
    GraphUpdateCommand::redo();
}

EdgeBendCommand::EdgeBendCommand(TikzScene *scene, Edge *edge,
                                 qreal oldWeight, int oldBend,
                                 int oldInAngle, int oldOutAngle, QUndoCommand *parent) :
    GraphUpdateCommand(scene, parent),
    _edge(edge),
    _oldWeight(oldWeight), _oldBend(oldBend),
    _oldInAngle(oldInAngle), _oldOutAngle(oldOutAngle)
{
    _newWeight = edge->weight();
    _newBend = edge->bend();
    _newInAngle = edge->inAngle();
    _newOutAngle = edge->outAngle();
}

void EdgeBendCommand::undo()
{
    _edge->setWeight(_oldWeight);
    _edge->setBend(_oldBend);
    _edge->setInAngle(_oldInAngle);
    _edge->setOutAngle(_oldOutAngle);

    foreach(EdgeItem *ei, _scene->edgeItems()) {
        if (ei->edge() == _edge) {
            ei->readPos();

            Path *p = ei->edge()->path();
            if (p) _scene->pathItems()[p]->readPos();
            break;
        }
    }
    GraphUpdateCommand::undo();
}

void EdgeBendCommand::redo()
{
    _edge->setWeight(_newWeight);
    _edge->setBend(_newBend);
    _edge->setInAngle(_newInAngle);
    _edge->setOutAngle(_newOutAngle);

    foreach(EdgeItem *ei, _scene->edgeItems()) {
        if (ei->edge() == _edge) {
            ei->readPos();

            Path *p = ei->edge()->path();
            if (p) _scene->pathItems()[p]->readPos();
            break;
        }
    }

    GraphUpdateCommand::redo();
}

DeleteCommand::DeleteCommand(TikzScene *scene,
                             QMap<int, Node *> deleteNodes,
                             QMap<int, Edge *> deleteEdges,
                             QSet<Node *> selNodes,
                             QSet<Edge *> selEdges,
                             QUndoCommand *parent) :
    GraphUpdateCommand(scene, parent),
    _deleteNodes(deleteNodes), _deleteEdges(deleteEdges),
    _selNodes(selNodes), _selEdges(selEdges)
{}

void DeleteCommand::undo()
{
    for (auto it = _deleteNodes.begin(); it != _deleteNodes.end(); ++it) {
        Node *n = it.value();
        n->attachStyle(); // in case styles have changed
        _scene->graph()->addNode(n, it.key());
        NodeItem *ni = new NodeItem(n);
        _scene->nodeItems().insert(n, ni);
        _scene->addItem(ni);
        if (_selNodes.contains(n)) ni->setSelected(true);
    }

    for (auto it = _deleteEdges.begin(); it != _deleteEdges.end(); ++it) {
        Edge *e = it.value();
		e->attachStyle();
        _scene->graph()->addEdge(e, it.key());
        EdgeItem *ei = new EdgeItem(e);
        _scene->edgeItems().insert(e, ei);
        _scene->addItem(ei);

        if (_selEdges.contains(e)) ei->setSelected(true);
    }

    _scene->refreshZIndices();
    GraphUpdateCommand::undo();
}

void DeleteCommand::redo()
{
    foreach (Edge *e, _deleteEdges.values()) {
        EdgeItem *ei = _scene->edgeItems()[e];
        _scene->edgeItems().remove(e);
        _scene->removeItem(ei);
        delete ei;

        _scene->graph()->removeEdge(e);
    }

    foreach (Node *n, _deleteNodes.values()) {
        NodeItem *ni = _scene->nodeItems()[n];
        _scene->nodeItems().remove(n);
        _scene->removeItem(ni);
        delete ni;

        _scene->graph()->removeNode(n);
    }

    _scene->refreshZIndices();
    GraphUpdateCommand::redo();
}

AddNodeCommand::AddNodeCommand(TikzScene *scene, Node *node, QRectF newBounds, QUndoCommand *parent) :
    GraphUpdateCommand(scene, parent), _node(node), _oldBounds(_scene->sceneRect()), _newBounds(newBounds)
{
}

void AddNodeCommand::undo()
{
    NodeItem *ni = _scene->nodeItems()[_node];
    _scene->removeItem(ni);
    _scene->nodeItems().remove(_node);
    delete ni;

    _scene->graph()->removeNode(_node);

    //_scene->setBounds(_oldBounds);

    _scene->refreshZIndices();
    GraphUpdateCommand::undo();
}

void AddNodeCommand::redo()
{
    _node->attachStyle(); // do for every redo, in case styles have changed
    _scene->graph()->addNode(_node);
    NodeItem *ni = new NodeItem(_node);
    _scene->nodeItems().insert(_node, ni);
    _scene->addItem(ni);

    //_scene->setBounds(_newBounds);

    _scene->refreshZIndices();
    GraphUpdateCommand::redo();
}

AddEdgeCommand::AddEdgeCommand(TikzScene *scene,
                               Edge *edge,
                               bool selectEdge,
                               QSet<Node*> selNodes,
                               QSet<Edge*> selEdges,
                               QUndoCommand *parent) :
    GraphUpdateCommand(scene, parent), _edge(edge),
    _selectEdge(selectEdge), _selNodes(selNodes), _selEdges(selEdges)
{
}


void AddEdgeCommand::undo()
{
    EdgeItem *ei = _scene->edgeItems()[_edge];
    _scene->removeItem(ei);
    _scene->edgeItems().remove(_edge);
    delete ei;

    _scene->graph()->removeEdge(_edge);
    _scene->refreshZIndices();

    if (_selectEdge) {
        foreach (NodeItem *ni, _scene->nodeItems()) {
            ni->setSelected(_selNodes.contains(ni->node()));
        }

        foreach (EdgeItem *ei, _scene->edgeItems()) {
            ei->setSelected(_selEdges.contains(ei->edge()));
        }
    }

    GraphUpdateCommand::undo();
}

void AddEdgeCommand::redo()
{
    _edge->attachStyle(); // do for every redo, in case styles have changed
    _scene->graph()->addEdge(_edge);
    EdgeItem *ei = new EdgeItem(_edge);
    _scene->edgeItems().insert(_edge, ei);
    _scene->addItem(ei);

    if (!_scene->graph()->nodes().isEmpty()) {
        ei->stackBefore(_scene->nodeItems()[_scene->graph()->nodes().first()]);
    }

    _scene->refreshZIndices();

    if (_selectEdge) {
        _scene->clearSelection();
        ei->setSelected(true);
    }
    GraphUpdateCommand::redo();
}

ChangeEdgeModeCommand::ChangeEdgeModeCommand(TikzScene *scene, Edge *edge, QUndoCommand *parent) :
    GraphUpdateCommand(scene, parent), _edge(edge)
{
}

void ChangeEdgeModeCommand::undo()
{
    // FIXME: this act strangely sometimes
	_edge->setBasicBendMode(!_edge->basicBendMode());
    _scene->edgeItems()[_edge]->readPos();
    Path *p = _edge->path();
    if (p) _scene->pathItems()[p]->readPos();

    GraphUpdateCommand::undo();
}

void ChangeEdgeModeCommand::redo()
{
    _edge->setBasicBendMode(!_edge->basicBendMode());
    _scene->edgeItems()[_edge]->readPos();
    Path *p = _edge->path();
    if (p) _scene->pathItems()[p]->readPos();

    GraphUpdateCommand::redo();
}

ApplyStyleToNodesCommand::ApplyStyleToNodesCommand(TikzScene *scene, QString style, QUndoCommand *parent) :
    GraphUpdateCommand(scene, parent), _style(style), _oldStyles()
{
    foreach (QGraphicsItem *it, scene->selectedItems()) {
        if (NodeItem *ni = dynamic_cast<NodeItem*>(it)) {
            _oldStyles.insert(ni->node(), ni->node()->styleName());
        }
    }
}

void ApplyStyleToNodesCommand::undo()
{
    foreach (Node *n, _oldStyles.keys()) {
        n->setStyleName(_oldStyles[n]);
        n->attachStyle();
    }
	_scene->refreshAdjacentEdges(_oldStyles.keys());

    GraphUpdateCommand::undo();
}

void ApplyStyleToNodesCommand::redo()
{
    foreach (Node *n, _oldStyles.keys()) {
        n->setStyleName(_style);
        n->attachStyle();
    }
	_scene->refreshAdjacentEdges(_oldStyles.keys());

    GraphUpdateCommand::redo();
}


ApplyStyleToEdgesCommand::ApplyStyleToEdgesCommand(TikzScene * scene, QString style, QUndoCommand * parent) :
	GraphUpdateCommand(scene, parent), _style(style), _oldStyles()
{
	foreach(QGraphicsItem *it, scene->selectedItems()) {
		if (EdgeItem *ei = dynamic_cast<EdgeItem*>(it)) {
			_oldStyles.insert(ei->edge(), ei->edge()->styleName());
		}
	}
}

void ApplyStyleToEdgesCommand::undo()
{
	foreach(Edge *e, _oldStyles.keys()) {
		e->setStyleName(_oldStyles[e]);
		e->attachStyle();
	}

	GraphUpdateCommand::undo();
}

void ApplyStyleToEdgesCommand::redo()
{
	foreach(Edge *e, _oldStyles.keys()) {
		e->setStyleName(_style);
		e->attachStyle();
	}
	GraphUpdateCommand::redo();
}

PasteCommand::PasteCommand(TikzScene *scene, Graph *graph, QUndoCommand *parent) :
    GraphUpdateCommand(scene, parent), _graph(graph)
{
	scene->getSelection(_oldSelectedNodes, _oldSelectedEdges);
}

void PasteCommand::undo()
{
    _scene->clearSelection();

    foreach (Path *p, _graph->paths()) {
        PathItem *pi = _scene->pathItems()[p];
        _scene->pathItems().remove(p);
        _scene->removeItem(pi);
        delete pi;

        p->removeEdges();
        _scene->graph()->removePath(p);
    }

    foreach (Edge *e, _graph->edges()) {
        EdgeItem *ei = _scene->edgeItems()[e];
        _scene->edgeItems().remove(e);
        _scene->removeItem(ei);
        delete ei;

        _scene->graph()->removeEdge(e);
    }

    foreach (Node *n, _graph->nodes()) {
        NodeItem *ni = _scene->nodeItems()[n];
        _scene->nodeItems().remove(n);
        _scene->removeItem(ni);
        delete ni;

        _scene->graph()->removeNode(n);
    }

	foreach(Node *n, _oldSelectedNodes) _scene->nodeItems()[n]->setSelected(true);
	foreach(Edge *e, _oldSelectedEdges) _scene->edgeItems()[e]->setSelected(true);

    _scene->refreshZIndices();
    GraphUpdateCommand::undo();
}

void PasteCommand::redo()
{
    _scene->clearSelection();
    _scene->graph()->insertGraph(_graph);

    foreach (Path *p, _graph->paths()) {
        PathItem *pi = new PathItem(p);
        _scene->pathItems().insert(p, pi);
        _scene->addItem(pi);
    }

    foreach (Edge *e, _graph->edges()) {
		e->attachStyle(); // in case styles have changed
        EdgeItem *ei = new EdgeItem(e);
        _scene->edgeItems().insert(e, ei);
        _scene->addItem(ei);
    }

    foreach (Node *n, _graph->nodes()) {
        n->attachStyle(); // in case styles have changed
        NodeItem *ni = new NodeItem(n);
        _scene->nodeItems().insert(n, ni);
        _scene->addItem(ni);
        ni->setSelected(true);
    }

    _scene->refreshZIndices();
    GraphUpdateCommand::redo();
}

ChangeLabelCommand::ChangeLabelCommand(TikzScene *scene, QMap<Node *, QString> oldLabels, QString newLabel, QUndoCommand *parent) :
    GraphUpdateCommand(scene, parent), _oldLabels(oldLabels), _newLabel(newLabel)
{
}

void ChangeLabelCommand::undo()
{
    foreach (Node *n, _oldLabels.keys()) {
        n->setLabel(_oldLabels[n]);
		NodeItem *ni = _scene->nodeItems()[n];
		if (ni != nullptr) ni->updateBounds();
    }

    GraphUpdateCommand::undo();
}

void ChangeLabelCommand::redo()
{
    foreach (Node *n, _oldLabels.keys()) {
        n->setLabel(_newLabel);
		NodeItem *ni = _scene->nodeItems()[n];
		if (ni != nullptr) ni->updateBounds();
    }

    GraphUpdateCommand::redo();
}

ReplaceGraphCommand::ReplaceGraphCommand(TikzScene *scene, Graph *oldGraph, Graph *newGraph, QUndoCommand *parent) :
    GraphUpdateCommand(scene, parent), _oldGraph(oldGraph), _newGraph(newGraph)
{
}

void ReplaceGraphCommand::undo()
{
    foreach (Node *n, _oldGraph->nodes()) n->attachStyle();
    foreach (Edge *e, _oldGraph->edges()) {
        e->attachStyle();
        e->updateControls();
    }
    _scene->tikzDocument()->setGraph(_oldGraph);
    _scene->graphReplaced();
    GraphUpdateCommand::undo();
}

void ReplaceGraphCommand::redo()
{
    foreach (Node *n, _newGraph->nodes()) n->attachStyle();
    foreach (Edge *e, _newGraph->edges()) {
        e->attachStyle();
        e->updateControls();
    }
    _scene->tikzDocument()->setGraph(_newGraph);
    _scene->graphReplaced();
    GraphUpdateCommand::redo();
}

ReflectNodesCommand::ReflectNodesCommand(TikzScene *scene, QSet<Node*> nodes, bool horizontal, QUndoCommand *parent) :
    GraphUpdateCommand(scene, parent), _nodes(nodes), _horizontal(horizontal)
{
}

void ReflectNodesCommand::undo()
{
    _scene->graph()->reflectNodes(_nodes, _horizontal);
    foreach (NodeItem *ni, _scene->nodeItems()) {
        if (_nodes.contains(ni->node())) {
            ni->readPos();
        }
    }

    _scene->refreshAdjacentEdges(_nodes.values());
    GraphUpdateCommand::undo();
}

void ReflectNodesCommand::redo()
{
    _scene->graph()->reflectNodes(_nodes, _horizontal);
    foreach (NodeItem *ni, _scene->nodeItems()) {
        if (_nodes.contains(ni->node())) {
            ni->readPos();
        }
    }

    _scene->refreshAdjacentEdges(_nodes.values());
    GraphUpdateCommand::redo();
}


RotateNodesCommand::RotateNodesCommand(TikzScene *scene, QSet<Node*> nodes, bool clockwise, QUndoCommand *parent) :
    GraphUpdateCommand(scene, parent), _nodes(nodes), _clockwise(clockwise)
{
}

void RotateNodesCommand::undo()
{
    _scene->graph()->rotateNodes(_nodes, !_clockwise);
    foreach (NodeItem *ni, _scene->nodeItems()) {
        if (_nodes.contains(ni->node())) {
            ni->readPos();
        }
    }

    _scene->refreshAdjacentEdges(_nodes.values());
    GraphUpdateCommand::undo();
}

void RotateNodesCommand::redo()
{
    _scene->graph()->rotateNodes(_nodes, _clockwise);
    foreach (NodeItem *ni, _scene->nodeItems()) {
        if (_nodes.contains(ni->node())) {
            ni->readPos();
        }
    }

    _scene->refreshAdjacentEdges(_nodes.values());
    GraphUpdateCommand::redo();
}

ReorderCommand::ReorderCommand(TikzScene *scene,
                               const QVector<Node *> &oldNodeOrder,
                               const QVector<Node *> &newNodeOrder,
                               const QVector<Edge *> &oldEdgeOrder,
                               const QVector<Edge *> &newEdgeOrder,
                               QUndoCommand *parent) :
    GraphUpdateCommand(scene, parent),
    _oldNodeOrder(oldNodeOrder), _newNodeOrder(newNodeOrder),
    _oldEdgeOrder(oldEdgeOrder), _newEdgeOrder(newEdgeOrder)
{
}

void ReorderCommand::undo()
{
    _scene->graph()->reorderNodes(_oldNodeOrder);
    _scene->graph()->reorderEdges(_oldEdgeOrder);
    _scene->refreshZIndices();
    GraphUpdateCommand::undo();
}

void ReorderCommand::redo()
{
    _scene->graph()->reorderNodes(_newNodeOrder);
    _scene->graph()->reorderEdges(_newEdgeOrder);
    _scene->refreshZIndices();
    GraphUpdateCommand::redo();
}

ReverseEdgesCommand::ReverseEdgesCommand(TikzScene *scene,
                                         QSet<Edge *> edgeSet,
                                         QUndoCommand *parent) :
    GraphUpdateCommand(scene, parent), _edgeSet(edgeSet)
{
}

void ReverseEdgesCommand::undo()
{
    EdgeItem *ei;
    foreach (Edge *e, _edgeSet) {
        e->reverse();
        ei = _scene->edgeItems()[e];
        if (ei) ei->readPos();
    }
    GraphUpdateCommand::undo();
}

void ReverseEdgesCommand::redo()
{
    EdgeItem *ei;
    foreach (Edge *e, _edgeSet) {
        e->reverse();
        ei = _scene->edgeItems()[e];
        if (ei) ei->readPos();
    }
    GraphUpdateCommand::redo();
}


MakePathCommand::MakePathCommand(TikzScene *scene,
                                 const QVector<Edge *> &edgeList,
                                 const QMap<Edge *, GraphElementData *> &oldEdgeData,
                                 QUndoCommand *parent) :
    GraphUpdateCommand(scene, parent),
    _edgeList(edgeList), _oldEdgeData(oldEdgeData)
{
}

void MakePathCommand::undo()
{
    Path *p = _edgeList.first()->path();

    PathItem *pi = _scene->pathItems()[p];
    _scene->removeItem(pi);
    _scene->pathItems().remove(p);
    delete pi;

    p->removeEdges();
    _scene->graph()->removePath(p);

    foreach (Edge *e, _edgeList) {
        if (e != _edgeList.first()) {
            // setData transfers ownership, so make a copy
            e->setData(_oldEdgeData[e]->copy());
        }
    }

    _scene->refreshZIndices();
    GraphUpdateCommand::undo();
}

void MakePathCommand::redo()
{
    GraphElementData *npd = _edgeList.first()->data()->nonPathData();
    GraphElementData *d;

    Path *p = new Path();
    foreach (Edge *e, _edgeList) {
        p->addEdge(e);

        if (e != _edgeList.first()) {
            d = e->data()->pathData();
            d->mergeData(npd);
            e->setData(d);
        }
    }

    delete npd;

    _scene->graph()->addPath(p);

    PathItem *pi = new PathItem(p);
    _scene->addItem(pi);
    _scene->pathItems().insert(p, pi);
    pi->readPos();

    _scene->refreshZIndices();
    GraphUpdateCommand::redo();
}

SplitPathCommand::SplitPathCommand(TikzScene *scene,
                                   const QSet<Path *> &paths,
                                   QUndoCommand *parent) :
    GraphUpdateCommand(scene, parent), _paths(paths)
{
    foreach (Path *p, paths) _edgeLists[p] = p->edges();
}

void SplitPathCommand::undo()
{
    foreach (Path *p, _paths) {
        foreach (Edge *e, _edgeLists[p]) {
            p->addEdge(e);
        }

        _scene->graph()->addPath(p);

        PathItem *pi = new PathItem(p);
        _scene->addItem(pi);
        _scene->pathItems().insert(p, pi);
        pi->readPos();
    }

    _scene->refreshZIndices();
    GraphUpdateCommand::undo();
}

void SplitPathCommand::redo()
{
    foreach (Path *p, _paths) {
        PathItem *pi = _scene->pathItems()[p];
        _scene->removeItem(pi);
        _scene->pathItems().remove(p);
        delete pi;

        p->removeEdges();
        _scene->graph()->removePath(p);
    }

    _scene->refreshZIndices();
    GraphUpdateCommand::redo();
}
