package bk;

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

import javax.swing.JTree;

import animation.AlgorithmStage;
import animation.CodeLine;
import animation.ControlFlow;
import animation.Memory;
import animation.PseudoCodeNode;
import animation.Memory.MemoryType;
import animation.Memory.ReadOnlyMemory;
import codelines.AbstractForLoop;
import codelines.DeclareVariable;
import codelines.ForEachLoop;
import codelines.FunctionDefinition;
import codelines.IfLoop;
import codelines.SetVariable;
import graph.LayeredGraphEdge;
import graph.LayeredGraphNode;
import lib.TextLayoutHelper;

/**
 * The stage of the BK node placement algorithm where the blocks are computed.
 * @author kolja
 *
 */
public class BlockCalc implements AlgorithmStage {

    @Override
    public PseudoCodeNode createPseudocodeTree( JTree tree ) {
        String[] vars = { "graph", "L", "r", "neighbors", "layout", "m", "i", "k", "mids" };
        PseudoCodeNode root = new PseudoCodeNode( TextLayoutHelper.setupPseudoCode( "function calculateBlockGraph( layout, graph )", vars ), tree, new FunctionDefinition( new String[]{ "layout", "graph" } ) );
        root.add( new PseudoCodeNode(TextLayoutHelper.setupPseudoCode( "L = graph.getContainedLayers();", vars), tree, new DeclareVariable<ArrayList<ArrayList<LayeredGraphNode>>>( "L" ) {
            @Override
            protected ArrayList<ArrayList<LayeredGraphNode>> value(ReadOnlyMemory m) {
                return m.<LayeredGraphNode>read( "graph", MemoryType.LOCAL ).getContainedLayers();
            }
        } ) );
        PseudoCodeNode layerLoop = new PseudoCodeNode( TextLayoutHelper.setupPseudoCode( "for i=layout.contains('DOWN') ? 0 : |L|-1 to layout.contains('DOWN') ? |L|-1 : 0 do", vars ), tree, new AbstractForLoop<Integer>( "i" ) {
            @Override
            protected Integer begin(ReadOnlyMemory m) {
                if( m.<String>read( "layout", MemoryType.LOCAL ).contains( "DOWN" ) )
                    return 0;
                return m.<ArrayList<ArrayList<LayeredGraphNode>>>read( "L", MemoryType.LOCAL ).size() - 1;
            }
            @Override
            protected Integer step(ReadOnlyMemory m) {
                if( m.<String>read( "layout", MemoryType.LOCAL ).contains( "DOWN" ) )
                    return m.<Integer>read( "i", MemoryType.LOCAL ) + 1;
                return m.<Integer>read( "i", MemoryType.LOCAL ) - 1;
            }
            @Override
            protected boolean condition(ReadOnlyMemory m) {
                if( m.<String>read( "layout", MemoryType.LOCAL ).contains( "DOWN" ) )
                    return m.<Integer>read( "i", MemoryType.LOCAL ) <= m.<ArrayList<ArrayList<LayeredGraphNode>>>read( "L", MemoryType.LOCAL ).size() - 1;
                return m.<Integer>read( "i", MemoryType.LOCAL ) >= 0;
            }
        });
        root.add( layerLoop );
        layerLoop.add( new PseudoCodeNode(TextLayoutHelper.setupPseudoCode( "r = layout.contains('RIGHT') ? -1 : INFINITY;", vars), tree, new DeclareVariable<Double>( "r" ) {
            @Override
            protected Double value(ReadOnlyMemory m) {
                if( m.<String>read( "layout", MemoryType.LOCAL ).contains( "RIGHT" ) )
                    return -1.0;
                return Double.POSITIVE_INFINITY;
            }
        } ) );
        PseudoCodeNode nodeLoop = new PseudoCodeNode( TextLayoutHelper.setupPseudoCode( "for k=layout.contains('RIGHT') ? 0 : |L[i]|-1 to layout.contains('RIGHT') ? |L[i]|-1 : 0 do", vars ), tree, new AbstractForLoop<Integer>( "k" ) {
            @Override
            protected Integer begin( ReadOnlyMemory m) {
                if( m.<String>read( "layout", MemoryType.LOCAL ).contains( "RIGHT" ) )
                    return 0;
                return m.<ArrayList<ArrayList<LayeredGraphNode>>>read( "L", MemoryType.LOCAL ).get( m.read( "i", MemoryType.LOCAL ) ).size() - 1;
            }
            @Override
            protected Integer step( ReadOnlyMemory m) {
                if( m.<String>read( "layout", MemoryType.LOCAL ).contains( "RIGHT" ) )
                    return m.<Integer>read( "k", MemoryType.LOCAL ) + 1;
                return m.<Integer>read( "k", MemoryType.LOCAL ) - 1;
            }
            @Override
            protected boolean condition( ReadOnlyMemory m) {
                if( m.<String>read( "layout", MemoryType.LOCAL ).contains( "RIGHT" ) )
                    return m.<Integer>read( "k", MemoryType.LOCAL ) <= m.<ArrayList<ArrayList<LayeredGraphNode>>>read( "L", MemoryType.LOCAL ).get( m.read( "i", MemoryType.LOCAL ) ).size() - 1;
                return m.<Integer>read( "k", MemoryType.LOCAL ) >= 0;
            }
        });
        nodeLoop.add( new PseudoCodeNode(TextLayoutHelper.setupPseudoCode( "neighbors = layout.contains('DOWN') ? L[i][k].predecessors : L[i][k].successors", vars), tree, new DeclareVariable<ArrayList<LayeredGraphNode>>( "neighbors" ) {
            @Override
            protected ArrayList<LayeredGraphNode> value(ReadOnlyMemory m) {
                ArrayList<LayeredGraphEdge> list = m.<ArrayList<ArrayList<LayeredGraphNode>>>read( "L", MemoryType.LOCAL ).get( m.read( "i", MemoryType.LOCAL ) ).get( m.read( "k", MemoryType.LOCAL ) ).getSortedOutgoingEdges();
                if( m.<String>read( "layout", MemoryType.LOCAL ).contains( "DOWN" ) )
                    list = m.<ArrayList<ArrayList<LayeredGraphNode>>>read( "L", MemoryType.LOCAL ).get( m.read( "i", MemoryType.LOCAL ) ).get( m.read( "k", MemoryType.LOCAL ) ).getSortedIncomingEdges();
                ArrayList<LayeredGraphNode> result = new ArrayList<LayeredGraphNode>();
                for( LayeredGraphEdge e : list )
                {
                    if( m.<String>read( "layout", MemoryType.LOCAL ).contains( "DOWN" ) )
                        result.add( e.getSources().get( 0 ) );
                    else
                        result.add( e.getTargets().get( 0 ) );
                }
                return result;
            }
        } ) );
        layerLoop.add( nodeLoop );
        nodeLoop.add( new PseudoCodeNode(TextLayoutHelper.setupPseudoCode( "neighbors = layout.contains('RIGHT') ? neighbors : reverse( neighbors )", vars), tree, new SetVariable<ArrayList<LayeredGraphNode>>( "neighbors" ) {
            @Override
            protected ArrayList<LayeredGraphNode> value(ReadOnlyMemory m) {
                ArrayList<LayeredGraphNode> list = m.read( "neighbors", MemoryType.LOCAL );
                if( m.<String>read( "layout", MemoryType.LOCAL ).contains( "RIGHT" ) )
                    return list;
                else
                {
                    ArrayList<LayeredGraphNode> result = new ArrayList<LayeredGraphNode>();
                    for( int i = list.size() - 1; i >= 0; i-- )
                        result.add( list.get( i ) );
                    return result;
                }
            }
        } ) );
        PseudoCodeNode ifNeighbors = new PseudoCodeNode( TextLayoutHelper.setupPseudoCode( "if |neighbors| > 0 then", vars ), tree, new IfLoop() {
            @Override
            protected boolean condition( ReadOnlyMemory m) {
                return m.<ArrayList<LayeredGraphNode>>read( "neighbors", MemoryType.LOCAL ).size() > 0;
            }
        });
        nodeLoop.add( ifNeighbors );
        ifNeighbors.add( new PseudoCodeNode(TextLayoutHelper.setupPseudoCode( "mids = [roundDown((|neighbors|-1)/2),roundUp((|neighbors|-1)/2)]", vars), tree, new DeclareVariable<ArrayList<Integer>>( "mids" ) {
            @Override
            protected ArrayList<Integer> value(ReadOnlyMemory m) {
                int size = m.<ArrayList<LayeredGraphNode>>read( "neighbors", MemoryType.LOCAL ).size() - 1;
                int m1 = size / 2, m2 = (int)(size / 2.0 + 0.5);
                ArrayList<Integer> list = new ArrayList<Integer>();
                list.add( m1 );
                if( m1 != m2 )
                    list.add( m2 );
                return list;
            }
        } ) );
        PseudoCodeNode midLoop = new PseudoCodeNode( TextLayoutHelper.setupPseudoCode( "foreach m in mids do", vars ), tree, new ForEachLoop<Integer>( "m" ) {
            @Override
            protected List<Integer> list(ReadOnlyMemory m) {
                return m.read( "mids", MemoryType.LOCAL );
            }
        } );
        ifNeighbors.add( midLoop );
        PseudoCodeNode ifAlign = new PseudoCodeNode( TextLayoutHelper.setupPseudoCode( "if align( L[i][k] ) == L[i][k] then", vars ), tree, new IfLoop() {
            @Override
            protected boolean condition(ReadOnlyMemory m) {
                LayeredGraphNode n = m.<ArrayList<ArrayList<LayeredGraphNode>>>read( "L", MemoryType.LOCAL ).get( m.read( "i", MemoryType.LOCAL ) ).get( m.read( "k", MemoryType.LOCAL ) );
                return n.getAlign( LayoutType.fromString( m.read( "layout", MemoryType.LOCAL ) ) ) == n;
            }
        });
        midLoop.add( ifAlign );
        PseudoCodeNode ifMarked = new PseudoCodeNode( TextLayoutHelper.setupPseudoCode( "if (neighbors[m],L[i][k]) not conflicted and ((r < pos(neighbors[m]) and layout.contains('RIGHT')) or (r > pos(neighbors[m]) and layout.contains('LEFT'))) ", vars ), tree, new IfLoop() {
            @Override
            protected boolean condition(ReadOnlyMemory m) {
                LayeredGraphEdge e = m.<LayeredGraphNode>read( "graph", MemoryType.LOCAL ).findEdgeBetween( 
                        m.<ArrayList<LayeredGraphNode>>read( "neighbors", MemoryType.LOCAL ).get( m.read( "m", MemoryType.LOCAL ) ), 
                        m.<ArrayList<ArrayList<LayeredGraphNode>>>read( "L", MemoryType.LOCAL ).get( m.read( "i", MemoryType.LOCAL ) ).get( m.read( "k", MemoryType.LOCAL ) ) );
                ArrayList<LayeredGraphNode> layerBefore;
                if( m.<String>read( "layout", MemoryType.LOCAL ).contains( "DOWN" ) )
                    layerBefore = m.<ArrayList<ArrayList<LayeredGraphNode>>>read( "L", MemoryType.LOCAL ).get( m.<Integer>read( "i", MemoryType.LOCAL ) - 1 );
                else
                    layerBefore = m.<ArrayList<ArrayList<LayeredGraphNode>>>read( "L", MemoryType.LOCAL ).get( m.<Integer>read( "i", MemoryType.LOCAL ) + 1 );
                int posU = layerBefore.indexOf( m.<ArrayList<LayeredGraphNode>>read( "neighbors", MemoryType.LOCAL ).get( m.read( "m", MemoryType.LOCAL ) ) );
                return e.isConflicted( LayoutType.fromString( m.read( "layout", MemoryType.LOCAL ) ) ) && 
                        ( ( m.<Integer>read( "r", MemoryType.LOCAL ) < posU && m.<String>read( "layout", MemoryType.LOCAL ).contains( "RIGHT" ) ) || 
                                ( m.<Integer>read( "r", MemoryType.LOCAL ) > posU && m.<String>read( "layout", MemoryType.LOCAL ).contains( "LEFT" ) ) );
            }
        });
        ifAlign.add( ifMarked );
        ifMarked.add( new PseudoCodeNode( TextLayoutHelper.setupPseudoCode( "align( neighbors[m] ) = L[i][k];", vars ), tree, new CodeLine() {
            @Override
            public ControlFlow runForward(Memory m) {
                LayeredGraphNode u = m.<ArrayList<LayeredGraphNode>>read( "neighbors", MemoryType.LOCAL ).get( m.read( "m", MemoryType.LOCAL ) );
                LayeredGraphNode v = m.<ArrayList<ArrayList<LayeredGraphNode>>>read( "L", MemoryType.LOCAL ).get( m.read( "i", MemoryType.LOCAL ) ).get( m.read( "k", MemoryType.LOCAL ) );
                LayeredGraphNode old = u.getAlign( LayoutType.fromString( m.read( "layout", MemoryType.LOCAL ) ) );
                u.setAlign( v, LayoutType.fromString( m.read( "layout", MemoryType.LOCAL ) ) );
                actions.push( (Memory mem) -> {
                    u.setAlign( old, LayoutType.fromString( mem.read( "layout", MemoryType.LOCAL ) ) );
                });
                return new ControlFlow( ControlFlow.STEP_OVER );
            }
        }) );
        ifMarked.add( new PseudoCodeNode( TextLayoutHelper.setupPseudoCode( "root( L[i][k] ) = root( neighbors[m] );", vars ), tree, new CodeLine() {
            @Override
            public ControlFlow runForward(Memory m) {
                LayeredGraphNode u = m.<ArrayList<LayeredGraphNode>>read( "neighbors", MemoryType.LOCAL ).get( m.read( "m", MemoryType.LOCAL ) );
                LayeredGraphNode v = m.<ArrayList<ArrayList<LayeredGraphNode>>>read( "L", MemoryType.LOCAL ).get( m.read( "i", MemoryType.LOCAL ) ).get( m.read( "k", MemoryType.LOCAL ) );
                LayeredGraphNode old = v.getRoot( LayoutType.fromString( m.read( "layout", MemoryType.LOCAL ) ) );
                v.setRoot( u.getRoot( LayoutType.fromString( m.read( "layout", MemoryType.LOCAL ) ) ), LayoutType.fromString( m.read( "layout", MemoryType.LOCAL ) ) );
                actions.push( (Memory mem) -> {
                    v.setRoot( old, LayoutType.fromString( m.read( "layout", MemoryType.LOCAL ) ) );
                });
                return new ControlFlow( ControlFlow.STEP_OVER );
            }
        }) );
        ifMarked.add( new PseudoCodeNode( TextLayoutHelper.setupPseudoCode( "align( L[i][k] ) = root( L[i][k] );", vars ), tree, new CodeLine() {
            @Override
            public ControlFlow runForward(Memory m) {
                LayeredGraphNode v = m.<ArrayList<ArrayList<LayeredGraphNode>>>read( "L", MemoryType.LOCAL ).get( m.read( "i", MemoryType.LOCAL ) ).get( m.read( "k", MemoryType.LOCAL ) );
                LayeredGraphNode old = v.getAlign( LayoutType.fromString( m.read( "layout", MemoryType.LOCAL ) ) );
                v.setAlign( v.getAlign( LayoutType.fromString( m.read( "layout", MemoryType.LOCAL ) ) ), LayoutType.fromString( m.read( "layout", MemoryType.LOCAL ) ) );
                actions.push( (Memory mem) -> {
                    v.setAlign( old, LayoutType.fromString( m.read( "layout", MemoryType.LOCAL ) ) );
                });
                return new ControlFlow( ControlFlow.STEP_OVER );
            }
        }) );
        ifMarked.add( new PseudoCodeNode( TextLayoutHelper.setupPseudoCode( "r = pos( neighbors[m] );", vars ), tree, new SetVariable<Integer>( "r" ) {
            @Override
            protected Integer value(ReadOnlyMemory m) {
                ArrayList<LayeredGraphNode> layerBefore;
                if( m.<String>read( "layout", MemoryType.LOCAL ).contains( "DOWN" ) )
                    layerBefore = m.<ArrayList<ArrayList<LayeredGraphNode>>>read( "L", MemoryType.LOCAL ).get( m.<Integer>read( "i", MemoryType.LOCAL ) - 1 );
                else
                    layerBefore = m.<ArrayList<ArrayList<LayeredGraphNode>>>read( "L", MemoryType.LOCAL ).get( m.<Integer>read( "i", MemoryType.LOCAL ) + 1 );
                LayeredGraphNode u = m.<ArrayList<LayeredGraphNode>>read( "neighbors", MemoryType.LOCAL ).get( m.read( "m", MemoryType.LOCAL ) );
                return layerBefore.indexOf( u );
            }
        }) );
        return root;
    }
}
