/* maze02.java   last mod 03 FEB 2012 by Ed James */
/* NOTE: Need to handle change in frame size, or not allow it?   */

import java.awt.*;
import java.awt.event.*;           // for the adapters
import java.util.Random;
import java.util.Date;
import javax.swing.JFrame;    // top level GUI widget
import javax.swing.JButton;   // for the GUI buttons.

class maze02Frame extends JFrame implements Runnable, MouseListener, ActionListener {
   public static final long serialVersionUID = 443L; // not currently used

   int myWidth = 500;  // should tie to frame size
   int myHeight = 300;  // should tie to frame size
   int minX = 0;
   int minY = 0;

   Thread mazeThread;

   BorderLayout myBorderLayout;
   JButton northButton, eastButton, westButton, southButton;

   maze02Canvas myCanvas;

   public maze02Frame () {  // JFrame constructor
      super ();  // need to explain why I need this, or if I do.

      setTitle ("Animated Maze Demo");
      setSize (myWidth, myHeight);

      setBackground (Color.black);
      setForeground (Color.red);

      setVisible (true);
      }

   void buildGUI () {
      myBorderLayout = new BorderLayout ();
      setLayout (myBorderLayout);

      northButton = new JButton ("North");
      northButton.setBackground (Color.yellow);
      northButton.addActionListener (this);
      add ("North", northButton);

      eastButton = new JButton ("East");
      eastButton.setBackground (Color.yellow);
      eastButton.addActionListener (this);
      add ("East", eastButton);

      westButton = new JButton ("West");
      westButton.setBackground (Color.yellow);
      westButton.addActionListener (this);
      add ("West", westButton);

      southButton = new JButton ("South");
      southButton.setBackground (Color.yellow);
      southButton.addActionListener (this);
      add ("South", southButton);

      myCanvas = new maze02Canvas ();
      myCanvas.addMouseListener (this);
      add ("Center", myCanvas);
      }

//   void makeOffscreenImage () {
//      myCanvas.makeOffscreenImage ();
//      }

   void addHierarchyBoundsListener () {
// This handles changes to main window location and size
      getContentPane().addHierarchyBoundsListener (new HierarchyBoundsListener () {
         @Override
         public void ancestorMoved (HierarchyEvent HE1) {
			
            }
         @Override
         public void ancestorResized (HierarchyEvent HE2) {
            myCanvas.resizeImage ();
            }
         });
      }

   void addWindowListener () {
// This handles closing the main window
      try {
         addWindowListener (new WindowAdapter() {
            public void windowClosing (WindowEvent myEvent) {
           // maybe call destroy() here?
               System.exit (0);
               }
            });
         }
      catch (java.security.AccessControlException ACE2) {
         System.out.println("Error 2: " + ACE2.getMessage());
         }
      }

// These overrides are needed for the MouseListener implementation:
   public void mouseExited   (MouseEvent ME) { }
   public void mouseEntered  (MouseEvent ME) { }
   public void mousePressed  (MouseEvent ME) { }
   public void mouseReleased (MouseEvent ME) { }
   public void mouseClicked  (MouseEvent ME) { /*myCanvas.mousePressed (ME);*/  }

// This override is needed for the ActionListener implementation:
   public void actionPerformed(ActionEvent AE) {
   // NOTE: This is for demo purposes only, doesn't do anything useful yet.
      String AC = AE.getActionCommand ();
      if      (AC == "North") handleNorthButton ();
      else if (AC == "East" ) handleEastButton ();
      else if (AC == "West" ) handleWestButton ();
      else if (AC == "South") handleSouthButton ();
      else
         System.out.println ("ERROR: unknown action command - " + AC);
      }

   void handleNorthButton () { System.out.println ("NORTH"); /*myCanvas.moveUp();   */}
   void handleEastButton  () { System.out.println ("EAST"); /*myCanvas.moveDown(); */}
   void handleWestButton  () { System.out.println ("WEST"); /*myCanvas.moveRight();*/}
   void handleSouthButton () { System.out.println ("SOUTH"); /*myCanvas.moveLeft(); */}
 
// Thread-related methods:
   public void start () {
      myCanvas.makeOffscreenImage();
      myCanvas.makeOnscreenImage();
      myCanvas.resizeImage();

      if (mazeThread == null) {
         mazeThread = new Thread (this);
         mazeThread.start ();
         }
      }

   public void stop () { mazeThread = null; }

   public void run () {
      Thread thisThread = Thread.currentThread ();

      while (mazeThread == thisThread) {
         myCanvas.repaint ();
         try { Thread.sleep(500); }  // 500 is safe
         catch (InterruptedException e) { }
         }
      }

   } // end of class maze02Frame

class maze02Canvas extends java.awt.Canvas {
   public static final long serialVersionUID = 448L; // not currently used

   final int MAX_X = 20;  // 20 rooms wide
   final int MAX_Y = 20;  // 20 rooms tall
   final int NORTH = 1;   // uses bitwise math to determine directionality
   final int EAST  = 2;
   final int WEST  = 4;
   final int SOUTH = 8;

// Maze variables:
   int rooms[][] = new int[MAX_X][MAX_Y];
   int xInc, yInc;

   int xLoc, yLoc;
   final int PIECE_COUNT = 10;
   maze02Piece blackPieces[] = new maze02Piece[PIECE_COUNT];
   maze02Piece redPieces[]   = new maze02Piece[PIECE_COUNT];
   maze02Piece greenPieces[] = new maze02Piece[PIECE_COUNT];

   int myWidth  = 500;  // canvas width
   int myHeight = 300; // canvas height

   Image myOnscreenImage;
   Graphics myOnscreenGraphics;
   Image myOffscreenImage;
   Graphics myOffscreenGraphics;

   Random randGen = new Random ();  // Random number generator stuff;
   int iRand;

   maze02Canvas () {
     createMaze ();

   // Initialize the pieces:
      for (int i = 0 ; i < PIECE_COUNT ; i++) {
         blackPieces[i] = new maze02Piece();
         blackPieces[i].currX   = (int) (randGen.nextDouble() * MAX_X);
         blackPieces[i].currY   = (int) (randGen.nextDouble() * MAX_Y);
         blackPieces[i].backDir = 0;
         blackPieces[i].color = Color.black;

         redPieces[i] = new maze02Piece();
         redPieces[i].currX   = (int) (randGen.nextDouble() * MAX_X);
         redPieces[i].currY   = (int) (randGen.nextDouble() * MAX_Y);
         redPieces[i].backDir = 0;
         redPieces[i].color = Color.red;

         greenPieces[i] = new maze02Piece();
         greenPieces[i].currX   = (int) (randGen.nextDouble() * MAX_X);
         greenPieces[i].currY   = (int) (randGen.nextDouble() * MAX_Y);
         greenPieces[i].backDir = 0;
         greenPieces[i].color = Color.green;
         }
      }

//   public void update (Graphics g) { paint (g); }

   void createMaze() {
      String dirs = "";
      int iLen;

      for (int j = 0 ; j < MAX_Y ; j++)
         for (int i = 0 ; i < MAX_X ; i++)
            rooms[i][j] = 0;

      rooms[MAX_X/2][MAX_Y/2]     += WEST;
      rooms[MAX_X/2 - 1][MAX_Y/2] += EAST;
      boolean notDone = true;

   // Build a random maze:
      while (notDone) {
         notDone = false;
         for (yLoc = 0 ; yLoc < MAX_Y ; yLoc++) {
            for (int xLoc = 0 ; xLoc < MAX_X ; xLoc++) {
               if (rooms[xLoc][yLoc] == 0) {
                  notDone = true;
                  dirs = "";
                  if (yLoc > 0)
                     if (rooms[xLoc][yLoc-1] != 0)
                        dirs = dirs + "N";

                  if (xLoc < MAX_X-1)
                     if (rooms[xLoc+1][yLoc] != 0)
                        dirs = dirs + "E";

                  if (xLoc > 0)
                     if (rooms[xLoc-1][yLoc] != 0)
                        dirs = dirs + "W";

                  if (yLoc < MAX_Y-1)
                     if (rooms[xLoc][yLoc+1] != 0)
                        dirs = dirs + "S";

                  iLen = dirs.length(); // how many attached neighbors"
                  if (iLen == 0)        // if curr room has no attached neighbors
                     continue;          //     go check another room.

                  iRand = (int) (randGen.nextDouble() * iLen);
                  if (dirs.charAt(iRand) == 'N') {
                     rooms[xLoc][yLoc]   += NORTH;
                     rooms[xLoc][yLoc-1] += SOUTH;
                     }
                  else if (dirs.charAt(iRand) == 'E') {
                     rooms[xLoc][yLoc]   += EAST;
                     rooms[xLoc+1][yLoc] += WEST;
                     }
                  else if (dirs.charAt(iRand) == 'W') {
                     rooms[xLoc][yLoc]   += WEST;
                     rooms[xLoc-1][yLoc] += EAST;
                     }
                  else if (dirs.charAt(iRand) == 'S') {
                     rooms[xLoc][yLoc]   += SOUTH;
                     rooms[xLoc][yLoc+1] += NORTH;
                     }
                  else
                     System.out.println("Houston, we have a problem.");
                  }   // end if block
               }   // end inner for loop
            }   // end outer for loop
         }  // end while
      }

   public void paint(Graphics g) {
      if (myOffscreenImage == null) makeOffscreenImage ();

      if (myOnscreenImage == null)  makeOnscreenImage ();

      myOnscreenGraphics  = myOnscreenImage.getGraphics();

      myOnscreenGraphics.drawImage (myOffscreenImage, 0, 0, this);

      for (int k = 0 ; k < PIECE_COUNT ; k++) {
         myOnscreenGraphics.setColor (Color.red);
         movePiece (redPieces[k]);

         myOnscreenGraphics.setColor (Color.black);
         movePiece (blackPieces[k]);

         myOnscreenGraphics.setColor (Color.green);
         movePiece (greenPieces[k]);
         }

      g.drawImage (myOnscreenImage, 0, 0, this);
      }

   void resizeImage () {
      //System.out.println ("resizeImage() started");
      myWidth  = getSize ().width;
      myHeight = getSize ().height;

   // safety checks - bit of a kludge
      if (myWidth <= 0) myWidth = 500;
      if (myHeight <= 0) myHeight = 300;

      makeOffscreenImage ();
      drawOffscreenImage ();

      makeOnscreenImage ();
      repaint();
      }

   void drawOffscreenImage () {
   // This is called when the program starts, and when the main Frame size changes
      makeOffscreenImage ();

      xInc = myWidth/MAX_X;
      yInc = myHeight/MAX_Y;
      if (xInc < yInc) yInc = xInc;
      else             xInc = yInc;

      myOffscreenGraphics.setColor (Color.blue);
      myOffscreenGraphics.fillRect (0, 0, myWidth, myHeight);

      myOffscreenGraphics.setColor (Color.black);
      myOffscreenGraphics.drawLine (0         , yInc*MAX_Y, xInc*MAX_X, yInc*MAX_Y);
      myOffscreenGraphics.drawLine (xInc*MAX_X, 0         , xInc*MAX_X, yInc*MAX_Y);

      for (int j = 0 ; j < MAX_Y ; j++)
         for (int i = 0 ; i < MAX_X ; i++)
            drawWalls (i, j);
      }

   void makeOffscreenImage () {
      myOffscreenImage    = createImage (myWidth, myHeight);
      myOffscreenGraphics = myOffscreenImage.getGraphics();
      }

   void makeOnscreenImage () {
      myOnscreenImage    = createImage (myWidth, myHeight);
      myOnscreenGraphics = myOnscreenImage.getGraphics();
      }

   void drawWalls (int i, int j) {
      if ((rooms[i][j] & WEST) == 0)
         myOffscreenGraphics.drawLine (i*xInc, j*yInc, i*xInc, (j+1)*yInc);

      if ((rooms[i][j] & NORTH) == 0)
         myOffscreenGraphics.drawLine (i*xInc, j*yInc, (i+1)*xInc, j*yInc);
      }

//   public void destroy() { myOnscreenGraphics.dispose(); }

   void movePiece (maze02Piece pPiece) {
      maze02Piece myPiece = pPiece;

      int iDir;
      int iDirs;

      iDirs = rooms[myPiece.currX][myPiece.currY];
      if (iDirs != myPiece.backDir)  // if we have more than one exit
         iDirs -= myPiece.backDir;   //    let's avoid constant backing up.

      while (iDirs > 0) {
         iDir = (int) (randGen.nextDouble() * 4);
         switch (iDir) {
            case 0 : if ((iDirs & NORTH) != 0) {
                        myPiece.currY--;
                        iDirs = 0;
                        myPiece.backDir = SOUTH;
                        } break;

            case 1 : if ((iDirs & EAST) != 0) {
                        myPiece.currX++;
                        iDirs = 0;
                        myPiece.backDir = WEST;
                        } break;

            case 2 : if ((iDirs & WEST) != 0) {
                        myPiece.currX--;
                        iDirs = 0;
                        myPiece.backDir = EAST;
                        } break;

            case 3 : if ((iDirs & SOUTH) != 0) {
                        myPiece.currY++;
                        iDirs = 0;
                        myPiece.backDir = NORTH;
                     } break;
               }
         }
      myOnscreenGraphics.fillOval (myPiece.currX*xInc, myPiece.currY*yInc, xInc, yInc);
      }
   } // end of class maze02Canvas

class maze02Piece {
// NOTE: don't *need* this to be a class, but perhaps for future dev...?
   int myID;
   int currX;
   int currY;
   int backDir;
   Color color;
   }

public class maze02 extends java.applet.Applet {
   public static final long serialVersionUID = 553L;   // not currently needed
   public static maze02Frame myFrame;

   public void init () {
//    NOTE: This is the starting point when this program is run as an Applet
      myFrame = new maze02Frame ();
      myFrame.buildGUI ();
      myFrame.addWindowListener ();
      myFrame.addHierarchyBoundsListener ();
      myFrame.start ();  // same start as in the Application version
      }

   public static void main (String[] args) {
      myFrame = new maze02Frame ();
      myFrame.buildGUI ();
      myFrame.addWindowListener ();
      myFrame.addHierarchyBoundsListener ();
      myFrame.start ();  // same start as in the Applet version
      }
   }