package bk;

import javax.swing.JFrame;
import javax.swing.JTree;

import animation.AnimatedAlgorithm;
import animation.AnimationController;
import animation.CodeLine;
import animation.ControlFlow;
import animation.Memory;
import animation.PseudoCodeNode;
import animation.PseudoCodeProcessor;
import animation.Memory.MemoryType;
import animation.Memory.ReadOnlyMemory;
import codelines.DeclareVariable;
import codelines.FunctionCall;
import codelines.FunctionDefinition;
import codelines.Comment;
import codelines.SetVariable;
import graph.LayeredGraphNode;
import lib.TextLayoutHelper;

/**
 * The main stage of the BK node placement algorithm.
 * @author kolja
 *
 */
public class BKNodePlacement extends AnimatedAlgorithm {

    public enum State
    {
        CONFLICTS,
        LAYOUT1,
        LAYOUT2,
        LAYOUT3,
        LAYOUT4,
        COMBINE
    }

    private State state;

    public BKNodePlacement(AnimationController controller, LayeredGraphNode graph, JFrame view) {
        super(controller, graph, view);
        state = State.CONFLICTS;
    }
    
    public State getAlgorithmState()
    {
        return state;
    }

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

			@Override
			public ControlFlow runForward(Memory m) {
				m.declare( "param1", graph, MemoryType.GLOBAL );
				if( m.isDefined( "Called", MemoryType.GLOBAL ) )
				{
					actions.push( (Memory mem) -> {
					    m.undeclare( "param1", MemoryType.GLOBAL );
					} );
					return new ControlFlow( ControlFlow.STEP_OVER );
				}
				m.declare( "Called", true, MemoryType.GLOBAL );
				actions.push( (Memory mem) -> {
                    m.undeclare( "param1", MemoryType.GLOBAL );
	                m.undeclare( "Called", MemoryType.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 = Combine.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 = 'DOWN_RIGHT'", vars, tree, new DeclareVariable<String>( "layout" ) {
            @Override
            protected String value(ReadOnlyMemory m) {
                return "DOWN_RIGHT";
            }
        }) );
        mainFunction.add( new PseudoCodeNode( "call calcLayout( layout, graph )", vars, tree, new FunctionCall( calcLayout, new String[]{ "layout", "graph" } ) ) );
        mainFunction.add( new PseudoCodeNode( "layout = 'DOWN_LEFT'", vars, tree, new SetVariable<String>( "layout" ) {
            @Override
            protected String value(ReadOnlyMemory m) {
                return "DOWN_LEFT";
            }
        }));
        mainFunction.add( new PseudoCodeNode( "call calcLayout( layout, graph )", vars, tree, new FunctionCall( calcLayout, new String[]{ "layout", "graph" } ) ) );
        mainFunction.add( new PseudoCodeNode( "layout = 'UP_RIGHT'", vars, tree, new SetVariable<String>( "layout" ) {
            @Override
            protected String value(ReadOnlyMemory m) {
                return "UP_RIGHT";
            }
        }) );
        mainFunction.add( new PseudoCodeNode( "call calcLayout( layout, graph )", vars, tree, new FunctionCall( calcLayout, new String[]{ "layout", "graph" } ) ) );
        mainFunction.add( new PseudoCodeNode( "layout = 'UP_LEFT'", vars, tree, new SetVariable<String>( "layout" ) {
            @Override
            protected String value(ReadOnlyMemory m) {
                return "UP_LEFT";
            }
        }) );
        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() ) {
            @Override
            public String getDebugOutput( Memory m ) {
                state = State.CONFLICTS;
                return ConflictDetection.buildDebugString( m );
            }
        };
        root.add( conflictsStage );
        conflictsStage.add( conflictDetectionFunction );
        PseudoCodeNode blockCalc = BlockCalc.calculateBlockGraph( tree, calcLayout );
        PseudoCodeNode placeBlock = PlaceBlock.place_block( tree );
        PseudoCodeNode horizontalCompaction = Compaction.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() ) {
            @Override
            public String getDebugOutput( Memory m ) {
                if( !m.isSomewhereDefined( "graph", MemoryType.COMPLETE_STACK ) || !m.isSomewhereDefined( "layout", MemoryType.COMPLETE_STACK ) )
                    return "";
                String info = "| Node | Shift | Sink | Root | Align |  x  |  xDef  |\n";
                info +=       "|------|-------|------|------|-------|-----|--------|\n";
                LayeredGraphNode graph = m.read( "graph", MemoryType.COMPLETE_STACK );
                LayoutType type = LayoutType.fromString( m.read( "layout", MemoryType.COMPLETE_STACK ) );
                switch( type ) {
                case TOP_BOTTOM_LEFT:
                    state = State.LAYOUT1;
                    break;
                case TOP_BOTTOM_RIGHT:
                    state = State.LAYOUT2;
                    break;
                case BOTTOM_TOP_LEFT:
                    state = State.LAYOUT3;
                    break;
                case BOTTOM_TOP_RIGHT:
                    state = State.LAYOUT4;
                    break;
                case COMBINED: // this will never happen here
                    break;
                }
                for( LayeredGraphNode n : graph.getContainedNodes() )
                {
                    info += "|" + TextLayoutHelper.strToLen( n.getName(), 6 ) +
                            "|" + TextLayoutHelper.strToLen( n.getShift( type ) + "", 7 ) +
                            "|" + TextLayoutHelper.strToLen( n.getSink( type ).getName(), 6 ) +
                            "|" + TextLayoutHelper.strToLen( n.getRoot( type ).getName(), 6 ) +
                            "|" + TextLayoutHelper.strToLen( n.getAlign( type ).getName(), 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() ) {
            @Override
            public String getDebugOutput( Memory m ) {
                state = State.COMBINE;
                if( !m.isSomewhereDefined( "graph", MemoryType.COMPLETE_STACK ) )
                    return "";
                String info = "| Node | x BR | x BL | x UR | x UL | x CO |\n";
                info +=       "|------|------|------|------|------|------|\n";
                LayeredGraphNode graph = m.read( "graph", MemoryType.COMPLETE_STACK );
                for( LayeredGraphNode n : graph.getContainedNodes() )
                {
                    info += "|" + TextLayoutHelper.strToLen( n.getName(), 6 ) +
                            "|" + TextLayoutHelper.strToLen( n.getX( LayoutType.TOP_BOTTOM_LEFT ) + "", 6 ) +
                            "|" + TextLayoutHelper.strToLen( n.getX( LayoutType.TOP_BOTTOM_RIGHT ) + "", 6 ) +
                            "|" + TextLayoutHelper.strToLen( n.getX( LayoutType.BOTTOM_TOP_LEFT ) + "", 6 ) +
                            "|" + TextLayoutHelper.strToLen( n.getX( LayoutType.BOTTOM_TOP_RIGHT ) + "", 6 ) +
                            "|" + TextLayoutHelper.strToLen( n.getX( LayoutType.COMBINED ) + "", 6 ) + "|\n";
                }
                return info;
            }
        };
        root.add( balancingStage );
        balancingStage.add( combine );
        processor = new PseudoCodeProcessor( root );
        return root;
    }
}
