/*
 * @(#)AutomataApplication.java    2.0  20 February 2002
 *
 * Copyright 2000, 2001
 * College of Computer Science
 * Northeastern University
 * Boston, MA  02115
 *
 * This software may be used for educational purposes as long as
 * this copyright notice is retained intact at the top of all files.
 *
 * To discuss commercial use of this software, contact 
 * Prof. Richard Rasala or Prof. Viera Proulx at the Northeastern 
 * University College of Computer Science: 617-373-2462.
 *
 * The principal software developer on this project is Jeff Raab.
 *
 * Should this software be modified, the words "Modified from 
 * Original" must be included as a comment below this notice.
 *
 * All publication rights are retained.  This software or its 
 * documentation may not be published in any media either in whole
 * or in part without explicit permission.
 *
 * This software was created with support from Northeastern 
 * University and from NSF grant DUE-9950829.
 */
 
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
import java.util.*;
import java.io.*;
import javax.swing.ImageIcon;

import edu.neu.ccs.*;
import edu.neu.ccs.gui.*;
import edu.neu.ccs.util.*;

/**
 * This is the main application class for the Interactive Pushdown Automata
 * Animation program.
 * 
 * @author  Jen McDonald
 * @version 2.0
 * @since   2.0
 */
public class AutomataApplication extends TablePanel
    implements JPTConstants {
    
    // constants: default text color
    public static final Color DEFAULT_COLOR = Color.black;
    
    
    // constants: buffer size
    public static final int bufferHeight = 500;
    public static final int bufferWidth  = 600;
    
    // constants: current operation
    public static final int NO_OP = 0;
    public static final int STATE_OP = 1;
    public static final int ARROW_OP = 2;
    public static final int RUN_OP = 3;
    public static final int CLEAR_OP = 4;
    public static final int LOAD_OP = 5;
    public static final int SAVE_OP = 6;
    
    
    //////////////////
    // Mouse Action //
    //////////////////
    
    // The mouse clicked action
    protected MouseAction mouseClicked =
        new MouseAction() {
            public void mouseActionPerformed(MouseEvent mevt) {
                mouseClicked(mevt);
            }
        };
  
  	/////////////////////
  	// EDITING ACTIONS //
  	/////////////////////

    protected ImageIcon folderIcon = 
    	new ImageIcon("icons/folder.gif");
    protected ImageIcon diskIcon = 
    	new ImageIcon("icons/disk.gif");    	
    protected ImageIcon stateIcon = 
    	new ImageIcon("icons/state.gif");
    protected ImageIcon arrowIcon = 
    	new ImageIcon("icons/transition.gif");
    protected ImageIcon editIcon   = 
    	new ImageIcon("icons/edit.gif");
    protected ImageIcon clearIcon = 
    	new ImageIcon("icons/clear.gif");

    protected SimpleAction loadAutomata = new SimpleAction("", folderIcon) {
        public void perform() {
 		    loadAutomata();	
 		}
    };
            
    protected SimpleAction saveAutomata = new SimpleAction("", diskIcon) {
        public void perform() {
            saveAutomata();
        }
    };
        
    protected SimpleAction addState = new SimpleAction("", stateIcon) {
        public void perform() {
            addState();
        }
    };
           
    protected SimpleAction addArrow = new SimpleAction("", arrowIcon) {
        public void perform() {
            addArrow();
        }
    };
   
    protected SimpleAction clearAutomata = new SimpleAction("", clearIcon) {
        public void perform() {
            clearAutomata();
        }
    };
    

    //////////////////////
    // EDITING CONTROLS //
    //////////////////////

    // View for state data    
    protected StateView stateView = 
    	new StateView();

    // View for arrow data
    protected ArrowView arrowView =
  		new ArrowView();
  		
    
        
    ///////////////////////
  	// ANIMATION ACTIONS //
  	///////////////////////
        
    protected SimpleAction startAnimation = new SimpleAction("Start") {
        public void perform() {
            startAnimation();             	
        }
    };
        
    protected SimpleAction pauseAnimation = new SimpleAction("Pause") {
        public void perform() {
            pauseAnimation();
        }
    };
        
    protected SimpleAction nextAnimation = new SimpleAction("Next") {
        public void perform() {
            nextAnimation();
        }
    };
       
   	////////////////////////
  	// ANIMATION CONTROLS //
  	////////////////////////
    
    protected static final String[] animationStrings
        = new String[] { "Run to Completion", "Run Step by Step" };
        
    // The options view to determine animation style
    protected OptionsView animationOptions =
    	new OptionsView(animationStrings, 
    					0, new GridLayout(0,1));
   	
   	// highlighted color input for the animation
    protected static ColorView highlightColor = 
    	new ColorView(Color.red);
    	
    // visited color input for the animation
    protected static ColorView visitedColor = 
    	new ColorView(Color.lightGray);
    	
	// slider for animation speed
    protected static SliderView animationSpeed = 
    	new SliderView(SwingConstants.HORIZONTAL, 
    				   100, 1000, 800);

    // TextFieldView(String text, String errorPrompt, 
    //               String dialogTitle, String suggestion, int width) 				   
    protected TextFieldView depthMaxTFV = new TextFieldView(
        "100", 
        "Please enter an integer value", 
        "Bad Depth Max", 
        "50",
        75);
	
	//////////////////////////
	//** DISPLAY CANVASES **//
	//////////////////////////
	
	 // scroll pane for showing the results of a test
    protected Canvas.DataDisplay pathCanvas = 
    	 new Canvas.DataDisplay(150, bufferHeight);
    
    // scroll pane to display the stack that
    // has its own stack to store data
    protected Canvas.StackDisplay stackCanvas = 
    	new Canvas.StackDisplay(25, bufferHeight);
    	
    protected Canvas.TapeDisplay tapeCanvas = 
    	new Canvas.TapeDisplay(bufferWidth + 50, 35);
    	
   //////////////
   // Graphics //
   //////////////
    
   // window for creating the automata
    protected BufferedPanel window = 
        new BufferedPanel(bufferWidth, bufferHeight, Color.white);
        
    // grahpics contained in the buffered panel
    protected Graphics2D graphics = window.getBufferGraphics();

    /////////////////
    // Member Data //
    /////////////////

    // automata structure to hold states and arrows
    protected Automata automata = new Automata();

    // machine that processes strings
    // given an automata
    protected Machine machine = null;

	// Boolean variable to control animation
	protected volatile boolean freeToAnimate = false;

    // Data structure containing the last clicked state
    protected State clickedState = null;

    // variable to track the current operation
    protected int current_op = NO_OP;

    // thread to control animation timing
    protected Thread thread = null;
    
    // Default file to save automata to
	protected File autFile = 
    	new File("c:/AutomataFiles");

    
    /////////////////////
    //** CONSTRUCTOR **//
    /////////////////////
    
    public AutomataApplication() {
 
        //TablePanel(int rows, int cols, int hgap, int vgap, int align)         
        super(1, 3, 5, 5, TOP);
        
        setUpMouseAdapter();        
            									
    	// Control panel  	     
		Display toolsDisplay = constructBuilderPanel();
					
		// animation panel
		Display animationDisplay = constructAnimatorPanel();
			
		// Automata window & tape display
		Display automataDisplay = constructWindowPanel();

        add(animationDisplay);
        add(automataDisplay);
        add(toolsDisplay);               
       
        // clear the automata window
        clear();

    } // end constructor
  
    protected void setUpMouseAdapter() {

        // Get the mouse adapter for the automata window
		MouseActionAdapter mouseAdapter = 
			window.getMouseActionAdapter();
	
		// Define the mouse clicked action -- when the mouse is
		// clicked, the mouseClicked method will be called
        mouseAdapter.addMouseClickedAction(mouseClicked);
    }
  
    protected Display constructBuilderPanel() {
    
		// TablePanel(Object[] contents, int orientation, int hgap, int vgap, int align) 
		TablePanel builderPanel = new TablePanel(
		    new Object[] {
		        loadAutomata, 
		        saveAutomata, 
		        addState, 
		        addArrow, 
		        clearAutomata,
		        stateView}, VERTICAL, 5, 5, CENTER);	
		
        return new Display(builderPanel, null, "Build");
    }
  
    protected Display constructWindowPanel() {
    
      	
      	// TablePanel(Object[][] contents, int hgap, int vgap, int align)
		TablePanel windowAndTapePanel = new TablePanel(
		    new Object[][] {
		        { tapeCanvas },
		        { window }
		    }, 5, 5, VERTICAL
		);
		
		// force the tape display to scroll horizontally
		tapeCanvas.setPreferredSize(new Dimension(
										bufferWidth-3, 60));

        return new Display(windowAndTapePanel, null, "Automata");
    }
    
    protected Display constructAnimatorPanel() {
    
      	ActionsPanel animationActions = new ActionsPanel(
      	    new Action[] {startAnimation, pauseAnimation, nextAnimation});

        // user cannot perform any animations until
        // he or she has added at least one state
      	startAnimation.setEnabled(false);
      	pauseAnimation.setEnabled(false);
      	nextAnimation.setEnabled(false);


      	TablePanel colorControlPanel = new TablePanel(
      	    new Object[][] { {"Hightlight Color ", " Visited Color"},
      	                     { highlightColor,    visitedColor  } }, 0, 0, VERTICAL);
      	      	
      	// TablePanel(Object[][] contents, int hgap, int vgap, int align)
      	TablePanel animatorControlsPanel = new TablePanel(
      	    new Object[] {
      	        animationActions,
      	        animationOptions,
      	        "Speed",
      	        animationSpeed,
      	        colorControlPanel }, VERTICAL, 5, 5, CENTER
        );
      	                
      	      
		// greater value means greater pause between repaints
		// thus inverting the slider makes the animation speed
		// increase from left to right as it should
   		animationSpeed.setInverted(true);

        Display pathDisplay = new Display(
            pathCanvas, "Path", null, TOP, CENTER);
            
        Display stackDisplay = new Display(
            stackCanvas, "Stack", null, TOP, CENTER);
            
        TablePanel tempPanel = new TablePanel(
            new Object[] { pathDisplay, stackDisplay}, HORIZONTAL, 5, 5, WEST);
            
        TablePanel depthMaxPanel = new TablePanel(
            new Object[] { "Max Recursion Depth:", depthMaxTFV }, HORIZONTAL, 5, 5, WEST);

   		TablePanel animatorPanel = new TablePanel(
   		    new Object[] { tempPanel, animatorControlsPanel, depthMaxPanel}, 
   		    VERTICAL, 5, 5, WEST);
   		
        // force the pathCanvas panel to scroll
      	pathCanvas.setPreferredSize(new Dimension(160, (int)(bufferHeight/1.5)));
   	
   		// force the stackCanvas panel to scroll
      	stackCanvas.setPreferredSize(new Dimension(40, (int)(bufferHeight/1.5)));
    
        return new Display(animatorPanel, null, "Animate");
    }
  
  
  	
  	/////////////////////////////
  	//** EDIT ACTION METHODS **//
  	/////////////////////////////
  
  	// Load automata action -- opens existing
    // automata, builds and displays it
  	public void loadAutomata() {
  	
  		current_op = LOAD_OP;
  		
  		clear();
  	
  		// Buffer to read file data into
        char[] buf = new char[10000];
                	
        // File chooser to display directories
        JFileChooser fc = new JFileChooser(autFile);
                	
        // Show dialog prompt user to open a file
        int result = fc.showOpenDialog(null);
                
        // If ok is clicked, open the file
        if (result == JFileChooser.APPROVE_OPTION) {
        	try {
        	
                File file = fc.getSelectedFile();
           
           		// build automata from file contents
                automata = new Automata(FileUtilities.readFile(file));
           			     
                // construct the automata gui from the
                // automata data structure
                reconstructAutomata();
                
                // allow the user to perform
            	// animations on the new automata
                startAnimation.setEnabled(true);      			
       		}
            catch(Exception e) {
                System.out.println("Exception: " + e);
            }
  		}                
  		
  		// Set focus to the automata window
        window.grabFocus();
  	}
  	
  
  	// Save automata action -- converts automata to 
    // string format and writes it to a file  
  	public void saveAutomata() {
  	
  		current_op = SAVE_OP;
  		
  		JFileChooser fc = new JFileChooser(autFile);
        int result = fc.showSaveDialog(null);
                
        if(result == JFileChooser.APPROVE_OPTION) {
        	try{
            	File file = fc.getSelectedFile();
            	
            	FileUtilities.writeFile(file, 
            							automata.toStringData(),
            							true);
          	}
            catch(Exception e) {
            	System.out.println("Exception: " + e);
            }
     	} 
     	
     	// Set the focus to the automata window
        window.grabFocus();               
  	}
  
  
  	// This action puts the application into
    // "State Mode." When this button is clicked, 
    // the cursor changes and each mouse click creates
    // a new state 	
  	public void addState() {
  		
  		window.setCursor(new Cursor
                (Cursor.CROSSHAIR_CURSOR));
                
     	current_op = STATE_OP;
                
        window.grabFocus();
   	}
   	
   	
   	// This action puts the application into
    // "Arrow Mode." When this button is clicked, 
    // the cursor changes and each mouse click creates
    // a new arrow 
   	public void addArrow() {
   	
   		window.setCursor(new Cursor
                (Cursor.HAND_CURSOR));
                
        current_op = ARROW_OP;
        
        window.grabFocus();
   	}
  	
  	// This action clears the automata panel and removes
    // all data from the automata structure
  	public void clearAutomata() {
  		 window.setCursor(new Cursor
         		(Cursor.DEFAULT_CURSOR));
                
         current_op = CLEAR_OP;
         
         // wipe out all states and arrows
         clear();
    }
	

	//////////////////////////////////
  	//** ANIMATION ACTION METHODS **//
  	//////////////////////////////////
  	
	public synchronized void startAnimation() {
		
		current_op = RUN_OP;
	
		reset();
                	
        freeToAnimate = true;
                	
        startAnimation.setEnabled(false);
        pauseAnimation.setEnabled(true);
        nextAnimation.setEnabled(false);
        run();
        
        // Set focus to the automata window
        window.grabFocus();
	}
		
		
	public synchronized void nextAnimation() {
	
		freeToAnimate = true;
		
		startAnimation.setEnabled(false);
        pauseAnimation.setEnabled(true);
        nextAnimation.setEnabled(false);
                        	
      	// Set focus to the automata window
      	window.grabFocus();
  	}
  	
  	public synchronized void pauseAnimation() {
  	
  		freeToAnimate = false;
  		
		startAnimation.setEnabled(false);
        pauseAnimation.setEnabled(false);
        nextAnimation.setEnabled(true);
        
      	// Set focus to the automata window
        window.grabFocus();
  	}
  	
  	public synchronized void resetAnimation() {
  	
  		freeToAnimate = false;
  		
  		startAnimation.setEnabled(true);
  		pauseAnimation.setEnabled(false);
  		nextAnimation.setEnabled(false);
  		
  		current_op = NO_OP;
  	}
	
	/////////////////////////////
	//** MOUSE ACTION METHOD **//
	/////////////////////////////
	
	public void mouseClicked(MouseEvent evt) {
			
		State clicked = null;
	
		// if the state operation is selected, 
		// add a state at the point clicked
		if (current_op == STATE_OP){
			
			try {
			
				// create state
				State s = (State) 
					stateView.requestObject(automata, evt.getPoint());
				
				startAnimation.setEnabled(true);
				
				// paint the new state	
				s.draw(graphics, null);
				
				repaint();
			}
			catch(CancelledException e) {}
								
		}
		
		// If the arrow operation is selected,
		// add an arrow between the last two states clicked
		if (current_op == ARROW_OP) {
			
			//Determine whether the user clicked
			//in a valid state			
			if ((clicked =
					automata.getStateContaining(evt.getPoint())) != null) {
							
				//Determine what action to take, 
				//depending on how many actions are 
				//in the queue
			
				if (clickedState == null) 
					clickedState = clicked;
				else {
					try {
						// make a new arrow
						Arrow a = (Arrow) arrowView.requestObject(
							clickedState, clicked, automata);				

						// paint the new arrow	
						a.draw(graphics, null);
						a.drawTransitions(graphics, null);
						
						repaint();
		
						// reset clickedState	
						clickedState = null;
													
					}
					catch(CancelledException e) {
						
						// reset clickedState
						clickedState = null;
					}
				}
			
			}
			else { 
				// reset clickedState
				clickedState = null; 
			}	
		}
	}
	
	
	///////////////////////////
	//** ANIMATION METHODS **//
	///////////////////////////
	
	// requests an input string from the user
	// processes that string
	// calls animate with the best path
	public void run() {
		try {
       		String input = 
           		new SimpleDialog().requestString(
               		"Enter a string to test",				
               		"Run Automata");
            
            Tape t = new Tape(input);
            
            int maxDepth = depthMaxTFV.demandInt();

			tapeCanvas.setData(t);
			stackCanvas.reset();	
			
			repaint();
			
			machine = new Machine(t, automata, maxDepth);
			
			animate(bestPath(machine.findPaths()));
		}
		catch(CancelledException e) { resetAnimation(); }
	}
	
	// animates a given path p
	public void animate(Stack p) {
			
			final Stack path = (Stack) p.clone();
			
			thread = new Thread() {
				public void run() {
					
					int pathPointer = 0;
				
					while(pathPointer < path.size()){
					
						if(freeToAnimate){
							
							showNextState((PathNode) path.get(pathPointer));
							pathPointer++;
							JPTUtilities.pauseThread(animationSpeed.getValue());
							
							if(animationOptions.getSelectedIndex() == 1)
								pauseAnimation();
						}
					}
					
					evaluate(path);
					resetAnimation();
				}
			};
			thread.setDaemon(true);
			thread.start();
	}
		
	// displays one step of the animation	
	public void showNextState(PathNode p) {
	
		// show tape read
		tapeCanvas.animate(p.tapeRead());
		repaint();
		
		JPTUtilities.pauseThread(animationSpeed.getValue());
		
		// animate the transition and the state reached
		// by that transition
		p.animate(window, animationSpeed.getValue());
				
		// animate the stack change			
		stackCanvas.animate(p.getStackAction());
		repaint();
		
		JPTUtilities.pauseThread(animationSpeed.getValue());
		
		// animate the path change
		pathCanvas.animate(p.getTapeCharRead());
		pathCanvas.animate(p.getActionString());
		pathCanvas.animate(p.getStateString());
		repaint();
	}
	
	
	// determines whether a path results 
	// in an accepted state
	public void evaluate(Stack path) {
	
		String evalString = "";
	
		if (accepted(path))
			evalString = "Input accepted";
		else
			evalString = "Input rejected";
		
		pathCanvas.animate(evalString);
		repaint();
	}
	
	// chooses the best path from a list 
	// of paths found by the machine
	public Stack bestPath(Vector paths) {
    
    	Stack goodPath = new Stack();
    	int pathLength = 0;
    	
    	for(int i = 0; i < paths.size(); i++) {
    		Stack s = (Stack) paths.get(i);
    		if (accepted(s))
    			return s;
    		else
    			if (s.size() > pathLength) {
    				goodPath = s;
    				pathLength = s.size();
    			}
    	}
    	
    	return goodPath;    
    }  
    
    // is the terminating state final?
    public boolean accepted(Stack stack) {
    	return ((PathNode) stack.peek()).isTerminatingNode();
    }
	
	
	///////////////////////
	//** OTHER METHODS **//
	///////////////////////
	
	// reset will erase automata window and 
	// erase all states, arrows, and transitions 
	// from the Automata data structure
    public void clear() {
    
    	automata.clear();
        
        // clear the clicked queue
        clickedState = null;
        
        window.clearPanel();
        
        stackCanvas.reset();
        tapeCanvas.reset();
        pathCanvas.reset();
        
        repaint();
    }
    
    public void reset() {
  		window.clearPanel();
    	reconstructAutomata();
    	stackCanvas.reset();
        tapeCanvas.reset();
        pathCanvas.reset();
   }
	
	public void reconstructAutomata() {
		automata.draw(graphics);
		repaint();
	}
      
    // Returns the automata - a structure containing 
	// all states and arrows
	public Automata getAutomata() {
		return automata;
	}
	
	// Returns the highlight color for the animation
	public static Color getHighlightColor() {
		return highlightColor.getColor();
	}
	
	// Returns the visited color for the animation
	public static Color getVisitedColor() {
		return visitedColor.getColor();
	}
	
	   
   	//////////////////////
    //** MAIN PROGRAM **//
    //////////////////////
    
    public static void main(String[] args) {
        
        // Create new frame with title
        JPTFrame.createQuickJPTFrame(
            "Interactive Pushdown Automata Animation",
            new AutomataApplication());
    }
}


