import java.applet.Applet;
import java.applet.AudioClip;
import java.lang.Object;
import java.awt.*;
import java.awt.image.*;
import java.net.*;

// Slider.java 



public class Slider extends Applet implements Runnable {
    Tile grid[][];
    public static AudioClip clinkSound,introSound,winSound,badSound;
    public boolean intro = true, random = false;
    Dimension GridSize;
    Point blank;
    Thread kicker = null;
    Dimension appletSize;
    int appletHeight;
    int appletWidth;
    Image imgBlock;          // the image block
    Image images[][];     // the constituent images
    int num_images = 16;  // number of images in block
    int width;            // width of each image in block
    int height;           // height of each image in block
    int temp;
    boolean first = true;
    MediaTracker track;
    
    public void init() {
	int x,y,block=1,maxBlocks=16;
	
        appletSize = this.getSize();
	appletHeight = appletSize.height;
	appletWidth = appletSize.width;

	track = new MediaTracker(this);

	// Set height and width	
	GridSize = new Dimension(4,4);
	grid = new Tile[GridSize.height][GridSize.width];
	
	// Layout the applet
	
	setLayout(new BorderLayout());
	resize(appletHeight,appletWidth);
	Panel p = new Panel();
	add("South",p);
	p.add(new Button("Randomize"));
	// Load sounds...
	try {
	    introSound = getAudioClip(new 
		URL(getDocumentBase(),"sound/spacemusics.au"));
	    introSound.loop();
	    clinkSound = getAudioClip(new URL(getDocumentBase(),"sound/clink.au"));
	    winSound = getAudioClip(new 
		URL(getDocumentBase(),"sound/laughter.au"));
	    badSound = getAudioClip(new URL(getDocumentBase(),"sound/crash.au"));
	} catch ( java.net.MalformedURLException e) {}


	// Load the blocks...
	cut("graphics/clock.jpg");
      	for (y=0;y<GridSize.height;y++) 
	    for (x=0;x<GridSize.width;x++) {
		if ( block < maxBlocks ) {
		    grid[y][x] = new Tile(images[x][y],block);
		    block++;
		}
		else {
		    grid[y][x] = new Tile(null,0);
		    blank = new Point(x,y);
		}
	    }
	update(this.getGraphics());
	//	repaint();
	
    }
    
    public boolean action(Event evt, Object arg) {
	
	if ( arg.equals("Randomize") ) {
	    random = true;
	    repaint();
	}
	
	return true;
    }
    
    private void randomize(Graphics g){
	
	int i,iter=GridSize.height*GridSize.width*GridSize.width;
	Point from;
	
	for (i=0; i<iter;i++) {
	    if ( Math.random() > 0.5 )
		move(new Point(blank.x,(int)(Math.random() * GridSize.height)));
	    else
		move(new Point((int)(Math.random() * GridSize.width),blank.y));
	    
	    drawGrid(g);
	}
    }
    
    public void run() {
	Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
	while (kicker != null) {
	    repaint();
	    try {
		Thread.sleep(100);
	    } catch (InterruptedException e){}
	}
    }
    
    private void drawAll() {
	int x,y;
	
	for (y=0;y<GridSize.height;y++)
	    for (x=0;x<GridSize.width;x++)
		grid[y][x].changed = true;

	repaint();
    }
    
    public void paint(Graphics g) {
	
	drawAll();

    }
    
    public void start() {
	if ( kicker == null ) {
	    kicker = new Thread(this);
	    kicker.start();
	}
    }
    
    public void stop() {
	if ( intro )
	    introSound.stop();
	kicker = null;
    }
    
    public void update(Graphics g) {
	
	if ( random )
	    randomize(g);
	else{
	    drawGrid(g);
	}
	random = false;
    }
    
    private void drawGrid(Graphics g) {
	int x,y;
	
	// Only draw blocks that have changed to improve display speed.
	
	for (y=0;y<GridSize.height;y++) 
	    for (x=0;x<GridSize.width;x++)  {
		if ( grid[y][x].changed ) {
		    if ( grid[y][x].number != 0 ) 
			g.drawImage(grid[y][x].img,x*width,y*height,this);
		    else {
			g.clearRect(x*width,y*height,width,height);
		    }
		    grid[y][x].changed = false;
		}
	    }
    }
    
    private boolean solved() {
	
	// Return true if the puzzle is solved, otherwise false
	
	int  y,x,block=1,iter = GridSize.height * GridSize.width;
	for (y=0;y<GridSize.height;y++)
	    for (x=0;x<GridSize.width;x++)
		if ( block < iter )
		    if ( grid[y][x].number != block++ )
			return false;
	return true;
    }
    
    private int delta(int a,int b) {
	
	// Return the direction to move the block in
	
	if ( a == b ) return 0;
	else return((b-a)/Math.abs(b-a));
    }
    
    public boolean mouseDown(Event evt, int x, int y) {
	
	// Mouse clicked somewhere
	
	Point from;	
	
	// Translate to block coordinates
	
	from = new Point(x/width,y/height);
	
	// Kill the intro sound if still playing...
	
	if ( intro )
	    introSound.stop();
	intro = false;
	
	if ( from.x >= GridSize.width || from.y >= GridSize.height )
	    return true;
	
	// If on the same row or column as the blank we can move
	
	if ( move(from) ) {
	    clinkSound.play();
	    
	    // Wait for about 250ms so that the sound and the
	    // animation appear to be in sync.
	    
	    try {
		Thread.sleep(250);
	    } catch (InterruptedException e) {}
	    repaint();
	    if ( solved() ) 
		winSound.play();
	}
	else 
	    badSound.play();
	
	return true;
    }
    
    private boolean move(Point from) {
	Point to,by;
	
	by = new Point(delta(from.x,blank.x),delta(from.y,blank.y));
	
	if ( (by.x == 0 && by.y == 0) || (by.x != 0 && by.y != 0) )
	    return false;
	
	to = new Point(from.x + by.x, from.y + by.y);
	
	// Try and move, if blocked by another block, move it first...
	
	if ( grid[to.y][to.x].number != 0 ) 
	    move(to);
	
	// Move the block and repaint
	
	grid[to.y][to.x].number = grid[from.y][from.x].number;	// to here...
	grid[to.y][to.x].img = grid[from.y][from.x].img;
	grid[to.y][to.x].changed = true;
	
	grid[from.y][from.x].number = 0;			// from here...
	grid[from.y][from.x].img = null;
	grid[from.y][from.x].changed = true;
	
	blank = from;				// blank is now here...
	return true;
    }
    
    public void cut(String inStr) {
	Image imgBlock = getImage(getCodeBase(), inStr);
	System.out.println(getCodeBase() + "\n" + inStr);
	// wait for image to load
	//   (here's how to do it without using MediaTracker)
	//while (imgBlock.getWidth(this) <0);

	track.addImage(imgBlock,0);
	System.out.println("Loading Images");
	//wait for image to finish loading //
	try{
	    track.waitForAll();
	} catch (InterruptedException e){}
	
	// check for errors//
	if(track.isErrorID(0)){
	    System.out.println("error");
	}
	else if(track.checkID(0)){
	    System.out.println("successfully loaded");
	}
		
	//define width of each image
	width = imgBlock.getWidth(this);
	temp = width % 4;
	width = width - temp;
	width = width/4;

	// define height of each image
	height = imgBlock.getHeight(this);
	temp = height % 4;
	height = height - temp;	
	height = height/4;
	
	// define array of constituent images
	images = new Image[4][4];

	// extract constituent images
	extractImages(imgBlock,images,num_images,width,height);
    }
    
    /////////////////////////////////////////////////////////////////
    // Extract the constituent images from a imgBlock.
    // There are num_images to extract, each with the
    // specified width and height.
    
    public void extractImages(Image imgBlock,
			      Image images[][],
			      int num_images,
			      int width,
			      int height) {
	ImageProducer source = imgBlock.getSource();
	
	for (int i = 0; i<4; i++) {
	    for (int j = 0; j<4; j++) { 
		// define filter to pull image at (i*width,j*height) with
		//   dimensions (width,height)
		ImageFilter extractFilter = new CropImageFilter(i*width,
								j*height,
								width,
								height);
		
		// define producer from source and filter
		ImageProducer producer =
		    new FilteredImageSource(source,extractFilter);
		
		// extract the subimage!
		images[i][j] = createImage(producer);
	    }
	}
    }
}

class Tile {
    Image img=null;
    int number=0;
    boolean changed=true;
    
    public Tile(Image theImage,int i) {
	
	number = i;
	img =  theImage;
    }
}
