package view;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;

import org.eclipse.elk.graph.ElkNode;

import animation.Action;
import animation.AnimationController;
import animation.PseudoCodeNode;
import bk.BKNodePlacement;
import bk.ExtremalLayoutCalc.LayoutType;
import graph.InitializeNodePositions;
import graph.LayeredGraphEdge;
import graph.LayeredGraphNode;
import graph.LayeredNode;
import graph.RandomGraphGenerator;
import graph.io.Reader;
import graph.io.Writer;
import lib.SweepCrossingMinimizer;
import lib.TextLayoutHelper;

/**
 * The main window of the application.
 * There should only be one instance of this class at the same time.
 * The JFrame of that single instance can be accessed by the static field {code MainView.frame}.
 * @author kolja
 *
 */
public class MainView {    
    /**
     * The 'frame' of the main window.
     * The reason why there can only be one instance of this class.
     */
    private static int frameCounter = 0;
    private JFrame frame;
    private AnimationController controller;
    private JButton stepForward;
    private JButton stepForwardInto;
    private JButton stepForwardOut;
    private JButton stepBackward;
    private JButton stepBackwardInto;
    private JButton stepBackwardOut;
    private JButton runForward;
    private JButton runBackward;
    private JButton pause;
    private JButton load;
    private JButton save;
    private JButton debug;
    private JButton randomGraph;
    private JLabel delayText;
    private JTextField delay;
    public JTree pseudoTree;
    private LayeredGraphNode graph;

    private String debugInfo()
    {
        String info = "Debug Information Table: \n";
        info += "_______________________________________________________________________________________________________________________________________________________________________________________________________________________\n";
        info += "|" + TextLayoutHelper.strToLen( "Top -> Bottom :> Left", 51 ) + "| |" + TextLayoutHelper.strToLen( "Top -> Bottom :> Right", 51 ) + "| |" + TextLayoutHelper.strToLen( "Bottom -> Top :> Left", 51 ) + "| |" + TextLayoutHelper.strToLen( "Bottom -> Top :> Right", 51 ) + "|\n";
        info += "|___________________________________________________| |___________________________________________________| |___________________________________________________| |___________________________________________________|\n";
        info += "| Node | Shift | Sink | Root | Align |  x  |  xDef  | | Node | Shift | Sink | Root | Align |  x  |  xDef  | | Node | Shift | Sink | Root | Align |  x  |  xDef  | | Node | Shift | Sink | Root | Align |  x  |  xDef  |\n";
        for( LayeredGraphNode n : graph.getContainedNodes() )
        {
            info += "|" + TextLayoutHelper.strToLen( n.getName(), 6 ) + 
                    "|" + TextLayoutHelper.strToLen( n.getShift( LayoutType.TOP_BOTTOM_LEFT ) + "", 7 ) + 
                    "|" + TextLayoutHelper.strToLen( n.getSink( LayoutType.TOP_BOTTOM_LEFT ).getName(), 6 ) + 
                    "|" + TextLayoutHelper.strToLen( n.getRoot( LayoutType.TOP_BOTTOM_LEFT ).getName(), 6 ) + 
                    "|" + TextLayoutHelper.strToLen( n.getAlign( LayoutType.TOP_BOTTOM_LEFT ).getName(), 7 ) + 
                    "|" + TextLayoutHelper.strToLen( n.getX( LayoutType.TOP_BOTTOM_LEFT ) + "", 5 ) + 
                    "|" + TextLayoutHelper.strToLen( !n.isXUndefined( LayoutType.TOP_BOTTOM_LEFT ) + "", 8 ) + "| " +
                    "|" + TextLayoutHelper.strToLen( n.getName(), 6 ) + 
                    "|" + TextLayoutHelper.strToLen( n.getShift( LayoutType.TOP_BOTTOM_RIGHT ) + "", 7 ) + 
                    "|" + TextLayoutHelper.strToLen( n.getSink( LayoutType.TOP_BOTTOM_RIGHT ).getName(), 6 ) + 
                    "|" + TextLayoutHelper.strToLen( n.getRoot( LayoutType.TOP_BOTTOM_RIGHT ).getName(), 6 ) + 
                    "|" + TextLayoutHelper.strToLen( n.getAlign( LayoutType.TOP_BOTTOM_RIGHT ).getName(), 7 ) + 
                    "|" + TextLayoutHelper.strToLen( n.getX( LayoutType.TOP_BOTTOM_RIGHT ) + "", 5 ) + 
                    "|" + TextLayoutHelper.strToLen( !n.isXUndefined( LayoutType.TOP_BOTTOM_RIGHT ) + "", 8 ) + "| " +
                    "|" + TextLayoutHelper.strToLen( n.getName(), 6 ) + 
                    "|" + TextLayoutHelper.strToLen( n.getShift( LayoutType.BOTTOM_TOP_LEFT ) + "", 7 ) + 
                    "|" + TextLayoutHelper.strToLen( n.getSink( LayoutType.BOTTOM_TOP_LEFT ).getName(), 6 ) + 
                    "|" + TextLayoutHelper.strToLen( n.getRoot( LayoutType.BOTTOM_TOP_LEFT ).getName(), 6 ) + 
                    "|" + TextLayoutHelper.strToLen( n.getAlign( LayoutType.BOTTOM_TOP_LEFT ).getName(), 7 ) + 
                    "|" + TextLayoutHelper.strToLen( n.getX( LayoutType.BOTTOM_TOP_LEFT ) + "", 5 ) + 
                    "|" + TextLayoutHelper.strToLen( !n.isXUndefined( LayoutType.BOTTOM_TOP_LEFT ) + "", 8 ) + "| " +
                    "|" + TextLayoutHelper.strToLen( n.getName(), 6 ) + 
                    "|" + TextLayoutHelper.strToLen( n.getShift( LayoutType.BOTTOM_TOP_RIGHT ) + "", 7 ) + 
                    "|" + TextLayoutHelper.strToLen( n.getSink( LayoutType.BOTTOM_TOP_RIGHT ).getName(), 6 ) + 
                    "|" + TextLayoutHelper.strToLen( n.getRoot( LayoutType.BOTTOM_TOP_RIGHT ).getName(), 6 ) + 
                    "|" + TextLayoutHelper.strToLen( n.getAlign( LayoutType.BOTTOM_TOP_RIGHT ).getName(), 7 ) + 
                    "|" + TextLayoutHelper.strToLen( n.getX( LayoutType.BOTTOM_TOP_RIGHT ) + "", 5 ) + 
                    "|" + TextLayoutHelper.strToLen( !n.isXUndefined( LayoutType.BOTTOM_TOP_RIGHT ) + "", 8 ) + "|\n";
        }
        info += "-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------";
        return info;
    }

    private void showDebugInfo()
    {
        JFrame debugFrame = new JFrame();
        JTextArea info = new JTextArea();
        info.setEditable( false );
        info.setFont( new Font( Font.MONOSPACED, Font.PLAIN, 11 ) );
        String infoS = debugInfo();
        info.setText( infoS );
        JScrollPane view = new JScrollPane( info );
        debugFrame.add( view );
        debugFrame.setSize( frame.getWidth(), frame.getHeight() );
        debugFrame.setVisible( true );
        System.out.println( infoS );
    }

    public MainView( ElkNode graph )
    {
        this( LayeredNode.convertToLayeredGraph( graph ) );
    }

    /**
     * Initialize the window and its contents.
     * @param graph the graph that is displayed in this window.
     */
    public MainView( LayeredGraphNode graph )
    {
        frameCounter++;
        this.graph = graph;
        controller = new AnimationController();
        controller.setTimeBetween( 50 );
        frame = new JFrame( "NodeShuffler" );
        frame.addWindowListener(new java.awt.event.WindowAdapter() {
            @Override
            public void windowClosing(java.awt.event.WindowEvent windowEvent) {
                frameCounter--;
                if( frameCounter == 0 )
                    System.exit( 0 );
            }
        });
        
        BKNodePlacement algorithm = new BKNodePlacement( controller, graph, frame );
        
        // Create Menu GUI
        stepForward = new NiceButton( "stepForward" );
        stepForward.setLocation( 10, 10 );
        stepForward.setMnemonic( KeyEvent.VK_DOWN );
        stepForward.setToolTipText( "Forward step over (alt + down arrow key)" );
        stepForward.addActionListener( new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                controller.setContinuous( false );
                controller.setNextAction( Action.FORWARD_OVER );
            }
            
        });
        stepForwardInto = new NiceButton( "stepForwardInto" );
        stepForwardInto.setLocation( 60, 10 );
        stepForwardInto.setMnemonic( KeyEvent.VK_RIGHT );
        stepForwardInto.setToolTipText( "Forward step into (alt + right arrow key)" );
        stepForwardInto.addActionListener( new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                controller.setContinuous( false );
                controller.setNextAction( Action.FORWARD );
            }
            
        });
        stepForwardOut = new NiceButton( "stepForwardOut" );
        stepForwardOut.setLocation( 110, 10 );
        stepForwardOut.setMnemonic( KeyEvent.VK_PAGE_DOWN );
        stepForwardOut.setToolTipText( "Forward step out (alt + page down key)" );
        stepForwardOut.addActionListener( new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                controller.setContinuous( false );
                controller.setNextAction( Action.FORWARD_OUT );
            }
            
        });
        runForward = new NiceButton( "runForward" );
        runForward.setLocation( 160, 10 );
        runForward.setMnemonic( KeyEvent.VK_P );
        runForward.setToolTipText( "Run forwards (alt + p)" );
        runForward.addActionListener( new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                controller.setContinuous( true );
                controller.setNextAction( Action.FORWARD );
            }
            
        });
        runBackward = new NiceButton( "runBackward" );
        runBackward.setLocation( 160, 60 );
        runBackward.setMnemonic( KeyEvent.VK_R );
        runBackward.setToolTipText( "Run backwards (alt + r)" );
        runBackward.addActionListener( new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                controller.setContinuous( true );
                controller.setNextAction( Action.BACKWARD );
            }
            
        });
        stepBackward = new NiceButton( "stepBackward" );
        stepBackward.setLocation( 10, 60 );
        stepBackward.setMnemonic( KeyEvent.VK_UP );
        stepBackward.setToolTipText( "Backward step over (alt + up arrow key)" );
        stepBackward.addActionListener( new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                controller.setContinuous( false );
                controller.setNextAction( Action.BACKWARD_OVER );
            }
            
        });
        stepBackwardInto = new NiceButton( "stepBackwardInto" );
        stepBackwardInto.setLocation( 60, 60 );
        stepBackwardInto.setMnemonic( KeyEvent.VK_LEFT );
        stepBackwardInto.setToolTipText( "Backward step into (alt + left arrow key)" );
        stepBackwardInto.addActionListener( new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                controller.setContinuous( false );
                controller.setNextAction( Action.BACKWARD );
            }
            
        });
        stepBackwardOut = new NiceButton( "stepBackwardOut" );
        stepBackwardOut.setLocation( 110, 60 );
        stepBackwardOut.setMnemonic( KeyEvent.VK_PAGE_UP );
        stepBackwardOut.setToolTipText( "Backward step out (alt + page up)" );
        stepBackwardOut.addActionListener( new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                controller.setContinuous( false );
                controller.setNextAction( Action.BACKWARD_OUT );
            }
            
        });
        pause = new NiceButton( "pause" );
        pause.setLocation( 210, 10 );
        pause.setMnemonic( KeyEvent.VK_PAUSE );
        pause.setToolTipText( "Pause (alt + pause)" );
        pause.addActionListener( new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                controller.setContinuous( false );
            }
            
        });
        debug = new NiceButton( "debug" );
        debug.setLocation( 360, 10 );
        debug.setMnemonic( KeyEvent.VK_D );
        debug.setToolTipText( "Show debug info (alt + d)" );
        debug.addActionListener( new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                showDebugInfo();
            }
            
        });
        randomGraph = new NiceButton( "random" );
        randomGraph.setLocation( 360, 60 );
        randomGraph.setMnemonic( KeyEvent.VK_G );
        randomGraph.setToolTipText( "Generate random graph (alt + g)" );
        randomGraph.addActionListener( new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                JDialog diag = new JDialog( frame, "Generate random graph" );
                diag.setLayout( new GridBagLayout() );
                GridBagConstraints c = new GridBagConstraints();
                c.gridx = 0;
                c.gridy = 0;
                diag.add( new JLabel( "P(subgraph exists)"), c );
                c = new GridBagConstraints();
                c.gridx = 1;
                c.gridy = 0;
                JTextField pSubgraph = new JTextField( "0.1" );
                pSubgraph.setPreferredSize( new Dimension( 100, 20 ) );
                pSubgraph.addFocusListener( new FocusListener() {
                    @Override
                    public void focusGained(FocusEvent e) {
                        pSubgraph.setBackground( Color.WHITE );
                    }

                    @Override
                    public void focusLost(FocusEvent e) {
                        try {
                            double d = Double.parseDouble( pSubgraph.getText() );
                            if( d > 1 || d < 0 )
                                pSubgraph.setBackground( Color.RED );
                        } catch( Exception e1 )
                        {
                            pSubgraph.setBackground( Color.RED );
                        }
                    }
                });
                diag.add( pSubgraph, c );
                c = new GridBagConstraints();
                c.gridx = 0;
                c.gridy = 1;
                diag.add( new JLabel( "P(edge exists)"), c );
                c = new GridBagConstraints();
                c.gridx = 1;
                c.gridy = 1;
                JTextField pEdge = new JTextField( "0.3" );
                pEdge.setPreferredSize( new Dimension( 100, 20 ) );
                pEdge.addFocusListener( new FocusListener() {
                    @Override
                    public void focusGained(FocusEvent e) {
                        pEdge.setBackground( Color.WHITE );
                    }

                    @Override
                    public void focusLost(FocusEvent e) {
                        try {
                            double d = Double.parseDouble( pEdge.getText() );
                            if( d > 1 || d < 0 )
                                pEdge.setBackground( Color.RED );
                        } catch( Exception e1 )
                        {
                            pEdge.setBackground( Color.RED );
                        }
                    }
                });
                diag.add( pEdge, c );
                c = new GridBagConstraints();
                c.gridx = 0;
                c.gridy = 2;
                diag.add( new JLabel( "min. num. layers"), c );
                c = new GridBagConstraints();
                c.gridx = 1;
                c.gridy = 2;
                JTextField minLayers = new JTextField( "5" );
                JTextField maxLayers = new JTextField( "5" );
                minLayers.setPreferredSize( new Dimension( 100, 20 ) );
                minLayers.addFocusListener( new FocusListener() {
                    @Override
                    public void focusGained(FocusEvent e) {
                        minLayers.setBackground( Color.WHITE );
                    }

                    @Override
                    public void focusLost(FocusEvent e) {
                        try {
                            int i = Integer.parseInt( minLayers.getText() );
                            int max = Integer.parseInt( maxLayers.getText() );
                            if( i < 1 || i > max )
                                minLayers.setBackground( Color.RED );
                            else
                                maxLayers.setBackground( Color.WHITE );
                        } catch( Exception e1 )
                        {
                            minLayers.setBackground( Color.RED );
                        }
                    }
                });
                diag.add( minLayers, c );
                c = new GridBagConstraints();
                c.gridx = 0;
                c.gridy = 3;
                diag.add( new JLabel( "max. num. layers"), c );
                c = new GridBagConstraints();
                c.gridx = 1;
                c.gridy = 3;
                maxLayers.setPreferredSize( new Dimension( 100, 20 ) );
                maxLayers.addFocusListener( new FocusListener() {
                    @Override
                    public void focusGained(FocusEvent e) {
                        maxLayers.setBackground( Color.WHITE );
                    }

                    @Override
                    public void focusLost(FocusEvent e) {
                        try {
                            int i = Integer.parseInt( maxLayers.getText() );
                            int min = Integer.parseInt( minLayers.getText() );
                            if( i < min )
                                maxLayers.setBackground( Color.RED );
                            else if( min > 0 )
                                minLayers.setBackground( Color.WHITE );
                        } catch( Exception e1 )
                        {
                            maxLayers.setBackground( Color.RED );
                        }
                    }
                });
                diag.add( maxLayers, c );
                c = new GridBagConstraints();
                c.gridx = 0;
                c.gridy = 4;
                diag.add( new JLabel( "min. num. nodes"), c );
                c = new GridBagConstraints();
                c.gridx = 1;
                c.gridy = 4;
                JTextField minNodes = new JTextField( "5" );
                JTextField maxNodes = new JTextField( "5" );
                minNodes.setPreferredSize( new Dimension( 100, 20 ) );
                minNodes.setToolTipText( "between 1 and 'min. num. nodes'" );
                minNodes.addFocusListener( new FocusListener() {
                    @Override
                    public void focusGained(FocusEvent e) {
                        minNodes.setBackground( Color.WHITE );
                    }

                    @Override
                    public void focusLost(FocusEvent e) {
                        try {
                            int i = Integer.parseInt( minNodes.getText() );
                            int max = Integer.parseInt( maxNodes.getText() );
                            if( i < 1 || i > max )
                                minNodes.setBackground( Color.RED );
                            else
                                minNodes.setBackground( Color.WHITE );
                        } catch( Exception e1 )
                        {
                            minNodes.setBackground( Color.RED );
                        }
                    }
                });
                diag.add( minNodes, c );
                c = new GridBagConstraints();
                c.gridx = 0;
                c.gridy = 5;
                diag.add( new JLabel( "max. num. nodes"), c );
                c = new GridBagConstraints();
                c.gridx = 1;
                c.gridy = 5;
                maxNodes.setPreferredSize( new Dimension( 100, 20 ) );
                maxNodes.setToolTipText( "between 'min. num. nodes' and +Inf" );
                maxNodes.addFocusListener( new FocusListener() {
                    @Override
                    public void focusGained(FocusEvent e) {
                        maxNodes.setBackground( Color.WHITE );
                    }

                    @Override
                    public void focusLost(FocusEvent e) {
                        try {
                            int i = Integer.parseInt( maxNodes.getText() );
                            int min = Integer.parseInt( minNodes.getText() );
                            if( i < min )
                                maxNodes.setBackground( Color.RED );
                            else if( min > 0 )
                                minNodes.setBackground( Color.WHITE );
                        } catch( Exception e1 )
                        {
                            maxNodes.setBackground( Color.RED );
                        }
                    }
                });
                diag.add( maxNodes, c );
                c = new GridBagConstraints();
                c.gridx = 0;
                c.gridy = 6;
                diag.add( new JLabel( "max. hier. depth"), c );
                c = new GridBagConstraints();
                c.gridx = 1;
                c.gridy = 6;
                JTextField maxDepth = new JTextField( "1" );
                maxDepth.setPreferredSize( new Dimension( 100, 20 ) );
                maxDepth.setToolTipText( "between 1 and +Inf" );
                maxDepth.addFocusListener( new FocusListener() {
                    @Override
                    public void focusGained(FocusEvent e) {
                        maxDepth.setBackground( Color.WHITE );
                    }

                    @Override
                    public void focusLost(FocusEvent e) {
                        try {
                            int i = Integer.parseInt( maxDepth.getText() );
                            if( i < 1 )
                                maxDepth.setBackground( Color.RED );
                        } catch( Exception e1 )
                        {
                            maxDepth.setBackground( Color.RED );
                        }
                    }
                });
                diag.add( maxDepth, c );
                c = new GridBagConstraints();
                c.gridx = 0;
                c.gridy = 7;
                c.gridwidth = 2;
                JButton gen = new JButton( "generate");
                gen.addActionListener( new ActionListener() {

                    @Override
                    public void actionPerformed(ActionEvent e) {
                        double pSubGraphD = Double.parseDouble( pSubgraph.getText() );
                        double pEdgeD = Double.parseDouble( pEdge.getText() );
                        int minLayerI = Integer.parseInt( minLayers.getText() );
                        int maxLayerI = Integer.parseInt( maxLayers.getText() );
                        int minNodeI = Integer.parseInt( minNodes.getText() );
                        int maxNodeI = Integer.parseInt( maxNodes.getText() );
                        int maxDepthI = Integer.parseInt( maxDepth.getText() );
                        boolean ok = true;
                        if( pSubGraphD < 0 || pSubGraphD > 1 )
                        {
                            pSubgraph.setBackground( Color.RED );
                            ok = false;
                        }
                        if( pEdgeD < 0 || pEdgeD > 1 )
                        {
                            pEdge.setBackground( Color.RED );
                            ok = false;
                        }
                        if( minLayerI < 1 )
                        {
                            minLayers.setBackground( Color.RED );
                            ok = false;
                        }
                        if( maxLayerI < minLayerI )
                        {
                            maxLayers.setBackground( Color.RED );
                            ok = false;
                        }
                        if( minNodeI < 1 )
                        {
                            minNodes.setBackground( Color.RED );
                            ok = false;
                        }
                        if( maxNodeI < minNodeI )
                        {
                            maxNodes.setBackground( Color.RED );
                            ok = false;
                        }
                        if( maxDepthI < 1 )
                        {
                            maxDepth.setBackground( Color.RED );
                            ok = false;
                        }
                        if( ok )
                        {                            
                            RandomGraphGenerator r = new RandomGraphGenerator( pSubGraphD, pEdgeD, minLayerI, maxLayerI, minNodeI, maxNodeI, maxDepthI );
                            try {
                                LayeredGraphNode graph = r.createRandomNode( null, 0, true );
                                SweepCrossingMinimizer cminzer = new SweepCrossingMinimizer();
                                for( int i = 0; i < 10; i++ )
                                  cminzer.minimizeCrossings( graph );
                                InitializeNodePositions.placeNodes( graph );
                                new MainView( graph );
                                diag.setVisible( false );
                            } catch( Exception e1 )
                            {
                                JOptionPane.showMessageDialog(frame, e1.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
                            }
                        }
                    }
                    
                });
                diag.add( gen, c );
                diag.setSize( 270, 220 );
                diag.setLocation( frame.getX() + frame.getWidth() / 2 - diag.getWidth() / 2, frame.getY() + frame.getHeight() / 2 - diag.getHeight() / 2 );
                diag.setVisible( true );
            }
        });
        delayText = new JLabel( "Delay (ms)" );
        delayText.setBounds( 260, 10, 80, 20 );
        delay = new JTextField( "50" );
        delay.setBounds( 260, 30, 90, 20 );
        delay.getDocument().addDocumentListener( new DocumentListener() {

            @Override
            public void insertUpdate(DocumentEvent e) {
                try
                {
                    controller.setTimeBetween( Integer.parseInt( delay.getText() ) );
                    delay.setBackground( Color.WHITE );
                } catch( Exception e1 )
                {
                    delay.setBackground( Color.RED );
                }
            }

            @Override
            public void removeUpdate(DocumentEvent e) {
                try
                {
                    controller.setTimeBetween( Integer.parseInt( delay.getText() ) );
                    delay.setBackground( Color.WHITE );
                } catch( Exception e1 )
                {
                    delay.setBackground( Color.RED );
                }
            }

            @Override
            public void changedUpdate(DocumentEvent e) {
                try
                {
                    controller.setTimeBetween( Integer.parseInt( delay.getText() ) );
                    delay.setBackground( Color.WHITE );
                } catch( Exception e1 )
                {
                    delay.setBackground( Color.RED );
                }
            }
            
        });
        load = new NiceButton( "load" );
        load.setLocation( 230, 60 );
        load.setMnemonic( KeyEvent.VK_L );
        load.setToolTipText( "Load a graph (alt + l)" );
        load.addActionListener( new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {

                JFileChooser chooser = new JFileChooser();
                chooser.setFileFilter( new FileNameExtensionFilter("Json Graph", "json") );
                chooser.showOpenDialog( frame );
                if( chooser.getSelectedFile() != null )
                {
                    Reader r = new Reader( chooser.getSelectedFile().getAbsolutePath() );
                    LayeredGraphNode graph = r.readInputGraph();
                    InitializeNodePositions.placeNodes( graph );
                    new MainView( graph );
                }
            }
            
        });
        save = new NiceButton( "save" );
        save.setLocation( 295, 60 );
        save.setMnemonic( KeyEvent.VK_S );
        save.setToolTipText( "Save graph (alt + s)" );
        save.addActionListener( new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {

                JFileChooser chooser = new JFileChooser();
                chooser.setFileFilter( new FileNameExtensionFilter("Json Graph", "json") );
                chooser.showSaveDialog( frame );
                if( chooser.getSelectedFile() != null )
                {
                    Writer w = new Writer( chooser.getSelectedFile().getAbsolutePath() );
                    w.writeOutputGraph( graph );
                }
            }
            
        });
        pseudoTree = new JTree();
        pseudoTree.setBackground(RenderHelper.BACKGROUND_COLOR);
        PseudoCodeNode tree = algorithm.createPseudocodeTree( pseudoTree );
        tree.setController( controller );
        pseudoTree.setModel( new DefaultTreeModel( tree ) );
        pseudoTree.setCellRenderer( new PseudoCodeRenderer() );
        pseudoTree.setSelectionModel( null );
        pseudoTree.addMouseListener( new MouseAdapter() {
            public void mousePressed(MouseEvent e) {
                TreePath selPath = pseudoTree.getPathForLocation(e.getX(), e.getY());
                if( selPath != null && e.getClickCount() == 3 ) {
                    ((PseudoCodeNode)selPath.getLastPathComponent()).setBreakPoint( !((PseudoCodeNode)selPath.getLastPathComponent()).hasBreakPoint() );
                    if( !pseudoTree.isExpanded( selPath ) )
                    {
                        pseudoTree.collapsePath( selPath );
                        pseudoTree.expandPath( selPath );
                    }
                    else
                    {
                        pseudoTree.expandPath( selPath );
                        pseudoTree.collapsePath( selPath );
                    }
                    pseudoTree.repaint();
                    frame.repaint();
                }
            }
        } );
        pseudoTree.setRowHeight(15);
        JScrollPane treeView = new JScrollPane( pseudoTree );
        treeView.setBounds( 10,  110,  390, 380 );
        
        JTextArea debugText = new JTextArea();
        debugText.setFont( new Font( "Monospaced", Font.PLAIN, 12 ) );
        debugText.setEditable( false );
        debugText.setBackground( RenderHelper.BACKGROUND_COLOR );
        debugText.setForeground( RenderHelper.FOREGROUND_COLOR );
        JScrollPane debugView = new JScrollPane( debugText );
        debugView.setBounds( treeView.getX(), treeView.getY() + 500, treeView.getWidth(), 250 );
        
        frame.setSize( (int)graph.getWidth( LayoutType.TOP_BOTTOM_LEFT ) * 2 + 575, (int)graph.getHeight( LayoutType.TOP_BOTTOM_LEFT ) * 2 + 200 );
        frame.setLocation( 100, 100 );
        frame.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
        frame.setVisible( true );

        JLayeredPane layne = new JLayeredPane();
        JPanel pl = new JPanel();
        GridLayout grout = new GridLayout( 2, 2, 10, 10 );
        pl.setLayout( grout );
        pl.setLocation( 0, 0 );
        pl.setSize( frame.getSize() );
        NodeView topLeft = createNodeView( graph, LayoutType.TOP_BOTTOM_LEFT );
        NodeView topRight = createNodeView( graph, LayoutType.TOP_BOTTOM_RIGHT );
        NodeView bottomLeft = createNodeView( graph, LayoutType.BOTTOM_TOP_LEFT );
        NodeView bottomRight = createNodeView( graph, LayoutType.BOTTOM_TOP_RIGHT );
        pl.add( topLeft );
        pl.add( topRight );
        pl.add( bottomLeft );
        pl.add( bottomRight );
        layne.add( pl, 1 );
        NodeView combined = createNodeView( graph, LayoutType.COMBINED );
        combined.setSize( 500, 500 );
        layne.add( combined, 0 );
        frame.add( layne );
        JPanel menue = new JPanel();
        menue.setLayout( null );
        menue.setPreferredSize( new Dimension( 410, 500 ) );
        menue.add( stepForward );
        menue.add( stepForwardInto );
        menue.add( stepForwardOut );
        menue.add( runForward );
        menue.add( pause );
        menue.add( debug );
        menue.add( stepBackward );
        menue.add( delayText );
        menue.add( delay );
        menue.add( treeView );
        menue.add( stepBackwardInto );
        menue.add( stepBackwardOut );
        menue.add( runBackward );
        menue.add( randomGraph );
        menue.add( save );
        menue.add( load );
        menue.add( debugView );
        frame.add( menue, BorderLayout.EAST );
        frame.setSize( frame.getWidth() + 1, frame.getHeight() );
        frame.setSize( frame.getWidth() - 1, frame.getHeight() );
        frame.validate();
        frame.repaint();

        frame.addComponentListener(new ComponentAdapter()
        {
            public void componentResized(ComponentEvent evt) {
                pl.setSize( layne.getSize() );
                menue.setSize( menue.getWidth(), layne.getHeight() );
                treeView.setSize( treeView.getWidth(), layne.getHeight() - 370 );
                debugView.setBounds( treeView.getX(), treeView.getY() + treeView.getHeight() + 10, treeView.getWidth(), 240 );
                if( graph.getColor( LayoutType.COMBINED ) == null )
                {
                    grout.setHgap( 10 );
                    grout.setVgap( 10 );
                }
                else
                {
                    grout.setHgap( layne.getWidth() / 3 );
                    grout.setVgap( layne.getHeight() / 3 );
                }
                combined.setSize( layne.getWidth() / 3, layne.getHeight() / 3 );
                combined.setLocation( layne.getWidth() / 3, layne.getHeight() / 3 );

                debugText.setText( algorithm.getDebugString() );
                layne.remove( pl );
                layne.add( pl, 1 );
                frame.repaint();
            }
        });
        
        algorithm.start();
    }

    private NodeView createNodeView( LayeredGraphNode gNode, LayoutType lt )
    {
        NodeView graphView = new NodeView( gNode, lt );
        graphView.setLayout( null );
        graphView.setOpaque( true );
        for( LayeredGraphNode n : gNode.getContainedNodes() )
        {
            NodeView nv = createNodeView( n, lt );
            nv.setBounds( nv.getX(), nv.getY(), nv.getWidth(), nv.getHeight() );
            graphView.add( nv );
        }
        for( LayeredGraphEdge e : gNode.getContainedEdges() )
        {
            EdgeView ev = new EdgeView( e, lt );
            ev.setOpaque( true );
            graphView.add( ev );
        }
        return graphView;
    }
}