package bk;

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

import javax.swing.JTree;

import codeline.AbstractForLoop;
import codeline.CodeLine;
import codeline.DeclareVariable;
import codeline.ForEachLoop;
import codeline.FunctionCall;
import codeline.FunctionDefinition;
import codeline.IfLoop;
import graph.LayeredGraphNode;
import processor.ControlFlow;
import processor.Memory;
import processor.PseudoCodeNode;
import processor.Memory.ReadOnlyMemory;
import processor.Memory.Visibility;

/**
 * The stage of compacting the layout.
 * @author kolja
 *
 */
public class HorizontalCompaction {

    /**
     * creates the pseudo code for the horizontal compaction stage
     * @param tree the tree where the code will be displayed
     * @param placeBlock the code for the placeBlock method for recursive calls
     * @return the code
     */
    public static PseudoCodeNode horizontalCompaction( JTree tree, PseudoCodeNode placeBlock ) {
        String[] vars = { "i", "L", "layout", "graph", "k", "v" };

        PseudoCodeNode root = new PseudoCodeNode( "function horizontalCompaction( layout, graph )", vars, tree, new FunctionDefinition( new String[]{ "layout", "graph" } ) ) {
            private static final long serialVersionUID = -2594556212373706809L;

            @Override
            public String getDebugOutput( Memory m )
            {
                if( m.isSomewhereDefined( "v", Visibility.LOCAL ) )
                    m.<LayeredGraphNode>read( "v", Visibility.LOCAL).setSelected( LayoutType.fromString( m.read( "layout", Visibility.LOCAL ) ) );
                return super.getDebugOutput( m );
            }
        };
        root.add( new PseudoCodeNode( "L = graph.getContainedLayers();", vars, tree, new DeclareVariable<ArrayList<ArrayList<LayeredGraphNode>>>( "L" ) {
            @Override
            protected ArrayList<ArrayList<LayeredGraphNode>> value(ReadOnlyMemory m) {
                return m.<LayeredGraphNode>read( "graph", Visibility.LOCAL ).getContainedLayers();
            }
        } ) );
        PseudoCodeNode firstLoop = new PseudoCodeNode( "for i=layout.contains('LOWER') ? 0 : |L|-1 to layout.contains('LOWER') ? |L|-1 : 0 do", vars, tree, new AbstractForLoop<Integer>( "i" ) {
            @Override
            protected Integer intialize(ReadOnlyMemory m) {
                if( m.<String>read( "layout", Visibility.LOCAL ).contains( "LOWER" ) )
                    return 0;
                return m.<ArrayList<ArrayList<LayeredGraphNode>>>read( "L", Visibility.LOCAL ).size() - 1;
            }
            @Override
            protected Integer step(ReadOnlyMemory m) {
                if( m.<String>read( "layout", Visibility.LOCAL ).contains( "LOWER" ) )
                    return m.<Integer>read( "i", Visibility.LOCAL ) + 1;
                return m.<Integer>read( "i", Visibility.LOCAL ) - 1;
            }
            @Override
            protected boolean condition(ReadOnlyMemory m) {
                if( m.<String>read( "layout", Visibility.LOCAL ).contains( "LOWER" ) )
                    return m.<Integer>read( "i", Visibility.LOCAL ) <= m.<ArrayList<ArrayList<LayeredGraphNode>>>read( "L", Visibility.LOCAL ).size() - 1;
                return m.<Integer>read( "i", Visibility.LOCAL ) >= 0;
            }
        });
        root.add( firstLoop );
        PseudoCodeNode nodeLoop = new PseudoCodeNode( "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 intialize( ReadOnlyMemory m) {
                if( m.<String>read( "layout", Visibility.LOCAL ).contains( "RIGHT" ) )
                    return 0;
                return m.<ArrayList<ArrayList<LayeredGraphNode>>>read( "L", Visibility.LOCAL ).get( m.read( "i", Visibility.LOCAL ) ).size() - 1;
            }
            @Override
            protected Integer step( ReadOnlyMemory m) {
                if( m.<String>read( "layout", Visibility.LOCAL ).contains( "RIGHT" ) )
                    return m.<Integer>read( "k", Visibility.LOCAL ) + 1;
                return m.<Integer>read( "k", Visibility.LOCAL ) - 1;
            }
            @Override
            protected boolean condition( ReadOnlyMemory m) {
                if( m.<String>read( "layout", Visibility.LOCAL ).contains( "RIGHT" ) )
                    return m.<Integer>read( "k", Visibility.LOCAL ) <= m.<ArrayList<ArrayList<LayeredGraphNode>>>read( "L", Visibility.LOCAL ).get( m.read( "i", Visibility.LOCAL ) ).size() - 1;
                return m.<Integer>read( "k", Visibility.LOCAL ) >= 0;
            }
        });
        firstLoop.add( nodeLoop );
        nodeLoop.add( new PseudoCodeNode( "v = L[i][k];", vars, tree, new DeclareVariable<LayeredGraphNode>( "v" ) {
            @Override
            protected LayeredGraphNode value(ReadOnlyMemory m) {
                return m.<ArrayList<ArrayList<LayeredGraphNode>>>read( "L", Visibility.LOCAL ).get( m.read( "i", Visibility.LOCAL ) ).get( m.read( "k", Visibility.LOCAL ) );
            }
        }));
        PseudoCodeNode ifRoot = new PseudoCodeNode( "if root[v] == v then", vars, tree, new IfLoop() {
            @Override
            protected boolean condition(ReadOnlyMemory m) {
                return m.<LayeredGraphNode>read( "v", Visibility.LOCAL ).getRoot( LayoutType.fromString( m.read( "layout", Visibility.LOCAL ) ) ) == m.<LayeredGraphNode>read( "v", Visibility.LOCAL );
            }
        });
        nodeLoop.add( ifRoot );
        ifRoot.add( new PseudoCodeNode( "call place_block(v, layout);", vars, tree, new FunctionCall( placeBlock, new String[]{ "v", "layout" } ) ) );
        PseudoCodeNode secondLoop = new PseudoCodeNode( "for i=layout.contains('LOWER') ? 0 : |L|-1 to layout.contains('LOWER') ? |L|-1 : 0 do", vars, tree, new AbstractForLoop<Integer>( "i" ) {
            @Override
            protected Integer intialize(ReadOnlyMemory m) {
                if( m.<String>read( "layout", Visibility.LOCAL ).contains( "LOWER" ) )
                    return 0;
                return m.<ArrayList<ArrayList<LayeredGraphNode>>>read( "L", Visibility.LOCAL ).size() - 1;
            }
            @Override
            protected Integer step(ReadOnlyMemory m) {
                if( m.<String>read( "layout", Visibility.LOCAL ).contains( "LOWER" ) )
                    return m.<Integer>read( "i", Visibility.LOCAL ) + 1;
                return m.<Integer>read( "i", Visibility.LOCAL ) - 1;
            }
            @Override
            protected boolean condition(ReadOnlyMemory m) {
                if( m.<String>read( "layout", Visibility.LOCAL ).contains( "LOWER" ) )
                    return m.<Integer>read( "i", Visibility.LOCAL ) <= m.<ArrayList<ArrayList<LayeredGraphNode>>>read( "L", Visibility.LOCAL ).size() - 1;
                return m.<Integer>read( "i", Visibility.LOCAL ) >= 0;
            }
        });
        root.add( secondLoop );
        PseudoCodeNode foreach = new PseudoCodeNode( "foreach v in L[i] do", vars, tree, new ForEachLoop<LayeredGraphNode>( "v" ) {
            @Override
            protected List<LayeredGraphNode> list(ReadOnlyMemory m) {
                return m.<ArrayList<ArrayList<LayeredGraphNode>>>read( "L", Visibility.LOCAL ).get( m.read( "i", Visibility.LOCAL ) );
            }
        });
        secondLoop.add( foreach );
        foreach.add( new PseudoCodeNode( "x[v] = x[root[v]];", vars, tree, new CodeLine() {
            @Override
            public ControlFlow runForward(Memory m) {
                LayeredGraphNode v = m.read( "v", Visibility.LOCAL );
                LayoutType layout = LayoutType.fromString( m.read( "layout", Visibility.LOCAL ) );
                double old = v.getX( layout );
                boolean oldDef = !v.isXUndefined( layout );
                v.setX( v.getRoot( layout ).getX( layout ), true, layout );
                actions.push( (Memory mem) -> {
                    v.setX( old, oldDef, layout );
                });
                return new ControlFlow( ControlFlow.STEP_OVER );
            }
        }));
        PseudoCodeNode ifShift = new PseudoCodeNode( "if v == root[v] and ((layout.contains('RIGHT') and shift[sink[v]] < ∞ ) or (layout.contains('LEFT') and shift[sink[v]] > -∞ )) then", vars, tree, new IfLoop() {
            @Override
            protected boolean condition(ReadOnlyMemory m) {
                LayeredGraphNode v = m.read( "v", Visibility.LOCAL );
                String lStr = m.read( "layout", Visibility.LOCAL );
                LayoutType layout = LayoutType.fromString( lStr );
                return v == v.getRoot( layout ) && ((lStr.contains( "RIGHT" ) && v.getSink( layout ).getShift( layout ) < Double.POSITIVE_INFINITY ) || (lStr.contains( "LEFT" ) && v.getSink( layout ).getShift( layout ) > Double.NEGATIVE_INFINITY ));
            }
        });
        foreach.add( ifShift );
        ifShift.add( new PseudoCodeNode( "x[v] += shift[sink[v]];", vars, tree, new CodeLine() {
            @Override
            public ControlFlow runForward(Memory m) {
                LayeredGraphNode v = m.read( "v", Visibility.LOCAL );
                LayoutType layout = LayoutType.fromString( m.read( "layout", Visibility.LOCAL ) );
                double old = v.getX( layout );
                boolean oldDef = !v.isXUndefined( layout );
                v.setX( old + v.getSink( layout ).getShift( layout ), true, layout );
                actions.push( (Memory mem) -> {
                    v.setX( old, oldDef, layout );
                });
                return new ControlFlow( ControlFlow.STEP_OVER );
            }
        }));
        return root;
    }
}
