import java.awt.BorderLayout;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.Random;
import java.util.Scanner;

import javax.imageio.ImageIO;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.JApplet;
import javax.swing.JPanel;
import javax.swing.Timer;

/* @author: Ethan Bennis
 * @created: Februrary 24, 2009
 * @description: This program is a simple quiz applet to be used on the Humanities ArtifactsorArtifice wikispaces page.
 * It will take the form of a memory-matching game with words and definitions that were used within the Archaeology page.
 * 
 * Applet is 800 pixels wide and 300 pixels high. Divided into 12 "cards", each 200x100 pixels.
 * 
 * Feel free to modify & redistribute so long as credit is given.
 */


public class MatchingGame extends JApplet{

	// Instance Variables
	private Card[] cards;
	private Card[][] cardArray2D;
	
	private int xCoord;
	private int yCoord;
	private int cardType;
	private int cardMatch;
	private int cardX;
	private int cardY;
	private int matchCount;
	
	private boolean waiting;
	private boolean cardSelected;
	
	private BufferedImage redCard = null; // Image for red card, default null
	private BufferedImage blueCard = null; // Image for blue card ,default null
	
	private java.net.URL audio1;
	private java.net.URL audio2;
	private java.net.URL audio3;
	                       
	// Constructor
	public void init() // Initializing (main) method
	{
		try {
		    redCard = ImageIO.read(getClass().getResource("redCard.png"));	// opens red card image
		    blueCard = ImageIO.read(getClass().getResource("blueCard.png"));	// opens blue card image
		} catch (IOException e) {}
		
		audio1 = Thread.currentThread().getContextClassLoader().getResource("right.wav");
		audio2 = Thread.currentThread().getContextClassLoader().getResource("wrong.wav");
		audio3 = Thread.currentThread().getContextClassLoader().getResource("win.wav");
		
		cards = new Card[12]; // Original array of cards (unshuffled)
		matchCount = 0;	// 0 matches have been made
		waiting = false;
		cardSelected = false;
		cardArray2D = new Card[4][3];
		
		readCardsFile();
		create2DCardArray();
		
		GamePanel panel = new GamePanel();
		add(panel, BorderLayout.CENTER);
		resize(800,300);
		setVisible(true);
	}
	                        
	// Methods
	public void readCardsFile() // Reads cards.txt to get info for each card
	{
		InputStream textFile = Thread.currentThread().getContextClassLoader().getResourceAsStream("cards.txt");
		Scanner reader = new Scanner(textFile);
		
		for(int index = 0; index < 12; index++)
			cards[index] = new Card(reader.nextInt(), reader.nextInt(), reader.nextLine(), true);
	}
	
	public void create2DCardArray() // Takes cards from ordered array and randomly puts them into a 2d array
	{
		int[] random = generateRandomArray();
		int counter = 0;
		
		for(int index = 0; index < 4; index++)
			for(int index2 = 0; index2 < 3; index2++)
			{
				cardArray2D[index][index2] = cards[random[counter]];
				counter++;
			}
	}
	
	public int[] generateRandomArray() // Generates a random array of numbers 0-11, without any repeats
	{
		Random gen = new Random();
		int temp, generated = 0;
		boolean tempAlreadyExists;
		int[] random = new int[12];
		
		while (generated < 12)
		{
			tempAlreadyExists = false;
			temp = gen.nextInt(12);
			for (int index2 = 0; index2 < generated; index2++)
				if (temp == random[index2])
					tempAlreadyExists = true;
			if (!tempAlreadyExists)
			{
				random[generated] = temp;
				generated++;
			}
		}
		return random;
	}
	
	class GamePanel extends JPanel implements MouseListener{ // Display Panel

		public GamePanel()
		{
			addMouse();
		}
		
		public void mouseClicked(MouseEvent e) 
		{
			xCoord = e.getX() / 200;
			yCoord = e.getY() / 100;
			
			if (!waiting)
			{
				if (!cardSelected && !cardArray2D[xCoord][yCoord].getSolved()) // if no card is selected
				{
					cardMatch = cardArray2D[xCoord][yCoord].getMatch();
					cardType = cardArray2D[xCoord][yCoord].getType();
					cardArray2D[xCoord][yCoord].setDisplay(false);
					cardSelected = true;
					cardX = xCoord;
					cardY = yCoord;
					repaint();
				}
				else // If a card is already selected
				{
					if(cardType != cardArray2D[xCoord][yCoord].getType() && !cardArray2D[xCoord][yCoord].getSolved())
					{ 
						if(cardMatch == cardArray2D[xCoord][yCoord].getMatch()) // If 2nd card matches 1st
						{
							cardArray2D[xCoord][yCoord].setDisplay(false);
							cardArray2D[xCoord][yCoord].setSolved(true);
							cardArray2D[cardX][cardY].setSolved(true);
							cardSelected = false;
							repaint();
							matchCount++;
							if (matchCount == 6)
								soundEffect(2); // play sound effect for game won!
							else
								soundEffect(0); // play sound effect for correct match
						}
						else // If 2nd card is incorrect
						{
							removeMouseListener(this); // Disable mouse (until end of timer)
							cardArray2D[xCoord][yCoord].setDisplay(false); // Uncovers selected card
							repaint();
							ActionListener taskPerformer = new ActionListener() {
								
								public void actionPerformed(ActionEvent arg0) 
								{
									cardArray2D[cardX][cardY].setDisplay(true); // replace card covers after timer
									cardArray2D[xCoord][yCoord].setDisplay(true);
									repaint();
									waiting = false; // no longer waiting
									cardSelected = false; // no card is selected anymore
									addMouse(); // re-enable mouse after timer
								}
							  };
							  Timer timer = new Timer(1300, taskPerformer); // Timer stuff
							  timer.setRepeats(false); // non-repeating timer
							  timer.start();
							  soundEffect(1); // play sound effect for incorrect match
						}
					}
				}
			}
		}
		
		public void addMouse() // Necessary, because addMouseListner() not callable in needed timer.
		{
			addMouseListener(this);
		}
		
		public void soundEffect(int sound)	// Plays one of three sounds based on parameter
		{
			Clip clickClip = null;
			
			try {
				clickClip = AudioSystem.getClip();
			} catch (LineUnavailableException e1) {}
			
			AudioInputStream ais = null;
			switch (sound)
			{	// Determines which sound to play based on parameters
			case 0:
				try {	// Parameter "1" = good match sound
					ais = AudioSystem.getAudioInputStream(audio1);
				} catch (UnsupportedAudioFileException e1) {
				} catch (IOException e1) {
				}
				break;
			case 1:
				try {	// Parameter "2" = bad match sound
					ais = AudioSystem.getAudioInputStream(audio2);
				} catch (UnsupportedAudioFileException e1) {
				} catch (IOException e1) {
				}
				break;
			case 2:
				try {	// Parameter "3" = win sound
					ais = AudioSystem.getAudioInputStream(audio3);
				} catch (UnsupportedAudioFileException e1) {
				} catch (IOException e1) {
				}
				break;
			}
		
			try {
				clickClip.open(ais);
			} catch (LineUnavailableException e1) {
			} catch (IOException e1) {
			}
			clickClip.start();
		}
		
		// Required methods for MouseListener implementation, unused.
		public void mouseEntered(MouseEvent e) {
		}

		public void mouseExited(MouseEvent e) {
		}

		public void mousePressed(MouseEvent e) {
		}

		public void mouseReleased(MouseEvent e) {
		}
		
		public void paintComponent(Graphics g) // draws everything to the applet whenever repainted
		{
			super.paintComponent(g);
			
			for(int index = 0; index < 4; index++)
				for(int index2 = 0; index2 < 3; index2++)
				{
					g.drawString(cardArray2D[index][index2].getText(), index*200, index2*100 + 20);
					if (cardArray2D[index][index2].getDisplay())
					{
						if(cardArray2D[index][index2].getType() == 0)
							g.drawImage(redCard, index*200, index2*100, null);
						else
							g.drawImage(blueCard, index*200, index2*100, null);
					}
				} 
		}
	}
	
	class Card { // Stores information for a card (see instance variables)
		
		// Instance Variables
		private int type; // 0 = red (word), 1 = blue (definition)
		private int match; // 0-5, card pairs
		private String text; // Message to dispay after reveal;
		private boolean display; // display card if not clicked
		private boolean solved; // dont replace card if solved
		
		
		// Constructors
		public Card()
		{
			type = 0;
			match = 0;
			text = "";
			display = true;
		}
		
		public Card(int setType, int setMatch, String setText, boolean setDisplay)
		{
			type = setType;
			match = setMatch;
			text = setText;
			display = setDisplay;
		}
		
		// Methods
		public Card(int setType)
		{
			type = setType;
		}
		
		// Methods (all getter & setter methods for the Card's instance variables)
		public void setType(int setType)
		{
			type = setType;
		}
		
		public int getType()
		{
			return type;
		}
		
		public void setMatch(int setMatch)
		{
			match = setMatch;
		}
		
		public int getMatch()
		{
			return match;
		}
		
		public void setText(String setText)
		{
			text = setText;
		}
		
		public String getText()
		{
			return text;
		}
		
		public void setDisplay(boolean setDisplay)
		{
			display = setDisplay;
		}
		
		public boolean getDisplay()
		{
			return display;
		}
		public void setSolved(boolean setSolved)
		{
			solved = setSolved;
		}
		
		public boolean getSolved()
		{
			return solved;
		}
	}
}
