package com.horstmann.violet.workspace.editorpart.behavior;

import java.util.ArrayList;
import java.util.List;

import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.CompoundEdit;
import javax.swing.undo.UndoableEdit;

import com.horstmann.violet.product.diagram.abstracts.IGraph;
import com.horstmann.violet.product.diagram.abstracts.edge.IEdge;
import com.horstmann.violet.product.diagram.abstracts.node.INode;
import com.horstmann.violet.workspace.editorpart.IEditorPart;

/**
 * Undo/Redo behavior triggered when node and edges are removed
 * 
 * @author Alexandre de Pellegrin
 *
 */
public class UndoRedoOnRemoveBehavior extends AbstractEditorPartBehavior
{

    /**
     * The concerned workspace
     */
    private IEditorPart editorPart;

    /**
     * The global undo/redo behavior which contains all individual undo/redo behaviors
     */
    private UndoRedoCompoundBehavior compoundBehavior;

    /**
     * Keeps all the node attached to the graph before the remove action
     */
    private List<INode> nodesOnGraphBeforeRemove = new ArrayList<INode>();

    /**
     * Keeps all the edges attached to the graph before the remove action
     */
    private List<IEdge> edgesOnGraphBeforeRemove = new ArrayList<IEdge>();

    /**
     * Default constructor
     * 
     * @param editorPart
     * @param compoundBehavior
     */
    public UndoRedoOnRemoveBehavior(IEditorPart editorPart, UndoRedoCompoundBehavior compoundBehavior)
    {
        this.editorPart = editorPart;
        this.compoundBehavior = compoundBehavior;
    }

    @Override
    public void beforeRemovingSelectedElements()
    {
        this.nodesOnGraphBeforeRemove.clear();
        this.edgesOnGraphBeforeRemove.clear();
        this.nodesOnGraphBeforeRemove.addAll(this.editorPart.getGraph().getAllNodes());
        this.edgesOnGraphBeforeRemove.addAll(this.editorPart.getGraph().getAllEdges());
    }

    @Override
    public void afterRemovingSelectedElements()
    {
        List<INode> nodesOnGraphAfterAction = new ArrayList<INode>(this.editorPart.getGraph().getAllNodes());
        List<IEdge> edgesOnGraphAfterAction = new ArrayList<IEdge>(this.editorPart.getGraph().getAllEdges());

        List<INode> nodesReallyRemoved = new ArrayList<INode>();
        nodesReallyRemoved.addAll(this.nodesOnGraphBeforeRemove);
        nodesReallyRemoved.removeAll(nodesOnGraphAfterAction);

        List<IEdge> edgesReallyRemoved = new ArrayList<IEdge>();
        edgesReallyRemoved.addAll(this.edgesOnGraphBeforeRemove);
        edgesReallyRemoved.removeAll(edgesOnGraphAfterAction);

        this.compoundBehavior.startHistoryCapture();
        CompoundEdit capturedEdit = this.compoundBehavior.getCurrentCapturedEdit();

        for (final IEdge aSelectedEdge : edgesReallyRemoved)
        {
            UndoableEdit edit = new AbstractUndoableEdit()
            {
                @Override
                public void undo() throws CannotUndoException
                {
                    IGraph graph = editorPart.getGraph();
                    graph.connect(aSelectedEdge, aSelectedEdge.getStartNode(), aSelectedEdge.getStartLocation(),
                            aSelectedEdge.getEndNode(), aSelectedEdge.getEndLocation());
                    super.undo();
                }

                @Override
                public void redo() throws CannotRedoException
                {
                    super.redo();
                    IGraph graph = editorPart.getGraph();
                    graph.removeEdge(aSelectedEdge);
                }
            };
            capturedEdit.addEdit(edit);
        }

        List<INode> filteredNodes = removeChildren(nodesReallyRemoved);
        for (final INode aSelectedNode : filteredNodes)
        {

            UndoableEdit edit = new AbstractUndoableEdit()
            {
                @Override
                public void undo() throws CannotUndoException
                {
                    IGraph graph = editorPart.getGraph();
                    graph.addNode(aSelectedNode, aSelectedNode.getLocationOnGraph());
                    super.undo();
                }

                @Override
                public void redo() throws CannotRedoException
                {
                    super.redo();
                    IGraph graph = editorPart.getGraph();
                    graph.removeNode(aSelectedNode);
                }
            };
            capturedEdit.addEdit(edit);
        }

        this.compoundBehavior.stopHistoryCapture();
        this.nodesOnGraphBeforeRemove.clear();
        this.edgesOnGraphBeforeRemove.clear();
    }

    /**
     * Checks if ancestorNode is a parent node_old of child node_old
     * 
     * @param childNode
     * @param ancestorNode
     * @return b
     */
    private boolean isAncestorRelationship(INode childNode, INode ancestorNode)
    {
        INode parent = childNode.getParent();
        if (parent == null)
        {
            return false;
        }
        List<INode> fifo = new ArrayList<INode>();
        fifo.add(parent);
        while (!fifo.isEmpty())
        {
            INode aParentNode = fifo.get(0);
            fifo.remove(0);
            if (aParentNode.equals(ancestorNode))
            {
                return true;
            }
            INode aGranParent = aParentNode.getParent();
            if (aGranParent != null)
            {
                fifo.add(aGranParent);
            }
        }
        return false;
    }

    /**
     * Takes a list of node and removes from this list all node which have ancestors node_old in this list.<br/>
     * 
     * @param nodes the list to filter
     * @return the filtered list
     */
    private List<INode> removeChildren(List<INode> nodes)
    {
        List<INode> result = new ArrayList<INode>();
        for (INode aNode : nodes)
        {
            boolean isOrphelin = true;
            for (INode aParent : nodes)
            {
                boolean isAncestorRelationship = isAncestorRelationship(aNode, aParent);
                if (isAncestorRelationship)
                {
                    isOrphelin = false;
                }
            }
            if (isOrphelin)
            {
                result.add(aNode);
            }
        }
        return result;
    }

}
