package Model;

import java.awt.Point;
import java.util.ArrayList;

import org.eclipse.elk.graph.ElkEdge;

import Algorithms.Animated.BK.ExtremalLayoutCalc.LayoutType;

/**
 * Die Implementation einer Kante aus einem Layered Graphen.
 * Implementiert {@link LayeredGraphEdge.}
 * 
 * @author kolja
 *
 */
public class LayeredEdge implements LayeredGraphEdge {

    ElkEdge original;
    ArrayList< LayeredGraphNode > sources;
    ArrayList< LayeredGraphNode > targets;
    LayeredGraphNode graph;
    boolean reversed;
    boolean dummy;
    ArrayList< Point >[] bindPoints;
    boolean[] conflicted;
    
    @SuppressWarnings("unchecked")
    public LayeredEdge( ElkEdge original, ArrayList< LayeredGraphNode > sources, ArrayList< LayeredGraphNode > targets, LayeredGraphNode graph )
    {
        this.original = original;
        this.sources = sources;
        this.targets = targets;
        this.graph = graph;
        reversed = false;
        dummy = false;
        bindPoints = new ArrayList[ 4 ];
        conflicted = new boolean[ 4 ];
        for( int i = 0; i < 4; i++ )
            bindPoints[ i ] = new ArrayList<>();
        for( ArrayList<Point> bps : bindPoints )
        {
            bps.add( null );
            bps.add( null );
        }
    }

    @Override
    public boolean isConflicted( LayoutType layout )
    {
        if( layout == LayoutType.TOP_BOTTOM_LEFT )
            return conflicted[ 0 ];
        if( layout == LayoutType.TOP_BOTTOM_RIGHT )
            return conflicted[ 1 ];
        if( layout == LayoutType.BOTTOM_TOP_LEFT )
            return conflicted[ 2 ];
        if( layout == LayoutType.BOTTOM_TOP_RIGHT )
            return conflicted[ 3 ];
        return false;
    }
    
    @Override
    public ArrayList<LayeredGraphEdge> calcConflictedEdges()
    {
    	ArrayList<LayeredGraphEdge> list = new ArrayList<>();
    	ArrayList<LayeredGraphNode> l = graph.getContainedLayers().get( sources.get( 0 ).getLayer() );
    	ArrayList<LayeredGraphNode> l2 = graph.getContainedLayers().get( targets.get( 0 ).getLayer() );
    	int startIndex = l.indexOf( sources.get( 0 ) );
    	int endIndex = l2.indexOf( targets.get( 0 ) );
    	for( int i = 0; i < l.size(); i++ )
    	{
    		if( i == startIndex )
    			continue;
    		for( LayeredGraphEdge e : l.get( i ).getOutgoingEdges() )
    		{
    			int i2 = l2.indexOf( e.getTargets().get( 0 ) );
    			if( i2 == endIndex )
    				continue;
    			if( i < startIndex && i2 > endIndex || i > startIndex && i2 < endIndex )
    				list.add( e );
    		}
    	}
    	return list;
    };
    
    @Override
    public void setConflicted( boolean conflicted, LayoutType layout )
    {
        if( layout == null )
        {
            this.conflicted[ 0 ] = conflicted;
            this.conflicted[ 1 ] = conflicted;
            this.conflicted[ 2 ] = conflicted;
            this.conflicted[ 3 ] = conflicted;
        }
        if( layout == LayoutType.TOP_BOTTOM_LEFT )
            this.conflicted[ 0 ] = conflicted;
        if( layout == LayoutType.TOP_BOTTOM_RIGHT )
            this.conflicted[ 1 ] = conflicted;
        if( layout == LayoutType.BOTTOM_TOP_LEFT )
            this.conflicted[ 2 ] = conflicted;
        if( layout == LayoutType.BOTTOM_TOP_RIGHT )
            this.conflicted[ 3 ] = conflicted;
    }
    
    @Override
    public void setStartPoint( int x, int y, LayoutType layout )
    {
        if( layout == null )
        {
            bindPoints[ 0 ].set( 0, new Point( x, y ) );
            bindPoints[ 1 ].set( 0, new Point( x, y ) );
            bindPoints[ 2 ].set( 0, new Point( x, y ) );
            bindPoints[ 3 ].set( 0, new Point( x, y ) );
        }
        if( layout == LayoutType.TOP_BOTTOM_LEFT )
            bindPoints[ 0 ].set( 0, new Point( x, y ) );
        if( layout == LayoutType.TOP_BOTTOM_RIGHT )
            bindPoints[ 1 ].set( 0, new Point( x, y ) );
        if( layout == LayoutType.BOTTOM_TOP_LEFT )
            bindPoints[ 2 ].set( 0, new Point( x, y ) );
        if( layout == LayoutType.BOTTOM_TOP_RIGHT )
            bindPoints[ 3 ].set( 0, new Point( x, y ) );
    }
    
    @Override
    public void addBindPoint( int x, int y, LayoutType layout )
    {
        if( layout == null )
        {
            bindPoints[ 0 ].add( bindPoints[ 0 ].size() - 1, new Point( x, y ) );
            bindPoints[ 1 ].add( bindPoints[ 1 ].size() - 1, new Point( x, y ) );
            bindPoints[ 2 ].add( bindPoints[ 2 ].size() - 1, new Point( x, y ) );
            bindPoints[ 3 ].add( bindPoints[ 3 ].size() - 1, new Point( x, y ) );
        }
        if( layout == LayoutType.TOP_BOTTOM_LEFT )
            bindPoints[ 0 ].add( bindPoints[ 0 ].size() - 1, new Point( x, y ) );
        if( layout == LayoutType.TOP_BOTTOM_RIGHT )
            bindPoints[ 1 ].add( bindPoints[ 1 ].size() - 1, new Point( x, y ) );
        if( layout == LayoutType.BOTTOM_TOP_LEFT )
            bindPoints[ 2 ].add( bindPoints[ 2 ].size() - 1, new Point( x, y ) );
        if( layout == LayoutType.BOTTOM_TOP_RIGHT )
            bindPoints[ 3 ].add( bindPoints[ 3 ].size() - 1, new Point( x, y ) );
    }
    
    @Override
    public void setEndPoint( int x, int y, LayoutType layout )
    {
        if( layout == null )
        {
            bindPoints[ 0 ].set( bindPoints[ 0 ].size() - 1, new Point( x, y ) );
            bindPoints[ 1 ].set( bindPoints[ 1 ].size() - 1, new Point( x, y ) );
            bindPoints[ 2 ].set( bindPoints[ 2 ].size() - 1, new Point( x, y ) );
            bindPoints[ 3 ].set( bindPoints[ 3 ].size() - 1, new Point( x, y ) );
        }
        if( layout == LayoutType.TOP_BOTTOM_LEFT )
            bindPoints[ 0 ].set( bindPoints[ 0 ].size() - 1, new Point( x, y ) );
        if( layout == LayoutType.TOP_BOTTOM_RIGHT )
            bindPoints[ 1 ].set( bindPoints[ 1 ].size() - 1, new Point( x, y ) );
        if( layout == LayoutType.BOTTOM_TOP_LEFT )
            bindPoints[ 2 ].set( bindPoints[ 2 ].size() - 1, new Point( x, y ) );
        if( layout == LayoutType.BOTTOM_TOP_RIGHT )
            bindPoints[ 3 ].set( bindPoints[ 3 ].size() - 1, new Point( x, y ) );
    }

    @Override
    public ArrayList<Point> getLinePoints( LayoutType layout )
    {
        //if( bindPoints.get( 0 ) == null && sources.size() > 0 )
        //{
            setStartPoint( (int)sources.get( 0 ).getX( layout ) + (int)sources.get( 0 ).getWidth( layout ) / 2, (int)sources.get( 0 ).getY( layout ) + (int)sources.get( 0 ).getHeight( layout ), layout );
        //}
        //if( bindPoints.get( bindPoints.size() - 1 ) == null && targets.size() > 0 )
        //{
            setEndPoint( (int)targets.get( 0 ).getX( layout ) + (int)targets.get( 0 ).getWidth( layout ) / 2, (int)targets.get( 0 ).getY( layout ), layout );
        //}
        if( layout == LayoutType.TOP_BOTTOM_LEFT )
            return bindPoints[ 0 ];
        if( layout == LayoutType.TOP_BOTTOM_RIGHT )
            return bindPoints[ 1 ];
        if( layout == LayoutType.BOTTOM_TOP_LEFT )
            return bindPoints[ 2 ];
        if( layout == LayoutType.BOTTOM_TOP_RIGHT )
            return bindPoints[ 3 ];
        return null;
    }
    
    @Override
    public ElkEdge getOriginalEdge() {
        return original;
    }

    @Override
    public void remove() {
        graph.removeEdge( this );
    }

    @Override
    public ArrayList<LayeredGraphNode> getSources() {
        return sources;
    }

    @Override
    public ArrayList<LayeredGraphNode> getTargets() {
        return targets;
    }

    @Override
    public boolean isCrossLayerEdge() {
        int sl = sources.get( 0 ).getLayer();
        for( LayeredGraphNode s : sources )
        {
            if( sl != s.getLayer() )
                return true;
        }
        int tl = targets.get( 0 ).getLayer();
        if( Math.abs( tl - sl ) != 1 )
            return true;
        for( LayeredGraphNode t : targets )
        {
            if( tl != t.getLayer() )
                return true;
        }
        return false;
    }

    @Override
    public void replaceByDummyNodes() {
        if( isCrossLayerEdge() )
        {
            remove();
            if( sources.size() == 1 && targets.size() == 1 )
            {
                LayeredGraphNode last = sources.get( 0 );
                int sl = last.getLayer();
                int tl = targets.get( 0 ).getLayer();
                for( int i = sl + 1; i < tl; i++ )
                {
                    LayeredGraphNode n = graph.createNode( null );
                    n.setLayer( i );
                    LayeredGraphEdge e = graph.createSimpleEdge( original, last, n );
                    e.setDummyEdge();
                    if( reversed )
                        e.setReversedEdge();
                    last = n;
                }
                LayeredGraphEdge e = graph.createSimpleEdge( original, last, targets.get( 0 ) );
                e.setDummyEdge();
                if( reversed )
                    e.setReversedEdge();
            }
        }
    }

    @Override
    public void reverse() {
        reversed = !reversed;
        ArrayList< LayeredGraphNode > tmp = sources;
        sources = targets;
        targets = tmp;
    }

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

    @Override
    public void removeDummyNodes() {
        if( isDummyEdge() )
        {
            remove();
            ArrayList< LayeredGraphNode > sours = sources;
            for( int i = 0; i < sours.size(); i++ )
            {
                LayeredGraphNode n = sours.get( i );
                if( n.getOriginalNode() == null )
                {
                    sours.remove( n );
                    for( LayeredGraphEdge e : n.getIncomingEdges() )
                    {
                        if( e.isDummyEdge() && e.getOriginalEdge() == original )
                            sours.addAll( e.getSources() );
                    }
                }
            }
            ArrayList< LayeredGraphNode > targs = targets;
            for( int i = 0; i < targs.size(); i++ )
            {
                LayeredGraphNode n = targs.get( i );
                if( n.getOriginalNode() == null )
                {
                    targs.remove( n );
                    for( LayeredGraphEdge e : n.getOutgoingEdges() )
                    {
                        if( e.isDummyEdge() && e.getOriginalEdge() == original )
                            targs.addAll( e.getTargets() );
                    }
                }
                    
            }
            LayeredGraphEdge e = graph.createEdge( original, sours, targs );
            if( reversed )
                e.setReversedEdge();
        }
    }

    @Override
    public void setDummyEdge() {
        dummy = true;
    }

    @Override
    public void setReversedEdge() {
        reversed = true;
    }

    @Override
    public void setGraph(LayeredGraphNode graph) {
        this.graph = graph;
    }

    @Override
    public boolean isReversedEdge() {
        return reversed;
    }
}
