package bk;

import javax.swing.JTree;

import animation.CodeLine;
import animation.ControlFlow;
import animation.Memory;
import animation.Memory.MemoryType;
import animation.Memory.ReadOnlyMemory;
import animation.PseudoCodeNode;
import codelines.DeclareVariable;
import codelines.FunctionCall;
import codelines.FunctionDefinition;
import codelines.IfLoop;
import codelines.SetVariable;
import codelines.WhileLoop;
import graph.LayeredGraphNode;
import graph.LayeredNode;
// PAPER = OUR
import view.NodeView;

// DOWN = RIGHT
// UP = LEFT
// RIGHT = DOWN
// LEFT = UP
public class PlaceBlock{

    public static PseudoCodeNode place_block(JTree tree) {
        String vars[] = { "v", "w", "layout", "x", "y", "graph", "u", "root", "align", "first" };
        @SuppressWarnings("serial")
        PseudoCodeNode root = new PseudoCodeNode( "function place_block( v, layout )", vars, tree, new FunctionDefinition(new String[]{ "v", "layout" } ) ) {
            @Override
            public String getDebugOutput( Memory m )
            {
                if( m.isSomewhereDefined( "v", MemoryType.LOCAL ) && !m.isSomewhereDefined( "w", MemoryType.LOCAL ) )
                    m.<LayeredGraphNode>read( "v", MemoryType.LOCAL ).setSelected( LayoutType.fromString( m.read( "layout", MemoryType.LOCAL ) ) );
                if( m.isSomewhereDefined( "w", MemoryType.LOCAL ) )
                    m.<LayeredGraphNode>read( "w", MemoryType.LOCAL ).setSelected( LayoutType.fromString( m.read( "layout", MemoryType.LOCAL ) ) );
                return super.getDebugOutput( m );
            }
        };
        PseudoCodeNode ifUndef = new PseudoCodeNode( "if x[v] == undefined then", vars, tree, new IfLoop() {
            @Override
            protected boolean condition(ReadOnlyMemory m) {
                return m.<LayeredGraphNode>read( "v", MemoryType.LOCAL ).isXUndefined( LayoutType.fromString( m.read( "layout", MemoryType.LOCAL ) ) );
            }
        });
        root.add( ifUndef );
        ifUndef.add( new PseudoCodeNode( "x[v] = 0; w = v; first = true;", vars, tree, new CodeLine() {
            @Override
            public ControlFlow runForward(Memory m) {
                m.declare( "w", m.read( "v", MemoryType.LOCAL ), MemoryType.LOCAL );
                m.declare( "first", true, MemoryType.LOCAL );
                double oldX = m.<LayeredGraphNode>read( "v", MemoryType.LOCAL ).getX( LayoutType.fromString( m.read( "layout", MemoryType.LOCAL ) ) );
                m.<LayeredGraphNode>read( "v", MemoryType.LOCAL ).setX( 0, true, LayoutType.fromString( m.read( "layout", MemoryType.LOCAL ) ) );
                actions.push( (Memory mem) -> {
                    mem.undeclare( "w", MemoryType.LOCAL );
                    mem.undeclare( "first", MemoryType.LOCAL );
                    m.<LayeredGraphNode>read( "v", MemoryType.LOCAL ).setX( oldX, false, LayoutType.fromString( m.read( "layout", MemoryType.LOCAL ) ) );
                });
                return new ControlFlow( ControlFlow.STEP_OVER );
            }
        }));
        PseudoCodeNode whilewv = new PseudoCodeNode( "while w != v or first", vars, tree, new WhileLoop() {
            @Override
            protected boolean condition(ReadOnlyMemory m) {
                return m.read( "w", MemoryType.LOCAL ) != m.read( "v", MemoryType.LOCAL ) || m.<Boolean>read( "first", MemoryType.LOCAL );
            }
        });
        ifUndef.add( whilewv );
        whilewv.add( new PseudoCodeNode( "first = false;", vars, tree, new SetVariable<Boolean>( "first" ) {
            @Override
            protected Boolean value(ReadOnlyMemory m) {
                return false;
            }
        }));
        PseudoCodeNode ifPos = new PseudoCodeNode( "if (layout.contains('RIGHT') and pos(w)>0) or (layout.contains('LEFT') and pos(w)<|layerOf(w)|-1) then", vars, tree, new IfLoop() {
            @Override
            protected boolean condition(ReadOnlyMemory m) {
                LayeredGraphNode w = m.read( "w", MemoryType.LOCAL );
                LayeredGraphNode graph = w.parent();
                return (m.<String>read( "layout", MemoryType.LOCAL ).contains( "RIGHT" ) && graph.getContainedLayers().get( w.getLayer() ).indexOf( w ) > 0 ) ||
                       (m.<String>read( "layout", MemoryType.LOCAL ).contains( "LEFT" ) && graph.getContainedLayers().get( w.getLayer() ).indexOf( w ) < graph.getContainedLayers().get( w.getLayer() ).size() - 1 );
            }
        });
        whilewv.add( ifPos );
        ifPos.add( new PseudoCodeNode( "u = layout.contains('RIGHT') ? root[fore(w)] : root[foll(w)];", vars, tree, new DeclareVariable<LayeredGraphNode>( "u" ){
            @Override
            protected LayeredGraphNode value(ReadOnlyMemory m) {
                LayeredGraphNode w = m.read( "w", MemoryType.LOCAL );
                LayeredGraphNode graph = w.parent();
                if( m.<String>read( "layout", MemoryType.LOCAL ).contains( "RIGHT" ) )
                    return graph.getContainedLayers().get( w.getLayer() ).get( graph.getContainedLayers().get( w.getLayer() ).indexOf( w ) - 1 ).getRoot( LayoutType.fromString( m.<String>read( "layout", MemoryType.LOCAL ) ) );
                return graph.getContainedLayers().get( w.getLayer() ).get( graph.getContainedLayers().get( w.getLayer() ).indexOf( w ) + 1 ).getRoot( LayoutType.fromString( m.<String>read( "layout", MemoryType.LOCAL ) ) );
            }
        } ));
        ifPos.add( new PseudoCodeNode( "call place_block( u, layout );", vars, tree, new FunctionCall( root, new String[]{ "u", "layout" } ) ) );
        ifPos.add( new PseudoCodeNode( "sink[v] = sink[v] == v ? sink[u] : sink[v];", vars, tree, new CodeLine() {
            @Override
            public ControlFlow runForward(Memory m) {
                LayeredGraphNode v = m.read( "v", MemoryType.LOCAL );
                LayeredGraphNode u = m.read( "u", MemoryType.LOCAL );
                LayoutType layout = LayoutType.fromString( m.read( "layout", MemoryType.LOCAL ) );
                LayeredGraphNode old = v.getSink( layout );
                v.setSink( old == v ? u.getSink( layout ) : old, layout );
                actions.push( (Memory mem) -> {
                   v.setSink( old, layout ); 
                });
                return new ControlFlow( ControlFlow.STEP_OVER );
            }
        }));
        PseudoCodeNode ifSink1 = new PseudoCodeNode( "if sink[v] != sink[u] then", vars, tree, new IfLoop() {
            @Override
            protected boolean condition(ReadOnlyMemory m) {
                LayoutType layout = LayoutType.fromString( m.read( "layout", MemoryType.LOCAL ) );
                return m.<LayeredGraphNode>read( "v", MemoryType.LOCAL ).getSink( layout ) != m.<LayeredGraphNode>read( "u", MemoryType.LOCAL ).getSink( layout );
            }
        });
        ifPos.add( ifSink1 );
        ifSink1.add( new PseudoCodeNode( "shift[sink[u]] = layout.contains('RIGHT') ? min(shift[sink[u]], x[v] - x[u] - spacing) : max(shift[sink[u]], x[v] + x[u] + spacing);", vars, tree, new CodeLine() {
            @Override
            public ControlFlow runForward(Memory m) {
                String lStr = m.read( "layout", MemoryType.LOCAL );
                LayoutType layout = LayoutType.fromString( lStr );
                LayeredGraphNode v = m.read( "v", MemoryType.LOCAL );
                LayeredGraphNode u = m.read( "u", MemoryType.LOCAL );
                double old = u.getSink( layout ).getShift( layout );
                u.getSink( layout ).setShift( lStr.contains( "RIGHT" ) ? 
                        Math.min( u.getSink( layout ).getShift( layout ), v.getX( layout ) - u.getX( layout ) - calcSpacing( u.parent(), layout ) ) : 
                            Math.max( u.getSink( layout ).getShift( layout ), v.getX( layout ) + u.getX( layout ) + calcSpacing( u.parent(), layout ) ), layout);
                actions.push( (Memory mem) -> {
                    u.getSink( layout ).setShift( old, layout );
                });
                return new ControlFlow( ControlFlow.STEP_OVER );
            }
        }));
        PseudoCodeNode ifSink2 = new PseudoCodeNode( "if sink[v] == sink[u] then", vars, tree, new IfLoop() {
            @Override
            protected boolean condition(ReadOnlyMemory m) {
                LayoutType layout = LayoutType.fromString( m.read( "layout", MemoryType.LOCAL ) );
                return m.<LayeredGraphNode>read( "v", MemoryType.LOCAL ).getSink( layout ) == m.<LayeredGraphNode>read( "u", MemoryType.LOCAL ).getSink( layout );
            }
        });
        ifPos.add( ifSink2 );
        ifSink2.add( new PseudoCodeNode( "x[v] = layout.contains('RIGHT') ? max(x[v], x[u] + spacing) : max(x[v], x[u] - spacing);", vars, tree, new CodeLine() {
            @Override
            public ControlFlow runForward(Memory m) {
                String lStr = m.read( "layout", MemoryType.LOCAL );
                LayoutType layout = LayoutType.fromString( lStr );
                LayeredGraphNode v = m.read( "v", MemoryType.LOCAL );
                LayeredGraphNode u = m.read( "u", MemoryType.LOCAL );
                double old = v.getX( layout );
                boolean oldDef = !v.isXUndefined( layout );
                v.setX( lStr.contains( "RIGHT" ) ? Math.max( v.getX( layout ), u.getX( layout ) + calcSpacing( u.parent(), layout ) ) : Math.min( v.getX( layout ), u.getX( layout ) - calcSpacing( u.parent(), layout ) ), true, layout );
                actions.push( (Memory mem) -> {
                    v.setX( old, oldDef, layout );
                });
                return new ControlFlow( ControlFlow.STEP_OVER );
            }
        }));
        whilewv.add( new PseudoCodeNode( "w = align[w];", vars, tree, new SetVariable<LayeredGraphNode>( "w" ) {
            @Override
            protected LayeredGraphNode value(ReadOnlyMemory m) {
                return m.<LayeredGraphNode>read( "w", MemoryType.LOCAL ).getAlign( LayoutType.fromString( m.read( "layout", MemoryType.LOCAL ) ) );
            }
        }));
        return root;
    }


    /**
     * calculates the minimum spacing needed between two left borders of nodes.
     * @return the spacing
     */
    public static double calcSpacing( LayeredGraphNode graph, LayoutType layout )
    {
        double max = 0;
        for( LayeredGraphNode n : graph.getContainedNodes() )
        {
            NodeView v = ((LayeredNode)n).getView( layout );
            max = Math.max( max, v.getOriginalWidth() );
        }
        return max + 20;
    }
}
