/**
 * @(#)RemoteScrabble.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.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingConstants;
import javax.swing.Timer;
import javax.swing.border.Border;

/**
 * RemoteScrabble describes the player's GUI interface for the remote Scrabble game.
 * All action listeners are defined. The Scrabble game is i18n-ed.
 * Most methods in RemoteScrabble relate to how the user interacts with the GUI,
 * however RemoteScrabble plays a more active roll in the course of a game than LocalScrabble,
 * and in certain specific circumstances acts as a relay for a client so that Game information
 * may be updated (e.g. <code>clientLeftGame()</code>.
 * Most game related methods are contained within different classes, notably <b>Game</b>
 * and <b>ScrabbleBuilder</b>.  RemoteScrabble utilises some remote specific methods within
 * <b>Game</b>, <b>Bag</b> and <b>Scrabble</b>.
 * 
 * @author 3GP01 - Hardev Bhamra, Deepak Chandarana, James Tompkin
 * @version 2.0
 * @see Game
 * @see ScrabbleBuilder
 * @see Scrabble
 *
 */

/* Swing is not natively slow, however it is the java look and feel's response that is slow.
 * This is mostly due to the existing code's ActionListener code, which we mostly left intact.
 * Need to clean up the action listeners, factor out into their own threads
 * so that swing may repaint the display and provide user feedback more frequently.
 * Using a 'windows' l&f gives the appearance of a speeded up GUI, however this l&f is
 * unacceptable for use, as it is not cross-platform.
 * 
 * Also, certain methods, such as g.endMove(), are particularly slow to repaint
 * due to their intensive nature.  Not having the time to rewrite the code but having enough time
 * to see slow implementations has been frustrating (see Board.clone() to find the scores).
 */
public class RemoteScrabble extends JFrame implements ActionListener 
{
	static RemoteScrabble window;

	//'turn' should NOT be used anywhere else - only used as a pass variable
	static int turn; //only used as a pass variable to use in game constructor 

	static int uid;

	private Timer timer; //a timer that will generate events

	private boolean timerRunning; // while the stopwatch is running

	private long startTime; //start time of timer, in milliseconds

	String languageForGUI = Dictionary.langForGUI; //use for output of current language for GUI use

	ResourceBundle messages = getScrabble().getI18n().getCurrentResourceBundle(); //localisation message bundle

	JPanel boardPanel = new JPanel();

	JPanel rackPanel = new JPanel();

	JPanel resultsPanel = new JPanel();

	JButton moveButton = new JButton(messages.getString("moveButton"));

	JButton resetMoveButton = new JButton(messages.getString("resetMoveButton"));

	JButton hintMoveButton = new JButton(messages.getString("hintMoveButton"));

	JButton endMoveButton = new JButton(messages.getString("endMoveButton"));

	JButton swapLettersButton = new JButton(messages.getString("swapLettersButton"));

	JButton skipTurnButton = new JButton(messages.getString("skipTurnButton"));

	JButton leaveGameButton = new JButton(messages.getString("leaveGameButton"));

	JButton helpButton = new JButton(messages.getString("helpButton"));

	JButton[][] boardButtons = new JButton[15][15];

	JButton[] rackButtons = new JButton[7];

	Map xcoord = new HashMap(); // JButton --> Integer

	Map ycoord = new HashMap(); // JButton --> Integer

	JLabel[] resultLabels = new JLabel[5];

	JLabel bagLabel;

	JLabel playerLabel = new JLabel(messages.getString("playerLabelNoPlayers"));

	JLabel messageLine = new JLabel(); //line for outputting text to user, attached to bottom of window, use messageLine.setText("...");

	JProgressBar progressBar; //animated progress bar.

	Game g;

	List history = new ArrayList(); // of Move

	List swapLetters = new ArrayList(); //new list containing all selected swap letters

	Letter current = null;

	Move currentMove;

	String moveToSend = "";

	int lastRack = -1; // current != null  =>  lastRack: 0..7

	int hintsPlayed = 0; //number of hints played by client

	boolean moveInProgress = false;

	boolean rackClicked = false;

	Color ivory = new Color(254, 254, 229);

	Color darkIvory = new Color(252, 252, 173);

	Color turquoise = new Color(1, 51, 52);

	Color lightTurquoise = new Color(0, 102, 102);

	Color orange = new Color(255, 102, 0);

	Border pad1 = BorderFactory.createMatteBorder(5, 17, 5, 17, ivory);

	Border pad2 = BorderFactory.createMatteBorder(2, 0, 2, 0, ivory);

	Border outlineBorder = BorderFactory.createEtchedBorder(Color.WHITE,
			Color.GRAY);

	Border border = BorderFactory.createCompoundBorder(outlineBorder, pad1);

	Border border2 = BorderFactory.createCompoundBorder(outlineBorder, pad2);

	/**
	 * Gets the current <b>Scrabble</b> game.
	 *  
	 * @return Scrabble
	 */
	public Scrabble getScrabble() 
	{
		return Scrabble.scrabble;
	}

	/**
	 * Returns RemoteScrabble.
	 * 
	 * @return RemoteScrabble
	 */
	public static RemoteScrabble getRemoteScrabble() 
	{
		return window;
	}

	/**
	 * Returns the current game <b>g</b> running on RemoteScrabble.
	 * 
	 * @return Game
	 */
	public Game getGame() 
	{
		return g;
	}

	/**
	 * RemoteScrabble creates and initialises a game, ready to accept response from the clients. 
	 */
	public RemoteScrabble() 
	{
		getScrabble().arithNumberOfWindows("++"); //increase the number of open windows
		getScrabble().incrementNumberOfGamesPlayed(); //increase the number of games played

		setMessageLine(" " + messages.getString("messageLineIntro")	+ languageForGUI + messages.getString("messageLineDict"));
		if (getScrabble().i18n.language.equals("ru"))
			setTitle(messages.getString("scrabbleWindowTitle") + " " + getScrabble().getNumberOfGamesPlayed() + "\u044F");
		else
			setTitle(messages.getString("scrabbleWindowTitle") + getScrabble().getNumberOfGamesPlayed());
		//tool tips for buttons
		moveButton.setToolTipText(messages.getString("scrabble.16"));
		resetMoveButton.setToolTipText(messages.getString("scrabble.17"));
		hintMoveButton.setToolTipText(messages.getString("scrabble.26")
				+ getScrabble().numberOfHints
				+ messages.getString("scrabble.27"));
		endMoveButton.setToolTipText(messages.getString("scrabble.18"));
		swapLettersButton.setToolTipText(messages.getString("scrabble.19"));
		skipTurnButton.setToolTipText(messages.getString("scrabble.20"));
		helpButton.setToolTipText(messages.getString("scrabble.25"));

		//Mnemonics of buttons
		moveButton.setMnemonic(KeyEvent.VK_M);
		resetMoveButton.setMnemonic(KeyEvent.VK_R);
		hintMoveButton.setMnemonic(KeyEvent.VK_H);
		swapLettersButton.setMnemonic(KeyEvent.VK_W);
		skipTurnButton.setMnemonic(KeyEvent.VK_P);
		helpButton.setMnemonic(KeyEvent.VK_H);
		leaveGameButton.setMnemonic(KeyEvent.VK_L);
		endMoveButton.setMnemonic(KeyEvent.VK_E);

		g = new Game(getScrabble().getScrabbleBuilder(), turn);

		String[] names = getScrabble().clientNames;
		//List players = new ArrayList();
		for (int i = 0; i < names.length; i++) 
		{
			if (names[i] != null) 
			{
				g.addPlayer(names[i], "h");
			}
		}

		boardPanel.setDoubleBuffered(true); //should reduce flickering on the board

		JPanel boardResultsPanel = new JPanel();
		boardResultsPanel.setLayout(new BoxLayout(boardResultsPanel,
				BoxLayout.X_AXIS));

		GridLayout gl = new GridLayout(15, 15, 0, 0);
		gl.preferredLayoutSize(boardPanel);
		boardPanel.setLayout(gl);
		boardPanel.setBorder(BorderFactory.createLineBorder(darkIvory, 2));
		//spacer
		boardResultsPanel.add(Box.createRigidArea(new Dimension(10, 10)));

		//add the board
		addSquares();
		boardPanel.setAlignmentY(Component.TOP_ALIGNMENT);
		boardResultsPanel.add(boardPanel);
		//spacer
		boardResultsPanel.add(Box.createRigidArea(new Dimension(10, 10)));
		boardResultsPanel.add(Box.createHorizontalGlue());
		//add the results
		addResults();
		resultsPanel.setAlignmentY(Component.TOP_ALIGNMENT);
		boardResultsPanel.add(resultsPanel);
		//spacer
		boardResultsPanel.add(Box.createRigidArea(new Dimension(10, 0)));

		addRack();

		//logo bar
		JPanel logoBar = new JPanel();
		logoBar.setLayout(new BoxLayout(logoBar, BoxLayout.X_AXIS));
		JLabel logo = new JLabel(new ImageIcon(
				"resources/images/scrabblelogo.png"));
		logoBar.add(Box.createRigidArea(new Dimension(10, 40)));
		logoBar.add(logo);

		//southPanel for info to user
		progressBar = new JProgressBar();
		progressBar.setStringPainted(true); //get space for the string
		progressBar.setString(""); //paint with 'please wait'
		progressBar.setIndeterminate(false); //makes progressbar indeterminate, ie constantly moving, no time/task constraints

		JPanel southPanel = new JPanel(); //south panel for progress bar/message line
		southPanel.setLayout(new BoxLayout(southPanel, BoxLayout.X_AXIS));
		southPanel.add(messageLine);
		messageLine.setAlignmentX(Component.LEFT_ALIGNMENT);
		southPanel.add(Box.createHorizontalGlue()); //stretchy glue box segment that keeps messageLine and progressBar apart
		southPanel.add(progressBar);
		southPanel.add(Box.createRigidArea(new Dimension(5, 0)));
		southPanel.add(helpButton);
		helpButton.addActionListener(this);
		southPanel.add(Box.createRigidArea(new Dimension(10,0)));
		progressBar.setAlignmentX(Component.RIGHT_ALIGNMENT);
		progressBar.setPreferredSize(new Dimension(216, 18));
		progressBar.setMaximumSize(new Dimension(300, 18));

		getContentPane().setLayout(
				new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
		logoBar.setAlignmentX(Component.LEFT_ALIGNMENT);
		boardResultsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
		rackPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
		southPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
		//if we need to ration our height, then take off the logo, because it is not a functional component of the GUI
		if (!getScrabble().isHeightChangesNecessary())
			getContentPane().add(logoBar);
		getContentPane().add(boardResultsPanel);
		getContentPane().add(rackPanel);
		getContentPane().add(southPanel);

		this.getRootPane().setDefaultButton(moveButton);
		this.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); //default close operation
		this.setResizable(false);

		addWindowListener(new WindowAdapter() 
		{	//SCRABBLE92
			//Fix for lack of prompt to save history
			public void windowClosing(WindowEvent e) 
			{
				JFrame frame = null;
				Object[] options = { messages.getString("yes"),
						messages.getString("no"), messages.getString("cancel") };
				int n = JOptionPane
						.showOptionDialog(frame, messages
								.getString("saveRequest"), messages
								.getString("saveRequestTitle"),
								JOptionPane.YES_NO_OPTION,
								JOptionPane.QUESTION_MESSAGE, null, options,
								options[0]);

				if (n == JOptionPane.YES_OPTION) //if want to save, run filechooser
				{	getScrabble().leaveServerButton.setEnabled(true);
					saveDataToFile();
				}
				else if (n == JOptionPane.CANCEL_OPTION) //cancel dialog, return to scrabble
				{
				}
				else if (n == JOptionPane.NO_OPTION) //quit
				{ 	//copied from action listener - will ensure that closing an ended game 
					//window does not disconnect a client from the server
					if(!getScrabble().hostLocation.equals(getScrabble().getLocalHostAddress()))	//if not server
					{	if (getScrabble().server.gameInProgress	&& !g.getGameEnded())
						{	getScrabble().client.setMessageOutput(9, null);
						}
					}
					else	//if server has closed a game in progress (as there is no leave button for the server client)
					{	if (getScrabble().server.gameInProgress	&& !g.getGameEnded())
						{	getScrabble().client.setMessageOutput(9, "Server");
						}
					}
					getScrabble().leaveServerButton.setEnabled(true);
					dispose();
				}
			}
		});
	}

	private void addSquares() //construct the board from squares iteratively
	{
		for (int j = 0; j < 15; j++) {
			for (int i = 0; i < 15; i++) {
				Square sq = g.getSquare(i, j);
				JButton jb = new JButton(sq.getIcon(sq.getColor()));
				//jb.setToolTipText(sq.getDescription());	//tool tips for the board
				jb.setVerticalTextPosition(SwingConstants.CENTER); //set text to be over the icon
				jb.setHorizontalTextPosition(SwingConstants.CENTER);//set text to be over the icon
				jb.setRolloverIcon(sq.getRolloverIcon(sq.getColor()));
				jb.setPressedIcon(sq.getPressedIcon(sq.getColor()));
				jb.setDisabledIcon(sq.getDisabledIcon(sq.getColor()));
				jb.setBorder(BorderFactory.createLineBorder(darkIvory, 2)); //set border
				jb.setBackground(g.getSquare(i, j).getColor()); //get background colour
				jb.setForeground(Color.BLACK); //set text colour
				jb.addActionListener(this);
				xcoord.put(jb, new Integer(i));
				ycoord.put(jb, new Integer(j));
				boardButtons[i][j] = jb;
				boardPanel.add(jb);
			}
		}
	}

	private void addRack() 
	{
		JPanel functionButtonsPanel = new JPanel();
		functionButtonsPanel.setLayout(new BoxLayout(functionButtonsPanel,
				BoxLayout.X_AXIS));
		functionButtonsPanel.add(moveButton);
		functionButtonsPanel.add(resetMoveButton);
		functionButtonsPanel.add(hintMoveButton);
		functionButtonsPanel.add(swapLettersButton);
		functionButtonsPanel.add(skipTurnButton);
		functionButtonsPanel.add(endMoveButton);
		moveButton.addActionListener(this);
		resetMoveButton.addActionListener(this);
		hintMoveButton.addActionListener(this);
		swapLettersButton.addActionListener(this);
		skipTurnButton.addActionListener(this);
		endMoveButton.addActionListener(this);

		JPanel rackButtonsPanel = new JPanel();
		rackButtonsPanel.setDoubleBuffered(true);
		rackButtonsPanel.setLayout(new BoxLayout(rackButtonsPanel,
				BoxLayout.X_AXIS));
		rackButtonsPanel.setMaximumSize(new Dimension(280, 40));

		for (int i = 0; i < 7; i++) 
		{
			JButton jb = new JButton("-");
			xcoord.put(jb, new Integer(i));
			jb.addActionListener(this);
			rackButtons[i] = jb;
			rackButtonsPanel.add(jb);

			jb.setEnabled(false);
			jb.setBackground(turquoise);
			jb.setIcon(new ImageIcon("resources/images/tiles/letter/letter.png"));
			jb.setRolloverIcon(new ImageIcon("resources/images/tiles/letter/letterrollover.png"));
			jb.setPressedIcon(new ImageIcon("resources/images/tiles/letter/letterpressed.png"));
			jb.setDisabledIcon(new ImageIcon("resources/images/tiles/letter/letter.png"));
			jb.setVerticalTextPosition(SwingConstants.CENTER);
			jb.setHorizontalTextPosition(SwingConstants.CENTER);
			jb.setBorder(null);
			jb.setForeground(Color.BLACK);
		}

		rackPanel.setLayout(new BoxLayout(rackPanel, BoxLayout.X_AXIS));
		//if screen height is rationed, dont add vertical spacer
		if (getScrabble().isHeightChangesNecessary()) 
		{
			rackPanel.add(Box.createRigidArea(new Dimension(10, 0)));
		} //spacer at left edge to add horizontal separation
		else 
		{
			rackPanel.add(Box.createRigidArea(new Dimension(10, 50)));
		} //spacer at left edge to add horizontal/vertical separation
		rackPanel.add(functionButtonsPanel);
		rackPanel.add(Box.createHorizontalGlue());
		rackPanel.add(playerLabel);
		rackPanel.add(Box.createHorizontalGlue());
		rackPanel.add(rackButtonsPanel);
		rackPanel.add(Box.createRigidArea(new Dimension(10, 0))); //spacer at right edge to add horizontal separation
	}

	private void addResults() 
	{
		resultsPanel.setLayout(new BoxLayout(resultsPanel, BoxLayout.Y_AXIS));

		JPanel buttonPanel = new JPanel();
		buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
		buttonPanel.setPreferredSize(new Dimension(250, 50));
		buttonPanel.setBackground(turquoise);

		buttonPanel.add(Box.createHorizontalGlue());
		//if NOT the server, add leave game button (server cannot leave game without everyone else leaving)
		if(!getScrabble().hostLocation.equals(getScrabble().getLocalHostAddress()))
			buttonPanel.add(leaveGameButton);
		buttonPanel.add(Box.createRigidArea(new Dimension(10, 0)));
		leaveGameButton.addActionListener(this);

		buttonPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
		resultsPanel.add(buttonPanel);
		resultsPanel.add(Box.createVerticalGlue());

		List players = g.getPlayers();
		for (int i = 0; i < 4; i++) 
		{
			JLabel jl = new JLabel(messages.getString("addPlayerButton"));
			resultLabels[i] = jl;
			jl.setAlignmentX(Component.LEFT_ALIGNMENT);
			resultsPanel.add(jl);
			resultsPanel.add(Box.createVerticalGlue());
		}
		resultsPanel.add(Box.createRigidArea(new Dimension(0, 10)));
		bagLabel = new JLabel("");
		resultLabels[4] = bagLabel;
		bagLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
		resultsPanel.add(bagLabel);
		resultsPanel.add(Box.createVerticalGlue());
	}

	/**
	 * Updates the board, refreshes each button's icon and text.
	 * This method also refreshes the active rack and the results panel.
	 *
	 */
	private void updateDisplay() 
	{
		for (int i = 0; i < 15; i++) 
		{
			for (int j = 0; j < 15; j++) 
			{
				Square sq = g.getSquare(i, j);
				JButton jb = boardButtons[i][j];
				jb.setText(sq.getText().toUpperCase());
				if (sq.isOccupied()) 
				{
					jb.setEnabled(false);
					jb.setIcon(new ImageIcon(
							"resources/images/tiles/letter/letter.png"));
					jb.setDisabledIcon(new ImageIcon(
							"resources/images/tiles/letter/letter.png"));
					jb.setBackground(ivory);
				} 
				else 
				{
					jb.setIcon(sq.getIcon(sq.getColor()));
					jb.setText(null);
					jb.setBackground(sq.getColor());
					jb.setEnabled(true);
				}
			}
		}

		if (moveInProgress) //if this is the updateDisplay call from 'moveButton'
		{
			if (g.getTurn() == uid) //if it is our client's turn, update their rack
			{
				Rack r = g.getRack();
				updateRack(rackButtons, r);
			}
		}
		else //if this is the updateDisplay call from endPlayerTurn
		{ 
			Player p = (Player) g.getPlayers().get(uid);
			updateRack(rackButtons, p.getRack());
		}
		updateResults();
	}

	/**
	 * Updates the results panel.
	 * If a game is not in progress, player labels are set to "Add Player" to show
	 * the user that more players can be added.  Once the game is started, these
	 * label slots appear to be 'closed'.
	 *
	 */
	private void updateResults() 
	{
		List players = g.getPlayers();
		int numPlayers = players.size();
		
		String skipsDisplay;
		
		for (int i = 0; i < numPlayers; i++) 
		{
			Player p = (Player) players.get(i);
			
			//special consideration for russian
			if (getScrabble().i18n.language.equals("ru"))
			{	skipsDisplay = (messages.getString("scrabble.1") + (2 - p.getSkipCounter()) + messages.getString("scrabble.4"));
				if (p.getSkipCounter() == 1)	//if a player only has one skip left
				{	skipsDisplay = (messages.getString("scrabble.2") + (2 - p.getSkipCounter()) + messages.getString("scrabble.5"));
				}
				if (p.getSkipCounter() >= 2)
				{	skipsDisplay = messages.getString("scrabble.3");
				}
			}
			else
			{	skipsDisplay = (2 - p.getSkipCounter()) + messages.getString("scrabble.1");
				if (p.getSkipCounter() == 1)	//if a player only has one skip left
				{	skipsDisplay = (2 - p.getSkipCounter())	+ messages.getString("scrabble.2");
				}
				if (p.getSkipCounter() >= 2)
				{	skipsDisplay = messages.getString("scrabble.3");
				}
			}
			
			resultLabels[i].setText(
									p.getName()
									+ "       "
									+ p.getScore()
									+ messages.getString("scrabble.48")
									+ "       " 
									+ skipsDisplay); //$NON-NLS-1$ //$NON-NLS-2$
		}

		bagLabel.setText(g.getBagSize() + messages.getString("scrabble.50"));

		for (int i = numPlayers; i < 4; i++) //until game has started, show player slots
		{
			resultLabels[i].setText("");
			if (g.getPlayers().size() == 1)
				playerLabel.setText(messages.getString("scrabble.53"));
			else
				playerLabel.setText(g.getPlayers().size() + messages.getString("scrabble.55"));
		}
	}

	/**
	 * 	Method to update the status of individual buttons in the rack.
	 *  Primarily used to update the display when the user 'clicks around the rack'.
	 * 
	 * @param i The index location in the rack of a specific button.
	 * @param r The rack to update.
	 */
	private void updateRackButton(int i, Rack r) {
		Letter lt = r.getLetter(i);
		if (lt.isBlank()) {
			lt.resetBlank(); //reset symbol of letter to be blank
			rackButtons[i].setText(" ");
		} else {
			rackButtons[i].setText("<html>"
					+ Character.toUpperCase(lt.getSymbol()) + " <sub>"
					+ lt.getScore() + "</sub></html>");
		}
	}

	/**
	 * Updates a desired rack.
	 * 
	 * @param rackButtons The rack of buttons to update, usually the 'control rack'.
	 * @param r The player's <b>Rack</b> to update.
	 */
	private void updateRack(JButton[] rackButtons, Rack r) {
		int i;
		for (i = 0; i < r.getRackSize(); i++) {
			Letter lt = r.getLetter(i);
			if (lt.isBlank()) {
				lt.resetBlank(); //reset symbol of letter to be blank
				rackButtons[i].setText(" ");
			} else {
				rackButtons[i].setText("<html>"
						+ Character.toUpperCase(lt.getSymbol()) + " <sub>"
						+ lt.getScore() + "</sub></html>");
			}
			rackButtons[i].setForeground(Color.BLACK);
		}
		for (; i < 7; i++)
		{ 	rackButtons[i].setText("-");
			rackButtons[i].setEnabled(false);
		}
	}

	/* (non-Javadoc)
	 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
	 */
	public void actionPerformed(ActionEvent e) 
	{
		Object eventSource = e.getSource();
		if (eventSource instanceof JButton) 
		{
			JButton cb = (JButton) eventSource;
			String command = cb.getText();
			if (command != null) 
			{
				if (command.equals(messages.getString("leaveGameButton"))) 
				{
					if (moveInProgress) //if a move is in progress, timer will be going,
						timerControl(e); //so stop the timer
					getScrabble().client.setMessageOutput(9, null); //, uid+""
					setButtonStates(false, false, false, false, false, false);
					leaveGameButton.setEnabled(false);
				}
				if (command.equals(messages.getString("helpButton"))) 
				{
					HelpSystem.createAndShowHS();
				}
				if (command.equals(messages.getString("hintMoveButton"))) 
				{
					Cursor cur_wait = new Cursor(Cursor.WAIT_CURSOR); //if computer player is moving, put a waiting cursor
					setCursor(cur_wait);
					Move mv = null;
					if (!g.board.getSquare(7, 7).isOccupied()) 
					{
						FirstMoveStrategy ss = new FirstMoveStrategy();
						mv = ss.generateMove(g);
					} 
					else 
					{
						CompositeMoveStrategy pms = new CompositeMoveStrategy();
						mv = pms.generateMove(g);
					}
					if (mv == null) 
					{
						setMessageLine(messages.getString("scrabble.81"));
					} 
					else 
					{
						List letterMoves = mv.getLetterMoves();

						boolean[] alreadySwapped = {false, false, false, false, false, false, false};
						
						//show this move on the player's rack
						for (int i = 0; i < letterMoves.size(); i++) 
						{	/*get first letter of letter move
						 	*compare with rack, find index of first instance of letter move in rack
						 	*swap the first letter in the rack with the letter at the index of the rack
						 	*update individual rack buttons
						 	*/

							LetterMove lm = (LetterMove) letterMoves.get(i);
							int index = g.getRack().getIndexOfLetter(lm.getLetter());
							
							//if letter has already been swapped, skip it (as it doesnt need to be swapped again)
							if(alreadySwapped[index] == true)
							{
							}
							else
							{	//swap letter at i with lettermove letter
								//if letter is a blank, swap with a blank letter instead
								Letter letterSelected = lm.getLetter();
								//get letter at index i
								Letter letterToSwap = g.getRack().getLetter(i);
								if (letterSelected.isBlank()) 
								{
									letterSelected = g.getRack().getLetter(index);
								}
								g.getRack().setLetter(i, letterSelected);
								//swap letter at index with letter at i
								g.getRack().setLetter(index, letterToSwap);
								updateRackButton(i, g.getRack());
								updateRackButton(index, g.getRack());
								alreadySwapped[i] = true;
							}
						}
					}
					hintsPlayed++;
					hintMoveButton.setEnabled(false);

					Cursor cur_default = new Cursor(Cursor.DEFAULT_CURSOR); //restore cursor to default
					setCursor(cur_default);
				}
				if (command.equals(messages.getString("moveButton"))) 
				{
					moveInProgress = true; //move is underway
					setButtonStates(false, true, true, true, true, true);
					setBoardButtonsEnabled(true);

					Player pl = g.getPlayer();
					moveToSend = ""; //reset move that will be sent to all clients
					currentMove = new Move(pl);
					updateDisplay();
					timerControl(e);
				}
				if (command.equals(messages.getString("resetMoveButton"))) 
				{
					updateDisplay();
					setRackButtonsEnabled(true); //reset rack so that all buttons are enabled
					currentMove = new Move(g.getPlayer());
					moveToSend = ""; //reset move to send
				}
				if (command.equals(messages.getString("skipTurnButton"))) 
				{
					timerControl(e);

					//when endMove is pressed, send currentMove to server thread for echo
					getScrabble().client.setMessageOutput(7, "skip");

					//receive currentMove from client
					while (currentMove == null) //wait for move from server...
					{
						try 
						{
							currentMove = getScrabble().client.getMove();
							Thread.sleep(100);
						}
						catch (InterruptedException e1) 
						{ 	getScrabble().client.clientServerException(e1);
						}
					}
					//System.out.println("Move received " + currentMove);

					if (g.getGameEnded()) //if we've reached the end of the game,
					{
						moveButton.setEnabled(false);
						leaveGameButton.setEnabled(false); //make sure this button is disabled
					}
				}
				if (command.equals(messages.getString("swapLettersButton"))) 
				{
					swapLetters.removeAll(swapLetters); //remove all elements from swapLetters

					JFrame f = new JFrame();
					final JDialog frame = new JDialog(f);

					//final List selectedLetters = new ArrayList();	//new list containing all selected letters

					final JButton[] swapLettersButtons = new JButton[7]; //new button rack for popup window

					JPanel swapPanel = new JPanel();
					JPanel buttonPanel = new JPanel();

					final JButton slOK = new JButton(messages.getString("ok"));
					slOK.setEnabled(false);
					JButton slReset = new JButton(messages
							.getString("scrabble.115"));
					JButton slCancel = new JButton(messages.getString("cancel"));

					buttonPanel.add(slOK);
					buttonPanel.add(slReset);
					buttonPanel.add(slCancel);

					for (int i = 0; i < 7; i++) {
						final JButton jb1 = new JButton("-");
						xcoord.put(jb1, new Integer(i));
						swapLettersButtons[i] = jb1;
						jb1.setBackground(turquoise); //setup cool buttons
						jb1.setIcon(new ImageIcon(
								"resources/images/tiles/letter/letter.png"));
						jb1.setRolloverIcon(new ImageIcon(
								"resources/images/tiles/letter/letter.png"));
						jb1
								.setPressedIcon(new ImageIcon(
										"resources/images/tiles/letter/letterpressed.png"));
						jb1
								.setDisabledIcon(new ImageIcon(
										"resources/images/tiles/letter/letterdisabled.png"));
						jb1.setVerticalTextPosition(SwingConstants.CENTER);
						jb1.setHorizontalTextPosition(SwingConstants.CENTER);
						jb1.setBorder(null);
						jb1.setForeground(Color.BLACK);
						swapPanel.add(jb1);
						final int j = i;
						jb1.addActionListener(new ActionListener() {
							public void actionPerformed(ActionEvent e) {
								Object eventSource = e.getSource();
								JButton jb = (JButton) eventSource;
								Integer coord = (Integer) xcoord.get(jb);
								int x = coord.intValue();
								//get letter from new rack, add to List selectedLetters, which contains all letters to swap
								swapLetters.add(g.getRackLetter(x));
								jb1.setEnabled(false);
								slOK.setEnabled(true);
							}
						});
					}

					final Rack rsl = g.getRack();
					updateRack(swapLettersButtons, rsl); //update swap rack buttons with rack from board

					slOK.addActionListener(new ActionListener() {
						public void actionPerformed(ActionEvent e) {
							String toSend = "";
							for (int i = 0; i < swapLetters.size(); i++) {
								toSend = toSend + " "
										+ swapLetters.get(i).toString();
							}

							//System.out.println("Swap Letters: " + toSend);

							getScrabble().client.setMessageOutput(8, toSend);

							frame.dispose();
							//System.out.println(toXml());
							setButtonStates(false, false, true, false, false,
									false);
							//updates board rack buttons with new rack
							updateRack(rackButtons, rsl);
							//sets board rack buttons to be disabled - this lets a player see what new letters they have, without letting them place a move
							setRackButtonsEnabled(false);
							timerControl(e);
						}
					});

					slReset.addActionListener(new ActionListener() {
						public void actionPerformed(ActionEvent e) {
							for (int i = 0; i < 7; i++) {
								swapLettersButtons[i].setEnabled(true);
							}
							swapLetters.removeAll(swapLetters); //remove all letters from list
							slOK.setEnabled(false);
						}
					});

					slCancel.addActionListener(new ActionListener() {
						public void actionPerformed(ActionEvent e) {
							setButtonStates(false, true, true, true, true, true);
							frame.dispose();
						}
					});

					Container contentPane = frame.getContentPane();
					frame.setContentPane(contentPane);
					contentPane.setLayout(new BorderLayout());
					contentPane.add(swapPanel, BorderLayout.NORTH);
					contentPane.add(buttonPanel, BorderLayout.SOUTH);

					f.setIconImage(new ImageIcon("resources/images/icon.png")
							.getImage());//default coffee cup icon.
					frame.setModal(true);
					frame.setTitle(messages.getString("swapLettersButton"));
					frame.pack();
					frame.setLocationRelativeTo(null);
					frame.setVisible(true);
				}

				//SCRABBLE393
				//fix for nullpointerexceptoin when hammering end move
				//uses boolean flag to denote when a move is in play, and when a move is not
				if (command.equals(messages.getString("endMoveButton"))) 
				{
					if (moveInProgress) //if a move is in play, do this, else, dont perform actions
					{
						timerControl(e);

						//System.out.println("moveToSend: " + moveToSend);

						//when endMove is pressed, send currentMove to server thread for echo
						getScrabble().client.setMessageOutput(7, moveToSend);

						//receive currentMove from client
						while (currentMove == null) //wait for move from server...
						{
							try 
							{
								currentMove = getScrabble().client.getMove();
								Thread.sleep(100);
							}
							catch (InterruptedException e1) 
							{	getScrabble().client.clientServerException(e1);
							}
						}

						if (g.getGameEnded()) //if we've reached the end of the game,
						{
							moveButton.setEnabled(false);
							leaveGameButton.setEnabled(false); //make sure this button is disabled
						}
					}
				}
			}
			if (ycoord.get(cb) != null) // clicked board button
			{
				if (current == null || currentMove == null) // nothing to place, ignore
				{
					setMessageLine(messages.getString("scrabble.0"));
				}
				else 
				{
					String resp = "";
					hintMoveButton.setEnabled(false);
					int x = ((Integer) xcoord.get(cb)).intValue();
					int y = ((Integer) ycoord.get(cb)).intValue();
					// g.placeLetter(current,x,y);
					if (current.isBlank()) 
					{
						List alphabet = getScrabble().getScrabbleBuilder().getAlphabet();
						Object[] letterArray = new Object[alphabet.size()];

						for (int i = 0; i < (alphabet.size() - 1); i++) //size()-1 so that it doesnt add the blank
						{
							letterArray[i] = alphabet.get(i).toString()
									.toUpperCase();
						}

						resp = (String) JOptionPane.showInputDialog(null,
								messages.getString("scrabble.136"), messages
										.getString("scrabble.137"),
								JOptionPane.PLAIN_MESSAGE, null, letterArray,
								letterArray[0]);

						if (resp != null) 
						{
							current.setSymbol(resp.toLowerCase().charAt(0));
							current.setAscii(resp.toLowerCase().charAt(0));
						}
					}
					if (resp != null) 
					{
						LetterMove lm = new LetterMove(x, y, current);
						String xx = "" + x; //get x into string
						String yy = "" + y; //get y into string
						if (x < 10) //if x is <10, append 0 to make x double width
							xx = "0" + x;
						if (y < 10) //if y is <10, append 0 to make x double width
							yy = "0" + y;
						moveToSend = moveToSend + xx + yy + current + " "; //construct a string to echo of letter moves
						currentMove.addLetterMove(lm);
						boardButtons[x][y].setText("<html><font size=4>"
								+ Character.toUpperCase(current.getSymbol())
								+ "</font></html>");
						boardButtons[x][y].setIcon(new ImageIcon(
								"resources/images/tiles/letter/letter.png"));
						boardButtons[x][y].setDisabledIcon(new ImageIcon(
								"resources/images/tiles/letter/letter.png"));
						boardButtons[x][y].setEnabled(false);
						rackButtons[lastRack].setForeground(Color.LIGHT_GRAY);
						rackButtons[lastRack].setEnabled(false);
						current = null;
						rackClicked = false;
						lastRack = -1;
					}
					else 
					{
						rackClicked = false;
						lastRack = -1;
					}
				}
			} else if (xcoord.get(cb) != null) // clicked rack button
			{
				if (rackClicked == false) //get letter clicked into current
				{
					Integer coord = (Integer) xcoord.get(cb);
					int x = coord.intValue();
					current = g.getRackLetter(x);
					lastRack = x;
					rackClicked = true;
				} else //if rackClicked = true, swap last clicked letter with newly clicked letter and update the rack's display
				{
					Integer coord = (Integer) xcoord.get(cb);
					int x = coord.intValue();
					Player p = (Player) g.getPlayers().get(uid); //get sitting player
					Rack r = p.getRack(); //get their rack
					//new letter last clicked
					Letter letterToSwap = g.getRackLetter(x);
					//set new letters
					g.getRack().setLetter(x, current);
					g.getRack().setLetter(lastRack, letterToSwap);
					//update the affected button's appearance
					updateRackButton(x, r); //use this rack for click swapping
					updateRackButton(lastRack, r);
					//reset rackClicked for next click
					rackClicked = false;
					current = null;
				}
			}
		}
	}

	
	/**
	 * Locks out a player if they have left the game.
	 * This makes it unable for that player to have a turn by skipping them out of the game.
	 * 
	 * @param id The unique identifier of the player who left.
	 */
	public void clientLeftGame(int id) 
	{	
		setMessageLine(getScrabble().clientNames[id] + messages.getString("Client.7"));
		Player p = (Player) g.getPlayers().get(id);
		p.incrementSkipCounter();
		p.incrementSkipCounter(); //lockout player
		p.setIsLockedOut();
		g.incrementLockOutCounter();
		updateResults();
	}

	/**
	 * 	Whenever a player ends their turn, this method is called.
	 *  It is a general 'perform and reset' method that encapsulates all tasks
	 *  that must be completed when a turn is ended.
	 * 
	 * @param move The Move the player has ended with.
	 * @param option Option allows for certain dialogs to be displayed or not depending on the level of user feedback required.
	 */
	public void endPlayerTurn(Move move, int option) 
	{
		for (int i = 0; i < g.getPlayers().size(); i++) 
		{
			Player p = (Player) g.getPlayers().get(i);
		}

		if (option == 1) //if swap letters, then dont end move, just end turn (no move in progress)
		{
			g.skipCounterIncreaseAndTest(2);
			g.endTurn();
		}
		else if (option == 2)	//if user skipped
		{
			if (g.getTurn() == uid)
			{
				g.endMove(move, 1);
			}
			else
			{
				g.endMoveLight(move);
			}
		}
		else	//if user played an actual move 
		{
			if (g.getTurn() == uid) 
			{ 
				g.endMove(move, 0);
			} 
			else 
			{
				g.endMoveLight(move);
			}
		}
		moveInProgress = false; //move is finished successfully
		history.add(move);
		setBoardButtonsEnabled(false); //this line must be before updateDisplay(), else invalid moves are not refreshed
		updateDisplay();
		currentMove = null;
		rackClicked = false; //reset rack clicking for swapping letters
		//clearRack();
		//System.out.println(toXml());
		//russian tag has a different structure due to langauge differences
		if (getScrabble().i18n.language.equals("ru"))
			playerLabel.setText(messages.getString("scrabble.75") + " " + g.getPlayer().getName()); //$NON-NLS-1$ //$NON-NLS-2$
		else
			playerLabel.setText(" " + g.getPlayer().getName() + messages.getString("scrabble.75")); //$NON-NLS-1$ //$NON-NLS-2$
		setMessageLine(" " + g.getLastPlayer().getName()
				+ messages.getString("scrabble.100") + g.getLastMoveScore()
				+ messages.getString("scrabble.48"));
		setRackButtonsEnabled(true);
		setButtonStates(false, false, false, false, false, false);
		leaveGameButton.setEnabled(true);
		if (g.getTurn() == uid) 
		{
			moveButton.setEnabled(true);
			leaveGameButton.setEnabled(false);
		}
	}


	/**
	 * 	Called at start of game to initialise:
	 *	 the random bag
	 *   racks for all players
	 *   update all player racks, etc
	 * 
	 * @param seed The seed that is received from the server, for repeatable randomization of bag.
	 */
	public void startRemoteGame(long seed) 
	{
		g.randomiseBag(seed);
		setRacksAtStartOfGame();

		Player p = (Player) g.getPlayers().get(uid);
		Rack r = p.getRack();
		updateRack(rackButtons, r);

		updateResults();
		if (getScrabble().i18n.language.equals("ru"))
			playerLabel.setText(messages.getString("scrabble.75") + " " + g.getPlayer().getName()); //$NON-NLS-1$ //$NON-NLS-2$
		else
			playerLabel.setText(g.getPlayer().getName()
					+ messages.getString("scrabble.75")); //$NON-NLS-1$ //$NON-NLS-2$
		setMessageLine(g.getPlayer().getName() + messages.getString("scrabble.77"));
		setButtonStates(false, false, false, false, false, false);
		setRackButtonsEnabled(true);
		setBoardButtonsEnabled(false);
		leaveGameButton.setEnabled(true);
		if (g.getTurn() == uid) 
		{	leaveGameButton.setEnabled(false);
			moveButton.setEnabled(true);
		}
		window.setVisible(true); //only display window when racks have been initialised, saves on the flicker :  )
	}

	/**
	 * 	Deals four racks at the start of the game so that all players can see their racks
	 *  displayed in the results panel.
	 *
	 */
	private void setRacksAtStartOfGame() 
	{
		for (int i = 0; i < g.getPlayers().size(); i++) { //System.out.println(g.getPlayers());
			Player p = (Player) g.getPlayers().get(i);
			Rack r = p.getRack();
			List selection = g.bag.giveLetters(7 - r.getRackSize(), true);
			r.addLetters(selection);
		}
	}

	/**
	 * Sets the functional controls button states all at once.
	 * 
	 * @param a moveButton boolean toggle.
	 * @param b resetMoveButton boolean toggle.
	 * @param c endMoveButton boolean toggle.
	 * @param d skipTurnButton boolean toggle.
	 * @param e swapLettersButton boolean toggle.
	 * @param f hintMoveButton boolean toggle.
	 */
	private void setButtonStates(boolean a, boolean b, boolean c, boolean d,
			boolean e, boolean f) 
	{
		moveButton.setEnabled(a);
		resetMoveButton.setEnabled(b);
		endMoveButton.setEnabled(c);
		skipTurnButton.setEnabled(d);
		swapLettersButton.setEnabled(e);
		if (hintsPlayed < getScrabble().getNumberOfHints()) //if hints played is less than hints limit,
			hintMoveButton.setEnabled(f); //enable hint button
	}

	/**
	 * Sets all board buttons to be the input parameter <b>on</b>'s boolean state.
	 * 
	 * @param on Boolean value controlling all board buttons.
	 */
	private void setBoardButtonsEnabled(boolean trigger) 
	{
		for (int j = 0; j < 15; j++) {
			for (int i = 0; i < 15; i++) {
				Square sq = g.getSquare(i, j);
				if (sq.isOccupied() && trigger == true) {
					boardButtons[i][j].setEnabled(!trigger);
				} else {
					boardButtons[i][j].setEnabled(trigger);
				}
			}
		}
	}

	/**
	 * Sets all rack buttons to be the input parameter <b>on</b>'s boolean state.
	 * These are not the results rack buttons, which never turn on.
	 * 
	 * @param on Boolean value controlling all rack buttons.
	 */
	private void setRackButtonsEnabled(boolean on) 
	{
		for (int i = 0; i < 7; i++) {
			rackButtons[i].setEnabled(on);
		}
	}

	/**
	 * Sets the message line's text.  The message line resids on the south of the main
	 * frame, to the left.  It displays non-crucial information to the client in a passive way.
	 * 
	 * @param s String input, desired message line text.
	 */
	public void setMessageLine(String s) 
	{
		messageLine.setText("   " + s);
	}

	/**
	 * Controls the progressBar status changes.
	 * 
	 * @param on Boolean value denoting desired state of the progress bar.
	 * @param progressBarLabel Desired String label for progress bar.
	 */
	private void progressBarControl(boolean on, String progressBarLabel) 
	{
		progressBar.setIndeterminate(on);
		progressBar.setString(progressBarLabel);
	}

	/**
	 * Controls the progressBar status changes. This method has no String input.
	 * Useful for resetting the progress bar.
	 * 
	 * @param on Boolean value denoting desired state of the progress bar.
	 */
	private void progressBarControl(boolean on) 
	{
		progressBar.setIndeterminate(on);
		progressBar.setString("");
	}

	/**
	 * Starts or stops the timer.
	 * When called, if the timer is stopped, it will start logging the time since the
	 * ActionEvent that triggered it.
	 * If the timer is already running, it will stop the timer and display the time since
	 * the last ActionEvent was called.
	 * 
	 * @param event ActionEvent that triggers timer to start/stop.
	 */
	private void timerControl(ActionEvent event) 
	{
		if (timerRunning == false) 
		{ // Record the time and start the stopwatch.
			timerRunning = true;
			startTime = event.getWhen(); // Time when mouse was clicked.
			//long endTime = event.getWhen();
			progressBarControl(true, messages.getString("scrabble.149"));
			if (timer == null) 
			{
				timer = new Timer(100, this);
				timer.start();
			}
			else
				timer.restart();
		} 
		else 
		{	// Stop the stopwatch.  Compute the elapsed time since the
			//stopwatch was started and display it.
			timer.stop();
			timerRunning = false;
			long endTime = event.getWhen();
			double seconds = (endTime - startTime) / 1000.0;
			progressBarControl(false, messages.getString("scrabble.150")
					+ seconds + messages.getString("scrabble.151"));
		}
	}

	/**
	 * Returns a string containing the current history, as defined in variable <b>history</b>.
	 * 
	 * @return String
	 */
	public String toXml() 
	{
		String res = "<history>\n";
		for (int i = 0; i < history.size(); i++) {
			Move m = (Move) history.get(i);
			res = res + m.toXml();
		}
		return res + "</history>\n";
	}

	/**
	 * Saves game history to .XML file.
	 */
	public void saveDataToFile() 
	{
		File startingpoint = new File("output");
		JFileChooser fc = new JFileChooser();
		fc.setCurrentDirectory(startingpoint);
		fc.setDialogTitle(messages.getString("scrabble.145"));
		int returnVal = fc.showSaveDialog(this);
		if (returnVal == JFileChooser.APPROVE_OPTION) {
			File file = fc.getSelectedFile();
			try {
				PrintWriter out = new PrintWriter(new BufferedWriter(
						new FileWriter(file)));
				out.println(toXml());
				out.close();
			} 
			catch (Exception e) 
			{	System.err.println(messages.getString("scrabble.146"));
			}
			//copied from action listener - will ensure that closing an ended game 
			//window does not disconnect a client from the server
			if (getScrabble().server.gameInProgress && !g.getGameEnded()) {
				getScrabble().client.setMessageOutput(9, null);
			}
			dispose();
			//System.exit(0); //exit if save successful
		} else {
			return;
		}
	}
	
	/**
	 * Creates the RemoteScrabble instance, displays the player's GUI interface.
	 * 
	 * @param t The turn the game should start on - this is which player starts.
	 * @param id The uid of the client game.
	 */
	public static void createAndShowGUI(int t, int id) {
		turn = t;
		uid = id;
		window = new RemoteScrabble();

		window.setIconImage(new ImageIcon("resources/images/icon.png").getImage());
		window.pack();
		window.setLocationRelativeTo(null); //centres the frame on the screen
	}
}