package bk;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

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.Comment;
import codelines.DeclareVariable;
import codelines.ForEachLoop;
import codelines.FunctionCall;
import codelines.FunctionDefinition;
import codelines.IfLoop;
import codelines.SetVariable;
import graph.LayeredGraphNode;

/**
 * The stage of the combination of the four extremal layouts.
 * @author kolja
 *
 */
public class Combine {

    public static PseudoCodeNode combine( JTree tree ) {
        String[] vars = { "graph", "l", "l_min", "v", "positions" };
        @SuppressWarnings("serial")
        PseudoCodeNode root = new PseudoCodeNode( "function combine( graph )", vars, tree, new FunctionDefinition( new String[]{ "graph" } ) ){
            @Override
            public String getDebugOutput( Memory m )
            {
                if( m.isSomewhereDefined( "v", MemoryType.LOCAL ) )
                    m.<LayeredGraphNode>read( "v", MemoryType.LOCAL).setSelected( null );
                return super.getDebugOutput( m );
            }
        };
        root.add( new PseudoCodeNode( "-- Call combine of subgraphs --", vars, tree, new CodeLine() {
            @Override
            public ControlFlow runForward(Memory m) {
                LayeredGraphNode graph = m.read( "graph", MemoryType.LOCAL );
                if( graph.parent() == null )
                    graph.setColor( Color.BLACK, null );
                actions.add( (Memory mem) -> {
                    if( graph.parent() == null )
                        graph.setColor( null, null );
                });
                return new ControlFlow( ControlFlow.STEP_OVER );
            }
        }));
        PseudoCodeNode recursiveLoop = new PseudoCodeNode( "foreach v in graph do", vars, tree, new ForEachLoop<LayeredGraphNode>( "v" ) {
            @Override
            protected List<LayeredGraphNode> list(ReadOnlyMemory m) {
                return m.<LayeredGraphNode>read( "graph", MemoryType.LOCAL ).getContainedNodes();
            }
        });
        root.add( recursiveLoop );
        PseudoCodeNode ifNode = new PseudoCodeNode( "if v has subgraph then", vars, tree, new IfLoop() {
            @Override
            protected boolean condition(ReadOnlyMemory m) {
                return m.<LayeredGraphNode>read( "v", MemoryType.LOCAL ).getContainedNodes().size() > 0;
            }
        } );
        recursiveLoop.add( ifNode );
        ifNode.add( new PseudoCodeNode( "call combine( v );", vars, tree, new FunctionCall( root, new String[]{ "v" } ) ) );
        root.add( new PseudoCodeNode( "-- Align all Layouts to the one with the smallest width --", vars, tree, new Comment()));
        PseudoCodeNode firstLoop = new PseudoCodeNode( "foreach l in ['DOWN_RIGHT', 'DOWN_LEFT', 'UP_RIGHT', 'UP_LEFT'] do", vars, tree, new ForEachLoop<String>( "l" ) {
            @Override
            protected List<String> list(ReadOnlyMemory m) {
                ArrayList< String > list = new ArrayList<String>();
                list.add( "DOWN_RIGHT" );
                list.add( "DOWN_LEFT" );
                list.add( "UP_RIGHT" );
                list.add( "UP_LEFT" );
                return list;
            }
        });
        root.add( firstLoop );
        firstLoop.add( new PseudoCodeNode( "min[l] = minimum x coordinate in l;", vars, tree, new CodeLine() {
            @Override
            public ControlFlow runForward(Memory m) {
                String layout = m.read( "l", MemoryType.LOCAL );
                m.declare( "min[" + layout + "]", calcMinX( m.read( "graph", MemoryType.LOCAL ), LayoutType.fromString( layout ) ), MemoryType.GLOBAL );
                actions.add( (Memory mem) -> {
                    mem.undeclare( "min[" + layout + "]", MemoryType.GLOBAL );
                });
                return new ControlFlow( ControlFlow.STEP_OVER );
            }
        }));
        firstLoop.add( new PseudoCodeNode( "max[l] = maximum x coordinate in l;", vars, tree, new CodeLine() {
            @Override
            public ControlFlow runForward(Memory m) {
                String layout = m.read( "l", MemoryType.LOCAL );
                m.declare( "max[" + layout + "]", calcMaxX( m.read( "graph", MemoryType.LOCAL ), LayoutType.fromString( layout ) ), MemoryType.GLOBAL );
                actions.add( (Memory mem) -> {
                    mem.undeclare( "max[" + layout + "]", MemoryType.GLOBAL );
                });
                return new ControlFlow( ControlFlow.STEP_OVER );
            }
        }));
        firstLoop.add( new PseudoCodeNode( "width[l] = max[l] - min[l];", vars, tree, new CodeLine() {
            @Override
            public ControlFlow runForward(Memory m) {
                String layout = m.read( "l", MemoryType.LOCAL );
                m.declare( "width[" + layout + "]", m.<Integer>read( "max[" + layout + "]", MemoryType.GLOBAL ) - m.<Integer>read( "min[" + layout + "]", MemoryType.GLOBAL ), MemoryType.GLOBAL );
                actions.add( (Memory mem) -> {
                    mem.undeclare( "width[" + layout + "]", MemoryType.GLOBAL );
                });
                return new ControlFlow( ControlFlow.STEP_OVER );
            }
        }));
        root.add( new PseudoCodeNode( "l_min = l with width[l] == min(width);", vars, tree, new DeclareVariable<String>( "l_min" ) {
            @Override
            protected String value(ReadOnlyMemory m) {
                String min = "DOWN_RIGHT";
                int minW = m.read( "width[DOWN_RIGHT]", MemoryType.GLOBAL );
                for( String l : new String[]{ "DOWN_LEFT", "UP_RIGHT", "UP_LEFT" } )
                {
                    if( minW > m.<Integer>read( "width[" + l + "]", MemoryType.GLOBAL ) )
                    {
                        minW = m.<Integer>read( "width[" + l + "]", MemoryType.GLOBAL );
                        min = l;
                    }
                }
                return min;
            }
        }));
        PseudoCodeNode secondLoop = new PseudoCodeNode( "foreach l in ['DOWN_RIGHT', 'DOWN_LEFT', 'UP_RIGHT', 'UP_LEFT'] do", vars, tree, new ForEachLoop<String>( "l" ) {
            @Override
            protected List<String> list(ReadOnlyMemory m) {
                ArrayList< String > list = new ArrayList<String>();
                list.add( "DOWN_RIGHT" );
                list.add( "DOWN_LEFT" );
                list.add( "UP_RIGHT" );
                list.add( "UP_LEFT" );
                return list;
            }
        });
        root.add( secondLoop );
        secondLoop.add( new PseudoCodeNode( "shift[l] = l.contains('RIGHT') ? min[l_min] - min[l] : max[l_min] - max[l];", vars, tree, new CodeLine() {
            @Override
            public ControlFlow runForward(Memory m) {
                String layout = m.read( "l", MemoryType.LOCAL );
                String lMin = m.read( "l_min", MemoryType.LOCAL );
                if( layout.contains( "RIGHT" ) )
                    m.declare( "shift[" + layout + "]", m.<Integer>read( "min[" + lMin + "]", MemoryType.GLOBAL ) - m.<Integer>read( "min[" + layout + "]", MemoryType.GLOBAL ), MemoryType.GLOBAL );
                else
                    m.declare( "shift[" + layout + "]", m.<Integer>read( "max[" + lMin + "]", MemoryType.GLOBAL ) - m.<Integer>read( "max[" + layout + "]", MemoryType.GLOBAL ), MemoryType.GLOBAL );
                actions.add( (Memory mem) -> {
                    mem.undeclare( "shift[" + layout + "]", MemoryType.GLOBAL );
                });
                return new ControlFlow( ControlFlow.STEP_OVER );
            }
        }));
        PseudoCodeNode thirdLoop = new PseudoCodeNode( "foreach v in graph do", vars, tree, new ForEachLoop<LayeredGraphNode>( "v" ) {
            @Override
            protected List<LayeredGraphNode> list(ReadOnlyMemory m) {
                return m.<LayeredGraphNode>read( "graph", MemoryType.LOCAL ).getContainedNodes();
            }
        });
        root.add( thirdLoop );
        thirdLoop.add( new PseudoCodeNode( "positions = [];", vars, tree, new DeclareVariable<ArrayList<Integer>>( "positions") {
            @Override
            protected ArrayList<Integer> value(ReadOnlyMemory m) {
                return new ArrayList<Integer>();
            }
        }));
        PseudoCodeNode innerLoop = new PseudoCodeNode( "foreach l in ['DOWN_RIGHT', 'DOWN_LEFT', 'UP_RIGHT', 'UP_LEFT'] do", vars, tree, new ForEachLoop<String>( "l" ) {
            @Override
            protected List<String> list(ReadOnlyMemory m) {
                ArrayList< String > list = new ArrayList<String>();
                list.add( "DOWN_RIGHT" );
                list.add( "DOWN_LEFT" );
                list.add( "UP_RIGHT" );
                list.add( "UP_LEFT" );
                return list;
            }
        });
        innerLoop.add( new PseudoCodeNode( "positions.add(x[v] in l);", vars, tree, new CodeLine() {
            @Override
            public ControlFlow runForward(Memory m) {
                String layout = m.read( "l", MemoryType.LOCAL );
                ArrayList<Integer> positions = m.read( "positions", MemoryType.LOCAL );
                positions.add( (int)m.<LayeredGraphNode>read( "v", MemoryType.LOCAL ).getX( LayoutType.fromString( layout ) ) + m.<Integer>read( "shift[" + layout + "]", MemoryType.GLOBAL ) );
                actions.add( (Memory mem) -> {
                    positions.remove( positions.size() - 1 );
                });
                return new ControlFlow( ControlFlow.STEP_OVER );
            }
        }));
        thirdLoop.add( innerLoop );
        thirdLoop.add( new PseudoCodeNode( "positions = sort( positions );", vars, tree, new SetVariable<ArrayList<Integer>>( "positions" ) {
            @Override
            public ArrayList<Integer> value(ReadOnlyMemory m) {
                ArrayList<Integer> positions = m.read( "positions", MemoryType.LOCAL );
                ArrayList<Integer> neu = new ArrayList<Integer>();
                for( int p : positions )
                    neu.add( p );
                Collections.sort( neu );
                return neu;
            }
        }));
        thirdLoop.add( new PseudoCodeNode( "x[v] = (positions[1]+positions[2]) / 2;", vars, tree, new CodeLine() {
            @Override
            public ControlFlow runForward(Memory m) {
                ArrayList<Integer> positions = m.read( "positions", MemoryType.LOCAL );
                LayeredGraphNode v = m.read( "v", MemoryType.LOCAL );
                double old = v.getX( LayoutType.COMBINED );
                v.setX( (positions.get( 1 ) + positions.get( 2 )) / 2.0, true, LayoutType.COMBINED );
                actions.add( (Memory mem) -> {
                    v.setX( old, false, LayoutType.COMBINED );
                });
                return new ControlFlow( ControlFlow.STEP_OVER );
            }
        }));
        return root;
    }

    private static int calcMinX( LayeredGraphNode graph, LayoutType layout )
    {
        int minX = 0;
        if( graph.getContainedNodes().size() > 0 )
            minX = (int)graph.getContainedNodes().get( 0 ).getX( layout );
        for( LayeredGraphNode n : graph.getContainedNodes() )
            minX = Math.min( minX, (int)n.getX( layout ) );
        return minX;
    }

    private static int calcMaxX( LayeredGraphNode graph, LayoutType layout )
    {
        int maxX = 0;
        if( graph.getContainedNodes().size() > 0 )
            maxX = (int)graph.getContainedNodes().get( 0 ).getX( layout );
        for( LayeredGraphNode n : graph.getContainedNodes() )
            maxX = Math.max( maxX, (int)n.getX( layout ) );
        return maxX;
    }
}
