package bk;

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

import javax.swing.JTree;

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

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


    /**
     * creates the pseudo code for the balancing stage
     * @param tree the tree where the code will be displayed
     * @return the code
     */
    public static PseudoCodeNode combine( JTree tree ) {
        String[] vars = { "graph", "l", "l_min", "v", "positions", "min", "shift", "max", "width" };

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

            @Override
            public String getDebugOutput( Memory m )
            {
                if( m.isSomewhereDefined( "v", Visibility.LOCAL ) )
                    m.<LayeredGraphNode>read( "v", Visibility.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", Visibility.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", Visibility.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", Visibility.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()));
        root.add( new PseudoCodeNode( "max = [];", vars, tree, new DeclareVariable<HashMap<String,Integer>>( "max") {
            @Override
            protected HashMap<String,Integer> value(ReadOnlyMemory m) {
                return new HashMap<String,Integer>();
            }
        }));
        root.add( new PseudoCodeNode( "min = [];", vars, tree, new DeclareVariable<HashMap<String,Integer>>( "min") {
            @Override
            protected HashMap<String,Integer> value(ReadOnlyMemory m) {
                return new HashMap<String,Integer>();
            }
        }));
        root.add( new PseudoCodeNode( "width = [];", vars, tree, new DeclareVariable<HashMap<String,Integer>>( "width") {
            @Override
            protected HashMap<String,Integer> value(ReadOnlyMemory m) {
                return new HashMap<String,Integer>();
            }
        }));
        PseudoCodeNode firstLoop = new PseudoCodeNode( "foreach l in ['RIGHTMOST_LOWER', 'LEFTMOST_LOWER', 'RIGHTMOST_UPPER', 'LEFTMOST_UPPER'] do", vars, tree, new ForEachLoop<String>( "l" ) {
            @Override
            protected List<String> list(ReadOnlyMemory m) {
                ArrayList< String > list = new ArrayList<String>();
                list.add( "RIGHTMOST_LOWER" );
                list.add( "LEFTMOST_LOWER" );
                list.add( "RIGHTMOST_UPPER" );
                list.add( "LEFTMOST_UPPER" );
                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", Visibility.LOCAL );
                m.<HashMap<String,Integer>>read( "min", Visibility.LOCAL ).put( layout, calcMinX( m.read( "graph", Visibility.LOCAL ), LayoutType.fromString( layout ) ));
                actions.add( (Memory mem) -> {
                    mem.<HashMap<String,Integer>>read( "min", Visibility.LOCAL ).remove( layout );
                });
                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", Visibility.LOCAL );
                m.<HashMap<String,Integer>>read( "max", Visibility.LOCAL ).put( layout, calcMaxX( m.read( "graph", Visibility.LOCAL ), LayoutType.fromString( layout ) ));
                actions.add( (Memory mem) -> {
                    mem.<HashMap<String,Integer>>read( "max", Visibility.LOCAL ).remove( layout );
                });
                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", Visibility.LOCAL );
                m.<HashMap<String,Integer>>read( "width", Visibility.LOCAL ).put( layout, m.<HashMap<String,Integer>>read( "max", Visibility.LOCAL ).get( layout ) - m.<HashMap<String,Integer>>read( "min", Visibility.LOCAL ).get( layout ) );
                actions.add( (Memory mem) -> {
                    mem.<HashMap<String,Integer>>read( "width", Visibility.LOCAL ).remove( layout );
                });
                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 = "RIGHTMOST_LOWER";
                int minW = m.<HashMap<String,Integer>>read( "width", Visibility.LOCAL ).get( min );
                for( String l : new String[]{ "LEFTMOST_LOWER", "RIGHTMOST_UPPER", "LEFTMOST_UPPER" } )
                {
                    if( minW > m.<HashMap<String,Integer>>read( "width", Visibility.LOCAL ).get( l ) )
                    {
                        minW = m.<HashMap<String,Integer>>read( "width", Visibility.LOCAL ).get( l );
                        min = l;
                    }
                }
                return min;
            }
        }));
        root.add( new PseudoCodeNode( "shift = [];", vars, tree, new DeclareVariable<HashMap<String,Integer>>( "shift") {
            @Override
            protected HashMap<String,Integer> value(ReadOnlyMemory m) {
                return new HashMap<String,Integer>();
            }
        }));
        PseudoCodeNode secondLoop = new PseudoCodeNode( "foreach l in ['RIGHTMOST_LOWER', 'LEFTMOST_LOWER', 'RIGHTMOST_UPPER', 'LEFTMOST_UPPER'] do", vars, tree, new ForEachLoop<String>( "l" ) {
            @Override
            protected List<String> list(ReadOnlyMemory m) {
                ArrayList< String > list = new ArrayList<String>();
                list.add( "RIGHTMOST_LOWER" );
                list.add( "LEFTMOST_LOWER" );
                list.add( "RIGHTMOST_UPPER" );
                list.add( "LEFTMOST_UPPER" );
                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", Visibility.LOCAL );
                String lMin = m.read( "l_min", Visibility.LOCAL );
                if( layout.contains( "RIGHT" ) )
                    m.<HashMap<String,Integer>>read( "shift", Visibility.LOCAL ).put( layout, m.<HashMap<String,Integer>>read( "min", Visibility.LOCAL ).get( lMin ) - m.<HashMap<String,Integer>>read( "min", Visibility.LOCAL ).get( layout ) );
                else
                    m.<HashMap<String,Integer>>read( "shift", Visibility.LOCAL ).put( layout, m.<HashMap<String,Integer>>read( "max", Visibility.LOCAL ).get( lMin ) - m.<HashMap<String,Integer>>read( "max", Visibility.LOCAL ).get( layout ) );
                actions.add( (Memory mem) -> {
                    mem.<HashMap<String,Integer>>read( "shift", Visibility.LOCAL ).remove( layout );
                });
                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", Visibility.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 ['RIGHTMOST_LOWER', 'LEFTMOST_LOWER', 'RIGHTMOST_UPPER', 'LEFTMOST_UPPER'] do", vars, tree, new ForEachLoop<String>( "l" ) {
            @Override
            protected List<String> list(ReadOnlyMemory m) {
                ArrayList< String > list = new ArrayList<String>();
                list.add( "RIGHTMOST_LOWER" );
                list.add( "LEFTMOST_LOWER" );
                list.add( "RIGHTMOST_UPPER" );
                list.add( "LEFTMOST_UPPER" );
                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", Visibility.LOCAL );
                ArrayList<Integer> positions = m.read( "positions", Visibility.LOCAL );
                positions.add( (int)m.<LayeredGraphNode>read( "v", Visibility.LOCAL ).getX( LayoutType.fromString( layout ) ) + m.<HashMap<String,Integer>>read( "shift", Visibility.LOCAL ).get( layout ) );
                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", Visibility.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", Visibility.LOCAL );
                LayeredGraphNode v = m.read( "v", Visibility.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;
    }
}
