package bk;

import javax.swing.JTree;

import codeline.CodeLine;
import codeline.DeclareVariable;
import codeline.FunctionCall;
import codeline.FunctionDefinition;
import codeline.Comment;
import codeline.SetVariable;
import graph.LayeredGraphNode;
import lib.TextLayoutHelper;
import processor.ControlFlow;
import processor.Memory;
import processor.PseudoCodeNode;
import processor.Memory.ReadOnlyMemory;
import processor.Memory.Visibility;

/**
 * The BK node placement algorithm.
 * @author kolja
 *
 */
public class BKNodePlacement {

    /**
     * A stage of the BK node placement algorithm.
     * @author kolja
     *
     */
    public enum Stage
    {
        CONFLICT_DETECTION,
        LEFTMOST_UPPER,
        RIGHTMOST_UPPER,
        LEFTMOST_LOWER,
        RIGHTMOST_LOWER,
        COMBINE
    }

    private Stage stage;
    
    public BKNodePlacement() {
        stage = Stage.CONFLICT_DETECTION;
    }
    
    public Stage getAlgorithmState()
    {
        return stage;
    }

    public PseudoCodeNode createPseudocodeTree( JTree tree )
    {
    	String[] vars = { "layout", "graph" };
    	PseudoCodeNode mainFunction = new PseudoCodeNode( "function bkNodePlacement( graph )", vars, tree, new FunctionDefinition( new String[]{"graph"} ) );
    	PseudoCodeNode root = new PseudoCodeNode( "-- BK Node Placement Algorithm --", vars, tree, new CodeLine() {

			@Override
			public ControlFlow runForward(Memory m) {
				m.declare( "param1", m.read( "graph", Visibility.GLOBAL ), Visibility.GLOBAL );
				if( m.isDefined( "Called", Visibility.GLOBAL ) )
				{
					actions.push( (Memory mem) -> {
					    m.undeclare( "param1", Visibility.GLOBAL );
					} );
					return new ControlFlow( ControlFlow.STEP_OVER );
				}
				m.declare( "Called", true, Visibility.GLOBAL );
				actions.push( (Memory mem) -> {
                    m.undeclare( "param1", Visibility.GLOBAL );
	                m.undeclare( "Called", Visibility.GLOBAL );
				} );
				return new ControlFlow( mainFunction );
			}

        } );
        root.setSelected( true );

        PseudoCodeNode conflictDetectionFunction = ConflictDetection.mark_conflicts( tree );
        PseudoCodeNode calcLayout = new PseudoCodeNode( "function calcLayout( layout, graph )", vars, tree, new FunctionDefinition( vars ) );
        PseudoCodeNode combine = Balancing.combine( tree );
        root.add( mainFunction );
        mainFunction.add( new PseudoCodeNode( "call detectConflicts( graph )", vars, tree, new FunctionCall( conflictDetectionFunction, new String[]{ "graph" } ) ) );
        mainFunction.add( new PseudoCodeNode( "layout = 'RIGHTMOST_LOWER'", vars, tree, new DeclareVariable<String>( "layout" ) {
            @Override
            protected String value(ReadOnlyMemory m) {
                return "RIGHTMOST_LOWER";
            }
        }) );
        mainFunction.add( new PseudoCodeNode( "call calcLayout( layout, graph )", vars, tree, new FunctionCall( calcLayout, new String[]{ "layout", "graph" } ) ) );
        mainFunction.add( new PseudoCodeNode( "layout = 'LEFTMOST_LOWER'", vars, tree, new SetVariable<String>( "layout" ) {
            @Override
            protected String value(ReadOnlyMemory m) {
                return "LEFTMOST_LOWER";
            }
        }));
        mainFunction.add( new PseudoCodeNode( "call calcLayout( layout, graph )", vars, tree, new FunctionCall( calcLayout, new String[]{ "layout", "graph" } ) ) );
        mainFunction.add( new PseudoCodeNode( "layout = 'RIGHTMOST_UPPER'", vars, tree, new SetVariable<String>( "layout" ) {
            @Override
            protected String value(ReadOnlyMemory m) {
                return "RIGHTMOST_UPPER";
            }
        }) );
        mainFunction.add( new PseudoCodeNode( "call calcLayout( layout, graph )", vars, tree, new FunctionCall( calcLayout, new String[]{ "layout", "graph" } ) ) );
        mainFunction.add( new PseudoCodeNode( "layout = 'LEFTMOST_UPPER'", vars, tree, new SetVariable<String>( "layout" ) {
            @Override
            protected String value(ReadOnlyMemory m) {
                return "LEFTMOST_UPPER";
            }
        }) );
        mainFunction.add( new PseudoCodeNode( "call calcLayout( layout, graph )", vars, tree, new FunctionCall( calcLayout, new String[]{ "layout", "graph" } ) ) );
        mainFunction.add( new PseudoCodeNode( "call combine( graph )", vars, tree, new FunctionCall( combine, new String[]{ "graph" } ) ) );
        PseudoCodeNode conflictsStage = new PseudoCodeNode( "-- mark type 1 conflicts --", vars, tree, new Comment() ) {
            private static final long serialVersionUID = 1041941539437672704L;

            @Override
            public String getDebugOutput( Memory m ) {
                stage = Stage.CONFLICT_DETECTION;
                return ConflictDetection.buildDebugString( m );
            }
        };
        root.add( conflictsStage );
        conflictsStage.add( conflictDetectionFunction );
        PseudoCodeNode blockCalc = VerticalAligment.calculateBlockGraph( tree, calcLayout );
        PseudoCodeNode placeBlock = PlaceBlock.place_block( tree );
        PseudoCodeNode horizontalCompaction = HorizontalCompaction.horizontalCompaction( tree, placeBlock );
        calcLayout.add( new PseudoCodeNode( "call calculateBlockGraph( layout, graph )", vars, tree, new FunctionCall( blockCalc, vars ) ) );
        calcLayout.add( new PseudoCodeNode( "call horizontalCompaction( layout, graph )", vars, tree, new FunctionCall( horizontalCompaction, vars ) ) );
        PseudoCodeNode extremalLayoutStage = new PseudoCodeNode( "-- Compute an extremal layout --", vars, tree, new Comment() ) {
            private static final long serialVersionUID = 4141215748809071958L;

            @Override
            public String getDebugOutput( Memory m ) {
                if( !m.isSomewhereDefined( "graph", Visibility.COMPLETE_STACK ) || !m.isSomewhereDefined( "layout", Visibility.COMPLETE_STACK ) )
                    return "";
                String info = "| Node | Shift | Sink | Root | Align |  x  |  xDef  |\n";
                info +=       "|------|-------|------|------|-------|-----|--------|\n";
                LayeredGraphNode graph = m.read( "graph", Visibility.COMPLETE_STACK );
                LayoutType type = LayoutType.fromString( m.read( "layout", Visibility.COMPLETE_STACK ) );
                switch( type ) {
                case LEFTMOST_UPPER:
                    stage = Stage.LEFTMOST_UPPER;
                    break;
                case RIGHTMOST_UPPER:
                    stage = Stage.RIGHTMOST_UPPER;
                    break;
                case LEFTMOST_LOWER:
                    stage = Stage.LEFTMOST_LOWER;
                    break;
                case RIGHTMOST_LOWER:
                    stage = Stage.RIGHTMOST_LOWER;
                    break;
                case COMBINED: // this will never happen here
                    assert false;
                    break;
                default:
                    assert false;
                    break;
                }
                for( LayeredGraphNode n : graph.getContainedNodes() )
                {
                    info += "|" + TextLayoutHelper.strToLen( n.toString(), 6 ) +
                            "|" + TextLayoutHelper.strToLen( n.getShift( type ) + "", 7 ) +
                            "|" + TextLayoutHelper.strToLen( n.getSink( type ).toString(), 6 ) +
                            "|" + TextLayoutHelper.strToLen( n.getRoot( type ).toString(), 6 ) +
                            "|" + TextLayoutHelper.strToLen( n.getAlign( type ).toString(), 7 ) +
                            "|" + TextLayoutHelper.strToLen( n.getX( type ) + "", 5 ) +
                            "|" + TextLayoutHelper.strToLen( !n.isXUndefined( type ) + "", 8 ) + "|\n";
                }
                return info;
            }
        };
        root.add( extremalLayoutStage );
        extremalLayoutStage.add( calcLayout );
        extremalLayoutStage.add( new PseudoCodeNode( "-- vertical alignment --", vars, tree, new Comment() ) );
        extremalLayoutStage.add( blockCalc );
        extremalLayoutStage.add( new PseudoCodeNode( "-- horizontal compaction --", vars, tree, new Comment() ) );
        extremalLayoutStage.add( horizontalCompaction );
        extremalLayoutStage.add( placeBlock );
        PseudoCodeNode balancingStage = new PseudoCodeNode( "-- balancing --", vars, tree, new Comment() ) {
            private static final long serialVersionUID = 4212377075998840086L;

            @Override
            public String getDebugOutput( Memory m ) {
                stage = Stage.COMBINE;
                if( !m.isSomewhereDefined( "graph", Visibility.COMPLETE_STACK ) )
                    return "";
                String info = "| Node | x BR | x BL | x UR | x UL | x CO |\n";
                info +=       "|------|------|------|------|------|------|\n";
                LayeredGraphNode graph = m.read( "graph", Visibility.COMPLETE_STACK );
                for( LayeredGraphNode n : graph.getContainedNodes() )
                {
                    info += "|" + TextLayoutHelper.strToLen( n.toString(), 6 ) +
                            "|" + TextLayoutHelper.strToLen( n.getX( LayoutType.LEFTMOST_UPPER ) + "", 6 ) +
                            "|" + TextLayoutHelper.strToLen( n.getX( LayoutType.RIGHTMOST_UPPER ) + "", 6 ) +
                            "|" + TextLayoutHelper.strToLen( n.getX( LayoutType.LEFTMOST_LOWER ) + "", 6 ) +
                            "|" + TextLayoutHelper.strToLen( n.getX( LayoutType.RIGHTMOST_LOWER ) + "", 6 ) +
                            "|" + TextLayoutHelper.strToLen( n.getX( LayoutType.COMBINED ) + "", 6 ) + "|\n";
                }
                return info;
            }
        };
        root.add( balancingStage );
        balancingStage.add( combine );
        return root;
    }
}
