import java.applet.*;
import java.awt.*;
import java.util.*;

class Item {
	String name_;
	boolean enabled_ = true;
	boolean pressed_ = false;
	
	int x_, y_, width_, height_;
	String label_;
	Font font_;
	Color fore_;
	Color back_;
	
	int key_;

	private int xAdd_ = -1, yAdd_;
	
	Item(String name, int x, int y, int width, int height,
		String label, Font font, Color fore, Color back, int key) {
		name_ = name;
		x_ = x;
		y_ = y;
		width_ = width;
		height_ = height;
		label_ = label;
		font_ = font;
		fore_ = fore;
		back_ = back;
		key_ = key;
	}

	void draw(Graphics g) {
		if (xAdd_ == -1) {
			FontMetrics m = g.getFontMetrics(font_);
			xAdd_ = (width_ - m.stringWidth(label_)) / 2;
 			yAdd_ = (height_ + m.getAscent() - m.getDescent()) / 2;		
		}
		
        g.setColor(back_);
 
        if (enabled_) {
            g.fill3DRect(x_, y_, width_, height_, !pressed_);
            g.setColor(fore_);
        }
        else {
            g.fillRect(x_, y_, width_, height_);
            g.setColor(Color.lightGray);
        }
 
        g.setFont(font_);

		if (pressed_)
			g.drawString(label_, x_ + xAdd_ + 1, y_ + yAdd_ + 1);
		else
			g.drawString(label_, x_ + xAdd_, y_ + yAdd_);			
	}

	boolean inside(int x, int y) {
		int offX = x - x_;
		if (offX >= 0 && offX < width_) {
			int offY = y - y_;
			if (offY >= 0 && offY < height_)
				return true;
		}
		return false;
	}
}

public class Calc extends Applet implements Runnable {

	/////////////////////////////////////////////////////////////////	
	// functionality associated with one shot
	/////////////////////////////////////////////////////////////////
	
	Vector items_ = new Vector();
	Vector dirtyItems_ = new Vector();
	Hashtable namesToItems_ = new Hashtable();
	Hashtable keysToItems_ = new Hashtable();

	boolean ignore_ = true;
	Graphics graphics_;
	Image image_;

	private boolean lastInside_;
	private Item clicked_;
//	private Item keypressed_;
	private Item activated_;
	private Vector keyQueue_ = new Vector();

	private Rectangle defaultBounds_ = new Rectangle(10, 10, 20, 20);
	private Font defaultFont_ = new Font("Helvetica", Font.PLAIN, 12);
	private Color defaultFore_ = Color.black;
	private Color defaultBack_ = Color.lightGray;

	private final static int PRESS_DELAY = 125;

	void addButton(String name, int x, int y, int width, int height,
				   String label) {
		addButton(name, x, y, width, height, label, null, null, null, 0);
	}

	void addButton(String name, int x, int y, int width, int height,
				   String label, int key) {
		addButton(name, x, y, width, height, label, null, null, null, key);
	}
	
	void addButton(String name, int x, int y, int width, int height,
			     String label, Font font, Color fore, Color back, int key) {
		if (x == -1) x = defaultBounds_.x;
		if (y == -1) y = defaultBounds_.y;
		if (width == -1) width = defaultBounds_.width;
		if (height == -1) height = defaultBounds_.height;		
		if (font == null) font = defaultFont_;
		if (fore == null) fore = defaultFore_;
		if (back == null) back = defaultBack_;		
		
		Item item = new Item(name, x, y, width, height, label,
							 font, fore, back, key);

		Integer id = new Integer(items_.size());
		
		items_.addElement(item);
		dirtyItems_.addElement(item);
		namesToItems_.put(name, item);

		if (key != 0)
			keysToItems_.put(new Integer(key), item);
	}

	Item nameToItem(String name) {
		return (Item)namesToItems_.get(name);
	}

	Item locationToItem(int x, int y) {
		Item items[] = new Item[items_.size()];
		items_.copyInto(items);
		for (int loop = 0; loop < items.length; ++loop) {
			if (items[loop].inside(x, y))
				return items[loop];
		}
		return null;
	}

	Item keyToItem(int key) {
		return (Item)keysToItems_.get(new Integer(key));
	}


	// set defaults
	void setDefaultBounds(int x, int y, int width, int height) {
		defaultBounds_.x = x;
		defaultBounds_.y = y;		
		defaultBounds_.width = width;
		defaultBounds_.height = height;		
	}
	
	void setDefaultFont(Font font) {
		defaultFont_ = font;
	}

	void setDefaultFore(Color fore) {
		defaultFore_ = fore;		
	}

	void setDefaultBack(Color back) {
		defaultBack_ = back;				
	}


	void setEnable(String name, boolean enable) {
		setEnable(name, enable, true);
	}
	
	void setEnable(String name, boolean enable, boolean repaint) {
		Item i = nameToItem(name);
		if (i.enabled_ != enable) {
			i.enabled_ = enable;
			dirtyItems_.addElement(i);
			if (repaint)
				repaint();
		}
	}
	

	public void update(Graphics g) {    
        paint(g);
	}

	public void paint(Graphics g) {
		if (ignore_)
			return;
		
		if (graphics_ == null) {
			resize(WIDTH, HEIGHT);
			image_ = createImage(WIDTH, HEIGHT);
			graphics_ = image_.getGraphics();

			initDisplay(graphics_);
		}

		if (dirtyItems_.size() > 0) {
			Item dirty[] = new Item[dirtyItems_.size()];
			dirtyItems_.copyInto(dirty);
			for (int loop = 0; loop < dirty.length; ++loop)
				dirty[loop].draw(graphics_);
			dirtyItems_.removeAllElements();
		}

		updateDisplay(graphics_);
		
		g.drawImage(image_, 0, 0, null);

		if (activated_ != null) {
			clicked(activated_.name_);
			
			activated_.pressed_ = false;
			dirtyItems_.addElement(activated_);
			try {
				Thread.sleep(PRESS_DELAY);
			} catch (Exception e) { ; }
			
			if (keyQueue_.size() > 0) {
				activated_ = (Item)keyQueue_.firstElement();
				keyQueue_.removeElementAt(0);
				activated_.pressed_ = true;
 				dirtyItems_.addElement(activated_);				
			}
			else
				activated_ = null;
			repaint();
		}
	}

	public void init() {
		ignore_ = true;
		setup();
		ignore_ = false;
		repaint();
	}
	
	public static void main(String args[]) {
		CloseFrame f = new CloseFrame(TITLE);
		f.setResizable(false);
		Applet a = new Calc();
 
		f.resize(10, 10);
		f.show();
		f.add(a);
 
		a.move(f.insets().left, f.insets().top);
			  a.resize(WIDTH, HEIGHT);
		f.resize(WIDTH + f.insets().left + f.insets().right,
			    HEIGHT + f.insets().top + f.insets().bottom);
	
		a.init();		
		a.start();
		f.setApplet(a);			
	}

	public boolean handleEvent(Event evt) {
		// if a button is being clicked
		if (clicked_ != null) {
			// track drags to make sure button state is correct
			if (evt.id == Event.MOUSE_DRAG) {
				boolean inside = clicked_.inside(evt.x, evt.y);
				if (inside != lastInside_) {
					lastInside_ = inside;
					clicked_.pressed_ = inside;
					dirtyItems_.addElement(clicked_);
					repaint();
                }
				return true;
			}
			// on mouse up, if mouse is inside call clicked
			else if (evt.id == Event.MOUSE_UP) {
				if (lastInside_)
					clicked(clicked_.name_);					
				lastInside_ = false;
				clicked_.pressed_ = false;
				dirtyItems_.addElement(clicked_);
				repaint();
				clicked_ = null;
				return true;
			}
			return false;
		}

		// on mouse down, if someone clicks inside an item start
		// tracking
		if (evt.id == Event.MOUSE_DOWN) {
			Item target = locationToItem(evt.x, evt.y);
			if (target != null && target.enabled_) {
				target.pressed_ = true;
				dirtyItems_.addElement(target);
				clicked_ = target;
				lastInside_ = true;
				repaint();
			}
			return true;
		}
		
		// if a key press is associated with an item, start
		// tracking
		else if (evt.id == Event.KEY_PRESS) {
			Item target = keyToItem(evt.key);
			if (target != null && target.enabled_) {
				if (activated_ == null) {
					target.pressed_ = true;
 					dirtyItems_.addElement(target);
					activated_ = target;
				}
				else
					keyQueue_.addElement(target);
				repaint();
			}
			else if (target == null)
				keypress(evt.key);
			return true;
		}
		else if (evt.id == Event.WINDOW_DESTROY) {
			try {
				Frame f = (Frame)getParent();
				f.hide();
				stop();
			} catch (Exception e) { ; }
		}
		return false;
	}


	
	/////////////////////////////////////////////////////////////////
	// specific to project
	/////////////////////////////////////////////////////////////////	

	// project MUST HAVE these
	
	final static String TITLE = "Chalk";
	final static int WIDTH = 173;
	final static int HEIGHT = 260;
	final static boolean OWN_WINDOW = false;

	void setup() {
		setDefaultFont(new Font("Helvetica", Font.BOLD, 18));
		setDefaultFore(Color.yellow);
		setDefaultBack(Color.gray);

		Color arith = Color.green;
		Color stack = Color.blue;
		
		Font bigger = new Font("Helvetica", Font.PLAIN, 24);
		int w = 26;
		int h = 26;
		int b = 3;
		int bx = b + w;
		int by = b + h;		

		int startY = STACK_HEIGHT + OFFSET * 5;
		
		int x;
		int y;
		
		setDefaultBounds(0, 0, w, h);
		addButton("ENTER", x = OFFSET,  y = startY,   84, -1, "ENTER",
				  null, stack, null, 10);
		addButton("DROP", x += 87,      y,            81, -1, "DROP",
				  null, stack, null, 127);
		
		addButton("d7",  x = OFFSET,y += by,  -1, -1, "7", '7');
		addButton("d8",  x += bx,   y,        -1, -1, "8", '8');
		addButton("d9",  x += bx,   y,        -1, -1, "9", '9');
		addButton("o/",  x += bx,   y,        -1, -1, "/",
				  bigger, arith, null, '/');		

		addButton("d4",  x = OFFSET,y += by,  -1, -1, "4", '4');
		addButton("d5",  x += bx,   y,        -1, -1, "5", '5');
		addButton("d6",  x += bx,   y,        -1, -1, "6", '6');
		addButton("o*",  x += bx,   y,        -1, -1, "*",
				  bigger, arith, null, '*');
		
		addButton("d1",  x = OFFSET,y += by,  -1, -1, "1", '1');
		addButton("d2",  x += bx,   y,        -1, -1, "2", '2');
		addButton("d3",  x += bx,   y,        -1, -1, "3", '3');
		addButton("o-",  x += bx,   y,        -1, -1, "-",
				  bigger, arith, null, '-');

		addButton("d0",  x = OFFSET,y += by,  -1, -1, "0",   '0');
		addButton("d.",   x += bx,   y,        -1, -1, ".",   '.');
		addButton("oC",  x += bx,   y,        -1, -1, "+/-",
				  null, arith, null, 0);
		addButton("o+",  x += bx,   y,        -1, -1, "+",
				  bigger, arith, null, '+');

		setDefaultBounds(0, 0, w * 2, h);
		addButton("oL",  x += bx,   y = startY + by, -1, -1, "LOG",
				  null, arith, null, 0);
		addButton("oR",  x,         y += by,     -1, -1, "SQRT",
				  null, arith, null, 0);
		addButton("o^",  x,         y += by,     -1, -1, "y^x",
				  null, arith, null, 0);		
		addButton("oI",  x,         y += by,     -1, -1, "1/x",
				  null, arith, null, 0);	
	}

	public void start() {
		if (blinker_ != null)
			blinker_.start();
	}

	public void stop() {
		if (blinker_ != null)
			blinker_.stop();	
	}
	
	void initDisplay(Graphics g) {
		g.setColor(Color.black);
		g.fillRect(0, 0, WIDTH, HEIGHT);
		g.setColor(Color.white);
		g.drawRect(OFFSET, OFFSET, STACK_WIDTH, STACK_HEIGHT);
		
		FontMetrics m = g.getFontMetrics(ABOUT_FONT);
		int x = (WIDTH - m.stringWidth(ABOUT)) / 2;
		int y = (nameToItem("d0").y_ + nameToItem("d0").height_ + HEIGHT) / 2 +
		        (m.getAscent() - m.getDescent()) / 2;
				
		g.setColor(ABOUT_COLOR);
		g.setFont(ABOUT_FONT);		
		g.drawString(ABOUT, x, y);
		enableButtons();
	}
	
	void updateDisplay(Graphics g) {
		drawStack(g);
	}

	void keypress(int key) {
		if (key == 8) {
			if (edit_ != null && edit_.length() > 0) {
				edit_ = edit_.substring(0, edit_.length() - 1);
				enableButtons();
				repaint();
			}
		}
	}
	
	void clicked(String name) {
		if (error_ != null)
			error_ = null;

		char a = name.charAt(0);
		char b = name.charAt(1);

		switch (a) {
		case 'D': oneOp(name); break;
		case 'E': enter(); break;			
			
		case 'd':
		case '.': digit(name); break;
		case 'o':
			switch (b) {
			case 'C':
			case 'L':
			case 'R':
			case 'I': oneOp(name); break;
				
			case '/':
			case '*':
			case '-':
			case '+':				
			case '^': twoOp(name); break;
			}
			break;
		}

		enableButtons();
		repaint();
	}


	/////////////////////////////////////////////////////////////////
	// private to this project
	/////////////////////////////////////////////////////////////////	

	final static String ABOUT = "by Adam Doppelt";
	final static Font ABOUT_FONT = new Font("Helvetica", Font.PLAIN, 18);
	final static Color ABOUT_COLOR = Color.red;
	
	final static Font STACK_FONT = new Font("Courier", Font.PLAIN, 14);
	final static Color STACK_COLOR = Color.green;
	final static Font ERROR_FONT = new Font("Helvetica", Font.PLAIN, 14);	
	final static int OFFSET = 3;
	final static int STACK_WIDTH = WIDTH - OFFSET - OFFSET;
	final static int STACK_HEIGHT = 75;
	final static int LINES = 5;

	final static int BLINK_DELAY = 600;

	final static String SPACES = "                                         ";
   	Stack stackStrings_ = new Stack();
	String edit_, error_;
	String display_[] = new String[LINES];

	int startX_ = -1;
	int startY_;
	int numChars_;
	int between_;


	boolean oneOn_ = true;
	boolean twoOn_ = true;

	Stack stack_ = new Stack();
	boolean underShows_ = false;
	Thread blinker_;
	
	void drawStack(Graphics g) {
		g.setColor(Color.black);
		g.fillRect(OFFSET + 1, OFFSET + 1, STACK_WIDTH - 2, STACK_HEIGHT - 2);

		if (startX_ == -1) {
			FontMetrics m = g.getFontMetrics(STACK_FONT);

			String test = " ";
			while (m.stringWidth(test) < STACK_WIDTH - OFFSET * 2)
				test += " ";
			test = test.substring(0, test.length() - 1);
			numChars_ = test.length();

			between_ = m.getHeight() - m.getDescent() + 1;

			startX_ = (STACK_WIDTH - m.stringWidth(test)) / 2 + OFFSET;
			int totalHeight = between_ * (LINES - 1) + m.getAscent();
			startY_ = (((STACK_HEIGHT - totalHeight) / 2) +
					   m.getAscent() + OFFSET) - 2;
		}

		int dNum = LINES - 1;
		int sNum = (edit_ != null) ? LINES - 1 : LINES;

		while (sNum > 0) {
			String current;

			int vNum = stackStrings_.size() - sNum;
			if (vNum < 0)
				current = "";
			else
				current = (String)stackStrings_.elementAt(vNum);
			current = ("" + sNum + ":" +
					   SPACES.substring(0, numChars_ -current.length() - 2) +
					   current);
			display_[dNum] = current;
			--sNum;
			--dNum;
		}

		if (edit_ != null)
			display_[0] = edit_ + (underShows_ ? "_" : "");

		int y = startY_;
		if (error_ != null) {
			g.setFont(ERROR_FONT);
			g.setColor(Color.red);			
			g.drawString(error_, startX_, y);
		}

		g.setFont(STACK_FONT);
		g.setColor(STACK_COLOR);
		
		if (error_ == null)
			g.drawString(display_[LINES - 1], startX_, y);
		y += between_;
		
		for (int loop = LINES - 2; loop >= 0; --loop) {
			g.drawString(display_[loop], startX_, y);
			y += between_;
		}
	}
	
	void enableButtons() {
		boolean oneOn;
		boolean twoOn;

		int numStack = stackStrings_.size();
		if (edit_ != null && edit_.length() > 0)
			++numStack;
		oneOn = numStack > 0;
		twoOn = numStack > 1;

		if (oneOn != oneOn_) {
			oneOn_ = oneOn;
			setEnable("ENTER", oneOn_, false);
			setEnable("DROP", oneOn_, false);
			setEnable("oC", oneOn_, false);
			setEnable("oL", oneOn_, false);
			setEnable("oR", oneOn_, false);
			setEnable("oI", oneOn_, false);
		}

		if (twoOn != twoOn_) {
			twoOn_ = twoOn;
			setEnable("o/", twoOn_, false);
			setEnable("o*", twoOn_, false);
			setEnable("o-", twoOn_, false);
			setEnable("o+", twoOn_, false);
			setEnable("o^", twoOn_, false);			
		}
	}

	public void run() {
		while (true) {
			try {
				blinker_.sleep(BLINK_DELAY);
				underShows_ = !underShows_;
				repaint();
			} catch (InterruptedException e) { ; }
		}
	}

	void digit(String name) {
		if (edit_ == null) {
			edit_ = "";
			blinker_ = new Thread(this);
			blinker_.start();
		}

		if (edit_.length() + 2 < numChars_)
			edit_ += name.charAt(1);
	}

	void oneOp(String name) {
		char a = name.charAt(1);
		if (edit_ != null && a == 'C') {
			if (edit_.charAt(0) == '-')
				edit_ = edit_.substring(1);
			else
				edit_ = "-" + edit_;
			return;
		}
		
		if (flush()) {
			double d = pop();
			if (!name.equals("DROP")) {
				switch (a) {
				case 'C': d *= -1; break;
				case 'L': d = Math.log(d) / Math.log(10); break;
				case 'R': d = Math.sqrt(d); break;
				case 'I': d = 1 / d; break;
				}
				push(d);
			}
		}
	}

	void twoOp(String name) {
		if (flush()) {
			double d2 = pop();
			double d1 = pop();
			double r = 0;
			switch (name.charAt(1)) {
			case '/': r = d1 / d2; break;
			case '*': r = d1 * d2; break;
			case '-': r = d1 - d2; break;
			case '+': r = d1 + d2; break;
			case '^': r = Math.pow(d1, d2); break;
			}
			push(r);
		}
	}

	void enter() {
		if (edit_ != null)
			flush();
		else {
			double d = pop();
			push(d);
			push(d);
		}
	}

	boolean flush() {
		if (edit_ == null)
			return true;
			
		if (edit_.length() == 0) {
			edit_ = null;
			return true;
		}
		
		Double d;
		try {
			d = new Double(edit_);
			push(d.doubleValue());
		} catch (NumberFormatException e) {
			error_ = "Invalid syntax";
		}

		edit_ = null;
		blinker_.stop();
		blinker_ = null;

		return error_ == null;
	}
	
	void push(double d) {
		Double o = new Double(d);
		stackStrings_.push(o.toString());
		stack_.push(o);
	}

	double pop() {
		stackStrings_.pop();
		return ((Double)stack_.pop()).doubleValue();
	}
}
