// Update July 14:
// Made stop button into a stop/pause button.
// Fixed slider so you can change speed even when not running.
//*********************************************************
//Creates the display of TMApplet
//*********************************************************

import java.util.StringTokenizer;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.Hashtable;
import javax.swing.plaf.metal.MetalLookAndFeel;

public class JTM extends JPanel
{
    static final Color non_head_colour = new Color(242,242,255); //light blue
    static final Color head_colour = new Color(255,255,175); // yellow
    static final Color msg_colour = new Color(255,255,210); // light yellow
	

    static final int FPS_MIN = 0;
    static final int FPS_MAX = 10;
    static final int FPS_INIT = 4;

    TM turing_machine;
    boolean tm_initialized = false;
    boolean w_initialized = false;
    boolean running = false;
    boolean done = false;

    private JLabel ruleLabel;
    private JTextArea msgWindow;
    private JTextArea TMProgram;
    private JButton loadTMButton, inputButton;
    private JToolBar tapeToolBar;

    public static JButton startButton;
    public static JButton plusButton;
    public static JSlider speedSlider;

    public static Timer timer;
    public static int delay= 1 << ( 3 + 11 - FPS_INIT); 
    public static ActionListener listener;
    public static ChangeListener change_listener;

    private JToolBar getMainToolBar()
    {     
        listener = new JTMListener();
        change_listener = new JTMListener();
        JToolBar tb = new JToolBar();
        tb.setFloatable(false);

	loadTMButton = new JButton("New TM");
	loadTMButton.addActionListener(listener);

	inputButton = new JButton("Input");
	inputButton.addActionListener(listener);

	startButton = new JButton("  Run  ");
	startButton.addActionListener(listener);

	plusButton = new JButton("  +   ");
	plusButton.addActionListener(listener);

        JSlider speedSlider = new JSlider(JSlider.HORIZONTAL,
                                      FPS_MIN, FPS_MAX, FPS_INIT);
        
        //Create the label table.
        Hashtable<Integer, JLabel> labelTable = 
            new Hashtable<Integer, JLabel>();
        labelTable.put( new Integer(FPS_MIN), new JLabel("Slow") );
        labelTable.put( new Integer( FPS_MAX ), new JLabel("Fast") );
        labelTable.put( new Integer( FPS_INIT ), new JLabel("Default") );
        speedSlider.setLabelTable( labelTable );

        speedSlider.addChangeListener(change_listener);
        speedSlider.setMajorTickSpacing(5);
        speedSlider.setMinorTickSpacing(1);
        speedSlider.setPaintTicks(true);
        speedSlider.setPaintLabels(true);

	// Anything else???

        tb.add(loadTMButton);
        tb.addSeparator();
        tb.add(inputButton);
        tb.addSeparator();
        tb.add(startButton);
        tb.addSeparator();
        tb.add(plusButton);
	tb.add(speedSlider);

        return(tb);
    }

    private JComponent initTape()
    {
        tapeToolBar = new JToolBar();
        tapeToolBar.setFloatable(false);
	//tapeToolBar.setAutoscrolls(false);
	// Add buttons to the tapeToolBar at this point?
	for (int i=0; i< TM.TAPE_LENGTH; i++)
	{
		JButton b = new JButton("#");
		b.setBackground(non_head_colour);

		tapeToolBar.add(b);
	}

	((JButton)tapeToolBar.getComponentAtIndex(1)).setBackground(head_colour);

	JScrollPane sp = new JScrollPane(tapeToolBar);
	return sp;
    }

    public JTM(int width, int height)
    {
	TM.TAPE_LENGTH= 40;
	 JFrame.setDefaultLookAndFeelDecorated(true);

	try{
		UIManager.setLookAndFeel(new MetalLookAndFeel());
	}catch (Exception e) {}
        setLayout(new BorderLayout());
        setPreferredSize(new Dimension(width, height));
        setBorder(BorderFactory.createEtchedBorder());
	
	// The top of the window will contain the main toolbar as well as the TM tape
	JSplitPane topsp = new JSplitPane(JSplitPane.VERTICAL_SPLIT, getMainToolBar(), initTape());
	//topsp.setAutoscrolls(false);
	topsp.setOneTouchExpandable(true);
	add(topsp, BorderLayout.NORTH);

	// The rest of the window
	ruleLabel = new JLabel("Use New TM button to read in your TM.");
	TMProgram = new JTextArea(15,20);
	TMProgram.setFont(new Font("Monospaced", 0, 12));
	TMProgram.setEditable(false);
	JScrollPane programsp = new JScrollPane(TMProgram);
	msgWindow = new JTextArea(2,20);
	msgWindow.setEditable(false);
	msgWindow.setBackground(msg_colour);

	JSplitPane tempsp = new JSplitPane(JSplitPane.VERTICAL_SPLIT, ruleLabel, msgWindow);

	JSplitPane bottomsp = new JSplitPane(JSplitPane.VERTICAL_SPLIT, tempsp, programsp);
	add(bottomsp, BorderLayout.CENTER);
    }

    private void handleLoad()
    {
	Input tm_input;

        JFileChooser fc = new JFileChooser(new File(System.getProperty("user.dir")));
	int retval = fc.showOpenDialog(JTM.this);
	if (retval == JFileChooser.APPROVE_OPTION)
	{
	    File tmToLoad = fc.getSelectedFile();

	    try
	    {
                    tm_initialized = false;
                    w_initialized = false;
                    running = false;
                    done = true;

		    tm_input= new Input(new FileInputStream(tmToLoad));
		    turing_machine= new TM(tm_input);
		    turing_machine.new_input(tm_input);
		    TMProgram.setText(turing_machine.tm_description);
		    set_tape_contents();
		    ruleLabel.setText("Turing machine initialized.");
		    msgWindow.setText("Press Run to start running\n or + to execute one step at a time\n");

                    tm_initialized = true;
                    w_initialized = true;
                    running = false;
                    done = false;
	    }
	    catch (FileNotFoundException ex)
	    {
                JOptionPane.showMessageDialog(JTM.this,
                   "Cannot access the file: " + tmToLoad,
                   "Cannot Access File", JOptionPane.ERROR_MESSAGE);
	    }

	    // Open file, parse it, update state to show TM loaded
	    // PRobably want to also set up all GUI elements to their default states
	}
    }

    public class JTMListener implements ActionListener, ChangeListener
    {
        public void stateChanged(ChangeEvent e) 
	{
	    int new_delay;
	    int pow;

            JSlider source = (JSlider)e.getSource();
            if (!source.getValueIsAdjusting()) 
	    {
                new_delay = source.getValue();
		pow= 3 + 11- new_delay;
		if (pow < 0) pow =0;
		if (pow > 30) pow= 30;
		delay= 1 << pow;
		if (running) // Changed July 14: only update timer when running.
	        {
                   timer.setDelay(delay);
                   timer.setInitialDelay(delay);
  		   timer.stop();
		   timer.start();
		}
            }
        }
	    
        public void actionPerformed(ActionEvent e)
        {
            Object source = e.getSource();

	    /* Take the code for handling the processing of
	     * interrupts for animated algorithms out of
	     * FUIGUI. Dec.2005.
	     */

	    //animated_algorithm.handle_interrupt(source);
	
	    /*
	    System.out.println("Interrupt being handled.");
	    if (running) System.out.println("TM is running");
	    else         System.out.println("TM is NOT running");
	    if (source == timer) 
		System.out.println("Interrupt is from timer");
	    else if (source == startButton)
		System.out.println("Interrupt is from StartButton");
    	    */

	    if (source != timer && running)
	    {
		 //System.out.println("Stop running due to action performed.");
		 running= false;
		 timer.stop(); 
		 startButton.setText("  Run  ");
		 if (source == startButton)
		 {
			 repaint(); return;
		 }
	    }

	    if (source == loadTMButton)
            {
		    handleLoad();
            }
            else if (source == inputButton)
            {
                    w_initialized = false;
                    done = true;
                    if (!tm_initialized)
		    {
		       ruleLabel.setText("Error- No TM initialized.");
		       msgWindow.setText("You must read in a TM before you type in an input string.\nUse the New TM button to get your TM\n");
                       repaint();
		       return;
		    }
		    String inputString = JOptionPane.showInputDialog(JTM.this,
			"Enter the input for the Turing Machine",
			"Enter input string",
			JOptionPane.QUESTION_MESSAGE);

		    if (inputString == null)
	            {
			    inputString="#";
	            }
		    turing_machine.set_input(inputString);
		    set_tape_contents();
                    w_initialized = true;
                    running = false;
                    done = false;
		    ruleLabel.setText("Input string " + inputString + " initialized.");
		    msgWindow.setText("Press Run to start running\n or + to execute one step at a time\n");
                    repaint();
		    return;
            }
	    else if (source == plusButton)
	    {
		    try_move();
	    }
	    else if (source == startButton)
	    {
		    msgWindow.setText("");
		    if (try_move())
		    {
                        timer = new Timer(delay, listener);
                        timer.setInitialDelay(delay);
                        timer.setCoalesce(true);
                        timer.start();
		        startButton.setText(" Pause ");
			// Changed July 14: assign running status so pause button works.
			running=true;
			done=false;
		    }
		    else
		    {
		        startButton.setText("  Run  ");
			running= false;
			done= true;
		    }
	    }
	    else if (source == timer)
	    {
		    if (!try_move())
		    {
			    timer.stop();
		            startButton.setText("  Run  ");
			    running= false;
			    done= true;
		    }
	    }
            repaint();
        }
    }
    public void set_tape_contents()
    {
	for (int i=0; i< TM.TAPE_LENGTH; i++)
	{
		if (i== turing_machine.head_position)
	            ((JButton)tapeToolBar.getComponentAtIndex(i)).setBackground(head_colour);
		else
	            ((JButton)tapeToolBar.getComponentAtIndex(i)).setBackground(non_head_colour);

	        ((JButton)tapeToolBar.getComponentAtIndex(i)).setText("" + turing_machine.tape[i]);
	}
    }
    public boolean try_move()
    {
	 boolean ok_move;

	 ok_move= true;

         if (!tm_initialized)
	 {
	       ruleLabel.setText("Error- No TM initialized.");
	       msgWindow.setText("You must read in a TM before you can run.\nUse the New TM button to get your TM\n");
	       ok_move= false;
	 }
	 else if (done)
	 {
	       ruleLabel.setText("Error- TM execution has already completed.");
	       if (turing_machine.message != null)
	       msgWindow.setText(turing_machine.message);
	       ok_move= false;
	 }
	 else 
	 {
	     if (!turing_machine.move())
	     {
		     done= true;
	             ok_move= false;
	     }
	     if (turing_machine.next_rule != null)
	     ruleLabel.setText("Step " + turing_machine.step + " : " + 
			                 turing_machine.next_rule);
	     if (turing_machine.message != null)
	     msgWindow.setText(turing_machine.message);
	     set_tape_contents();
	 }
	 return(ok_move);
    }
    /**
     * Create the GUI and show it.  For thread safety,
     * this method should be invoked from the
     * event-dispatching thread.
     */
    private static void createAndShowGUI() {
        //Create and set up the window.
        JFrame frame = new JFrame("Java Turing Machine Simulator");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        frame.getContentPane().add(new JTM(800,800));

        //Display the window.
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        //Schedule a job for the event-dispatching thread:
        //creating and showing this application's GUI.
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
}
