/**
 * @(#)Game.java	11/03/05
 * 
 * Copyright 2005 3GP01, KCL.  All unmodified code is copyright of its respective owner.
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * The GNU General Public Licence should be contained within licence.txt;
 * if not, write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA  02110-1301, USA.
 */

import java.awt.Frame;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.ResourceBundle;

import javax.swing.ImageIcon;
import javax.swing.JOptionPane;

import sun.audio.AudioPlayer;
import sun.audio.AudioStream;

/**
 * This class contains methods for game operation.
 * Game contains methods to end a turn by validating a move, performing turn's end operations (turn increases, player skips etc),
 * and ending the game.
 * 
 * @author 3GP01 - Hardev Bhamra, Deepak Chandarana, James Tompkin
 * @version 2.0
 * @see Player
 * @see Bag
 * @see Board
 */

public class Game {
	private List players = new ArrayList();

	private int turn;

	private int lastturn = 0; //keeps track of last player turn for rack updating at end of game

	private int moveNumber = 1;

	private int lockOutCounter = 0;

	private int lastMoveScore = 0;

	private boolean gameEnded = false;

	protected Bag bag;

	protected Board board;

	ResourceBundle messages = getScrabble().getI18n()
			.getCurrentResourceBundle(); //localisation message bundle

	//constructor for local game
	public Game(ScrabbleBuilder builder) {
		board = new Board(builder);
		bag = new Bag(builder);
		turn = 0;
	}

	//constructor for remote game
	public Game(ScrabbleBuilder builder, int t) {
		board = new Board(builder);
		bag = new Bag(builder);
		turn = t;
	}

	public Scrabble getScrabble() {
		return Scrabble.scrabble;
	}

	//randomise bag based on a seed, generated from the system time in milliseconds
	//seed is sent to all clients
	public void randomiseBag(long seed) {
		Random r = new Random(seed);
		//perform operations on the bag 
		List tempbag = bag.getLetters();
		//shuffle the bag
		Collections.shuffle(tempbag, r);
		bag.setLetters(tempbag);
	}

	public void setRandomTurn() {
		Random r = new Random();
		turn = r.nextInt(players.size());
	}

	public boolean getGameEnded() {
		return gameEnded;
	}

	public int getLastMoveScore() {
		return lastMoveScore;
	}

	public int getLastTurn() {
		return lastturn;
	}

	public int getTurn() {
		return turn;
	}

	public int getMoveNumber() {
		return moveNumber;
	}

	public void addPlayer(String nme, String corh) {
		Player pl = null;
		if (corh != null && (corh.equals("C") || corh.equals("c"))) //$NON-NLS-1$ //$NON-NLS-2$
		{
			pl = new ComputerPlayer(nme); // IS NOW UNIQUE - SCRABBLE:225 onwards
		} else if (corh != null && (corh.equals("H") || corh.equals("h"))) //$NON-NLS-1$ //$NON-NLS-2$
		{
			pl = new HumanPlayer(nme); // IS NOW UNIQUE - SCRABBLE:225 onwards
		}
		players.add(pl);
	}

	public Board getBoard() {
		return board;
	}

	public int getBagSize() {
		return bag.getSize();
	}

	public List getPlayers() {
		return players;
	}

	public Player getPlayer() {
		return (Player) players.get(turn);
	}

	public Player getLastPlayer() {
		return (Player) players.get(lastturn);
	}

	public Rack getRack() {
		return ((Player) players.get(turn)).getRack();
	}

	public void setRack(Rack r) {
		((Player) players.get(turn)).setRack(r);
	}

	public Letter getRackLetter(int i) {
		return getRack().getLetter(i);
	}

	public Square getSquare(int i, int j) {
		return board.getSquare(i, j);
	}

	public void placeLetter(Letter l, int x, int y) {
		board.placeLetter(l, x, y);
	}

	public void updateCurrentPlayersRack() { // turn player selects letters from bag
		Player p = (Player) players.get(turn);
		Rack r = p.getRack();
		List selection;
		if (Scrabble.remote == 0) //if local game, 
		{
			selection = bag.giveLetters(7 - r.getRackSize());
		} else //if remote game, use overloaded method which doesnt get random characters
		{
			selection = bag.giveLetters(7 - r.getRackSize(), true);
		}
		r.addLetters(selection);
	}

	//notification for player that they've performed illegal ops
	public void invalidMoveNotification(int x) {
		Player p = (Player) players.get(turn);
		if (x == 0) {
			JOptionPane.showMessageDialog(null, messages.getString("Game.4"));}//dialog box to warn players rather than a console message //$NON-NLS-1$
		if (x == 1 && (p.getSkipCounter() == 0)) {
			JOptionPane.showMessageDialog(null, messages.getString("Game.5"));} //$NON-NLS-1$
		if (x == 2) //do nothing
		{
		}
		skipCounterIncreaseAndTest(x);
	}
	
	public void incrementLockOutCounter()
	{	
		lockOutCounter++;

	}
	
	//method that increments the skip counter and tests to see if a player is locked out
	public void skipCounterIncreaseAndTest(int x) {
		Player p = (Player) players.get(turn);
		p.incrementSkipCounter(); //increase skip counter
		if (p.getSkipCounter() >= 2) 
		{
			if (x == 0 || x == 1) 
			{
				JOptionPane.showMessageDialog(null, messages
						.getString("Game.6")); //$NON-NLS-1$
			}
			p.setIsLockedOut();
			lockOutCounter++;
		}
	}

	//used for the remote game, for non-client turn end
	public void endMoveLight(Move m) //int x is for skip/invalid move notification
	{
		Board b = (Board) board.clone();
		Board b2 = (Board) board.clone();
		Player p = (Player) players.get(turn);
		Rack r = p.getRack();

		lastMoveScore = 0;
		lastturn = turn;

		if (board.validateMove(m, moveNumber)) //word is on one row/column, starting square check
		{
			if (/*!board.getSquare(7,7).isOccupied() || */b
					.placeMove(m, board)) //connected/disconnected - occupiedAdjacend
			{
				if (/*!board.getSquare(7,7).isOccupied() || */m.findWords(b)) {
					m.clearWords();
					board.placeMove(m, board);
					m.findWords(board);
					//GAME159
					//if word is in dictionary
					if (m.inDictionary()) {
						int s = m.getScore(b2);
						lastMoveScore = s;
						//System.out.println("Valid move, score is: " + s);
						p.addScore(s);
						r.removeLetters(m.getLetters()); //removes letters that have been used
						p.resetSkipCounter(); //if valid move, reset skip counter
					} else {
						board.resetMoveSquares(m);
						skipCounterIncreaseAndTest(3);
					} //resets the squares to no stuff
				} else {
					board.resetMoveSquares(m);
					skipCounterIncreaseAndTest(3);
				}
			} else {
				board.resetMoveSquares(m);
				skipCounterIncreaseAndTest(3);
			}
		} else {
			board.resetMoveSquares(m);
			skipCounterIncreaseAndTest(3);
		}
		endTurn();
	}

	//GAME114
	//contains a fix to present the player with a popup box when they make an invalid move
	//includes code to increment/reset skip counter based on valid/invalid moves
	//
	//method used locally to end move, and for the local player in a remote game
	public void endMove(Move m, int x) //int x is for skip/invalid move notification
	{
		Board b = (Board) board.clone();
		Board b2 = (Board) board.clone();
		Player p = (Player) players.get(turn);
		Rack r = p.getRack();

		lastMoveScore = 0;
		lastturn = turn;

		if (board.validateMove(m, moveNumber)) //word is on one row/column, starting square check
		{
			if (/*!board.getSquare(7,7).isOccupied() || */b
					.placeMove(m, board)) //connected/disconnected - occupiedAdjacend
			{
				if (/*!board.getSquare(7,7).isOccupied() || */m.findWords(b)) {
					m.clearWords();
					board.placeMove(m, board);
					m.findWords(board);
					//System.out.println("Letter moves are: " + m.getLetterMoves()); 
					//System.out.println("All words are: " + m.getWords());
					//GAME159
					//if word is in dictionary
					if (m.inDictionary()) {
						int s = m.getScore(b2);
						lastMoveScore = s;
						//System.out.println("Valid move, score is: " + s);
						p.addScore(s);
						r.removeLetters(m.getLetters()); //removes letters that have been used
						p.resetSkipCounter(); //if valid move, reset skip counter
					} else {
						board.resetMoveSquares(m); //resets the squares to no stuff
						if (x != 2) //if this is a swap, dont display this message
							JOptionPane.showMessageDialog(null, messages
									.getString("Game.7")); //dialog box to tell player their word is not in the dictionary //$NON-NLS-1$
						invalidMoveNotification(x);
					}
				} else {
					board.resetMoveSquares(m);
					invalidMoveNotification(x);
				}
			} else {
				board.resetMoveSquares(m);
				invalidMoveNotification(x);
			}
		} else {
			board.resetMoveSquares(m);
			invalidMoveNotification(x);
		}
		endTurn();
	}

	public void endTurn() 
	{	
		Player p = (Player) players.get(turn);
		if (!bag.isEmpty())
			updateCurrentPlayersRack(); //fill rack as much as possible with new random letters
		turn = (turn + 1) % players.size(); //iterates through 1,2,3,4 (for max players.size()); equating to player names - effectively a turn selector
		Player nextp = (Player) players.get((turn) % players.size()); //get next player in player's list
		while (nextp.isLockedOut()) //for the length of the player list, if the next player in list is locked out,
		{
			turn = (turn + 1) % players.size(); //add an extra one to the turn
			nextp = (Player) players.get((turn) % players.size());
			if (lockOutCounter == players.size()) //if number of players locked out = player size,
			{
				break;
			} //break the loop.  this saves on infinite loop catches
		}
		moveNumber++;

		boolean atLeastOneEmptyRack = false; //flag to raise if at least one player has an empty rack

		for (int i = 0; i < players.size(); i++) //test to see if a player has an empty rack
		{
			p = (Player) players.get(i);
			if (p.getRack().isEmpty()) 
			{
				atLeastOneEmptyRack = true;
			}
		}
		
		if ((bag.isEmpty() && atLeastOneEmptyRack) || lockOutCounter == players.size()) //test for end of game
			gameEnd();
	}

	public void gameEnd() 
	{	
		String[] names = new String[players.size()]; //new array to store final scores of all players
		int[] finalScores = new int[players.size()]; //put score of player into array
		String[] scoreDisplay = new String[players.size()]; //new array containing the Strings to be printed in the score display

		for (int i = 0; i < players.size(); i++) {
			Player p = (Player) players.get(i); //get player
			finalScores[i] = p.getScore() - p.getRack().getRackValue(); //subtract value of player's rack from score
			names[i] = p.getName();

			if (p.getRack().getRackSize() == 0) //if the player's rack is empty,
			{
				finalScores[i] = finalScores[i] + bag.getBagValue(); //add value of bag to final score
			}
		}

		for (int i = 0; i < players.size() - 1; i++) //for each player
		{
			if (finalScores[i] > finalScores[i + 1]) {
				int tempScore;
				tempScore = finalScores[i];
				finalScores[i] = finalScores[i + 1];
				finalScores[i + 1] = tempScore;

				String tempName;
				tempName = names[i];
				names[i] = names[i + 1];
				names[i + 1] = tempName;
			}
		}

		for (int i = 0; i < players.size(); i++) {
			scoreDisplay[i] = finalScores[i] + "     " + names[i]; //$NON-NLS-1$
			//turns last player in ordered 'list' (ie the winner) to red - DOES NOT WORK FOR TIED GAMES
			if (i == (players.size() - 1)) {
				scoreDisplay[i] = "<html><font color=#D41A1F>" + finalScores[i]
						+ "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" + names[i]
						+ "</font></html>";
			}

		}

		String display = ""; //$NON-NLS-1$
		for (int i = 0; i < players.size(); i++) {
			display = display + "\n" + scoreDisplay[i]; //$NON-NLS-1$
		}

		//possibly find a 'winner', 'joint winner' if time - sort final scores

		//play a sound, as per deepak's request
		try 
		{
			InputStream in = new FileInputStream("resources/sounds/endgame.wav");
			AudioStream as = new AudioStream(in);
			AudioPlayer.player.start(as);
		} 
		catch (FileNotFoundException fnf) 
		{	//no need, just wont play sound
			//fnf.printStackTrace();
		} 
		catch (IOException ioe) 
		{ 	//no need, just wont play sound
			//ioe.printStackTrace();
		}

		//show final dialog
		JOptionPane.showMessageDialog(null,
						messages.getString("Game.15") + display, //$NON-NLS-1$
						messages.getString("Game.16"), //$NON-NLS-1$
						JOptionPane.INFORMATION_MESSAGE,
						new ImageIcon(
								"resources/images/scrabbletrophy.png", "Here 'The Cheat', have a trophy!")); //$NON-NLS-1$ //$NON-NLS-2$

		String localEndGameDialog1 = messages.getString("Game.19"); //$NON-NLS-1$
		String localEndGameDialog2 = messages.getString("Game.20"); //$NON-NLS-1$
		String remoteEndGameDialog1 = messages.getString("Game.21"); //$NON-NLS-1$
		String remoteEndGameDialog2 = messages.getString("Game.22"); //$NON-NLS-1$

		String[] endGameDialog = new String[2];
		if (Scrabble.remote == 0) 
		{
			endGameDialog[0] = localEndGameDialog1;
			endGameDialog[1] = localEndGameDialog2;
		} 
		else 
		{
			endGameDialog[0] = remoteEndGameDialog1;
			endGameDialog[1] = remoteEndGameDialog2;
		}

		//play another game?
		Object[] options = {messages.getString("yes"), messages.getString("no") }; //uses a modified YES/NO box with custom labels //$NON-NLS-1$ //$NON-NLS-2$
		int n = JOptionPane.showOptionDialog(null, endGameDialog[0], //$NON-NLS-1$
				endGameDialog[1], //$NON-NLS-1$
				JOptionPane.YES_NO_OPTION,
				JOptionPane.QUESTION_MESSAGE, null, options,
				options[0]);
		
		if (n == JOptionPane.YES_OPTION) 
		{
			getScrabble().builder = null;
			getScrabble().builder = new ScrabbleBuilder();
			if (Scrabble.remote == 0) {
				LocalScrabble.createAndShowGUI(); //create new scrabble window containing new game
			} else {
				if (Scrabble.remote == 1) //if server
				{
					getScrabble().resetLobby();
					RemoteScrabble.window.setState(Frame.ICONIFIED);
					getScrabble().frame.requestFocus();
				} else //if client
				{
					RemoteScrabble.window.setState(Frame.ICONIFIED);
					getScrabble().frame.requestFocus();
				}
			}
		} else if (n == JOptionPane.NO_OPTION) {
			if (Scrabble.remote == 0)//do nothing
			{
			} else {
				getScrabble().closeLobby();
			}
		}
		gameEnded = true;
	}
}

