//package puzzle;
// Puzzle.java	William Dubel	Puzzle Game		June 15, 2001

import java.io.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.util.ArrayList;
import java.util.Collections;
import javax.swing.JOptionPane;
import javax.swing.border.TitledBorder;
import java.applet.AudioClip;	// for sound
import java.applet.Applet;
import java.net.URL;
import java.net.MalformedURLException;

/** This is a fun puzzle game
  * @author William Dubel
  */
public class Puzzle extends JFrame
{
   private JMenuItem newGame, loadGame, help, quit, about, feel, saveGame, scramble;
   private JCheckBoxMenuItem showTimer, easy, medium, high;
   private JMenu fileMenu, actionsMenu, helpMenu, complexity;
   private JPanel hintArea, puzzleArea, puzzleGrid;
   private JMenuBar bar;
   private JFileChooser fileChooser = new JFileChooser();

   private Container c;

   private int tiles = 9;				//EASY = 3*3, MEDIUM = 3*4, HARD = 4*4
   private ArrayList pieces = new ArrayList();
   private Game currentGame;
   private URL currentDirURL;
   private AudioClip swap, win, lose;

   private boolean gameInProgress = false;
   private MouseHandler mouseHandler = new MouseHandler();
   private ItemHandler itemHandler = new ItemHandler();

   public Puzzle()
   {
	 super("Tile Scrambler");
	 c = getContentPane();

	 try
	 {
	   currentDirURL = (new File("sounds")).toURL();
	   swap = Applet.newAudioClip(new URL(currentDirURL,"chirp.au"));
	   lose = Applet.newAudioClip(new URL(currentDirURL,"loser.wav"));
	   win = Applet.newAudioClip(new URL(currentDirURL,"victorious.wav"));
	 }
	 catch(MalformedURLException e) { e.printStackTrace();}

	 setJMenuBar(bar = new JMenuBar());

	 (fileMenu = new JMenu("File")).setMnemonic('F');;			// This is the File Menu

	 (newGame = new JMenuItem("New Game...",'N')).addActionListener(itemHandler);
	 (loadGame = new JMenuItem("Load Game...",'L')).addActionListener(itemHandler);
	 (saveGame = new JMenuItem("Save Game...",'S')).addActionListener(itemHandler);
	 (quit = new JMenuItem("Exit",'x')).addActionListener(itemHandler);

	 saveGame.setEnabled(false);

	 fileMenu.add(newGame);
	 fileMenu.add(loadGame);
	 fileMenu.add(saveGame);
	 fileMenu.addSeparator();
	 fileMenu.add(quit);

	 fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);

	 (actionsMenu = new JMenu("Actions")).setMnemonic('A');	// This is the Actions Menu

	 (easy = new JCheckBoxMenuItem("Easy", true)).setMnemonic('E');
	 easy.addActionListener(itemHandler);

	 (medium = new JCheckBoxMenuItem("Medium")).setMnemonic('M');
	 medium.addActionListener(itemHandler);

	 (high = new JCheckBoxMenuItem("High")).setMnemonic('H');
	 high.addActionListener(itemHandler);

	 (scramble = new JMenuItem("Scramble",'S')).addActionListener(itemHandler);
	 actionsMenu.add(scramble);

	 (complexity = new JMenu("Complexity")).setMnemonic('C');
	 complexity.addActionListener(itemHandler);
	 complexity.add(easy);
	 complexity.add(medium);
	 complexity.add(high);
	 actionsMenu.add(complexity);

	 (helpMenu = new JMenu("Help")).setMnemonic('H');			 // This is the Console Menu

	 (about = new JMenuItem("About Tile Scrambler...",'A')).addActionListener(itemHandler);
	 (help = new JMenuItem("Help",'H')).addActionListener(itemHandler);
	 (feel = new JMenuItem("Look and Feel...",'L')).addActionListener(itemHandler);
	 (showTimer = new JCheckBoxMenuItem("Show Timer")).addActionListener(itemHandler);

	 helpMenu.add(about);
	 helpMenu.add(help);
	 helpMenu.add(feel);
	 helpMenu.add(showTimer);

	 bar.add(fileMenu);
	 bar.add(actionsMenu);
	 bar.add(helpMenu);

	 (puzzleGrid = new JPanel()).setLayout(new GridLayout(1, 1));
	 puzzleGrid.setSize(300,300);

	 (puzzleArea = new JPanel()).setLayout(new FlowLayout());
	 puzzleArea.add(puzzleGrid);
	 puzzleArea.setBorder(new TitledBorder("Image Puzzle"));

	 (hintArea = new JPanel()).setBorder(new TitledBorder("Preview"));
	 hintArea.add(new SidePanel());

	 c.add(puzzleArea,BorderLayout.CENTER);
	 c.add(hintArea,BorderLayout.EAST);
	 setSize(500,400);

	 addWindowListener( new WindowAdapter()	{
		 public void windowClosing( WindowEvent e )
		 {
			 int answer = -1;
			 if (gameInProgress == true) answer = askSave();
			 if (answer!=JOptionPane.CANCEL_OPTION)
			 {
				 if (answer==JOptionPane.OK_OPTION)	{		save();		}
				 System.exit(0);
								 }
						 }	}
						 );
					 }

	private class MouseHandler extends MouseAdapter
	{
		private int first = -1;
		private boolean won = false;

		public void mousePressed(MouseEvent e)
		{
			if (first<0 || first >= tiles)
			{
				for (int n=0; n<tiles; n++) if (e.getSource()==pieces.get(n))
					{
						((Piece)pieces.get(first = n)).setSelected(true);
						((Piece)pieces.get(first)).repaint();
					}
			}
			else
			{
				for (int n=0; n<tiles; n++)
					if (e.getSource()==pieces.get(n))
					{
						swap.play();
						((Piece)pieces.get(n)).repaint();
						((Piece)pieces.get(first)).setSelected(false);

						puzzleGrid.removeAll();

						//Collections.swap(pieces, first, n);   //jdk1.4
						Piece temp = (Piece)pieces.get(first);
						pieces.set(first, pieces.get(n));
						pieces.set(n, temp);

						for (int i=0; i<tiles; i++) puzzleGrid.add((Piece)pieces.get(i));

						puzzleGrid.validate();
					}
				first = -1;

				won = true;
				for (int i=0; i<tiles; i++) if (((Piece)pieces.get(i)).getOrder()!=i+1) won = false;
				if (won)
				{
					win.play();
					JOptionPane.showMessageDialog(c,"You completed the puzzle!");
				}
			}
		}
	}

	private class ItemHandler implements ActionListener
	{
		public void actionPerformed( ActionEvent e )
		{
			// Code to handle File events
			if (e.getSource()==newGame)
			{
				int answer = -1;
				if (gameInProgress) answer = askSave();
				if (answer!=JOptionPane.CANCEL_OPTION)
				{
					if (answer==JOptionPane.OK_OPTION)	{		save();		}

					int result = fileChooser.showOpenDialog(c);
					if (result!=JFileChooser.CANCEL_OPTION)
					{
						File filename = fileChooser.getSelectedFile();

						currentGame = new Game((filename), tiles);

						puzzleArea.remove(puzzleGrid);
						puzzleGrid = new JPanel();
						puzzleGrid.setLayout(new GridLayout(currentGame.getRows(), currentGame.getCols()));

						pieces = new ArrayList();
						for (int i=0; i<tiles; i++)
						{
							Piece temp = new Piece(currentGame, i+1);
							temp.addMouseListener(mouseHandler);
							pieces.add(temp);
							puzzleGrid.add(temp);
						}
						puzzleGrid.setSize(	(int)currentGame.getSize().getWidth(),
											(int)currentGame.getSize().getHeight()	);
						puzzleArea.add(puzzleGrid);

						hintArea.removeAll();
						hintArea.add(new SidePanel(currentGame));
						pack();

						puzzleGrid.validate();
						saveGame.setEnabled(true);
						gameInProgress = true;
					}
				}
			}
			if (e.getSource()==loadGame)
			{
				int answer = -1;
				if (gameInProgress) answer = askSave();
				if (answer!=JOptionPane.CANCEL_OPTION)
				{
					if (answer==JOptionPane.OK_OPTION)	{		save();		}

					int result = fileChooser.showOpenDialog(c);
					if (result!=JFileChooser.CANCEL_OPTION)
					{
						ObjectInputStream in=null;
						try
						{
							File filename = fileChooser.getSelectedFile();

							in = new ObjectInputStream(new FileInputStream(filename));

							currentGame = (Game)in.readObject();

							tiles = currentGame.getComplexity();
							puzzleArea.remove(puzzleGrid);
							puzzleGrid = new JPanel();
							puzzleGrid.setLayout(new GridLayout(currentGame.getRows(), currentGame.getCols()));

							pieces = new ArrayList();
							for (int i=0; i<tiles; i++)
							{
								Piece temp = new Piece(currentGame, currentGame.getOrder()[i]);
								temp.addMouseListener(mouseHandler);
								pieces.add(temp);
								puzzleGrid.add(temp);
							}
							puzzleGrid.setSize(	(int)currentGame.getSize().getWidth(),
												(int)currentGame.getSize().getHeight()	);
							puzzleArea.add(puzzleGrid);

							hintArea.removeAll();
							hintArea.add(new SidePanel(currentGame));
							pack();

							puzzleGrid.validate();
							saveGame.setEnabled(true);
							gameInProgress = true;

							in.close();
						}
						catch(FileNotFoundException fnfe)	{
						JOptionPane.showMessageDialog(c,"Exception: File not found.");	}
					catch(IOException ioe)
					{
						JOptionPane.showMessageDialog(c,"Exception: Input Problem.");
						try { if (in!=null) in.close(); }
					catch(IOException ioe2)
					{
						JOptionPane.showMessageDialog(c,"Exception: Can't close file.");
					}
						}
						catch(ClassNotFoundException se)	{
						JOptionPane.showMessageDialog(c,"Invalid or currupt data file.");	}
				}
			}
		}
		if (e.getSource()==saveGame)					{		save();				}
			if (e.getSource()==quit)
			{
				int answer = -1;
				if (gameInProgress) answer = askSave();
				if (answer!=JOptionPane.CANCEL_OPTION)
				{
					if (answer==JOptionPane.OK_OPTION)	{		save();				}
					System.exit(0);
				}
			}
			// Code to handle Actions menu item events
			if (e.getSource()==scramble)
			{
				puzzleGrid.removeAll();

				Collections.shuffle(pieces);
				for (int i=0; i<tiles; i++) puzzleGrid.add((Piece)pieces.get(i));

				puzzleGrid.validate();
			}
			if (e.getSource()==easy)
			{
				if (medium.isSelected() || high.isSelected())
				{
					medium.setSelected(false);			//need some more code here!
					high.setSelected(false);
					setComplexity(tiles = 9);
				}
				else easy.setSelected(true);
			}
			if (e.getSource()==medium)
			{
				if (easy.isSelected() || high.isSelected())
				{
					easy.setSelected(false);
					high.setSelected(false);
					setComplexity(tiles = 12);
				}
				else medium.setSelected(true);
			}
			if (e.getSource()==high)
			{
				if (easy.isSelected() || medium.isSelected())
				{
					medium.setSelected(false);
					easy.setSelected(false);
					setComplexity(tiles = 16);
				}
				else high.setSelected(true);
			}
			// Code to handle Help menu item events
			if (e.getSource()==about) {
				JOptionPane.showMessageDialog(c,
					"Tile Scrambler Puzzle Game\n\nAuthor: William Dubel\n" +
					"Created: June 14, 2001\n\nFIU COP3338-1 Faisal Kaleem","About Tile Scrambler",
					JOptionPane.PLAIN_MESSAGE);
			}
			if (e.getSource()==help)
			{
				JOptionPane.showMessageDialog(c,
											  "Tile Scrambler Help:\n\nYou are an idiot. Quit now before you lose.\n\n",
											  "Tile Scrambler Help", JOptionPane.PLAIN_MESSAGE);
			}
			if (e.getSource()==feel)  {
				UIManager.LookAndFeelInfo looks[] = UIManager.getInstalledLookAndFeels();
				String options[] = new String[looks.length];
				for (int i=0; i<looks.length; i++) options[i]=looks[i].getClassName();

				Object selection = JOptionPane.showInputDialog(c, "Choose a Look and Feel:",
					"Look and Feel", JOptionPane.INFORMATION_MESSAGE, null, options, options[0]);
				if (selection!=null)  {
					try	{
						UIManager.setLookAndFeel((String)selection);
						SwingUtilities.updateComponentTreeUI(c.getParent());
					}
					catch(Exception lnfe)	{
						JOptionPane.showMessageDialog(c,"Exception: " + lnfe.getMessage());
					}
				}
			}
			if (e.getSource()==showTimer)		{		/*	If I am so inclined...*/		}
		}

		private void setComplexity(int tiles)
		{
			if (gameInProgress)
			{
				currentGame.setComplexity(tiles);

				puzzleGrid.removeAll();
				puzzleGrid.setLayout(new GridLayout(currentGame.getRows(), currentGame.getCols()));

				pieces = new ArrayList();
				for (int i=0; i<tiles; i++)
				{
					pieces.add(new Piece(currentGame, i+1));
					((Piece)pieces.get(i)).addMouseListener(mouseHandler);
				}

				Collections.shuffle(pieces);
				for (int i=0; i<tiles; i++) puzzleGrid.add((Piece)pieces.get(i));

				puzzleGrid.validate();
				puzzleArea.add(puzzleGrid);
			}
		}
	}

	private int askSave()
	{
		return JOptionPane.showConfirmDialog(c, "The current game has not been saved, " +
											 "would you like to save it?", "Game In Progress",
											 JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
	}

	private void save()
	{
		int result = fileChooser.showSaveDialog(c);
		if (result!=JFileChooser.CANCEL_OPTION)
		{
			ObjectOutputStream out=null;
			try
			{
				File filename = fileChooser.getSelectedFile();
				out = new ObjectOutputStream(new FileOutputStream(filename));
				currentGame.setOrder(pieces);

				out.writeObject(currentGame);
				out.close();
			}
			catch(IOException ioe)
			{
				JOptionPane.showMessageDialog(c,"Exception: Read Problem.");
				try { if (out!=null) out.close(); }
			catch(IOException ioe2)
			{
				JOptionPane.showMessageDialog(c,"Exception: Can't close file.");
			}
		}
		catch(Exception se)	{
		JOptionPane.showMessageDialog(c,"General Exception.");	}
}
	}

	public static void main(String [] args)
	{
		JFrame puzzlegame = new Puzzle();		//Main creates a new instance of our Puzzle
		Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
		Dimension frameSize = puzzlegame.getSize();

		if (frameSize.height > screenSize.height) frameSize.height = screenSize.height;
		if (frameSize.width > screenSize.width) frameSize.width = screenSize.width;

		puzzlegame.setLocation((screenSize.width - frameSize.width) / 2, (screenSize.height - frameSize.height) / 2);
		puzzlegame.setVisible(true);
	}
}
/** This class contains the actual image and information
 * @author William Dubel
 */
class Game implements Serializable
{
	private ImageIcon imageObject;
	private int complexity;
	private int order[] = null;

	public Game(File filename, int complexity)
	{
		this.complexity = complexity;
		imageObject = new ImageIcon(filename.getPath());
		try	{ while (imageObject.getImageLoadStatus()==MediaTracker.LOADING) Thread.sleep(10); }
	catch(InterruptedException ie)	{	ie.printStackTrace();				}
}

public Image getImage()					{	return imageObject.getImage();	}
public int getComplexity()				{	return complexity;					}
public String toString()				{	return "Image: " + getSize();		}
public int[] getOrder()					{	return order;							}
public Dimension getSize()				{
	return new Dimension(imageObject.getIconWidth(),imageObject.getIconHeight());
}

public void setComplexity(int complexity)
{
	if (complexity>0 && complexity<=100) this.complexity = complexity;
}

public void setOrder(ArrayList pieces)
{
	order = new int[pieces.size()];
	for (int i=0; i<pieces.size(); i++)	order[i]=((Piece)pieces.get(i)).getOrder();
}

public int getRows()	{
	return (getComplexity()==12) ? 4 : (int)Math.sqrt(getComplexity());
}

public int getCols()	{
	return (getComplexity()==12) ? 3 : (int)Math.sqrt(getComplexity());
}
}

class SidePanel extends JPanel
{
private Dimension size;
private Image mini;
private boolean loaded = false;

public SidePanel(Game currentGame)
{
	size = new Dimension((int)(currentGame.getSize().getWidth()/2),
						 (int)(currentGame.getSize().getHeight()/2));

		mini = currentGame.getImage();
		loaded = true;
	}

	public SidePanel()
	{
		size = new Dimension(150,150);
		loaded = false;
	}

	public Dimension getPreferredSize()		{	return size;						}

	public void paintComponent(Graphics g)
	{
		Graphics2D g2 = (Graphics2D)g;

		if (loaded)
			g2.drawImage(mini, 0, 0, (int)size.getWidth(), (int)size.getHeight(), this);

		else g2.draw3DRect(0,0, (int)size.getWidth()-1, (int)size.getHeight()-1, false);
	}
}

class Piece extends JPanel implements Runnable
{
	private Dimension size;
	private Image mini;
	private int order;
	private MediaTracker loader;
	private boolean selected = false;

	public Piece(Game currentGame, int tile)
	{
		size = new Dimension((int)(currentGame.getSize().getWidth()/currentGame.getCols()),
							 (int)(currentGame.getSize().getHeight()/currentGame.getRows()));

		mini = currentGame.getImage();

		CropImageFilter cropFilter =
			new CropImageFilter(	(((tile%currentGame.getCols()!=0) ? tile%currentGame.getCols()
								   : currentGame.getCols())-1)*(int)size.getWidth(),

								 (int)((Math.ceil((double)tile/currentGame.getCols())-1)*size.getHeight()),
								 (int)size.getWidth(),
								 (int)size.getHeight());

		mini = createImage(new FilteredImageSource(mini.getSource(),cropFilter));
		order = tile;

		loader = new MediaTracker(this);
		loader.addImage(mini, order);
		new Thread(this).start();
	}

	public void run()
	{
		try
		{	while (loader.statusID(order, true)!=MediaTracker.COMPLETE) Thread.sleep(20);	}
	catch(InterruptedException ie)		{	ie.printStackTrace();		}
	repaint();
}

public Dimension getPreferredSize()		{		return size;				}
public int getOrder()					{		return order;				}
public boolean isSelected()				{		return selected;			}
public void setSelected(boolean yes)	{		selected = yes;			}

public void paintComponent(Graphics g)
{
	Graphics2D g2 = (Graphics2D)g;

		if (loader.statusID(order, true)!=MediaTracker.COMPLETE)
		{
			GradientPaint gp = new GradientPaint(0.0f, 0.0f, Color.lightGray,
												 (float)size.getWidth(), (float)size.getHeight(), Color.darkGray);
			g2.setPaint(gp);
			g2.fillRect(0, 0, (int)size.getWidth()-1, (int)size.getHeight()-1);
		}
		else
		{
			g2.drawImage(mini, 0, 0, (int)size.getWidth(), (int)size.getHeight(), this);
			if (selected)
			{
				g2.draw3DRect(1, 1, (int)size.getWidth()-3, (int)size.getHeight()-3, false);
				g2.draw3DRect(0, 0, (int)size.getWidth()-1, (int)size.getHeight()-1, false);
			}
		}
	}
}
