//Puzzle 2.0 by Axel Fontaine 1998
//axel-fontaine@usa.net
//http://freezone.exmachina.net/Axel

//This is my first java applet!
//Feel free to use it as you wish!
//If you use it, or if you have any suggestions, I'd love to hear from you !

//Development history
//1.0 Initial release
//1.1 - Added support for an URL to jump to when the puzzle is completed
//        Thanks to Joe for the suggestion
//    - Added support for loading images located at any URL
//2.0 - Added support for multiple images (animation) and a delay separating them
//        Thanks to my dad for the suggestion
//    - Puzzle doesn't generate any impossible puzzles anymore

//Known issues
//  Applet gets stuck at Loading images when an Image isn't found.

//It is distributed under the GNU GPL (www.gnu.org to find a copy).
//It is provided "as is" with no warranty of any kind.
//I may not be held liable for any damage this software caused.

import java.applet.*;
import java.awt.*;
import java.awt.image.*;
import java.lang.Math;
import java.lang.Integer;
import java.lang.NumberFormatException;
import java.util.Random;
import java.net.URL;
import java.net.MalformedURLException;

public class Puzzle extends Applet implements Runnable {

  int AppletW, AppletH;

  Thread t;

  Image[] Bitmaps;

  URL[] bitmapURL;

  int CurrentImage;
  int NumImages = 0;

  int imageDelay = 200;  //Delay between 2 consecutive images

  boolean Initialized = false;

  Image offScrImage;
  Graphics offScrGC;

  Image[][][] Pieces;
  int[] PieceW, PieceH;
  Point[][] RightPos;
  Point pblack;

  boolean error = false;

  boolean completed = false;

  MediaTracker tracker;

  URL JumpTo;

  static final int dirLeft  = 0;
  static final int dirRight = 1;
  static final int dirUp    = 2;
  static final int dirDown  = 3;




  public String getAppletInfo() {
    return "Puzzle v2.0, by Axel Fontaine";
  }




  void urlError(String urlType) {
    String errorMsg = "Puzzle: "+urlType+" URL not found";
    showStatus(errorMsg);
    System.err.println(errorMsg);
  }
    



  public void init() {
    Dimension d = size();
    AppletW = d.width;
    AppletH = d.height;

    bitmapURL = new URL[255];

    String bitmap = null;

    do {
      bitmap = getParameter("IMAGE"+NumImages);
      if (bitmap != null) {
        try {
          bitmapURL[NumImages] = new URL(bitmap);
        } catch (MalformedURLException e) {
          try {
            bitmapURL[NumImages] = new URL(getDocumentBase(),bitmap);
          } catch (MalformedURLException e2) {
            urlError("Image"+NumImages);
            bitmap = null;
            NumImages--;
          }
        }
        NumImages++;
      } 
    } while (bitmap != null);

    System.err.println("Puzzle: Total images:"+NumImages);

    if (NumImages == 0) {
      error = true;
      repaint();
      return;
    }

    String jump = getParameter("JUMPTO");

    try {
      JumpTo = new URL(jump);

    } catch (MalformedURLException e) {
      try {
        JumpTo = new URL(getDocumentBase(),jump);
      } catch (MalformedURLException e2) {
        urlError("Jump To");
      }
    }

    String delayparam = getParameter("DELAY");

    try {
      Integer dp = new Integer(delayparam);
      if (dp.intValue() > 0) {
        imageDelay = dp.intValue();
      }
    } catch (NumberFormatException n) {}

    offScrImage = createImage(AppletW, AppletH);
    offScrGC = offScrImage.getGraphics();
    offScrGC.setColor(Color.black);
  }




  public void start() {
    if ((t == null) && (!error)) {
      t = new Thread(this);
      t.start();
    }
  }




  public void stop() {
    if ((t != null) && (!error)) {
      t.stop();
      t = null;
    }
  }    




  public void updateOffScreen() {
    int posx = 0;
    int posy = 0;

    for (int i = 0; i<4; i++) {
      for (int j = 0; j<4; j++) {
        if ((i == pblack.x) && (j == pblack.y)) {
//          offScrGC.setColor(Color.black);   //already set in init
          offScrGC.fillRect(posx, posy, AppletW/4, AppletH/4);
        } else {
          offScrGC.drawImage(Pieces[CurrentImage][RightPos[i][j].x][RightPos[i][j].y], posx, posy, AppletW/4, AppletH/4, Color.white, this);
        }

        posy += AppletH/4;
      }

      posx += AppletW/4;
      posy = 0;
      }
  }




  public void run() {
    if (!Initialized) {
      repaint();
      Bitmaps = new Image[NumImages];

      MediaTracker tracker = new MediaTracker(this);

      showStatus("Puzzle: Loading images...");
      for (int i=0; i<NumImages; i++) {
        System.err.println("Puzzle: Loading "+bitmapURL[i]);
        Bitmaps[i] = getImage(bitmapURL[i]);
        tracker.addImage(Bitmaps[i], i);
      }

      boolean AllLoaded = false;
      try {
        tracker.waitForAll();
        AllLoaded = !tracker.isErrorAny();
      } catch (InterruptedException e) {}
			
      if (!AllLoaded) {
        stop();
        showStatus("Puzzle: Error loading images !");
        error = true;
        repaint();
        return;
      }

      showStatus("Puzzle: Creating pieces... Please wait.");     

      PieceW = new int[NumImages];
      PieceH = new int[NumImages];

      for (int i=0; i<NumImages; i++) {
        PieceW[i] = Bitmaps[i].getWidth(this)/4;
        PieceH[i] = Bitmaps[i].getHeight(this)/4;
      }

      Pieces = new Image[NumImages][4][4];
      RightPos = new Point[4][4];

      for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 4; j++) {
          showStatus("Puzzle: creating piece "+((i*4)+j+1)+"/"+(4*4));

          RightPos[i][j] = new Point(i,j);

          for (int k = 0; k < NumImages; k++) {
            ImageFilter filter = new CropImageFilter(i * PieceW[k], j * PieceH[k], PieceW[k], PieceH[k]);
            Pieces[k][i][j] = createImage(new FilteredImageSource(Bitmaps[k].getSource(), filter));
            tracker.addImage(Pieces[k][i][j], 0);
          } 
        }
      }      

      pblack = new Point((4-1),(4-1));

      boolean AllPiecedUp = false;

      try {
        tracker.waitForAll();
        AllPiecedUp = !tracker.isErrorAny();
      } catch (InterruptedException e) {}

      if (!AllPiecedUp) {
        stop();
        error = true;
        repaint();
        return;
      }

      showStatus("Puzzle: suffling pieces...");

      int dir;
      int illegaldir = dirDown; //Impossible to start with

      Random r = new Random();

      boolean legal; //Is the swap legal

      for (int i = 0; i < 255; i++) {   //255 swaps
        Point p = new Point(RightPos[pblack.x][pblack.y]);
        legal = false;
        do {
          do {
            dir = Math.round(r.nextFloat()*(4-1));
          } while (dir == illegaldir);
          switch (dir) {
            case dirLeft: 
              if (pblack.x > 0) {
                RightPos[pblack.x][pblack.y] = RightPos[pblack.x-1][pblack.y];              
                RightPos[pblack.x-1][pblack.y] = p;
                pblack.x--;
                illegaldir = dirRight;
                legal = true;
              }
              break;

            case dirRight:
              if (pblack.x < (4-1)) {
                RightPos[pblack.x][pblack.y] = RightPos[pblack.x+1][pblack.y];
                RightPos[pblack.x+1][pblack.y] = p;
                pblack.x++;
                illegaldir = dirLeft;
                legal = true;
              }
              break;

            case dirUp:
              if (pblack.y > 0) {
                RightPos[pblack.x][pblack.y] = RightPos[pblack.x][pblack.y-1];              
                RightPos[pblack.x][pblack.y-1] = p;
                pblack.y--;
                illegaldir = dirDown;
                legal = true;
              }
              break;

            case dirDown:
              if (pblack.y < (4-1)) {
                RightPos[pblack.x][pblack.y] = RightPos[pblack.x][pblack.y+1];              
                RightPos[pblack.x][pblack.y+1] = p;
                pblack.y++;
                illegaldir = dirUp;
                legal = true;
              }
              break;
          }
        } while (!legal);
      }

      showStatus("");
      Initialized = true;
    }

    repaint();

    while (true) {
      try {
        CurrentImage++;
        if (CurrentImage == NumImages) {
          CurrentImage = 0;
        }
        updateOffScreen();
        repaint();
        Thread.sleep(imageDelay);
      }	catch (InterruptedException e) {
        stop();
      }
    }
  }




  public Point posToPuzzle(Point pos) {
    return new Point(pos.x/(AppletW/4),pos.y/(AppletH/4));
  }




  public void didSucceed() {
    completed = true;
    for (int i = 0; i<4; i++) {
      for (int j = 0; j<4; j++) {
        if ((RightPos[i][j].x != i) || (RightPos[i][j].y != j)) {
          completed = false;
          return;
        }
      }
    }
  }




  public boolean mouseDown(Event event, int x, int y) {
    if (completed || error || (!Initialized)) {
      return false;
    }
    Point ppos;
    ppos = new Point(x,y);
    ppos = posToPuzzle(ppos);
    if (((pblack.y == ppos.y) && ((pblack.x == (ppos.x+1)) || (pblack.x == (ppos.x-1)))) ||
        ((pblack.x == ppos.x) && ((pblack.y == (ppos.y+1)) || (pblack.y == (ppos.y-1))))) {
      Point tmp = RightPos[ppos.x][ppos.y];
      RightPos[ppos.x][ppos.y] = RightPos[pblack.x][pblack.y];
      RightPos[pblack.x][pblack.y] = tmp;
      pblack = ppos;
      didSucceed();
      repaint();
      if (completed && (JumpTo != null)) {
        getAppletContext().showDocument(JumpTo);
      }
    }
    return false;
  }




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



  public void paint(Graphics g) {
    if (error) {
      g.setColor(Color.black);
      g.drawString("Error !",10,10);
      g.drawString("Details on Java Console",10,40);
      return;
    }

    if (completed) {
      // Scale the big image to the width and height of the applet
      g.drawImage(Bitmaps[CurrentImage], 0, 0, AppletW, AppletH, Color.black, this);
      return;
    }

    if (Initialized) {
      g.drawImage(offScrImage, 0, 0, this);
    } else {
      g.setColor(Color.black);
      g.drawString("Loading...",10,25);
    }  
  }
}
