/**
 * @(#)LocalScrabble.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;

/**
 * LocalScrabble describes the player's GUI interface for the local Scrabble game.
 * All action listeners are defined. The Scrabble game is i18n-ed.
 * All methods in LocalScrabble relate to how the user interacts with the GUI.
 * All game related methods are contained within different classes, notably <b>Game</b>
 * and <b>ScrabbleBuilder</b>.
 * 
 * @author 3GP01 - Hardev Bhamra, Deepak Chandarana, James Tompkin
 * @version 2.0
 * @see Game
 * @see ScrabbleBuilder
 *
 */

/* Swing is not natively slow, however it is the java look and feel's response that is slow.
 * This is 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 LocalScrabble extends JFrame implements ActionListener 
{
	static LocalScrabble window;

	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 endMoveButton = new JButton(messages.getString("endMoveButton"));

	JButton hintMoveButton = new JButton(messages.getString("hintMoveButton"));

	JButton swapLettersButton = new JButton(messages
			.getString("swapLettersButton"));

	JButton skipTurnButton = new JButton(messages.getString("skipTurnButton"));

	JButton startGameButton = new JButton(messages.getString("startGameButton"));

	JButton addPlayerButton = new JButton(messages.getString("addPlayerButton"));

	JButton endGameButton = new JButton(messages.getString("endGameButton"));

	JButton helpButton = new JButton(messages.getString("helpButton"));

	JButton[][] boardButtons = new JButton[15][15];

	JButton[] rackButtons = new JButton[7];

	JButton[][] resultsRackDisplay = new JButton[4][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

	Move currentMove;

	Letter current = null;

	int numberOfPlayers = 0;

	int lastRack = -1; // current != null  =>  lastRack: 0..7

	boolean gameInProgress = false;

	boolean moveInProgress = false;

	boolean rackClicked = false;

	boolean swapClicked = 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);

	//define icons for use throughout game
	//
	ImageIcon letter = new ImageIcon("resources/images/tiles/letter/letter.png");

	ImageIcon letterdisabled = new ImageIcon("resources/images/tiles/letter/letterdisabled.png");

	ImageIcon letterpressed = new ImageIcon("resources/images/tiles/letter/letterpressed.png");

	ImageIcon letterrollover = new ImageIcon("resources/images/tiles/letter/letterrollover.png");

	/**
	 * Gets the current <b>Scrabble</b> game.
	 *  
	 * @return Scrabble
	 */
	public Scrabble getScrabble() 
	{
		return Scrabble.scrabble;
	}

	/**
	 * LocalScrabble creates and initialises a game, ready to accept response from user. 
	 */
	public LocalScrabble() 
	{
		getScrabble().arithNumberOfWindows("++"); //increase the number of open windows //$NON-NLS-1$
		getScrabble().incrementNumberOfGamesPlayed(); //increase the number of games played

		setMessageLine(" " + messages.getString("messageLineIntro") + languageForGUI + messages.getString("scrabble.14")); //$NON-NLS-2$ //$NON-NLS-3$
		if (getScrabble().i18n.language.equals("ru"))
			setTitle(messages.getString("scrabbleWindowTitle") + " "
					+ getScrabble().getNumberOfGamesPlayed() + "\u044F");
		else
			setTitle(messages.getString("scrabbleWindowTitle")
					+ getScrabble().getNumberOfGamesPlayed());
		//set tooltips
		moveButton.setToolTipText(messages.getString("scrabble.16")); //$NON-NLS-1$
		resetMoveButton.setToolTipText(messages.getString("scrabble.17")); //$NON-NLS-1$
		endMoveButton.setToolTipText(messages.getString("scrabble.18")); //$NON-NLS-1$
		swapLettersButton.setToolTipText(messages.getString("scrabble.19")); //$NON-NLS-1$
		skipTurnButton.setToolTipText(messages.getString("scrabble.20")); //$NON-NLS-1$
		startGameButton.setToolTipText(messages.getString("scrabble.21")); //$NON-NLS-1$
		addPlayerButton.setToolTipText(messages.getString("scrabble.22")); //$NON-NLS-1$
		endGameButton.setToolTipText(messages.getString("scrabble.23")); //$NON-NLS-1$
		hintMoveButton.setToolTipText(messages.getString("scrabble.24")); //$NON-NLS-1$
		helpButton.setToolTipText(messages.getString("scrabble.25")); //$NON-NLS-1$

		//Mnemonics of buttons
		moveButton.setMnemonic(KeyEvent.VK_M);
		resetMoveButton.setMnemonic(KeyEvent.VK_R);
		endMoveButton.setMnemonic(KeyEvent.VK_E);
		hintMoveButton.setMnemonic(KeyEvent.VK_H);
		swapLettersButton.setMnemonic(KeyEvent.VK_W);
		skipTurnButton.setMnemonic(KeyEvent.VK_P);
		startGameButton.setMnemonic(KeyEvent.VK_S);
		addPlayerButton.setMnemonic(KeyEvent.VK_A);
		endGameButton.setMnemonic(KeyEvent.VK_G);
		helpButton.setMnemonic(KeyEvent.VK_H);

		g = new Game(getScrabble().getScrabbleBuilder());

		boardPanel.setDoubleBuffered(true); //should reduce flickering on the board

		numberOfPlayers = 0; //changed with addition of Start Game button

		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")); //$NON-NLS-1$
		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' //$NON-NLS-1$
		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);
		progressBar.setAlignmentX(Component.RIGHT_ALIGNMENT);
		progressBar.setPreferredSize(new Dimension(216, 18));
		progressBar.setMaximumSize(new Dimension(300, 18));
		southPanel.add(Box.createRigidArea(new Dimension(10,0)));

		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 functional
		if (!getScrabble().isHeightChangesNecessary())
			getContentPane().add(logoBar);
		getContentPane().add(boardResultsPanel);
		getContentPane().add(rackPanel);
		getContentPane().add(southPanel);

		setButtonStates(false, false, false, false, false, false);
		startGameButton.setEnabled(false);
		endGameButton.setEnabled(false);

		this.getRootPane().setDefaultButton(addPlayerButton);
		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") }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				int n = JOptionPane
						.showOptionDialog(frame, messages
								.getString("saveRequest"), //$NON-NLS-1$
								messages.getString("saveRequestTitle"), //$NON-NLS-1$
								JOptionPane.YES_NO_OPTION,
								JOptionPane.QUESTION_MESSAGE, null, options,
								options[0]);

				if (n == JOptionPane.YES_OPTION) //if want to save, run filechooser
				{
					saveDataToFile();
				} 
				else if (n == JOptionPane.CANCEL_OPTION) //cancel dialog, return to scrabble
				{
				} 
				else if (n == JOptionPane.NO_OPTION) //quit
				{
					dispose();
					getScrabble().arithNumberOfWindows("--"); //$NON-NLS-1$
					if (getScrabble().getNumberOfWindows() == 0) {
						System.exit(0);
					}
				}
			}
		});
	}

	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.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("-"); //$NON-NLS-1$
			xcoord.put(jb, new Integer(i));
			jb.addActionListener(this);
			rackButtons[i] = jb;
			rackButtonsPanel.add(jb);
			jb.setEnabled(false);
			jb.setBackground(turquoise);
			jb.setIcon(letter); //new ImageIcon("resources/images/tiles/letter/letter.png")); //$NON-NLS-1$
			jb.setDisabledIcon(letter); //new ImageIcon("resources/images/tiles/letter/letter.png")); //$NON-NLS-1$
			jb.setRolloverIcon(letterrollover); //new ImageIcon("resources/images/tiles/letter/letterrollover.png")); //$NON-NLS-1$
			jb.setPressedIcon(letterpressed); //new ImageIcon("resources/images/tiles/letter/letterpressed.png")); //$NON-NLS-1$
			//jb.setSelectedIcon(new ImageIcon("resources/images/tiles/letter/letterpressed.png")); //$NON-NLS-1$
			jb.setVerticalTextPosition(SwingConstants.CENTER);
			jb.setHorizontalTextPosition(SwingConstants.CENTER);
			jb.setBorder(null); //v. important - tells UIManager not to display custom button UI rollover
			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 buttPanel = new JPanel();
		buttPanel.setLayout(new BoxLayout(buttPanel, BoxLayout.X_AXIS));
		buttPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
		buttPanel.add(startGameButton);
		startGameButton.addActionListener(this);
		buttPanel.add(addPlayerButton);
		addPlayerButton.addActionListener(this);
		buttPanel.add(endGameButton);
		endGameButton.addActionListener(this);
		resultsPanel.add(buttPanel);
		resultsPanel.add(Box.createVerticalGlue());

		for (int i = 0; i < 4; i++) 
		{	//label for name
			JLabel jl = new JLabel(messages.getString("addPlayerButton")); //$NON-NLS-1$
			jl.setAlignmentX(Component.LEFT_ALIGNMENT);
			resultLabels[i] = jl;
			resultsPanel.add(jl);
			resultsPanel.add(Box.createRigidArea(new Dimension(0, 5)));
			//panel for results display rack
			JPanel panel = new JPanel();
			panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
			panel.setAlignmentX(Component.LEFT_ALIGNMENT);
			panel.setMaximumSize(new Dimension(280, 40));
			panel.add(Box.createHorizontalGlue());

			//add row of buttons under player's nam/score to show racks of player
			for (int j = 0; j < 7; j++) 
			{
				final JButton jb = new JButton("-"); //$NON-NLS-1$
				xcoord.put(jb, new Integer(j));
				resultsRackDisplay[i][j] = jb;

				jb.setEnabled(false);
				jb.setBackground(turquoise);
				jb.setForeground(Color.GRAY);
				jb.setIcon(letter); //new ImageIcon("resources/images/tiles/letter/letter.png")); //$NON-NLS-1$
				jb.setDisabledIcon(letter); //new ImageIcon("resources/images/tiles/letter/letter.png")); //$NON-NLS-1$
				jb.setVerticalTextPosition(SwingConstants.CENTER);
				jb.setHorizontalTextPosition(SwingConstants.CENTER);
				jb.setBorder(null);
				jb.setVisible(false);
				panel.add(jb);
			}
			panel.add(Box.createHorizontalGlue());
			resultsPanel.add(panel);
			//resultsPanel.add(Box.createRigidArea(new Dimension(0,5)));
			resultsPanel.add(Box.createVerticalGlue());
		}
		bagLabel = new JLabel(""); //$NON-NLS-1$
		//resultsPanel.add(Box.createRigidArea(new Dimension(0, 10)));
		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(letter); //new ImageIcon("resources/images/tiles/letter/letter.png")); //$NON-NLS-1$
					jb.setDisabledIcon(letter); //new ImageIcon("resources/images/tiles/letter/letter.png")); //$NON-NLS-1$
					jb.setBackground(ivory);
				} 
				else 
				{
					jb.setIcon(sq.getIcon(sq.getColor()));
					jb.setText(null);
					jb.setBackground(sq.getColor());
					jb.setEnabled(true);
				}
			}
		}
		Rack r = g.getRack();
		updateRack(rackButtons, r);
		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$
			for (int j = 0; j < 7; j++) 
			{
				resultsRackDisplay[i][j].setVisible(true);
			}
		}
		if (gameInProgress) //should blank out unneeded player labels once in game
		{
			for (int i = numPlayers; i < 5; i++) 
			{
				resultLabels[i].setText(""); //$NON-NLS-1$
				bagLabel.setText(g.getBagSize()
						+ messages.getString("scrabble.50")); //$NON-NLS-1$
			}
		} 
		else 
		{
			for (int i = numPlayers; i < 5; i++) //until game has started, show player slots
			{
				resultLabels[i].setText(messages.getString("addPlayerButton")); //$NON-NLS-1$
				bagLabel.setText(""); //$NON-NLS-1$
				if (g.getPlayers().size() == 1)
					playerLabel.setText(messages.getString("scrabble.53")); //$NON-NLS-1$
				else
					playerLabel.setText(g.getPlayers().size() + messages.getString("scrabble.55")); //$NON-NLS-1$ //$NON-NLS-2$
			}
		}
	}

	/**
	 * 	Updates a results rack of a player of a turn.
	 * 
	 * @param r The results rack to be updated.
	 * @param turn The turn of the player.
	 */
	private void updateResultsRack(Rack r, int turn) 
	{
		for (int i = 0; i < r.getRackSize(); i++) 
		{
			Letter lt = r.getLetter(i);
			if (lt.isBlank()) 
			{
				resultsRackDisplay[turn][i].setText(" "); //$NON-NLS-1$
			} 
			else 
			{
				resultsRackDisplay[turn][i]
						.setText("<html>" + Character.toUpperCase(lt.getSymbol()) + " <sub>" + lt.getScore() + "</sub></html>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			}
		}
		for (int i = r.getRackSize(); i < 7; i++) 
		{
			resultsRackDisplay[turn][i].setText("-");
		}
	}

	/**
	 * 	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(" "); //$NON-NLS-1$
		} else {
			rackButtons[i]
					.setText("<html>" + Character.toUpperCase(lt.getSymbol()) + " <sub>" + lt.getScore() + "</sub></html>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		}
		//rackButtons[i].setToolTipText(messages.getString("scrabble.64") + lt.getScore());  //$NON-NLS-1$
	}

	/**
	 * 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(" "); //$NON-NLS-1$
			}
			else 
			{
				rackButtons[i].setText("<html>" + Character.toUpperCase(lt.getSymbol()) + " <sub>" + lt.getScore() + "</sub></html>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			}
			//rackButtons[i].setToolTipText(messages.getString("scrabble.64") + lt.getScore()); //$NON-NLS-1$
			rackButtons[i].setForeground(Color.BLACK);
			rackButtons[i].setEnabled(true);
		}
		for (; i < 7; i++) 
		{
			rackButtons[i].setText("-"); //$NON-NLS-1$
			rackButtons[i].setEnabled(false);
		}
	}

	/**
	 * Resets the display of the 'control rack'.
	 *
	 */
	private void clearRack() 
	{
		for (int i = 0; i < 7; i++) {
			rackButtons[i].setText("-"); //$NON-NLS-1$
			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("startGameButton"))) //$NON-NLS-1$
				{
					if (numberOfPlayers < 1) 
					{
						JFrame frame = null;
						JOptionPane.showMessageDialog(frame, messages
								.getString("scrabble.73")); //$NON-NLS-1$
					} 
					else 
					{
						gameInProgress = true;
						setRacksAtStartOfGame();
						g.setRandomTurn(); //randomly selects a player to go first
						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$
						//verbose displaying of whose turn it is
						setMessageLine(g.getPlayer().getName() + messages.getString("scrabble.77")); //$NON-NLS-1$ //$NON-NLS-2$
						updateResults(); //updates player labels
						cb.setEnabled(false);
						addPlayerButton.setEnabled(false);
						moveButton.setEnabled(true);
						endGameButton.setEnabled(true);
					}
				}
				if (command.equals(messages.getString("endGameButton"))) //$NON-NLS-1$
				{
					if (moveInProgress) //if a move is in progress, timer will be going,
						timerControl(e); //so stop the timer
					setButtonStates(false, false, false, false, false, false);
					endGameButton.setEnabled(false);
					gameInProgress = false;
					g.gameEnd();
				}
				if (command.equals(messages.getString("helpButton"))) //$NON-NLS-1$
				{
					HelpSystem.createAndShowHS();
				}
				if (command.equals(messages.getString("hintMoveButton"))) //$NON-NLS-1$
				{
					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")); //$NON-NLS-1$
					} 
					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;
							}
						}
					}
					hintMoveButton.setEnabled(false);

					Cursor cur_default = new Cursor(Cursor.DEFAULT_CURSOR); //restore cursor to default
					setCursor(cur_default);
				}
				if (command.equals(messages.getString("addPlayerButton"))) //$NON-NLS-1$
				{
					boolean uniqueNames = true;

					//SCRABBLE224
					//Fix for bad input of computer/human
					//Fix for non-unique names leading to player confusion

					//computer or human
					String corh = null; //dialog box for selecting whether a player should be human or computer
					Object[] options = {messages.getString("scrabble.83"), messages.getString("scrabble.84") }; //uses a modified YES/NO box with custom labels //$NON-NLS-1$ //$NON-NLS-2$
					int n = JOptionPane.showOptionDialog(null, messages
							.getString("scrabble.85"), //$NON-NLS-1$
							messages.getString("scrabble.86"), //$NON-NLS-1$
							JOptionPane.YES_NO_OPTION,
							JOptionPane.QUESTION_MESSAGE, null, options,
							options[0]);
					if (n == JOptionPane.YES_OPTION) {
						corh = "H";} //Computer option //$NON-NLS-1$
					else if (n == JOptionPane.NO_OPTION) {
						corh = "C";} //Human option //$NON-NLS-1$

					//name of player, has to be unique
					String name = null;
					boolean nonNullName = false;
					while (!nonNullName) //loop to keep name from not being null
					{
						name = JOptionPane.showInputDialog(messages
								.getString("scrabble.89")); //ask for name //$NON-NLS-1$
						if ((null == name)) //effectively, this equates to the user pressing 'cancel' or 'close', in which case break the loop and end the name request
						{
							break;
						} else if (name.equals("") || name.equals("*")) //if the player presses ok without entering any name, string 'name' is set to "" (or "*" for computer player) - this is unacceptable, so we test for it here //$NON-NLS-1$ //$NON-NLS-2$
						{
							nonNullName = false;
						} else if (name.length() > 20) {
							nonNullName = false;
							JOptionPane.showMessageDialog(null, messages
									.getString("scrabble.92")); //$NON-NLS-1$
						} else //if a valid name has been entered, set the flag and end the loop
						{
							nonNullName = true;
						}
					}

					if (corh.equals("C")) //affix star to end of name if player is computer //$NON-NLS-1$
					{
						name = "*" + name;} //this differentiates between computer and human players //$NON-NLS-1$

					List players = g.getPlayers(); //gets list of players 
					int numPlayers = players.size(); //gets size of list of players
					for (int i = 0; i < numPlayers; i++) //for each player
					{
						Player p = (Player) players.get(i); //get player info
						if (p.getName().equals(name)) //if name of player equals name being entered,
						{
							JOptionPane.showMessageDialog(null, messages
									.getString("scrabble.95")); //show dialog //$NON-NLS-1$
							uniqueNames = false; //non unique name
						}
					}

					//if all names are unique (ie no flag has been raised), then add player
					//needs to check if no names are null so that if the player cancels at the
					//enter name stage, it doesnt add a player with no name
					if (uniqueNames && nonNullName) {
						g.addPlayer(name, corh); //add player
						numberOfPlayers++; //increment the number of players in the game
						if (numberOfPlayers >= 1) {
							startGameButton.setEnabled(true);
						}
						if (numberOfPlayers >= 4) {
							cb.setEnabled(false);
						} //disables the 'Add Players' button
						updateResults();
					}
				}
				if (command.equals(messages.getString("moveButton"))) //$NON-NLS-1$
				{
					if (gameInProgress) {
						moveInProgress = true; //move is underway
						setButtonStates(false, true, true, true, true, true);
						setRackButtonsEnabled(true);

						Player pl = g.getPlayer();
						currentMove = new Move(pl);
						updateDisplay();
						timerControl(e);
						if (pl instanceof ComputerPlayer) {
							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) {
								g.endMove(mv, 0); //end computer's turn
								updateResultsRack(g.getLastPlayer().getRack(),
										g.getLastTurn());
								history.add(mv);
								timerControl(e);
								updateDisplay();
								currentMove = null;
								clearRack();
								setButtonStates(true, false, false, false,
										false, false);
								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.101")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
							} else //if computer cannot find a word
							{
								g.getPlayer().incrementSkipCounter();
								g.endTurn();
								updateResultsRack(g.getLastPlayer().getRack(),
										g.getLastTurn());
								timerControl(e);
								updateDisplay();
								currentMove = null;
								clearRack();
								setButtonStates(true, false, false, false,
										false, false);
								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(messages
										.getString("scrabble.104") + g.getLastPlayer().getName() + messages.getString("scrabble.105")); //$NON-NLS-1$ //$NON-NLS-2$
							}
							Cursor cur_default = new Cursor(
									Cursor.DEFAULT_CURSOR); //restore cursor to default
							setCursor(cur_default);
						}
					}
					return;
				}
				if (command.equals(messages.getString("resetMoveButton"))) //$NON-NLS-1$
				{
					updateDisplay();
					hintMoveButton.setEnabled(true);
					currentMove = new Move(g.getPlayer());
				}
				if (command.equals(messages.getString("skipTurnButton"))) //$NON-NLS-1$
				{
					timerControl(e);

					currentMove = new Move(g.getPlayer()); //empty out move : )
					//process move (as endMove)
					g.endMove(currentMove, 1);
					//update the results rack of the (now) previous player's turn
					//this is the player who has just performed a move
					updateResultsRack(g.getLastPlayer().getRack(), g.getLastTurn());
					history.add(currentMove);
					updateDisplay();
					currentMove = null;
					rackClicked = false; //reset rack clicking for swapping letters
					clearRack();
					//System.out.println(toXml());
					moveInProgress = false; //move is finished successfully
					setButtonStates(true, false, false, false, false, false);
					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.101")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

					if (g.getGameEnded()) //if we've reached the end of the game,
					{
						moveButton.setEnabled(false);
						endGameButton.setEnabled(false); //make sure this button is disabled
						setRackButtonsEnabled(false);
					}

				}
				if (command.equals(messages.getString("swapLettersButton"))) //$NON-NLS-1$
				{
					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")); //$NON-NLS-1$
					slOK.setEnabled(false);
					JButton slReset = new JButton(messages
							.getString("scrabble.115")); //$NON-NLS-1$
					JButton slCancel = new JButton(messages.getString("cancel")); //$NON-NLS-1$

					buttonPanel.add(slOK);
					buttonPanel.add(slReset);
					buttonPanel.add(slCancel);

					for (int i = 0; i < 7; i++) {
						final JButton jb1 = new JButton("-"); //$NON-NLS-1$
						xcoord.put(jb1, new Integer(i));
						swapLettersButtons[i] = jb1;
						jb1.setBackground(turquoise); //setup cool buttons
						jb1.setIcon(letter); //new ImageIcon("resources/images/tiles/letter/letter.png")); //$NON-NLS-1$
						jb1.setRolloverIcon(letterrollover); //new ImageIcon("resources/images/tiles/letter/letterrollover.png")); //$NON-NLS-1$
						jb1.setPressedIcon(letterpressed); //new ImageIcon("resources/images/tiles/letter/letterpressed.png")); //$NON-NLS-1$
						jb1.setDisabledIcon(letterdisabled); //new ImageIcon("resources/images/tiles/letter/letterdisabled.png")); //$NON-NLS-1$
						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) { //need to return selected letters to bag	
							rsl.removeLetters(swapLetters); //remove from rack selected letters
							g.bag.replaceLetters(swapLetters); //replace letters into bag

							//gives amount of letters less than 7
							List selection = g.bag.giveLetters(7 - rsl
									.getRackSize());
							rsl.addLetters(selection); //add new random selection letters

							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);
							swapClicked = true;
						}
					});

					slReset.addActionListener(new ActionListener() {
						public void actionPerformed(ActionEvent e) {
							for (int i = 0; i < 7; i++) {
								swapLettersButtons[i].setEnabled(true);
							}
							swapLetters.removeAll(swapLetters);
							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. //$NON-NLS-1$
					frame.setModal(true);
					frame.setTitle(messages.getString("swapLettersButton")); //$NON-NLS-1$
					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"))) //$NON-NLS-1$
				{
					if (moveInProgress) //if a move is in play, do this, else, dont perform actions
					{
						timerControl(e);

						if (swapClicked) {
							g.endMove(currentMove, 2);
							swapClicked = false;
						} else {
							g.endMove(currentMove, 0);
						}

						//update the results rack of the (now) previous player's turn
						//this is the player who has just performed a move
						updateResultsRack(g.getLastPlayer().getRack(), g.getLastTurn());
						history.add(currentMove);
						updateDisplay();
						currentMove = null;
						rackClicked = false; //reset rack clicking for swapping letters
						clearRack();
						//System.out.println(toXml());
						moveInProgress = false; //move is finished successfully
						setButtonStates(true, false, false, false, false, false);
						//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")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
					}
					if (g.getGameEnded()) //if we've reached the end of the game,
					{
						moveButton.setEnabled(false);
						endGameButton.setEnabled(false); //make sure this button is disabled
						setRackButtonsEnabled(false);
					}
				}
			}
			if (ycoord.get(cb) != null) // clicked board button
			{
				if (current == null || currentMove == null) // nothing to place, ignore
				{
					setMessageLine(messages.getString("scrabble.0"));  //$NON-NLS-1$
				}
				else
				{
					String resp = ""; //$NON-NLS-1$
					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"), //$NON-NLS-1$
								messages.getString("scrabble.137"), //$NON-NLS-1$
								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);
						currentMove.addLetterMove(lm);
						boardButtons[x][y]
								.setText("<html><font size=4>" + Character.toUpperCase(current.getSymbol()) + "</font></html>"); //$NON-NLS-1$ //$NON-NLS-2$
						boardButtons[x][y].setIcon(letter); //new ImageIcon("resources/images/tiles/letter/letter.png")); //$NON-NLS-1$
						boardButtons[x][y].setDisabledIcon(letter); //new ImageIcon("resources/images/tiles/letter/letter.png")); //$NON-NLS-1$
						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();
					//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, g.getRack());
					updateRackButton(lastRack, g.getRack());
					//reset rackClicked for next click
					rackClicked = false;
					current = null;
				}
			}
		}
	}

	/**
	 * 	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() 
	{
		int turn = g.getTurn();
		for (int i = 0; i < numberOfPlayers; i++) {
			Player p = (Player) g.getPlayers().get(turn);
			Rack r = p.getRack();
			List selection = g.bag.giveLetters(7 - r.getRackSize());
			r.addLetters(selection);
			updateResultsRack(r, turn);
			turn++;
		}
	}

	/**
	 * 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);
		hintMoveButton.setEnabled(f);
	}

	/**
	 * 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 on) 
	{
		for (int j = 0; j < 15; j++) 
		{
			for (int i = 0; i < 15; i++) 
			{
				boardButtons[i][j].setEnabled(on);
			}
		}
	}

	/**
	 * 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);	 //$NON-NLS-1$
	}

	/**
	 * 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(""); //$NON-NLS-1$
	}

	/**
	 * 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")); //$NON-NLS-1$
			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")); //$NON-NLS-1$ //$NON-NLS-2$
		}
	}
	
	/**
	 * Returns a string containing the current history, as defined in variable <b>history</b>.
	 * 
	 * @return String
	 */
	public String toXml() 
	{
		String res = "<history>\n"; //$NON-NLS-1$
		for (int i = 0; i < history.size(); i++) {
			Move m = (Move) history.get(i);
			res = res + m.toXml();
		}
		return res + "</history>\n"; //$NON-NLS-1$
	}

	/**
	 * Saves game history to .XML file.
	 */
	public void saveDataToFile() 
	{
		File startingpoint = new File("output"); //$NON-NLS-1$
		JFileChooser fc = new JFileChooser();
		fc.setCurrentDirectory(startingpoint);
		fc.setDialogTitle(messages.getString("scrabble.145")); //$NON-NLS-1$
		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"));} //$NON-NLS-1$
				System.exit(0); //exit if save successful
			}
		else
		{
			return;
		}
	}

	/**
	 * Creates the LocalScrabble instance, displays the player's GUI interface.
	 */
	public static void createAndShowGUI() 
	{
		window = new LocalScrabble();

		window.setIconImage(new ImageIcon("resources/images/icon.png").getImage()); //$NON-NLS-1$

		window.pack();
		window.setLocationRelativeTo(null); //centres the frame on the screen
		window.setVisible(true);
	}
}