import javax.swing.*;
import java.awt.*;
import java.util.Arrays;
import java.util.Collections;
import java.awt.event.*;

/* This program creates a sliding puzzle game 
 * 
 * Adam Tran
 * Ethan Bennis
 * Graham Plata
 *
 * 10/31/08
 * 
 */

/* JApplet containing one panel, the game frame
 * 
 */

public class Puzzle extends JApplet{
	
	public void init(){
		
		//New array of Image objects, in the init() so that getDocumentBase() is accessible
		Image[] images = new Image[16];

		for(int file = 0; file < 16; file = file + 1){ //For the 16 pieces, 0-15
			 images[file] = getImage(getDocumentBase(), "MapOfTheInternet_" + (file + 1) + ".jpg"); //Get the file name and instantiate Images
		}
		
		Frame frame = new Frame(images); //New frame object, pass in array of Images 
		add(frame); //Print frame to JApplet
		resize(740,699); //Set applet size
		
	}
	
}

/* This class creates a frame containing 16 Tiles and holds width and height for one tile
 * 
 */

class Frame extends JPanel{
	
	private Tile[] tiles; //Array of Tile objects
	private int width;
	private int height;
	
	public Frame(Image[] images){
		
		tiles = new Tile[16];
		
		ImageIcon[] imageIcons; //Array of ImageIcons
		imageIcons = new ImageIcon[16];
		
		/* This loop takes an array of Images from the constructor and creates an array of ImageIcons
		 * 
		 */
		
		for(int file = 0; file < 16; file = file + 1){
			imageIcons[file] = new ImageIcon(images[file]);
		}

		/* This loop creates an array of Tile objects using the array of ImageIcons
		 * 
		 */
		
		for(int tile = 0; tile < 16; tile = tile + 1){
			if (tile == 15)
				tiles[tile] = new Tile(imageIcons[tile],tile, true);
			else
				tiles[tile] = new Tile(imageIcons[tile],tile);
		}
		
		//Same width and heght from the first tile
		width = tiles[0].getImage().getIconWidth(); 
		height = tiles[0].getImage().getIconHeight();
		
		shuffle(); //Shuffle
		
		addMouseListener(new PanelListener()); //Mouse events
	}
	

	public void paintComponent(Graphics g){
		super.paintComponent(g);
		
		/* This loop plots painting position for each tile, like a grid, and paints the tile 
		 * 
		 */
		
		for(int tile = 0; tile < 16; tile = tile + 1){
			tiles[tile].setX(tile % 4 * width); //The X coordinate, much like a grid
			tiles[tile].setY(tile / 4 * height); //Y coordinate, uses div to get row number
			tiles[tile].setLocation(tile); //Set the location of the tile in the array to an instance variable in the Tile object
			tiles[tile].getImage().paintIcon(this, g, tiles[tile].getX(), tiles[tile].getY()); //Paint away
		}
			
	}
	
	/* This method takes a click as an x, y coordinate on the JPanel and converts it to a grid position
	 * 
	 *  1  2  3  4
	 *  5  6  7  8
	 *  9 10 11 12
	 * 13 14 15 16
	 * 
	 */
	
	private int coordToPos(int x, int y){
		int position = 0;
		
		//Split into four columns and test where the click falls 
		if (x < width)
			position = position + 0;
		else if (x < 2 * width)
			position = position + 1;
		else if (x < 3 * width)
			position = position + 2;
		else if (x < 4 * width)
			position = position + 3;
		
		//Split into four rows and test where the click falls
		if (y < height)
			position = position + 0;
		else if (y < 2 * height)
			position = position + 4;
		else if (y < 3 * height)
			position = position + 8;
		else if (y < 4 * height)
			position = position + 12;
		
		return position;
	}
	
	/* This method uses the integer value of the location of a Tile object in the array to swap Tile reference variables
	 * 
	 */
	
	private void swapTiles(int first, int second){
		
		Tile temp; //Temporary tile
		
		temp = tiles[first];
		tiles[first] = tiles[second];
		tiles[second] = temp;
		
	}
	
	/* This method is executed by the Mouse Adapter. It builds a cross around the click,
	 * tests to see if there is a blank space, and if so, swaps positions with it using the swapTiles method.
	 * 
	 *   x        x
	 * x C x -> x O x 
	 *   O        C
	 *   
	 *   C = Click
	 *   O = Open tile
	 *   
	 *        +4
	 *         ^
	 *         |
	 *   -1 <-- --> +1
	 *         |
	 *         v
	 *        -4
	 *        
	 *  This method uses short-circuit evaluation to null-pointer exception
	 *  
	 */
	
	public void clickAt(int x, int y){
		int thisTile = coordToPos(x, y); //Convert coordinates of click to a grid coordinate
		if (thisTile + 1 < 16 && tiles[thisTile + 1].getBlank()) //Check to the right
			swapTiles(thisTile, thisTile + 1);
		else if (thisTile - 1 >= 0 && tiles[thisTile - 1].getBlank()) //Check to the left
			swapTiles(thisTile, thisTile - 1);
		else if (thisTile + 4 < 16 && tiles[thisTile + 4].getBlank()) //Check above
			swapTiles(thisTile, thisTile + 4);
		else if (thisTile - 4 >= 0 && tiles[thisTile - 4].getBlank()) //Check below
			swapTiles(thisTile, thisTile - 4);
	}
	
	private void shuffle(){
		Collections.shuffle(Arrays.asList(tiles)); //A Collections method to shuffle tiles
	}
	
	private class PanelListener extends MouseAdapter{
		
		public void mousePressed(MouseEvent e){
			clickAt(e.getX(), e.getY()); //Call clickAt method with coordinates of click
			repaint(); //Call paintComponent
		}
	}
}

class Tile {
	
	private ImageIcon imageIcon; //Image icon associated with tile
	private int location; //Tile's location in the array, also its grid location
	private int x; //Up left location of image
	private int y; //Up right location of image
	private boolean isBlank; //Whether or not the tile is the blank tile

	
	public Tile(){
		this(null, 0, false);
	}
	
	//Constructor for non-blank
	public Tile(ImageIcon imageIcon, int location){
		this(imageIcon, location, false);
	}
	
	//Use for the blank
	public Tile(ImageIcon imageIcon, int location, boolean blank){
		this.imageIcon = imageIcon;
		this.location = location;
		isBlank = blank;
	}

	//Returns ImageIcon object
	public ImageIcon getImage(){
		return imageIcon;
	}

	//Returns location in the array
	public int getLocation(){
		return location;
	}

	//Set the location in the array
	public void setLocation(int location){
		this.location = location;
	}
	
	//Return true if the tile is blank
	public boolean getBlank(){
		return isBlank;
	}

	//Set blank to true or false
	public void getBlank(boolean blank){
		isBlank = blank;
	}
	
	/* Capital coordinates denote input from arguments
	 * 
	 */
	
	//Get X and Y for up left of tile
	
	public int getX(){
		return x;
	}
	
	public int getY(){
		return y;
	}
	
	//Set X and Y for up left of tile
	
	public void setX(int X){
		x = X;
	}
	
	public void setY(int Y){
		y = Y;
	}

}

