/*
MazeIcon.java 3.0

Written by Kevin N. Haw and JoAnn K. Haw, www.thehaws.org.  Please mail comments
via the link at our website.

All contents copyright 2000-2005 by Kevin N. Haw and JoAnn K. Haw.  The authors 
grant permission to freely distribute and modify this program as long as this
copyright notice and all copyright notices embedded in the program 
remain intact.

This applet draws small mazes based on square, diamond, and hex shaped
tiles for use as icons or decorative borders.  It requires the MazeTile class
(distributed with this class or found at our website) to create the maze.
the two are usually placed in a common .jar file to allow for ease of use.

The applet is invoked with the following parameters:

  fgcolor - an RBG hex value      Color of maze walls (blue is default)
  bgcolor - an RBG hex value      Color of maze background (white is default)
  solcolor- an RBG hex value      Color of maze solution (red is default)
  tileShape - square|diamond|hex  Shape of tiles in maze (square is default)
  HREF - URL (begining "http://") URL to go to on a mouseclick (default is none)
  ALT - any text                  Browser text when mouse crosses applet
  solution - on|off|toggleOn|toggleOff on/off displays maze solution.  
	                                     toggleOn/Off allows toggling solution 
	                                     w/ r-click (toggleOff is default)
  refresh - integer               Draw a new maze after a number of seconds 
                                    (default is a new maze only when 
                                    the whole page is refreshed)

Example 1: A simple icon linked to a website
<center>
  <applet code=MazeIcon.class width=50 height=50>
  <param name=URL value="http://www.thehaws.org">
  <param name=ALT value="Click for the author's homepage...">
  </applet>
</center>

Example 2: A horizontal, hex shaped border on a tan background with 
  black foreground.  The solution is displayed (but can be r-clicked
  to toggle off) and the maze refreshes every three seconds.

  <applet code=MazeIcon.class name="diamond" width=600 height=50>
  <param name=tileShape value="hex">
  <param name=fgColor value="000000">
  <param name=bgColor value="FFCC99">
  <param name=URL value="http://www.thehaws.org">
  <param name=ALT value="Back to our homepage...">
  <param name=solution value="toggleOn">
  <param name=refresh value="3">
  </applet>


See getParameterInfo() below for documentation of applet parameters.

*/

import java.awt.*;
import java.awt.event.*;

import java.awt.Graphics;
import java.util.Vector;
import java.awt.Point;
import java.awt.Rectangle;
import java.io.PrintStream;
import java.awt.Color;
import java.awt.Polygon;
import java.applet.AppletContext;
import java.net.URL;
import java.lang.Thread;

// Define an applet to create mazes
public class MazeIcon extends java.applet.Applet implements MouseListener, Runnable
  {
  // Copyright and version data
  public static String copyMsg = "MazeIcon: Copyright Kevin N. Haw, 2000-2001.";
  public static String verMsg = "MazeIcon v 3.0";
  public static int verno = 0x300000;   // Byte0=revision, byte1= minor version, byte2= major version

  Thread refreshThread;           // The thread that redraws maze
  int refreshTimer;               // Time to run refreshThread

  MazeTile firstTile = null;
  String tileShapeParam;
  URL clickURL = null;            // URL to go to on a mouseclick
  String linkMsg = null;

  // Flags reflecting parameters passed into applet
  Color fgColor = Color.blue;     // Color of maze walls (default blue)
  Color bgColor = Color.white;    // Color for maze background (default white)
  Color solColor = Color.red;     // Color for solution & labels (default red)
  Boolean labelParam = new Boolean(false);
  boolean toggleSolutionParam = true;
  Polygon tileShape;              // Base shape of each tile in the maze


  boolean drawSolution = false;
  Rectangle r;
  Vector theSolution;  // Solution to the maze

  MazeTile startTile;             // Start of maze in solution
  MazeTile endTile;               // End of maze in solution

  // Override the init() method to set up mouse listener and parse
  // applet parameters.
  public void init() 
    {
    // Set up mouseListener to listen to mouse input commands
    addMouseListener(this);

    // Read the bgColor parameter - default to white
    try {
        bgColor = new Color(Integer.parseInt(getParameter("bgcolor"),16));
    } catch (Exception e) { }
    // Set the background color to parameter
    setBackground(bgColor);

    // Read the fgColor parameter (maze walls) - default to blue
    try {
        fgColor = new Color(Integer.parseInt(getParameter("fgcolor"),16));
    } catch (Exception e) { }

    // Read the solColor parameter (solution & labels) - default to red
    try {
        solColor = new Color(Integer.parseInt(getParameter("solcolor"),16));
    } catch (Exception e) { }

    // Read the refresh parameter - default is to refresh maze on repainting only
    try {
        refreshTimer = Integer.parseInt(getParameter("refresh"),10);
    } catch (Exception e) { refreshTimer=0; }

	// Read the label parameter - default is false
	try {
	    labelParam = new Boolean( getParameter("label") );
	} catch (Exception e) {	}

    // Read the ALT parameter as message to drive on browser's status line
	// (like alternate text on a link).  Default is none.
	try {
	    linkMsg = new String(getParameter("alt"));
	} catch (Exception e) {	}

	// Read the HREF parameter as a URL to go to when clicked
	try {
	    clickURL = new URL(getParameter("href"));
	} catch (Exception e) { }

    // Read the solution parameter.  Default is "toggleOff"
	try {
	    String solutionParam = new String(getParameter("solution"));
		solutionParam = solutionParam.toLowerCase();
		if (solutionParam.compareTo("on") == 0)
		  {
		  // Parameter "Solution = On" means always show the solution, never toggle
          toggleSolutionParam = false;
          drawSolution = true;
		  }
		else if (solutionParam.compareTo("off") == 0)
		  {
		  // Parameter "Solution = Off" means never show the solution, never toggle
          toggleSolutionParam = false;
          drawSolution = false;
		  }
		else if (solutionParam.compareTo("toggleon") == 0)
		  {
		  // Parameter "Solution = toggleOn" means start showing the 
		  // solution, but toggle it on when right-clicked.
          toggleSolutionParam = true;
          drawSolution = true;
		  }
		else if (solutionParam.compareTo("toggleoff") == 0)
		  {
		  // Parameter "Solution = toggleOff" means start w/o showing the 
		  // solution, but toggle it on when right-clicked.  (DEFAULT)
          toggleSolutionParam = false;
          drawSolution = true;
		  }
	} catch (Exception e) { }

	// Read the tileShape parameter 
	try {
	    tileShapeParam = getParameter("tileShape");
	} catch (Exception e) { }

    // Set up polygon for maze tiles based on tileShape parameter
	tileShape = new Polygon();
	try {
	if ( tileShapeParam.equalsIgnoreCase("hex") )
	  {
      // Make tiny hex shaped tiles 
      tileShape.addPoint(  5,  3); // Northeast 
      tileShape.addPoint(  0,  6); // North 
      tileShape.addPoint( -5,  3); // Northwest 
      tileShape.addPoint( -5, -3); // Southwest 
      tileShape.addPoint(  0, -6); // South 
      tileShape.addPoint(  5, -3); // Southeast 
	  }
    else if ( tileShapeParam.equalsIgnoreCase("diamond") )
	  {
      // Now, make a tiny diamond shaped tile 
      tileShape.addPoint(  3,  0); // North 
	  tileShape.addPoint(  0,  3); // East 
	  tileShape.addPoint( -3,  0); // South 
	  tileShape.addPoint(  0, -3); // West 
	  }
    else
	  {
      // By default, make square shaped tiles 
      tileShape.addPoint( 3, 3); // Northeast 
	  tileShape.addPoint(-3, 3); // Southeast 
	  tileShape.addPoint(-3,-3); // Southwest 
	  tileShape.addPoint( 3,-3); // Southeast 
	  }
	} catch (Exception e) {
      // By default, make square shaped tiles 
      tileShape.addPoint( 3, 3); // Northeast 
	  tileShape.addPoint(-3, 3); // Southeast 
	  tileShape.addPoint(-3,-3); // Southwest 
	  tileShape.addPoint( 3,-3); // Southeast 
	}

    }

  // Override the destroy() method to remove mouse listener.
  public void destroy()
    {
    removeMouseListener(this);
    }


  // Override the stop() method.
  public void stop()
    {
	// Kill the refresh thread
	refreshThread = null;
    }

  // Set up a run() method for refreshing the maze
  public void run()
    {
    Thread me = Thread.currentThread();
    // If requested, redraw the maze periodically
    if(refreshTimer > 0)
      {
      while (refreshThread == me) 
        {
        // Sleep for number of second in "refresh" parameter
        try {
        Thread.currentThread().sleep(refreshTimer*1000);
        } catch (InterruptedException e) {  }

        // Now awake - make a new maze, get a new solution for it, and repaint
        firstTile.constructMaze();
        theSolution = firstTile.solve(startTile, endTile);
        repaint();
        }
      }
    }

  // Override the start() method.  This will be rerun whenever the 
  // HTML page is reloaded or a different page with the same applet
  // is loaded.  It creates a brand new maze each time it is run.
  public void start()
	{
    // Get boundaries of the drawing plane
	r= getBounds();
	
    // If not yet done, allocate the tiles for the new maze
    if (firstTile == null)
	  {
      firstTile = MazeTile.AllocateMaze(tileShape, r);
	  }

    // Now, actually create paths in the maze
	firstTile.constructMaze();

    // Find the maze's solution
	startTile = firstTile.corner("lt"); // Left, topmost tile
	endTile = firstTile.corner("rb");   // Right, bottommost tile
	theSolution = firstTile.solve(startTile, endTile);

    // Set up a thread for refresh requests
    if(refreshTimer > 0)
	  {
      refreshThread = new Thread(this);
      refreshThread.start();
	  }
	}

  // Draw the applet
  public void paint(Graphics g) 
    {
    int i;
	Point p1, p2;

	// Clear the rectangle
    g.setColor(bgColor);
	g.fillRect(r.x, r.y, r.width, r.height);

	// Draw the maze
	firstTile.paint(g, fgColor);

    // Draw the solution if requested
	if (drawSolution)
	  {
      g.setColor(solColor);
      for (i=1; i<theSolution.size(); i++)
  	    {
  	    p1 = ( (MazeTile)(theSolution.elementAt(i-1)) ).Center;
  	    p2 = ( (MazeTile)(theSolution.elementAt(i)) ).Center;
  	  
  	    // Draw a line from cell to cell
        g.drawLine(p1.x, p1.y, p2.x, p2.y);
  	    }
      }
	}

  // The user has clicked in the applet. Figure out where
  // and see if a legal move is possible. If it is a legal
  // move, respond with a legal move (if possible).
  public void mouseReleased(MouseEvent e) 
    {
    Point p = new Point(e.getPoint());
    boolean leftButtonPush =
      (e.getModifiers()&java.awt.event.InputEvent.BUTTON1_MASK) != 0;
    boolean centerButtonPush =
      (e.getModifiers()&java.awt.event.InputEvent.BUTTON2_MASK) != 0;
    boolean rightButtonPush =
      (e.getModifiers()&java.awt.event.InputEvent.BUTTON3_MASK) != 0;


    int x = e.getX();
    int y = e.getY();
	int i, j;
	
    // On a left click, see if the applet parameters specified a URL 
    // to click to.  If so, go there.
    if(leftButtonPush && (clickURL != null) )
	  {
	  getAppletContext().showDocument(clickURL);
	  }

    // On a right click, see if the applet parameters specified toggling 
    // display of the solution.  If so, toggle and repaint applet.
    if(rightButtonPush && toggleSolutionParam)
	  {
      drawSolution = !drawSolution;  // Toggle solution
	  this.repaint();                // Schedule repaint of applet
	  }
    }

  public void mousePressed(MouseEvent e) 
    {
    }

  public void mouseClicked(MouseEvent e) 
    {
    }

  public void mouseEntered(MouseEvent e) 
    {
	// If requested, drive text to the browser's status bar
	if (linkMsg != null)
	  {
	  this.showStatus(linkMsg);
	  }
    else
	  {
	  // If no text was requested, drive link URL to the browser's status bar
	  if (clickURL != null)
	    {
	    this.showStatus(clickURL.toString());
		}
	  }
    }

  public void mouseExited(MouseEvent e) 
    {
	// If we put up a message earlier on the browser status bar, clear it
	if (linkMsg != null || clickURL != null)
	  {
	  this.showStatus("");
	  } 
    }

  public String getAppletInfo() 
    {
    return verMsg+
      " - Draws a maze based on different shaped tiles.";
    }


  //Get parameter info
  public String[][] getParameterInfo() {
    return new String[][] {{"Parameters:", "", ""},
            {"label",     "boolean",   "If true, label \"start\" and \"end\""},
            {"fgcolor",   "<RBG hex value>",   
              "Color of maze walls (blue is default)"},
            {"bgcolor",   "<RBG hex value>",   
              "Color of maze background (white is default)"},
            {"solcolor",  "<RBG hex value>",   
              "Color of maze solution & labels (red is default)"},
            {"tileShape",
              "square|diamond|hex|bowtie|tiny|tinyhex|tinydiamond",
              "shape of tiles in maze (square is default)"},
            {"HREF",      "<valid URL>",       
              "URL to go to on a mouseclick (often used w/ tileShape=tiny)"},
            {"ALT",       "<any text>","Alternate text for URL"},
			{"solution",  "on|off|toggleOn|toggleOff", 
			  "on/off displays maze soltuion.  toggleOn/Off allows toggling "
			  +"solution w/ r-click (toggleOff is default)"},
            {"refresh",   "integer",   
              "Draw a new maze after a number of seconds (default is not to)"}
          };
  }
}
