/*
* 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 Scrabaid extends Frame {

	// Panel and textfield array
	Canvas m_canvasHeading = new ScrabaidHeading();
	TextArea m_textAreaMessage = new TextArea(5,30);
	PatternLookup m_patternLookup;
	WordList m_wordList;

	java.awt.List m_listMatches;
	Button m_buttonFindMatches;
	Button m_buttonChangeBoard;
	Button m_buttonAcceptSuggestion;

	ScrabaidBoard m_scrabaidBoard;
	ScrabaidMatch[] m_matches;

	// 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;

	// Pop up frames for the help menu
	Frame m_frameHelpDeveloper = new HelpDeveloperFrame();
	Frame m_frameHelpHelp = new HelpHelpFrame();

	public static void main(String []args) {
		Scrabaid s = new Scrabaid();
	}

	//
	// The following set up the GUI components of Scrabaid
	//
	private Panel setupMatches() {
		Panel panel = new Panel(new BorderLayout());

		panel.add("North", new Label("Scrabaid suggests the following:"));
		m_listMatches = new java.awt.List(10, false);
		MatchSelectedHandler msh = new MatchSelectedHandler();
		m_listMatches.addItemListener(msh);
		m_listMatches.addFocusListener(msh);
		panel.add("Center",m_listMatches);
		return panel;
	}
	private void setupMenu() {
		MenuBar mb = new MenuBar();
		MenuItem mi;
		Menu menu = new Menu("Scrabaid");
		menu.add(mi = new MenuItem("Clear board"));
		mi.addActionListener(new MenuScrabaidClearHandler());
		menu.addSeparator();
		menu.add(mi = new MenuItem("Exit"));
		mi.addActionListener(new MenuScrabaidExitHandler());
		mb.add(menu);
		menu = new Menu("Help");
		menu.add(mi = new MenuItem("Developer information"));
		mi.addActionListener(new MenuHelpDeveloperHandler());
		menu.add(mi = new MenuItem("Help"));
		mi.addActionListener(new MenuHelpHelpHandler());
		mb.setHelpMenu(menu);
		setMenuBar(mb);
	}
	private Panel setupMessages() {
		Panel panel = new Panel(new BorderLayout());
		panel.add("North", new Label("Current status:"));
		panel.add("Center", m_textAreaMessage);
		m_textAreaMessage.setEnabled(false);
		return panel;
	}
	private Panel setupButtons() {
		Panel panel = new Panel(new GridLayout(3,1,5,5));
		m_buttonChangeBoard = new Button("Change board");	
		m_buttonChangeBoard.addActionListener(new ChangeBoardHandler());
		panel.add(m_buttonChangeBoard);

		m_buttonFindMatches = new Button("Find all matches");
		m_buttonFindMatches.addActionListener(new FindMatchesHandler());
		panel.add(m_buttonFindMatches);

		m_buttonAcceptSuggestion = new Button("Accept suggestion");
		m_buttonAcceptSuggestion.addActionListener(new AcceptSuggestionHandler());
		m_buttonAcceptSuggestion.setEnabled(false);
		panel.add(m_buttonAcceptSuggestion);
		return panel;
	}

	//
	// Methods for writing to the message textbox
	//
	private void messageClear() {
		m_textAreaMessage.setText("");
	}
	private void messageAppend(String s) {
		m_textAreaMessage.setText( m_textAreaMessage.getText() + s + "\n");
	}


	Scrabaid() {
		super("Scrabaid - the essential Scrabble aid");
		
		try {
			m_patternLookup = PatternLookup.getInstance();
			m_wordList = WordList.getInstance();
		}
		catch (ScrabaidException se) {
			System.err.println("Error whilst instantiating PatternLookup object.");
			System.err.println(se.getMessage());
			System.exit(1);
		}

//		setBounds(50,50, 200, 250);

		setupMenu();
		Panel buttonsPanel = setupButtons();
		Panel matchesPanel = setupMatches();
		Panel messagesPanel = setupMessages();

		m_scrabaidBoard = new ScrabaidBoard();

		GridBagLayout gridBagLayout = new GridBagLayout();
		setLayout(gridBagLayout);

		// The left panel, which contains the Scrabaid canvas,
		// the scrabble board and the tray of letters
		GridBagLayout gbl = new GridBagLayout();
		Panel leftPanel = new Panel(gbl);
		GridBagConstraints gbc = new GridBagConstraints();
		gbc.insets.left = 0; gbc.insets.right = 0; gbc.insets.top = 0; gbc.gridx = 0; gbc.gridy = 0;
		leftPanel.add(m_canvasHeading);
		gbl.setConstraints(m_canvasHeading, gbc);

		gbc = new GridBagConstraints();
		gbc.insets.left = 20; gbc.insets.top = 20; gbc.gridx = 0; gbc.gridy = 1;
		leftPanel.add(m_scrabaidBoard);
		gbl.setConstraints(m_scrabaidBoard, gbc);

		add(leftPanel);
		GridBagConstraints gridBagConstraints = new GridBagConstraints();
		gridBagConstraints.ipadx = gridBagConstraints.ipady = 20;
		gridBagConstraints.weightx = 0; gridBagConstraints.weighty = 0;
		gridBagConstraints.anchor = GridBagConstraints.NORTH;
		gridBagConstraints.fill = GridBagConstraints.NONE;
		gridBagLayout.setConstraints(leftPanel, gridBagConstraints);

		gridBagConstraints = new GridBagConstraints();
		gridBagConstraints.insets.left = 20; gridBagConstraints.insets.right = 20;
		gridBagConstraints.insets.top = 20; gridBagConstraints.insets.bottom = 20;
		gridBagConstraints.weightx = 1; gridBagConstraints.weighty = 1;
		gridBagConstraints.fill = GridBagConstraints.BOTH;

		Panel rightPanel = new Panel(new BorderLayout());
		rightPanel.add("North", buttonsPanel);
		rightPanel.add("Center", matchesPanel);
		rightPanel.add("South", messagesPanel);

		add(rightPanel);
		gridBagLayout.setConstraints(rightPanel, gridBagConstraints);

		addWindowListener(new WindowEventHandler());

		pack();
		setVisible(true);
	}

	class WindowEventHandler extends WindowAdapter {
		public void windowClosing(WindowEvent e) {
			setVisible(false);
			System.exit(0);
		}
	}

	class FindMatchesHandler implements ActionListener {
		private Vector phase1Initial(char []trayChars, int numBlanks) throws ScrabaidException {
			Vector phase1 = new Vector();

			Vector v = m_patternLookup.scrabbleSearchInitial(trayChars, numBlanks);
			for (int c = 0; c < v.size(); c++) {
				ScrabaidMatch sm = (ScrabaidMatch) v.elementAt(c);
				sm.row = 7;
				phase1.add(sm);
			}
			messageAppend("Phase 1: found " + phase1.size() + " possibilities.");
			return phase1;
		}
		private Vector phase1(char []trayChars, int numBlanks) throws ScrabaidException {
			Vector phase1 = new Vector();
			for (int row = 0; row < 15; row++) {
				String s = m_scrabaidBoard.getRow(row);

				if (s.trim().length() != 0) {
					Vector v = m_patternLookup.scrabbleSearch(s, trayChars, numBlanks);
					for (int c = 0; c < v.size(); c++) {
						ScrabaidMatch sm = (ScrabaidMatch) v.elementAt(c);
						sm.row = row;
						phase1.add(sm);
						if (sm.numCharsUsed == 1) {
						
							// We have an additional search to do here....
							// Work out which character was used.
							Object []o = m_scrabaidBoard.getSecondaryPattern(sm, trayChars, numBlanks);
							String newPattern = (String) o[0];
							char replacementChar = ((Character)o[1]).charValue();
							int newCol = ((Integer)o[2]).intValue();
							char []newTray = (char[])o[3];
							int newNumBlanks = ((Integer)o[4]).intValue();

							// Substitute that letter
							Vector v2 = m_patternLookup.scrabbleSearch(newPattern, newTray, newNumBlanks);
							for (int c2 = 0; c2 < v2.size(); c2++) {
								ScrabaidMatch sm2 = (ScrabaidMatch) v2.elementAt(c2);
								sm2.col = newCol;
								phase1.add(sm2);
							}
						}
					}
				}
			}
			for (int col = 0; col < 15; col++) {
				String s = m_scrabaidBoard.getCol(col);

				if (s.trim().length() != 0) {
					Vector v = m_patternLookup.scrabbleSearch(s, trayChars, numBlanks);
					for (int c = 0; c < v.size(); c++) {
						ScrabaidMatch sm = (ScrabaidMatch) v.elementAt(c);
						sm.col = col;
						phase1.add(sm);
						if (sm.numCharsUsed == 1) {
						
							// We have an additional search to do here....
							// Work out which character was used.
							Object []o = m_scrabaidBoard.getSecondaryPattern(sm, trayChars, numBlanks);
							String newPattern = (String) o[0];
							char replacementChar = ((Character)o[1]).charValue();
							int newRow = ((Integer)o[2]).intValue();
							char []newTray = (char[])o[3];
							int newNumBlanks = ((Integer)o[4]).intValue();

							// Substitute that letter
							Vector v2 = m_patternLookup.scrabbleSearch(newPattern, newTray, newNumBlanks);
							for (int c2 = 0; c2 < v2.size(); c2++) {
								ScrabaidMatch sm2 = (ScrabaidMatch) v2.elementAt(c2);
								sm2.row = newRow;
								phase1.add(sm2);
							}
						}
					}
				}
			}
			messageAppend("Phase 1: found " + phase1.size() + " possibilities.");
			return phase1;
		}

		private Vector phase2(Vector possibilities) {
			Vector phase2 = new Vector();
			for (int counter = 0; counter < possibilities.size(); counter++) {
				boolean valid = true;
				// Examine each match for validity...
				ScrabaidMatch sm = (ScrabaidMatch) possibilities.elementAt(counter);
				int row = (sm.row == -1) ? sm.startingPosition : sm.row;
				int col = (sm.col == -1) ? sm.startingPosition : sm.col;
				if (sm.row == -1) { // This match is aligned by column
					for (int r = row; valid && r < row + sm.word.length(); r++) {
						// Does the character already exist on the board? If so, it's an
						// existing word... only test for new words formed
						if (m_scrabaidBoard.getContents(r,col).equals(" ")) {
							String testWord = m_scrabaidBoard.findWordRow(r, col, sm.word.getString().charAt(r-row));
							if (testWord.length() > 1 && !m_wordList.isWord(testWord)) {
								// It's not a word!
								valid = false;
							}
						}
					}
				}
				else { // This match is aligned by row
					for (int c = col; valid && c < col + sm.word.length(); c++) {
						if (m_scrabaidBoard.getContents(row,c).equals(" ")) {
							String testWord = m_scrabaidBoard.findWordCol(row, c, sm.word.getString().charAt(c-col));
							if (testWord.length() > 1 && !m_wordList.isWord(testWord)) {
								// It's not a word!
								valid = false;
							}
						}
					}
				}
				if (valid) {
					phase2.add(sm);
				}
			}
			messageAppend("Phase 2: restricted to " + phase2.size() + " possibilities.");
			return phase2;
		}
		private ScrabaidMatch[] phase3(Vector possibilities) {
			TreeSet ts = new TreeSet();
			ScrabaidMatch[] sms;
			for (int c = 0; c < possibilities.size(); c++) {
				ScrabaidMatch sm = (ScrabaidMatch) possibilities.elementAt(c);
				m_scrabaidBoard.findScore(sm);
				ts.add(sm);
			}
			sms = new ScrabaidMatch[ts.size()];
			Iterator i = ts.iterator();
			int c = ts.size()-1;
			while (i.hasNext()) {
				sms[c--] = (ScrabaidMatch) i.next();
			}
			messageAppend("Phase 3: scored and sorted.");
			return sms;
		}

		public void actionPerformed(ActionEvent e) {
			try {
				messageClear();
				m_listMatches.removeAll();
				m_buttonAcceptSuggestion.setEnabled(false);
				String tray;
				if ((tray = m_scrabaidBoard.getTray()).length() < 1) {
					messageAppend("Your tray can't be empty!");
					return;
				}
				int numBlanks = 0;
				for (int q = 0; q < tray.length(); q++)
					if (tray.charAt(q) == ' ')
						numBlanks++;
				char[] trayChars = new char[tray.length() - numBlanks];
				int pos = 0;
				for (int q = 0; q < tray.length(); q++)
					if (tray.charAt(q) != ' ')
						trayChars[pos++] = tray.charAt(q);

				Vector v;
				if (m_scrabaidBoard.isBlank())
					v = phase1Initial(trayChars, numBlanks);
				else
					v = phase1(trayChars, numBlanks);
				v = phase2(v);
				m_matches = phase3(v);

				// Iterate through vector
				for (int q = 0; q < m_matches.length; q++) {
					m_listMatches.add("[" + m_matches[q].score + "]   " + m_matches[q].word.getString());
				}
			}
			catch (ScrabaidException se) {
				System.err.println("Error whilst performing search.");
				System.err.println(se.getMessage());
				System.exit(1);
			}
		}
	}
	class ChangeBoardHandler implements ActionListener {
		public void actionPerformed(ActionEvent e) {
			System.out.println("Changing board...");
			if (m_buttonFindMatches.isEnabled()) {
				m_scrabaidBoard.colourBoard(false);
				m_scrabaidBoard.setBoardEnabled(true);
				m_buttonFindMatches.setEnabled(false);
				m_buttonAcceptSuggestion.setEnabled(false);
				m_buttonChangeBoard.setLabel("Finish changes");
			}
			else {
				m_scrabaidBoard.colourBoard(true);
				m_scrabaidBoard.setBoardEnabled(false);
				m_buttonFindMatches.setEnabled(true);
				m_buttonChangeBoard.setLabel("Change board");

			}
		}
	}

	// Menu handlers
	class MenuScrabaidExitHandler implements ActionListener {
		public void actionPerformed(ActionEvent e) {
			setVisible(false);
			System.exit(0);
		}
	}
	class MenuScrabaidClearHandler implements ActionListener {
		public void actionPerformed(ActionEvent e) {
			m_scrabaidBoard.colourBoard(false);
			m_listMatches.removeAll();
			m_buttonAcceptSuggestion.setEnabled(false);
			m_scrabaidBoard.hideMatch();
			m_scrabaidBoard.clear();
		}
	}
	class MenuHelpDeveloperHandler implements ActionListener {
		public void actionPerformed(ActionEvent e) {
			m_frameHelpDeveloper.setVisible(true);
		}
	}
	class MenuHelpHelpHandler implements ActionListener {
		public void actionPerformed(ActionEvent e) {
			m_frameHelpHelp.setVisible(true);
		}
	}

	class MatchSelectedHandler extends FocusAdapter implements ItemListener {
		public void focusLost(FocusEvent e) {
			m_scrabaidBoard.hideMatch();
		}
		public void itemStateChanged(ItemEvent e) {
			m_scrabaidBoard.hideMatch();
			m_scrabaidBoard.showMatch(m_matches[((Integer)e.getItem()).intValue()]);
			m_buttonAcceptSuggestion.setEnabled(true);
		}
	}
	class AcceptSuggestionHandler implements ActionListener {
		public void actionPerformed(ActionEvent e) {
			if (m_listMatches.getSelectedIndex() >= 0 && m_listMatches.getSelectedIndex() < m_matches.length) {
				System.out.println("Accepting suggestion.");
				System.out.println(m_listMatches.getSelectedIndex());
				m_scrabaidBoard.showMatch(m_matches[m_listMatches.getSelectedIndex()]);
				m_scrabaidBoard.acceptMatch();
				m_listMatches.removeAll();
				m_buttonAcceptSuggestion.setEnabled(false);
			}
		}
	}
}


