/* InteractiveTurtleApplet.java -- Interactive Turtle using the com.lrdev.turtle package.
**
** Copyright (C) 1997-2003 Eric Laroche.  All rights reserved.
**
** @author Eric Laroche <laroche@lrdev.com>
** @version 1.2 @(#)$Id: InteractiveTurtleApplet.java,v 1.2 2003/02/16 12:40:56 laroche Exp $
**
** This program is free software;
** you can redistribute it and/or modify it.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
**
*/

// note: no wildcard imports

// Use the AWT based graphics Turtle package.
import com.lrdev.turtle.PersistentTurtle;

import java.applet.Applet;
import java.awt.Color;
import java.awt.Event;
import java.awt.Graphics;
import java.awt.Rectangle;

/** InteractiveTurtleApplet: Interactive Turtle using the com.lrdev.turtle package.
*
* @author Eric Laroche <laroche@lrdev.com>
* @version 1.2 @(#)$Id: InteractiveTurtleApplet.java,v 1.2 2003/02/16 12:40:56 laroche Exp $
*/
public class InteractiveTurtleApplet
	extends Applet
{
	/** Version.
	*/
	private final static String m_version =
		"Version: 1.2 @(#)$Id: InteractiveTurtleApplet.java,v 1.2 2003/02/16 12:40:56 laroche Exp $";

	/** Author.
	*/
	private final static String m_author =
		"Author: Eric Laroche <laroche@lrdev.com>";

	/** Applet usage.
	*/
	private final static String[][] m_usage = {};

	// ----------------

	/** Associated Turtle.
	*/
	private PersistentTurtle m_turtle = new PersistentTurtle();

	/** Default Turtle advance length.
	*/
	private final static double m_defaultLength = 16;

	/** Turtle advance length.
	*/
	private double m_length = m_defaultLength;

	/** Default Turtle heading change.
	*/
	private final static double m_defaultOmega = Math.PI / 2;

	/** Turtle heading change.
	*/
	private double m_omega = m_defaultOmega;

	/** Last command.
	*/
	private int m_lastCommand = 'h'; // home

	/** User interface update flag.
	*/
	private boolean m_uiNeedsUpdate = false;

	// ----------------

	/** Called by the browser.
	* Returns info on this applet.
	*
	* Overwrites Applet.getAppletInfo.
	*/
	public String getAppletInfo()
	{
		return
			m_version + "\n" +
			m_author + "\n\n" +
			"interactive usage:\n" + interactiveUsage();
	}

	/** Called by the browser.
	* Returns info on the parameters of this applet.
	*
	* Overwrites Applet.getParameterInfo.
	*/
	public String[][] getParameterInfo()
	{
		return m_usage;
	}

	/** Constructor.
	*/
	public InteractiveTurtleApplet()
	{
		// NOTE: must postpone getParameter() calls to init()
	}

	/** Called after the constructor and before start().
	* Sets the background color and reads in the applet parameters.
	*
	* Overwrites Applet.init.
	*/
	public void init()
	{
		// white background, black foreground
		setBackground(Color.white);
		setForeground(Color.black);
	}

	/** Called by the browser.
	*
	* Overwrites Applet.start.
	*/
	public void start()
	{
	}

	/* Called by the browser.
	*
	* Overwrites Applet.stop.
	*/
	public void stop()
	{
	}

	/** Called by the browser.
	*
	* Overwrites Applet.destroy.
	*/
	public void destroy()
	{
	}

	/** Repaint the component if needed.
	*/
	protected void repaintIfNeeded()
	{
		if (m_uiNeedsUpdate) {
			repaint();
		}
	}

	/** Paint the component.
	*
	* Overwrites Component.paint.
	*/
	public void paint(Graphics graphics)
	{
		m_uiNeedsUpdate = false;

		// get drawing box
		Rectangle b = bounds();

		// 2 pixel border
		int border = 2;

		b.width -= 2 * border;
		b.height -= 2 * border;
		b.x += border;
		b.y += border;

		// check if the box is large enough
		if (b.width < 0 || b.height < 0) {
			// we're too small
			return;
		}

		// have the thing centered at all times
		m_turtle.center();

		m_turtle.paint(graphics, b);
   	}

	/** Handle keyboard input.
	*
	* Overwrites Component.keyDown.
	*/
	public boolean keyDown(Event event, int key)
	{
		boolean handled = doCommand(key);
		repaintIfNeeded();
		return handled;
	}

	/** Command interpreter.
	*
	* @return command handled
	*
	* <p>
	* doCommand() does not call repaint() to indicate GUI needs an
	* update, since doCommand() is allowed to be called by
	* non-UI-handling threads [in that case, that thread and paint()
	* would have to be synchronized].  Instead, m_uiNeedsUpdate is
	* updated.  Note however that m_uiNeedsUpdate is not a
	* (synchronized) monitor, it's only used synchronous.
	*/
	protected boolean doCommand(int command)
	{
		if (command == 'D') { // reDo
			command = m_lastCommand;
		}

		m_lastCommand = command;

		switch (command) {

		case 'u' : // penUp
			m_turtle.penup();
			return true; // event handled

		case 'd' : // penDown
			m_turtle.pendown();
			return true; // event handled

		case 'f' : // Forward
			m_turtle.forward(m_length);
			m_uiNeedsUpdate = true; // GUI needs update
			return true; // event handled

		case 'F' : // Forward less
			m_turtle.forward(m_length / 4);
			m_uiNeedsUpdate = true; // GUI needs update
			return true; // event handled

		case 'b' : // Back
			m_turtle.back(m_length);
			m_uiNeedsUpdate = true; // GUI needs update
			return true; // event handled

		case 'B' : // Back less
			m_turtle.back(m_length / 4);
			m_uiNeedsUpdate = true; // GUI needs update
			return true; // event handled

		case 'l' : // Left
			m_turtle.left(m_omega);
			return true; // event handled

		case 'L' : // Left less
			m_turtle.left(m_omega / 4);
			return true; // event handled

		case 'r' : // Right
			m_turtle.right(m_omega);
			return true; // event handled

		case 'R' : // Right less
			m_turtle.right(m_omega / 4);
			return true; // event handled

		case 'h' : // Home
			m_turtle.home();
			m_uiNeedsUpdate = true; // GUI needs update
			return true; // event handled

		// not supported:
		// setxy
		// setx
		// sety
		// setheading
		// towards

		case 'S' : // reSet
			m_turtle.reset();
			// reset current turtle parameters
			m_length = m_defaultLength;
			m_omega = m_defaultOmega;
			m_uiNeedsUpdate = true; // GUI needs update
			return true; // event handled

		case 'U' : // Undo
			m_turtle.undo();
			m_uiNeedsUpdate = true; // GUI needs update
			return true; // event handled

		case 'D' : // reDo
			// done above.
			// we shouldn't get here.
			// leave the case in, to catch reused labels.
			break;

		case '+' : // increase length
			m_length += m_defaultLength;
			return true; // event handled

		case '-' : // decrease length
			m_length -= m_defaultLength;
			if (m_length < 1) {
				m_length = 1;
			}
			return true; // event handled

		case '*' : // double length
			m_length *= 2;
			return true; // event handled

		case '/' : // half length
			m_length /= 2;
			if (m_length < 1) {
				m_length = 1;
			}
			return true; // event handled
		}

		return false; // event not handled
	}

	/** Return interactive usage.
	*/
	protected static String interactiveUsage()
	{
		return
			"u: penUp" + "\n" +
			"d: penDown" + "\n" +
			"f: Forward" + "\n" +
			"F: Forward less" + "\n" +
			"b: Back" + "\n" +
			"B: Back less" + "\n" +
			"l: Left" + "\n" +
			"L: Left less" + "\n" +
			"r: Right" + "\n" +
			"R: Right less" + "\n" +
			"h: Home" + "\n" +
			"S: reSet" + "\n" +
			"U: Undo" + "\n" +
			"D: reDo" + "\n" +
			"+: increase length" + "\n" +
			"-: decrease length" + "\n" +
			"*: double length" + "\n" +
			"/: half length" + "\n" +
			"\n";
	}
}

