/*
 * Created on Apr 2, 2004
 *
 * VisStackApplet
 * An applet that can be modified to produce an interactive
 * demonstration for a data structure. Written for CSE 373.
 * It is currently set up to show a stack.
 * It takes textual commands in a TextArea and when the user
 * clicks on the Execute button, it processes the commands,
 * updating the display as it goes.
 * Here is a sample command sequence:
 */
 
 /*
 PUSH John
 PUSH Mary
 SIZE
 POP
 STATS
 RESET
 ; This is a comment - we are beginning with a new stack.
 DELAY 500  ; from here wait only 500 ms between updates
 PUSH 3.14159
 PUSH 25
 PUSH 13
 STATS
 */

/**
 * @author Steve Tanimoto, Copyright, 2004.
 *
 */ 
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;

public class VisStackApplet extends JApplet implements ActionListener, Runnable {

	ScrolledPanel visPanel; //Where to paint graphics
	MyScrollPane msp;
	Button executeButton; 
	Button historyButton;
	TextArea userInputText;
	TextArea history;
	JFrame historyFrame;
	JTextField statusLine;
	MyStack theStack; // The data structure being demonstrated
	Font stackFont;
	int cellHeight = 20; // For drawing the stack.
	int cellWidth = 200; // How wide to plot pink rectangles
	int cellGap = 4; // vertical space between successive cells
	int topMargin = 25; // Space above top of stack.
	int fontSize = 16; // Height of font for displaying stack elemens.
	int leftMargin = 20; // x value for left side of cells
	int bottomMargin = 10; // Minimum space betw. bot. of visPanel and bot. of lowest cell.
	int leftOffset = 5; // space between left side of cell and contents string.
	int delay = 300; // default is to wait 300 ms between updates.
	Thread displayThread = null;
	
	public void init() {
		setSize(300,300); // default size of applet.
		visPanel = new ScrolledPanel();
		visPanel.setPreferredSize(new Dimension(400,400));
		msp = new MyScrollPane(visPanel);
		msp.setPreferredSize(new Dimension(400,200));
		
		Container c = getContentPane();
		c.setLayout(new BorderLayout());
		c.add(msp, BorderLayout.CENTER);
		JPanel buttons = new JPanel();
		buttons.setLayout(new FlowLayout());
		JPanel controls = new JPanel();
		controls.setLayout(new BorderLayout());
		executeButton = new Button("Execute");
		executeButton.addActionListener(this);
		buttons.add(executeButton);
		historyButton = new Button("History");
		historyButton.addActionListener(this);
		buttons.add(historyButton);
		userInputText = new TextArea(";Enter commands here.");
		statusLine = new JTextField();
		statusLine.setBackground(Color.lightGray);
		controls.add(buttons, BorderLayout.WEST);
		controls.add(userInputText, BorderLayout.CENTER);
		controls.add(statusLine, BorderLayout.SOUTH);
		controls.setPreferredSize(new Dimension(400,100));
		c.add(controls, BorderLayout.SOUTH);
		c.validate();
		
		theStack = new MyStack();
		stackFont = new Font("Helvetica", Font.PLAIN, 20);
		history = new TextArea("VisStackApplet history:\n", 20, 40); 
	}

	class ScrolledPanel extends JPanel {
		public void paintComponent(Graphics g) {
			super.paintComponent(g);
			paintStack(g);
		}
	}
	class MyScrollPane extends JScrollPane {
		MyScrollPane(JPanel p) {
			super(p,
				JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
				JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
		}
	}

	class MyStack extends Vector {

		int n; // number of elements in the stack
		int npushes; // number of PUSH operations so far.
		int npops; // number of POP operations so far.

		void init() {
			n = 0; npushes = 0; npops = 0;
		}

		void push(Object elt) {
			add(n, elt);
			n++; 
			npushes++;
		}

		Object pop() {
			if (n == 0) { return null; }
			Object o = lastElement();
			n--;
			npops++;
			remove(n); 
			return o;
		}	
	}

	public void actionPerformed(ActionEvent e) {
		if (e.getActionCommand().equals("Execute")) {
			displayThread = new Thread(this);
			displayThread.start();
			return;
		}
		if (e.getActionCommand().equals("History")) {
			if (historyFrame == null) { 
				historyFrame = new JFrame("History of the VisStackApplet"); 
				historyFrame.getContentPane().add(history);
				historyFrame.setSize(new Dimension(300,300));
			}
			historyFrame.show();
			System.out.println("Should have displayed the history window");
		}
	}
	
	// The following is executed by a separate thread for the display.
	public void run() {
		String commands = userInputText.getText();
		String line = "";
		StringTokenizer lines;
		for (lines = new StringTokenizer(commands, "\n\r\f");
				lines.hasMoreTokens();) {
			line = lines.nextToken();
			process(line);
		}
		userInputText.setText(""); // Erase all the processed input.
	}
	
	// Helper function called by the run method above:
	void process(String command) {
		String arg = "";
		StringTokenizer st = new StringTokenizer(command);
		if (! st.hasMoreTokens()) { return; }
		String firstToken = st.nextToken();
		if (firstToken.startsWith(";")) { return; }
		history.appendText(command + "\n");
		statusLine.setText(command);
		if (firstToken.equals("RESET")) { 
			theStack = new MyStack();
			updateDisplay(); return;			
		}
		if (firstToken.equals("SIZE")) { 
			String stats = "Current number of elements: " + theStack.n;
			statusLine.setText(stats);
			history.appendText("; " + stats + "\n");
			return;
		}
		if (firstToken.equals("STATS")) { 
			String stats = "npushes: " + theStack.npushes +
			  "; npops: " + theStack.npops;
			statusLine.setText(stats);
			history.appendText("; " + stats + "\n");
			return;
		}
		if (firstToken.equals("DELAY")) { 
			if (st.hasMoreTokens()) { 
				arg = st.nextToken();
				try { delay =(new Integer(arg)).intValue(); }
				catch(NumberFormatException e) {
					delay = 0;
				}
				statusLine.setText("delay = " + delay);
			}
			history.appendText("; delay is now " + delay + "\n");
			return;
		}
		if (firstToken.equals("PUSH")) { 
			arg = "UNDEFINED ELEMENT";
			if (st.hasMoreTokens()) { arg = st.nextToken(); }
			theStack.push(arg);
			checkScrolledPanelSize();
			updateDisplay(); return;			
		}
		if (firstToken.equals("POP")) { 
			theStack.pop();
			updateDisplay(); return;			
		}
		history.appendText("[Unknown Stack command]\n");
		statusLine.setText("Unknown Stack command: " + command);		
	}
	
	// Here is a "middleman" method that updates the display waiting with
	// the current time delay after each repaint request.
	void updateDisplay() {
		visPanel.repaint();
		if (delay > 0) {
			try { 
				Thread.sleep(delay);
			}
			catch(InterruptedException e) {}
		}
	}
	
	// Here is the graphics method to actually draw the stack.
	// It's called by the ScrolledPanel paintComponent method.
	void paintStack(Graphics g) {
		g.setFont(stackFont);
		g.drawString( "Top of Stack", 10,20);	
		int ystart = theStack.n * (cellHeight + cellGap) + topMargin;
		int ycentering = (cellHeight - fontSize) / 2;
		int ypos = ystart;
		for (Enumeration e = theStack.elements(); e.hasMoreElements();) {
			String elt = (String) e.nextElement();
			g.setColor(Color.pink);
			g.fillRect(leftMargin, ypos, cellWidth, cellHeight);
			g.setColor(Color.black);
			g.drawString(elt, leftMargin + leftOffset, ypos+cellHeight - ycentering);			
			ypos -= (cellHeight + cellGap);
		}
	}
	
	// The following computes the height of the display area needed by the current
	// stack, and if it won't fit in the scrolled panel, it enlarges the scrolled panel.
	// In the current implementation, the panel never gets smaller, even if the stack
	// becomes empty.  This could easily be changed.
	void checkScrolledPanelSize() {
		int heightNeeded = topMargin + theStack.n * (cellHeight + cellGap) + cellHeight+ bottomMargin;
		Dimension d = visPanel.getPreferredSize();
		int currentHeight = (int) d.getHeight();
		int currentWidth = (int) d.getWidth();
		if (heightNeeded > currentHeight) { 
			visPanel.setPreferredSize(new Dimension(currentWidth, heightNeeded));
			visPanel.revalidate(); // Adjust the vertical scroll bar.
		}
	}
}