/*
* Scrabaid
*
* Revisions:
*
* 18/05/2001 lc - v1.0 release
*
* ----------------------------------------------------------------------------
*
* Scrabaid version 1.0, Copyright 2001 Linus Chang
* Scrabaid comes with ABSOLUTELY NO WARRANTY; for details, view the LICENCE
* file included with this distribution. This is free software, and you are
* welcome to redistribute it under certain conditions; view the LICENCE
* file for details.
*/

import java.awt.*;
import java.awt.event.*;
import java.util.*;

class ScrabaidBoard extends Panel {

	TextField[][] m_board = new TextField[15][15];
	TextField m_tray = new TextField();

	// These 5 for the temporary showing of matches
	private String [] m_matchOldLetters;
	private Color [] m_matchOldColors;
	private boolean m_matchShowing;
	private ScrabaidMatch m_match;
	private String m_matchOldTray;

	private final int WIDTH = 25;
	private final int SPACING = 2;
	private final int FONTSIZE = 20;

	public ScrabaidBoard() {
		setLayout(new BorderLayout());
		Panel boardPanel = setupBoard();
		Panel trayPanel = setupTray();
	
		add("North", boardPanel);
		add("Center", trayPanel);
	}
	private Panel setupBoard() {
		Panel panel = new Panel();
		BoardKeyHandler keyHandler = new BoardKeyHandler();
		panel.setBackground(Color.darkGray);
		panel.setLayout(null);
		panel.setSize(15 * (WIDTH + SPACING) + SPACING, 15 * (WIDTH + SPACING) + SPACING);
		Font boardFont = new Font("Monospaced", Font.BOLD, FONTSIZE);
		for (int row = 0; row < 15; row++) {
			for (int col = 0; col < 15; col++) {
				m_board[row][col] = new TextField();
				panel.add(m_board[row][col]);
				m_board[row][col].setBounds(SPACING + col * (WIDTH+SPACING), SPACING + row * (WIDTH+SPACING), WIDTH, WIDTH);
				m_board[row][col].addKeyListener(keyHandler);
				m_board[row][col].setFont(boardFont);
				m_board[row][col].setText(" ");
			}
		}
		colourBoard(true);
		setBoardEnabled(false);
		return panel;
	}
	private Panel setupTray() {
		Panel panel = new Panel();
		panel.add(new Label("Letters in your tray:"));
		m_tray.setFont(new Font("Monospaced",Font.PLAIN,12));
		m_tray.setColumns(7);
		m_tray.setEnabled(false);
		m_tray.addKeyListener(new TrayKeyHandler());
		panel.add(m_tray);
		return panel;
	}

	// Clearing the board
	public void clear() {
		for (int row = 0; row < 15; row++)
			for (int col = 0; col < 15; col++)
				m_board[row][col].setText(" ");
		m_tray.setText("");
	}
	public boolean isBlank() {
		for (int row = 0; row < 15; row++)
			for (int col = 0; col < 15; col++)
				if (!m_board[row][col].getText().equals(" "))
					return false;
		return true;

	}

	//
	// Method for colouring the board
	//
	public void colourBoard(boolean showTiles) {
		// Colour the board with its natual colours, its natual colours together with
		// yellow for filled in tiles
		for (int row = 0; row < 15; row++) {
			for (int col = 0; col < 15; col++) {
				if (!showTiles || m_board[row][col].getText().equals(" ")) {
					// Revert to the board's natural colouring
					if (Score.isTripleWord(row, col))
						m_board[row][col].setBackground(Color.red);
					else if (Score.isDoubleWord(row, col))
						m_board[row][col].setBackground(Color.pink);
					else if (Score.isTripleLetter(row, col))
						m_board[row][col].setBackground(Color.blue);
					else if (Score.isDoubleLetter(row,col))
						m_board[row][col].setBackground(Color.cyan);
					else
						m_board[row][col].setBackground(Color.lightGray);
				}
				else {
					// Colour the board yellow for a filled-in tile.
					m_board[row][col].setBackground(Color.yellow);
				}
			}
		}
	}
	//
	// Methods for showing or hiding a match on a board
	//
	public void showMatch(ScrabaidMatch sm) {
		String s = sm.word.getString();
		String usedLetters = "";

		m_matchOldTray = m_tray.getText();
		m_matchShowing = true;
		m_match = sm;
		m_matchOldLetters = new String[s.length()];
		m_matchOldColors = new Color[s.length()];
		if (sm.row == -1) { // Column word
			for (int c = 0; c < s.length(); c++) {
				m_matchOldLetters[c] = m_board[c + sm.startingPosition][sm.col].getText();
				m_matchOldColors[c] = m_board[c + sm.startingPosition][sm.col].getBackground();

				m_board[c + sm.startingPosition][sm.col].setText(new Character(s.charAt(c)).toString());
				m_board[c + sm.startingPosition][sm.col].setBackground(Color.white);
				if (m_matchOldLetters[c].equals(" ")) {
					usedLetters = usedLetters + new Character(s.charAt(c)).toString();
				}
			}
		}
		else { // Row word
			for (int c = 0; c < s.length(); c++) {
				m_matchOldLetters[c] = m_board[sm.row][c + sm.startingPosition].getText();
				m_matchOldColors[c] = m_board[sm.row][c + sm.startingPosition].getBackground();

				m_board[sm.row][c + sm.startingPosition].setText(new Character(s.charAt(c)).toString());
				m_board[sm.row][c + sm.startingPosition].setBackground(Color.white);
				if (m_matchOldLetters[c].equals(" ")) {
					usedLetters = usedLetters + new Character(s.charAt(c)).toString();
				}
			}
		}
		String newTray = calculateTray(m_matchOldTray, usedLetters);
		m_tray.setText(newTray);
	}
	public void hideMatch() {
		if (!m_matchShowing)
			return;

		String s = m_match.word.getString();
		if (m_match.row == -1) { // Column word
			for (int c = 0; c < s.length(); c++) {
				m_board[c + m_match.startingPosition][m_match.col].setText(m_matchOldLetters[c]);
				m_board[c + m_match.startingPosition][m_match.col].setBackground(m_matchOldColors[c]);
			}
		}
		else { // Row word
			for (int c = 0; c < s.length(); c++) {
				m_board[m_match.row][c + m_match.startingPosition].setText(m_matchOldLetters[c]);
				m_board[m_match.row][c + m_match.startingPosition].setBackground(m_matchOldColors[c]);
			}
		}
		m_tray.setText(m_matchOldTray);
		m_matchShowing = false;
	}
	public void acceptMatch() {
		m_matchShowing = false;
		colourBoard(true);
	}

	//
	// Set the board to be enabled or disabled
	//
	public void setBoardEnabled(boolean flag) {
		for (int row = 0; row < 15; row++)
			for (int col = 0; col < 15; col++)
				m_board[row][col].setEnabled(flag);
		m_tray.setEnabled(flag);
	}

	public Object[] getSecondaryPattern(ScrabaidMatch sm, char[] tray, int numBlanks) {
		// Returns the "secondary pattern" created by a match that uses one letter out of a tray.
		if (sm.numCharsUsed != 1) {
			System.err.println("INTERNAL ERROR - findLetterUsed - sm has more than one letter used.");
		}
		String s = null; // The string of the secondary pattern
		char ch = 0;	// The character that's used
		int retPos = 0;	// Return position (col/row of the new pattern)

		if (sm.row == -1) {	// Aligned by a column
			for (int pos = 0; pos < sm.word.length(); pos++) {
				if (m_board[pos + sm.startingPosition][sm.col].getText().equals(" ")) {
					// This is the character that's used!
					char []newPattern = getRow(pos + sm.startingPosition).toCharArray();
					ch = sm.word.getString().charAt(pos);
					newPattern[sm.col] = ch;
					retPos = pos + sm.startingPosition;
					s = new String(newPattern);
				}
			}
		}
		else {		// Aligned by a row
			for (int pos = 0; pos < sm.word.length(); pos++) {
				if (m_board[sm.row][pos + sm.startingPosition].getText().equals(" ")) {
					// This is the character
					char []newPattern = getCol(pos + sm.startingPosition).toCharArray();
					ch = sm.word.getString().charAt(pos);
					newPattern[sm.row] = ch;
					retPos = pos + sm.startingPosition;
					s = new String(newPattern);
				}
			}
		}
		// Calculate the new tray
		int newTraySize = tray.length;
		int newNumBlanks = numBlanks;
		for (int pos = 0; pos < tray.length; pos++) {
			if (ch == tray[pos]) {	// Found it!
				newTraySize--;
				break;
			}
		}
		if (newTraySize == tray.length) {
			newNumBlanks--;
		}
		char []newTray = new char[newTraySize];
		boolean found = false;
		int newPos = 0;
		for (int pos = 0; pos < tray.length; pos++) {
			if (!found && ch == tray[pos])
				found = true;
			else {
				newTray[newPos++] = tray[pos];
			}
		}
		Object[] o = new Object[5];
		o[0] = s;
		o[1] = new Character(ch);
		o[2] = new Integer(retPos);
		o[3] = newTray;
		o[4] = new Integer(newNumBlanks);
		return o;
	}

/*	public char findLetterUsed(ScrabaidMatch sm) {
		// Finds the letter used in a ScrabaidMatch, for matches that only use one additional letter!
		char ch = 0;
		if (sm.numCharsUsed != 1) {
			System.err.println("INTERNAL ERROR - findLetterUsed - sm has more than one letter used.");
		}
		if (sm.row == -1) {	// Aligned by a column
			for (int pos = 0; pos < sm.word.length(); pos++) {
				if (m_board[pos + sm.startingPosition][sm.col].getText().equals(" ")) {
					// This is the character
					ch = sm.word.getString().charAt(pos);
				}
			}
		}
		else {		// Aligned by a row
			for (int pos = 0; pos < sm.word.length(); pos++) {
				if (m_board[sm.row][pos + sm.startingPosition].getText().equals(" ")) {
					// This is the character
					ch = sm.word.getString().charAt(pos);
				}
			}
		}
		return ch;
	}
*/
	

	public String findWordRow(int row, int col, char replacement) {
		// Returns a string of the word with a character at (row, col)
		// and oriented horizontally

		int start = col, end = col;
		// Go left as far as possible
		while (start > 0 && !m_board[row][start-1].getText().equals(" ") )
			start--;
		// Go right as far as possible
		while (end < 14 && !m_board[row][end+1].getText().equals(" "))
			end++;
		String s = "";
		for (int c = start; c <= end; c++)
			if (c == col)
				s += new Character(replacement).toString();
			else
				s += m_board[row][c].getText();
		return s;

	}
	public String findWordCol(int row, int col, char replacement) {
		// Returns a string of the word with a character at (row, col)
		// and oriented vertically

		int start = row, end = row;
		// Go up as far as possible
		while (start > 0 && !m_board[start-1][col].getText().equals(" ") )
			start--;
		while (end < 14 && !m_board[end+1][col].getText().equals(" "))
			end++;
		String s = "";
		for (int c = start; c <= end; c++)
			if (c == row)
				s += new Character(replacement).toString();
			else
				s += m_board[c][col].getText();
		return s;
	}

	public boolean isLetterInTray(char letter, char []tray, boolean[] used) {
		// Internal method for the findScore method
		boolean found = false;
		boolean ret = false;
		for (int c = 0; !found && c < tray.length; c++) {
			if (!used[c] && tray[c] == letter) {
				found = true;
				used[c] = true;
				ret = true;
			}
		}
		if (!found) {
			for (int c = 0; !found && c < tray.length; c++) {
				if (!used[c] && tray[c] == ' ') {
					found = true;
					used[c] = true;
				}
			}
		}
		if (!found) {
			System.out.println("isLetterInTray: matching word has a letter not in the tray. D'oh.");
		}
		return ret;
	}
	static public String calculateTray(String origTray, String usedLetters) {
		// Work out the remaining letters.....
		char []t1 = origTray.toCharArray();
		char []t2 = usedLetters.toCharArray();
		boolean []b1 = new boolean[t1.length];
		for (int c2 = 0; c2 < t2.length; c2++) {
			// For each used letter, search for a used up letter in the tray.
			boolean found = false;
			for (int c1 = 0; !found && c1 < t1.length; c1++) {
				if (!b1[c1] && t2[c2] == t1[c1]) {
					found = true;
					b1[c1] = true;
				}
			}
			if (!found) {
				// We must be using up a space...
				for (int c1 = 0; !found && c1 < t1.length; c1++) {
					if (!b1[c1] && t1[c1] == ' ') {
						found = true;
						b1[c1] = true;
					}
				}
			}
			if (!found) {
				System.out.println("INTERNAL ERROR - matching word has a letter not in the tray. D'oh.");
			}
		}
		// Build up new tray...
		String newTray = "";
		for (int c1 = 0; c1 < t1.length; c1++)
			if (!b1[c1])
				newTray = newTray + new Character(t1[c1]).toString();
		return newTray;
	}

	public int findScore(ScrabaidMatch sm) {
		int primaryMultiplier = 1;
		int primaryScore = 0;
		int secondaryScore = 0;
		int row = (sm.row == -1) ? sm.startingPosition : sm.row;	// Starting row of the word
		int col = (sm.col == -1) ? sm.startingPosition : sm.col;	// Starting col of the word

		char []tray = m_tray.getText().toCharArray();
		boolean []trayUsed = new boolean[tray.length];

		if (sm.row == -1) { // This match is aligned by column
			for (int charPos = 0; charPos < sm.word.length(); charPos++) {
				// Does the character already exist on the board? If so:
				// don't check to see if it's on a premium tile, and don't
				// calculate any secondary words
				char currentLetter = sm.word.getString().charAt(charPos);
				if (m_board[row + charPos][col].getText().equals(" ")) {	// We're laying a new tile
					if (!isLetterInTray(currentLetter, tray, trayUsed)) currentLetter = ' ';
					if (Score.isTripleWord(row+charPos,col))
						primaryMultiplier *= 3;
					else if (Score.isDoubleWord(row+charPos,col))
						primaryMultiplier *= 2;
					if (Score.isTripleLetter(row+charPos,col))
						primaryScore += 3 * Score.score(currentLetter);
					else if (Score.isDoubleLetter(row+charPos, col))
						primaryScore += 2 * Score.score(currentLetter);
					else
						primaryScore += Score.score(currentLetter);

					// Now search for "secondary words" created by the match, and add their scores...
					String testWord = findWordRow(row + charPos, col, currentLetter);
					if (testWord.length() > 1) {
						// Given that this word is "secondary" to the primary orientation
						// of the matching word, there can only be one letter that contributes
						// to this word. Thus, this is the only character that we have to
						// worry about premium squares for...
						int wordScore = 0;
						int wordMultiplier = 1;
						for (int qw = 0; qw < testWord.length(); qw++)
							wordScore += Score.score(testWord.charAt(qw));
						if (Score.isTripleLetter(row + charPos, col))
							wordScore += Score.score(currentLetter) * 2;
						else if (Score.isDoubleLetter(row + charPos, col))
							wordScore += Score.score(currentLetter);
						else if (Score.isTripleWord(row + charPos, col))
							wordMultiplier = 3;
						else if (Score.isDoubleWord(row + charPos, col))
							wordMultiplier = 2;
						secondaryScore += wordScore;
					}
				}
				else {	// Existing tile - just count it once in the primary score
					primaryScore += Score.score(currentLetter);
				}
			}
		}
		else { // This match is aligned by row
			for (int charPos = 0; charPos < sm.word.length(); charPos++) {
				// Does the character already exist on the board? If so:
				// don't check to see if it's on a premium tile, and don't
				// calculate any secondary words
				char currentLetter = sm.word.getString().charAt(charPos);
				if (m_board[row][col + charPos].getText().equals(" ")) {	// We're laying a new tile
					if (!isLetterInTray(currentLetter, tray, trayUsed)) currentLetter = ' ';
					if (Score.isTripleWord(row,col+charPos))
						primaryMultiplier *= 3;
					else if (Score.isDoubleWord(row,col+charPos))
						primaryMultiplier *= 2;
					if (Score.isTripleLetter(row,col+charPos))
						primaryScore += 3 * Score.score(currentLetter);
					else if (Score.isDoubleLetter(row, col+charPos))
						primaryScore += 2 * Score.score(currentLetter);
					else
						primaryScore += Score.score(currentLetter);

					// Now search for "secondary words" created by the match, and add their scores...
					String testWord = findWordCol(row, col + charPos, currentLetter);
					if (testWord.length() > 1) {
						// Given that this word is "secondary" to the primary orientation
						// of the matching word, there can only be one letter that contributes
						// to this word. Thus, this is the only character that we have to
						// worry about premium squares for...
						int wordScore = 0;
						int wordMultiplier = 1;
						for (int qw = 0; qw < testWord.length(); qw++)
							wordScore += Score.score(testWord.charAt(qw));
						if (Score.isTripleLetter(row, col + charPos))
							wordScore += Score.score(currentLetter) * 2;
						else if (Score.isDoubleLetter(row, col + charPos))
							wordScore += Score.score(currentLetter);
						else if (Score.isTripleWord(row, col + charPos))
							wordMultiplier = 3;
						else if (Score.isDoubleWord(row, col + charPos))
							wordMultiplier = 2;
						secondaryScore += wordScore;
					}
				}
				else {	// Existing tile - just count it once in the primary score
					primaryScore += Score.score(currentLetter);
				}
			}
		}
		// Now that the score is calculated, set it in sm and return it.
		sm.score = primaryScore * primaryMultiplier + secondaryScore;
		// Final step - have we used up all 7 letters in the tray? If so, then
		// add 50 to the score.
		if (trayUsed.length == 7) {
			boolean allUsed = true;
			for (int c = 0; allUsed && c < 7; c++)
				allUsed &= trayUsed[c];
			if (allUsed)
				sm.score += 50;
		}
		return sm.score;
	}

	public String getRow(int row) {
		String s = "";
		for (int col = 0; col < 15; col++)
			s = s + m_board[row][col].getText();
		return s;
	}
	public String getCol(int col) {
		String s = "";
		for (int row = 0; row < 15; row++)
			s = s + m_board[row][col].getText();
		return s;
	}
	public String getContents(int row, int col) {
		return m_board[row][col].getText();
	}
	public String getTray() {
		return m_tray.getText();
	}
	private class TrayKeyHandler extends KeyAdapter {
		public void keyTyped(KeyEvent e) {
			char ch = Character.toLowerCase(e.getKeyChar());
			TextField tf = (TextField) e.getSource();
			if (tf.getText().length() == 7 && e.getKeyChar() != KeyEvent.VK_BACK_SPACE) {
				e.consume();
			}
			else {
				if (ch == ' ' || ( ch >= 'a' && ch <= 'z') ) {
					e.setKeyChar(ch);
				}
				else if (e.getKeyChar() == KeyEvent.VK_BACK_SPACE) {
				}
				else {
					System.out.println("Bad key " + e.getKeyCode() + " " + e.getKeyText(e.getKeyCode()));
					e.consume();
				}
			}
		}
	}
	private class BoardKeyHandler extends KeyAdapter {
		public void keyTyped(KeyEvent e) {
			char ch = Character.toLowerCase(e.getKeyChar());
			if (ch == ' ' || ( ch >= 'a' && ch <= 'z') ) {
				TextField tf = (TextField) e.getSource();
				tf.setText(new Character(ch).toString());
			}
			e.consume();
		}
		public void keyPressed(KeyEvent e) {
			if (e.getKeyCode() == KeyEvent.VK_DELETE) {
				TextField tf = (TextField) e.getSource();
				tf.setText(" ");
				e.consume();
			}
		}
	}
}


