package graph;

import java.awt.Color;
import java.util.ArrayList;

import org.eclipse.elk.graph.ElkConnectableShape;
import org.eclipse.elk.graph.ElkEdge;
import org.eclipse.elk.graph.ElkNode;
import org.eclipse.emf.common.util.EList;

import bk.LayoutType;
import view.NodeView;

/**
 * Die Implementation eines Knotens in einem Layered Graph. Implementiert {@link LayeredGraphNode}.
 * 
 * @author kolja
 *
 */
public class LayeredNode implements LayeredGraphNode {
    private ElkNode original;
    private LayeredGraphNode parent;
    private boolean dummy;

    private class LayoutInfo {
        public double x;
        public double y;
        public double w;
        public double h;
        public Color color;
        public boolean selected;
        public boolean xUndef;

        // Block Calculation
        public LayeredGraphNode align;
        public LayeredGraphNode root;
        // HorizontalCompaction
        public LayeredGraphNode sink;
        public double shift;
        private NodeView view;
    }

    private class CombinedLayoutInfo {
        public double x;
        public double y;
        public double w;
        public double h;
        public boolean selected;
        private NodeView view;
    }

    private LayoutInfo[] layouts;
    private CombinedLayoutInfo combined;
    private String name;
    private boolean mouseOver;

    // for subgraph in this node
    private ArrayList<LayeredGraphEdge> edges;
    private ArrayList<LayeredGraphNode> nodes;
    private ArrayList<ArrayList<LayeredGraphNode>> layers;

    /**
     * Konvertiert einen Graph aus dem Elk format in einen Graph, der mehr Informationen enth�lt
     *
     * @param n
     *            Der Graph, welcher konvertiert werden soll
     * @return Ein layered Graph, welcher im wesentlichen ein Wrapper f�r den urspr�nglichen Graphen ist
     */
    public static LayeredGraphNode convertToLayeredGraph(ElkNode n) {
        LayeredNode ln = new LayeredNode(n, null);
        for (ElkNode node : n.getChildren())
            ln.addNode(convertToLayeredGraph(node));
        for (ElkEdge edge : n.getContainedEdges()) {
            // TODO n.getProperty(Properties.LAYERPOS) reads position of node n in his layer
            ArrayList<LayeredGraphNode> sources = new ArrayList<>();
            ArrayList<LayeredGraphNode> targets = new ArrayList<>();
            EList<ElkConnectableShape> s = edge.getSources();
            EList<ElkConnectableShape> t = edge.getTargets();
            for (ElkConnectableShape shape : s)
                sources.add(ln.findNodeFromOriginal(shape));
            for (ElkConnectableShape shape : t)
                targets.add(ln.findNodeFromOriginal(shape));
            ln.createEdge(edge, sources, targets);
        }
        return ln;
    }

    public LayeredNode(ElkNode original, LayeredGraphNode parent) {
        this.original = original;
        this.parent = parent;

        edges = new ArrayList<>();
        nodes = new ArrayList<>();
        layers = new ArrayList<>();
        layouts = new LayoutInfo[ 4 ];
        for( int i = 0; i < 4; i++ )
            layouts[ i ] = new LayoutInfo();
        int index = 0;
        for( LayoutInfo l : layouts )
        {
            if( original != null )
            {
            	l.x = original.getX();
            	l.y = original.getX();
            	l.w = original.getWidth();
            	l.h = original.getHeight();
            }
            l.align = this;
            l.root = this;
            l.sink = this;
            if( index == 0 || index == 2 )
                l.shift = Double.POSITIVE_INFINITY;
            else
                l.shift = Double.NEGATIVE_INFINITY;
            l.xUndef = true;
            index++;
        }
        dummy = false;
        combined = new CombinedLayoutInfo();
        if (original != null) {
            combined.x = original.getX();
            combined.y = original.getX();
            combined.w = original.getWidth();
            combined.h = original.getHeight();
        }
        combined.selected = false;
        setMouseOver(false);
    }

    public void setMouseOver( boolean over ) {
        mouseOver = over;
    }

    public boolean isMouseOver() {
        return mouseOver;
    }

    public void setView(NodeView view, LayoutType layout) {
        if (layout == LayoutType.LEFTMOST_UPPER)
            this.layouts[0].view = view;
        if (layout == LayoutType.RIGHTMOST_UPPER)
            this.layouts[1].view = view;
        if (layout == LayoutType.LEFTMOST_LOWER)
            this.layouts[2].view = view;
        if (layout == LayoutType.RIGHTMOST_LOWER)
            this.layouts[3].view = view;
        if (layout == LayoutType.COMBINED)
            this.combined.view = view;
    }

    public NodeView getView(LayoutType layout) {
        if (layout == LayoutType.LEFTMOST_UPPER)
            return layouts[0].view;
        if (layout == LayoutType.RIGHTMOST_UPPER)
            return layouts[1].view;
        if (layout == LayoutType.LEFTMOST_LOWER)
            return layouts[2].view;
        if (layout == LayoutType.RIGHTMOST_LOWER)
            return layouts[3].view;
        if (layout == LayoutType.COMBINED)
            return combined.view;
        return null;
    }

    @Override
    public void setDummyNode(boolean dummy) {
        this.dummy = dummy;
    }

    @Override
    public boolean isDummyNode() {
        return dummy;
    }

    @Override
    public void setShift(double shift, LayoutType layout) {
        if (layout == null) {
            this.layouts[0].shift = shift;
            this.layouts[1].shift = shift;
            this.layouts[2].shift = shift;
            this.layouts[3].shift = shift;
        }
        if (layout == LayoutType.LEFTMOST_UPPER)
            this.layouts[0].shift = shift;
        if (layout == LayoutType.RIGHTMOST_UPPER)
            this.layouts[1].shift = shift;
        if (layout == LayoutType.LEFTMOST_LOWER)
            this.layouts[2].shift = shift;
        if (layout == LayoutType.RIGHTMOST_LOWER)
            this.layouts[3].shift = shift;
    }

    @Override
    public double getShift(LayoutType layout) {
        if (layout == LayoutType.LEFTMOST_UPPER)
            return this.layouts[0].shift;
        if (layout == LayoutType.RIGHTMOST_UPPER)
            return this.layouts[1].shift;
        if (layout == LayoutType.LEFTMOST_LOWER)
            return this.layouts[2].shift;
        if (layout == LayoutType.RIGHTMOST_LOWER)
            return this.layouts[3].shift;
        return 0;
    }

    @Override
    public void setSink(LayeredGraphNode sink, LayoutType layout) {
        if (layout == null) {
            this.layouts[0].sink = sink;
            this.layouts[1].sink = sink;
            this.layouts[2].sink = sink;
            this.layouts[3].sink = sink;
        }
        if (layout == LayoutType.LEFTMOST_UPPER)
            this.layouts[0].sink = sink;
        if (layout == LayoutType.RIGHTMOST_UPPER)
            this.layouts[1].sink = sink;
        if (layout == LayoutType.LEFTMOST_LOWER)
            this.layouts[2].sink = sink;
        if (layout == LayoutType.RIGHTMOST_LOWER)
            this.layouts[3].sink = sink;
    }

    @Override
    public LayeredGraphNode getSink(LayoutType layout) {
        if (layout == LayoutType.LEFTMOST_UPPER)
            return this.layouts[0].sink;
        if (layout == LayoutType.RIGHTMOST_UPPER)
            return this.layouts[1].sink;
        if (layout == LayoutType.LEFTMOST_LOWER)
            return this.layouts[2].sink;
        if (layout == LayoutType.RIGHTMOST_LOWER)
            return this.layouts[3].sink;
        return null;
    }

    @Override
    public boolean isXUndefined(LayoutType layout) {
        if (layout == LayoutType.LEFTMOST_UPPER)
            return this.layouts[0].xUndef;
        if (layout == LayoutType.RIGHTMOST_UPPER)
            return this.layouts[1].xUndef;
        if (layout == LayoutType.LEFTMOST_LOWER)
            return this.layouts[2].xUndef;
        if (layout == LayoutType.RIGHTMOST_LOWER)
            return this.layouts[3].xUndef;
        return true;
    }

    @Override
    public void setAlign(LayeredGraphNode align, LayoutType layout) {
        if (layout == null) {
            this.layouts[0].align = align;
            this.layouts[1].align = align;
            this.layouts[2].align = align;
            this.layouts[3].align = align;
        }
        if (layout == LayoutType.LEFTMOST_UPPER)
            this.layouts[0].align = align;
        if (layout == LayoutType.RIGHTMOST_UPPER)
            this.layouts[1].align = align;
        if (layout == LayoutType.LEFTMOST_LOWER)
            this.layouts[2].align = align;
        if (layout == LayoutType.RIGHTMOST_LOWER)
            this.layouts[3].align = align;
    }

    @Override
    public LayeredGraphNode getAlign(LayoutType layout) {
        if (layout == LayoutType.LEFTMOST_UPPER)
            return this.layouts[0].align;
        if (layout == LayoutType.RIGHTMOST_UPPER)
            return this.layouts[1].align;
        if (layout == LayoutType.LEFTMOST_LOWER)
            return this.layouts[2].align;
        if (layout == LayoutType.RIGHTMOST_LOWER)
            return this.layouts[3].align;
        return null;
    }

    @Override
    public void setRoot(LayeredGraphNode root, LayoutType layout) {
        if (layout == null) {
            this.layouts[0].root = root;
            this.layouts[1].root = root;
            this.layouts[2].root = root;
            this.layouts[3].root = root;
        }
        if (layout == LayoutType.LEFTMOST_UPPER)
            this.layouts[0].root = root;
        if (layout == LayoutType.RIGHTMOST_UPPER)
            this.layouts[1].root = root;
        if (layout == LayoutType.LEFTMOST_LOWER)
            this.layouts[2].root = root;
        if (layout == LayoutType.RIGHTMOST_LOWER)
            this.layouts[3].root = root;
    }

    @Override
    public LayeredGraphNode getRoot(LayoutType layout) {
        if (layout == LayoutType.LEFTMOST_UPPER)
            return this.layouts[0].root;
        if (layout == LayoutType.RIGHTMOST_UPPER)
            return this.layouts[1].root;
        if (layout == LayoutType.LEFTMOST_LOWER)
            return this.layouts[2].root;
        if (layout == LayoutType.RIGHTMOST_LOWER)
            return this.layouts[3].root;
        return null;
    }

    @Override
    public void setSelected(LayoutType layout) {
        if (layout == null) {
            this.layouts[0].selected = true;
            this.layouts[1].selected = true;
            this.layouts[2].selected = true;
            this.layouts[3].selected = true;
            combined.selected = true;
        }
        if (layout == LayoutType.LEFTMOST_UPPER)
            this.layouts[0].selected = true;
        if (layout == LayoutType.RIGHTMOST_UPPER)
            this.layouts[1].selected = true;
        if (layout == LayoutType.LEFTMOST_LOWER)
            this.layouts[2].selected = true;
        if (layout == LayoutType.RIGHTMOST_LOWER)
            this.layouts[3].selected = true;
        if (layout == LayoutType.COMBINED)
            combined.selected = true;
    }

    @Override
    public void unselectGraph() {
        for (LayeredGraphNode n : nodes) {
            n.unselectGraph();
        }
        this.layouts[0].selected = false;
        this.layouts[1].selected = false;
        this.layouts[2].selected = false;
        this.layouts[3].selected = false;
        combined.selected = false;
    }

    @Override
    public boolean isSelected(LayoutType layout) {
        if (layout == LayoutType.LEFTMOST_UPPER) {
            return layouts[0].selected;
        }
        if (layout == LayoutType.RIGHTMOST_UPPER) {
            return layouts[1].selected;
        }
        if (layout == LayoutType.LEFTMOST_LOWER) {
            return layouts[2].selected;
        }
        if (layout == LayoutType.RIGHTMOST_LOWER) {
            return layouts[3].selected;
        }
        if (layout == LayoutType.COMBINED) {
            return combined.selected;
        }
        return false;
    }

    @Override
    public void setColor( Color c, LayoutType layout )
    {
        if( layout == null )
        {
            this.layouts[ 0 ].color = c;
            this.layouts[ 1 ].color = c;
            this.layouts[ 2 ].color = c;
            this.layouts[ 3 ].color = c;
        }
        if( layout == LayoutType.LEFTMOST_UPPER )
            this.layouts[ 0 ].color = c;
        if( layout == LayoutType.RIGHTMOST_UPPER )
            this.layouts[ 1 ].color = c;
        if( layout == LayoutType.LEFTMOST_LOWER )
            this.layouts[ 2 ].color = c;
        if( layout == LayoutType.RIGHTMOST_LOWER )
            this.layouts[ 3 ].color = c;
    }

    private int calcClassSize(LayeredGraphNode sink, LayoutType layout) {
        if (parent == null)
            return 1;
        int ret = 0;
        for (LayeredGraphNode n : parent.getContainedNodes()) {
            if (n.getRoot(layout).getSink(layout) == sink)
                ret++;
        }
        return ret;
    }

    @Override
    public Color getClassColor(LayoutType layout) {
        if (layout == LayoutType.LEFTMOST_UPPER && this.layouts[0].sink == this && calcClassSize(this, layout) == 1)
            return Color.LIGHT_GRAY;
        if (layout == LayoutType.LEFTMOST_UPPER && this.layouts[0].sink == this)
            return this.layouts[0].color;
        else if (layout == LayoutType.LEFTMOST_UPPER)
            return this.layouts[0].sink.getClassColor(layout);
        if (layout == LayoutType.RIGHTMOST_UPPER && this.layouts[1].sink == this && calcClassSize(this, layout) == 1)
            return Color.LIGHT_GRAY;
        if (layout == LayoutType.RIGHTMOST_UPPER && this.layouts[1].sink == this)
            return this.layouts[1].color;
        else if (layout == LayoutType.RIGHTMOST_UPPER)
            return this.layouts[1].sink.getClassColor(layout);
        if (layout == LayoutType.LEFTMOST_LOWER && this.layouts[2].sink == this && calcClassSize(this, layout) == 1)
            return Color.LIGHT_GRAY;
        if (layout == LayoutType.LEFTMOST_LOWER && this.layouts[2].sink == this)
            return this.layouts[2].color;
        else if (layout == LayoutType.LEFTMOST_LOWER)
            return this.layouts[2].sink.getClassColor(layout);
        if (layout == LayoutType.RIGHTMOST_LOWER && this.layouts[3].sink == this && calcClassSize(this, layout) == 1)
            return Color.LIGHT_GRAY;
        if( layout == LayoutType.RIGHTMOST_LOWER && this.layouts[ 3 ].sink == this )
            return this.layouts[ 3 ].color;
        else if( layout == LayoutType.RIGHTMOST_LOWER )
            return this.layouts[ 3 ].sink.getClassColor( layout );
        if( layout == LayoutType.COMBINED )
            return Color.LIGHT_GRAY;
        return null;
    }

    @Override
    public Color getColor( LayoutType layout )
    {
        if( layout == null )
            return this.layouts[ 0 ].color;
        if( layout == LayoutType.LEFTMOST_UPPER && this.layouts[ 0 ].root == this && this.layouts[ 0 ].align == this )
            return Color.LIGHT_GRAY;
        if (layout == LayoutType.LEFTMOST_UPPER && this.layouts[0].root != this)
            return this.layouts[0].root.getColor(layout);
        if (layout == LayoutType.LEFTMOST_UPPER)
            return this.layouts[0].color;
        if (layout == LayoutType.RIGHTMOST_UPPER && this.layouts[1].root == this && this.layouts[1].align == this)
            return Color.LIGHT_GRAY;
        if (layout == LayoutType.RIGHTMOST_UPPER && this.layouts[1].root != this)
            return this.layouts[1].root.getColor(layout);
        if (layout == LayoutType.RIGHTMOST_UPPER)
            return this.layouts[1].color;
        if (layout == LayoutType.LEFTMOST_LOWER && this.layouts[2].root == this && this.layouts[2].align == this)
            return Color.LIGHT_GRAY;
        if (layout == LayoutType.LEFTMOST_LOWER && this.layouts[2].root != this)
            return this.layouts[2].root.getColor(layout);
        if (layout == LayoutType.LEFTMOST_LOWER)
            return this.layouts[2].color;
        if (layout == LayoutType.RIGHTMOST_LOWER && this.layouts[3].root == this && this.layouts[3].align == this)
            return Color.LIGHT_GRAY;
        if( layout == LayoutType.RIGHTMOST_LOWER && this.layouts[ 3 ].root != this )
            return this.layouts[ 3 ].root.getColor( layout );
        if( layout == LayoutType.RIGHTMOST_LOWER )
            return this.layouts[ 3 ].color;
        if( layout == LayoutType.COMBINED )
            return Color.LIGHT_GRAY;
        return null;
    }

    @Override
    public void setName(String n) {
        name = n;
    }

    @Override
    public ElkNode getOriginalNode() {
        return original;
    }

    @Override
    public void remove() {
        parent.removeNode(this);
    }

    @Override
    public LayeredGraphNode parent() {
        return parent;
    }

    @Override
    public void setLayer(int index) {
        parent.setNodeLayer(this, index);
    }

    @Override
    public int getLayer() {
        return parent.getNodeLayer(this);
    }

    @Override
    public void setX(double x, boolean def, LayoutType layout) {
        if (layout == null) {
            layouts[0].x = x;
            layouts[1].x = x;
            layouts[2].x = x;
            layouts[3].x = x;
            combined.x = x;
        }
        if (layout == LayoutType.LEFTMOST_UPPER) {
            layouts[0].xUndef = !def;
            layouts[0].x = x;
        }
        if (layout == LayoutType.RIGHTMOST_UPPER) {
            layouts[1].xUndef = !def;
            layouts[1].x = x;
        }
        if (layout == LayoutType.LEFTMOST_LOWER) {
            layouts[2].xUndef = !def;
            layouts[2].x = x;
        }
        if (layout == LayoutType.RIGHTMOST_LOWER) {
            layouts[3].xUndef = !def;
            layouts[3].x = x;
        }
        if (layout == LayoutType.COMBINED)
            combined.x = x;
    }

    @Override
    public void setY(double y, LayoutType layout) {
        if (layout == null) {
            layouts[0].y = y;
            layouts[1].y = y;
            layouts[2].y = y;
            layouts[3].y = y;
            combined.y = y;
        }
        if (layout == LayoutType.LEFTMOST_UPPER)
            layouts[0].y = y;
        if (layout == LayoutType.RIGHTMOST_UPPER)
            layouts[1].y = y;
        if (layout == LayoutType.LEFTMOST_LOWER)
            layouts[2].y = y;
        if (layout == LayoutType.RIGHTMOST_LOWER)
            layouts[3].y = y;
        if (layout == LayoutType.COMBINED)
            combined.y = y;
    }

    @Override
    public double getX(LayoutType layout) {
        if (layout == LayoutType.LEFTMOST_UPPER)
            return this.layouts[0].x;
        if (layout == LayoutType.RIGHTMOST_UPPER)
            return this.layouts[1].x;
        if (layout == LayoutType.LEFTMOST_LOWER)
            return this.layouts[2].x;
        if (layout == LayoutType.RIGHTMOST_LOWER)
            return this.layouts[3].x;
        if (layout == LayoutType.COMBINED)
            return combined.x;
        return 0;
    }

    @Override
    public double getY(LayoutType layout) {
        if (layout == LayoutType.LEFTMOST_UPPER)
            return this.layouts[0].y;
        if (layout == LayoutType.RIGHTMOST_UPPER)
            return this.layouts[1].y;
        if (layout == LayoutType.LEFTMOST_LOWER)
            return this.layouts[2].y;
        if (layout == LayoutType.RIGHTMOST_LOWER)
            return this.layouts[3].y;
        if (layout == LayoutType.COMBINED)
            return combined.y;
        return 0;
    }

    @Override
    public double getWidth(LayoutType layout) {
        if (nodes.size() > 0) {
            double max = 0;
            double min = Double.POSITIVE_INFINITY;
            for( LayeredGraphNode n : nodes )
            {
                if( max < n.getX(layout) + n.getWidth(layout) )
                    max = n.getX(layout) + n.getWidth(layout);
                min = Math.min( n.getX(layout), min);
            }
            if (layout == LayoutType.LEFTMOST_UPPER)
                return Math.max(max - min, layouts[0].w);
            if (layout == LayoutType.RIGHTMOST_UPPER)
                return Math.max(max - min, layouts[1].w);
            if (layout == LayoutType.LEFTMOST_LOWER)
                return Math.max(max - min, layouts[2].w);
            if (layout == LayoutType.RIGHTMOST_LOWER)
                return Math.max(max - min, layouts[3].w);
            if (layout == LayoutType.COMBINED)
                return Math.max(max - min, combined.w);
        }
        if (layout == LayoutType.LEFTMOST_UPPER)
            return layouts[0].w;
        if (layout == LayoutType.RIGHTMOST_UPPER)
            return layouts[1].w;
        if (layout == LayoutType.LEFTMOST_LOWER)
            return layouts[2].w;
        if (layout == LayoutType.RIGHTMOST_LOWER)
            return layouts[3].w;
        if (layout == LayoutType.COMBINED)
            return combined.w;
        return 0;
    }

    @Override
    public double getHeight( LayoutType layout ) {
        if( nodes.size() > 0 )
        {
        	double max = 0;
        	for( LayeredGraphNode n : nodes )
        	{
        		if( max < n.getY(layout) + n.getHeight(layout) )
        			max = n.getY(layout) + n.getHeight(layout);
        	}
            if( layout == LayoutType.LEFTMOST_UPPER )
                return Math.max( max, layouts[ 0 ].h );
            if( layout == LayoutType.RIGHTMOST_UPPER )
                return Math.max( max, layouts[ 1 ].h );
            if( layout == LayoutType.LEFTMOST_LOWER )
                return Math.max( max, layouts[ 2 ].h );
            if( layout == LayoutType.RIGHTMOST_LOWER )
                return Math.max( max, layouts[ 3 ].h );
            if( layout == LayoutType.COMBINED )
                return Math.max( max, combined.h );
        }
        if (layout == LayoutType.LEFTMOST_UPPER)
            return layouts[0].h;
        if (layout == LayoutType.RIGHTMOST_UPPER)
            return layouts[1].h;
        if (layout == LayoutType.LEFTMOST_LOWER)
            return layouts[2].h;
        if (layout == LayoutType.RIGHTMOST_LOWER)
            return layouts[3].h;
        if (layout == LayoutType.COMBINED)
            return combined.h;
        return 0;
    }

    @Override
    public void setWidth(double w, LayoutType layout) {
        if (layout == null) {
            this.layouts[0].w = w;
            this.layouts[1].w = w;
            this.layouts[2].w = w;
            this.layouts[3].w = w;
            combined.w = w;
        }
        if (layout == LayoutType.LEFTMOST_UPPER)
            this.layouts[0].w = w;
        if (layout == LayoutType.RIGHTMOST_UPPER)
            this.layouts[1].w = w;
        if (layout == LayoutType.LEFTMOST_LOWER)
            this.layouts[2].w = w;
        if (layout == LayoutType.RIGHTMOST_LOWER)
            this.layouts[3].w = w;
        if (layout == LayoutType.COMBINED)
            combined.w = w;
    }

    @Override
    public void setHeight(double h, LayoutType layout) {
        if (layout == null) {
            this.layouts[0].h = h;
            this.layouts[1].h = h;
            this.layouts[2].h = h;
            this.layouts[3].h = h;
            combined.h = h;
        }
        if (layout == LayoutType.LEFTMOST_UPPER)
            this.layouts[0].h = h;
        if (layout == LayoutType.RIGHTMOST_UPPER)
            this.layouts[1].h = h;
        if (layout == LayoutType.LEFTMOST_LOWER)
            this.layouts[2].h = h;
        if (layout == LayoutType.RIGHTMOST_LOWER)
            this.layouts[3].h = h;
        if (layout == LayoutType.COMBINED)
            combined.h = h;
    }

    @Override
    public void removeEdge(LayeredGraphEdge e) {
        edges.remove(e);
    }

    @Override
    public void removeNode(LayeredGraphNode n) {
        for (LayeredGraphEdge e : n.getIncomingEdges())
            e.remove();
        for (LayeredGraphEdge e : n.getOutgoingEdges())
            e.remove();
        nodes.remove(n);
        for (ArrayList<LayeredGraphNode> l : layers) {
            l.remove(n);
        }
    }

    @Override
    public void setNodeLayer(LayeredGraphNode n, int index) {
        while (index >= layers.size())
            layers.add(new ArrayList<>());
        int old = n.getLayer();
        if (old >= 0)
            layers.get(old).remove(n);
        layers.get(index).add(n);
    }

    @Override
    public void setOrderedLayer(ArrayList<Double> indizes, int layerIndex) {
        ArrayList<LayeredGraphNode> l2 = layers.get(layerIndex);
        ArrayList<LayeredGraphNode> result = new ArrayList<LayeredGraphNode>();
        while (indizes.size() > 0) {
            int mIndex = 0;
            double min = indizes.get(0);
            for (int i = 1; i < indizes.size(); i++) {
                if (min > indizes.get(i)) {
                    mIndex = i;
                    min = indizes.get(i);
                }
            }
            result.add(l2.get(mIndex));
            l2.remove(mIndex);
            indizes.remove(mIndex);
        }
        layers.set(layerIndex, result);
    }

    @Override
    public ArrayList<LayeredGraphEdge> getOutgoingEdges() {
        return parent.getOutgoingEdges(this);
    }

    @Override
    public ArrayList<LayeredGraphEdge> getSortedOutgoingEdges() {
        return parent.getSortedOutgoingEdges(this);
    }

    @Override
    public ArrayList<LayeredGraphEdge> getIncomingEdges() {
        return parent.getIncomingEdges(this);
    }

    @Override
    public ArrayList<LayeredGraphEdge> getSortedIncomingEdges() {
        return parent.getSortedIncomingEdges(this);
    }

    @Override
    public ArrayList<LayeredGraphEdge> getContainedEdges() {
        return edges;
    }

    @Override
    public ArrayList<LayeredGraphNode> getContainedNodes() {
        return nodes;
    }

    @Override
    public ArrayList<LayeredGraphNode> getSortedContainedNodes() {
        ArrayList<LayeredGraphNode> result = new ArrayList<>();
        for (ArrayList<LayeredGraphNode> l : layers) {
            result.addAll(l);
        }
        return result;
    }

    @Override
    public ArrayList<ArrayList<LayeredGraphNode>> getContainedLayers() {
        return layers;
    }

    @Override
    public int getNodeLayer(LayeredGraphNode n) {
        for (int i = 0; i < layers.size(); i++) {
            if (layers.get(i).contains(n))
                return i;
        }
        return -1;
    }

    @Override
    public ArrayList<LayeredGraphEdge> getOutgoingEdges(LayeredGraphNode n) {
        ArrayList<LayeredGraphEdge> result = new ArrayList<>();
        for( LayeredGraphEdge e : edges )
        {
            if( e.getSources().contains( n ) && !result.contains( e ) )
                result.add( e );
        }
        return result;
    }

    @Override
    public ArrayList<LayeredGraphEdge> getIncomingEdges(LayeredGraphNode n) {
        ArrayList<LayeredGraphEdge> result = new ArrayList<>();
        for( LayeredGraphEdge e : edges )
        {
            if( e.getTargets().contains( n ) && !result.contains( e ) )
                result.add( e );
        }
        return result;
    }

    @Override
    public ArrayList<LayeredGraphEdge> getSortedOutgoingEdges(LayeredGraphNode n) {
        ArrayList<LayeredGraphEdge> result = new ArrayList<>();
    	if( n.getLayer() + 1 >= layers.size() )
    		return result;
        ArrayList< LayeredGraphEdge > unsorted = getOutgoingEdges( n );
        for( LayeredGraphNode node : layers.get( n.getLayer() + 1 ) )
        {
        	for( LayeredGraphEdge e : unsorted )
            {
                if( e.getTargets().contains( node ) && !result.contains( e ) )
                	result.add( e );
            }
        }
        return result;
    }

    @Override
    public ArrayList<LayeredGraphEdge> getSortedIncomingEdges(LayeredGraphNode n) {
        ArrayList<LayeredGraphEdge> result = new ArrayList<>();
    	if( n.getLayer() - 1 < 0 )
    		return result;
        ArrayList< LayeredGraphEdge > unsorted = getIncomingEdges( n );
        for( LayeredGraphNode node : layers.get( n.getLayer() - 1 ) )
        {
        	for( LayeredGraphEdge e : unsorted )
            {
                if( e.getSources().contains( node ) && !result.contains( e ) )
                	result.add( e );
            }
        }
        return result;
    }

    @Override
    public LayeredGraphEdge findEdgeBetween( LayeredGraphNode source, LayeredGraphNode target )
    {
        for( LayeredGraphEdge e : edges )
        {
            if( e.getSources().contains( source ) && e.getTargets().contains( target ) )
                return e;
        }
        return null;
    }

    @Override
    public LayeredGraphNode createNode(ElkNode original) {
        LayeredGraphNode n = new LayeredNode(original, this);
        nodes.add(n);
        return n;
    }

    @Override
    public LayeredGraphEdge createEdge(ElkEdge original, ArrayList<LayeredGraphNode> sources,
            ArrayList<LayeredGraphNode> targets) {
        LayeredGraphEdge e = new LayeredEdge(original, sources, targets, this);
        edges.add(e);
        return e;
    }

    @Override
    public LayeredGraphEdge createSimpleEdge(ElkEdge original, LayeredGraphNode source, LayeredGraphNode target) {
        ArrayList<LayeredGraphNode> sources = new ArrayList<>();
        ArrayList<LayeredGraphNode> targets = new ArrayList<>();
        sources.add(source);
        targets.add(target);
        LayeredGraphEdge e = new LayeredEdge(original, sources, targets, this);
        edges.add(e);
        return e;
    }

    @Override
    public LayeredGraphEdge findEdgeFromOriginal(Object original) {
        for (LayeredGraphEdge e : edges) {
            if (e.getOriginalEdge() == original)
                return e;
        }
        return null;
    }

    @Override
    public LayeredGraphNode findNodeFromOriginal(Object original) {
        for (LayeredGraphNode n : nodes) {
            if (n.getOriginalNode() == original)
                return n;
        }
        return null;
    }

    @Override
    public LayeredGraphNode findNodeByName(String name) {
        for (LayeredGraphNode n : nodes) {
            if (name != null && n.toString().equals(name))
                return n;
        }
        return null;
    }

    @Override
    public void addNode(LayeredGraphNode n) {
        nodes.add(n);
        n.setParent(this);
    }

    @Override
    public void addEdge(LayeredGraphEdge e) {
        edges.add(e);
        e.setGraph(this);
    }

    @Override
    public void setParent(LayeredGraphNode parent) {
        this.parent = parent;
    }

    @Override
    public String toString() {
        if( name != null )
            return name;
        return "unnamed node";
    }
}
