/*
* 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.io.*;
import java.text.*;
import java.util.*;

public class PatternLookup {
	int maxLengthLookup;
	PatternCollection[] m_patternCollections;
	WordList m_wordList;

	static PatternLookup m_instance;

	static public synchronized PatternLookup getInstance() throws ScrabaidException {
		if (m_instance == null) {
			m_instance = new PatternLookup();
		}
		return m_instance;
	}
	private PatternLookup() throws ScrabaidException {
		m_wordList = WordList.getInstance();
		DecimalFormat format = new DecimalFormat("00");

		File patDir = new File("patterns");

		// Load the PatternCollections
		m_patternCollections = new PatternCollection[16];
		int c = 2;
		for (; c <= 15; c++) {
			try {
				m_patternCollections[c]  = new PatternCollection(patDir, format.format(c), true );
			}
			catch (ScrabaidException se) {
				// Couldn't open pattern collection file....
				System.out.println(" error");
				break;
			}
		}
		maxLengthLookup = c - 1;
		if (maxLengthLookup == 1) {
			throw new ScrabaidException ("No pattern collection files could be found. Make sure they're generated with the TableGenerator program.");
		}
		System.out.println("Pattern searching will match words of up to length " + maxLengthLookup);
	}
	private Vector simpleLookup(Pattern p) throws ScrabaidException {
		Vector v = new Vector();
		if (p.isBlank()) {
			Word [] words = m_wordList.getWords()[p.length()];
			for (int c = 0; c < words.length; c++) {
				v.add(new ScrabaidMatch(p, words[c]));
			}
		}
		else {
			short [] indices = m_patternCollections[p.length()].getPatternIndices(p);
			Word [] words = m_wordList.getWords()[p.length()];
			for (int c = 0; c < indices.length; c++) {
				if (p.doesWordMatch(words[indices[c]]))
					v.add(new ScrabaidMatch(p, words[indices[c]]));
			}
		}
		return v;
	}
	private Vector simpleLookup(Pattern p, char[] trayLetters, int numBlanks) throws ScrabaidException {
		Vector v = new Vector();
		if (p.isBlank()) {
			Word [] words = m_wordList.getWords()[p.length()];
			for (int c = 0; c < words.length; c++) {
				int numLettersUsed = getWordCompositionLettersUsed(words[c], p, trayLetters, numBlanks);
				if (numLettersUsed >= 0)
					v.add(new ScrabaidMatch(p, words[c], numLettersUsed));
			}
		}
		else {
			short [] indices = m_patternCollections[p.length()].getPatternIndices(p);
			Word [] words = m_wordList.getWords()[p.length()];
			String dictWord;
			for (int c = 0; c < indices.length; c++)
				if (p.doesWordMatch(words[indices[c]])) {
					int numLettersUsed = getWordCompositionLettersUsed(words[indices[c]], p, trayLetters, numBlanks);
					if (numLettersUsed >= 0)
						v.add(new ScrabaidMatch(p, words[indices[c]], numLettersUsed));
				}
		}
		return v;
	}
	private int getWordCompositionLettersUsed(Word word, Pattern p, char[] trayLetters, int numBlanks) {
		// Returns -1 if the word cannot be composed by the letters in the tray...
		// Otherwise, it returns the number of letters consumed from the tray to form the word
		boolean[] lettersUsed = new boolean[trayLetters.length];
		String sword = word.getString();
		String sp = p.getString();
		int numLettersUsed = 0;
		for (int c = 0; c < sword.length(); c++) {
			char wordChar;
			if ((wordChar = sword.charAt(c)) != sp.charAt(c)) {
				// Letter must come from trayLetters
				boolean found = false;
				for (int c2 = 0; c2 < trayLetters.length; c2++) {
					if (trayLetters[c2] == wordChar && !lettersUsed[c2]) {
						found = true;
						lettersUsed[c2] = true;
						numLettersUsed++;
						break;
					}
				}
				if (!found) {
					if (numBlanks == 0)
						return -1;
					else {
						--numBlanks;
						numLettersUsed++;
					}
				}
			}
		}
		return numLettersUsed;
	}
	private boolean isWordComposable(Word word, Pattern p, char[] trayLetters, int numBlanks) {
		boolean[] lettersUsed = new boolean[trayLetters.length];
		String sword = word.getString();
		String sp = p.getString();
		
		for (int c = 0; c < sword.length(); c++) {
			char wordChar;
			if ((wordChar = sword.charAt(c)) != sp.charAt(c)) {
				// Letter must come from trayLetters
				boolean found = false;
				for (int c2 = 0; c2 < trayLetters.length; c2++) {
					if (trayLetters[c2] == wordChar && !lettersUsed[c2]) {
						found = true;
						lettersUsed[c2] = true;
						break;
					}
				}
				if (!found) {
					if (numBlanks == 0)
						return false;
					else
						--numBlanks;
				}
			}
		}
		return true;
	}
	public Vector scrabbleSearchInitial(char[] trayLetters, int numBlanks) throws ScrabaidException {
		// Work out the maximum length of a word we can form here...
		// It's the number of non-blank characters in boardSequence + trayLetters.length
		Vector allMatches = new Vector();
	
		int maxWordLen = trayLetters.length;
		if (maxWordLen > maxLengthLookup)
			maxWordLen = maxLengthLookup;

		// Go through each character length....
		for (int wordLen = 2; wordLen <= maxWordLen; wordLen++) {
			String s = "";
			for (int c = 0; c < wordLen; c++)
				s += " ";
			Pattern pattern = new Pattern(s);

			Vector matches = simpleLookup(pattern, trayLetters, numBlanks);
			int startPos = (wordLen > 8) ? 0 : 8 - wordLen;
			int endPos = (wordLen > 8) ? 15 - wordLen : 7;
			for (int q = 0; q < matches.size(); q++) {
				ScrabaidMatch sm = (ScrabaidMatch) matches.elementAt(q);
				// Starting pos must cater for word fitting over the middle position
				for (int pos = startPos; pos <= endPos; pos++) {
					ScrabaidMatch newsm = new ScrabaidMatch(sm);
					newsm.startingPosition = pos;
					allMatches.add(newsm);
				}
			}
		}
		return allMatches;
	}
	public Vector scrabbleSearch(String boardSequence, char[] trayLetters, int numBlanks) throws ScrabaidException {
		if (boardSequence.length() != 15) {
			throw new ScrabaidException("Board sequence must be 15 characters long.");
		}
		// Work out the maximum length of a word we can form here...
		// It's the number of non-blank characters in boardSequence + trayLetters.length
		Vector allMatches = new Vector();
		int maxWordLen = 0;
		for (int c = 0; c < boardSequence.length(); c++)
			if (boardSequence.charAt(c) != ' ')
				++maxWordLen;
	
		maxWordLen += trayLetters.length;
		if (maxWordLen > maxLengthLookup)
			maxWordLen = maxLengthLookup;

		// Go through each character length....
		for (int wordLen = 2; wordLen <= maxWordLen; wordLen++) {
			for (int startPos = 0; startPos <= 15 - wordLen; startPos++) {
				// Is it a valid position to start at?
				if ( (startPos == 0 || boardSequence.charAt(startPos-1) == ' ') &&
				    (wordLen + startPos == 15 || boardSequence.charAt(wordLen + startPos) == ' ') ) {
					String subSequence = boardSequence.substring(startPos,startPos+wordLen);
					if (subSequence.trim().length() > 0) {
						if (subSequence.indexOf(' ') != -1) {
							// We have a valid pattern here.........
							// Baby do a search...
							Vector matches = simpleLookup(new Pattern(subSequence), trayLetters, numBlanks);
							for (int q = 0; q < matches.size(); q++) {
								ScrabaidMatch sm = (ScrabaidMatch) matches.elementAt(q);
								sm.startingPosition = startPos;
								allMatches.add(sm);
							}
						}
					}
				}
			}
		}
		return allMatches;
	}

	public static void main(String []args) {
//		simpleSearchInteractive();
//		scrabbleSearchInteractive();
		scrabbleSearchInitialInteractive();
	}
	private static void simpleSearchInteractive() {
		try {
			BufferedReader inReader = new BufferedReader(new InputStreamReader(System.in));
			PatternLookup pl = PatternLookup.getInstance();

			System.out.println("Enter your pattern, or blank to quit.");
			String s = inReader.readLine();
			while (s.length() > 0) {
				Pattern pattern = new Pattern(s);

				long t1 = System.currentTimeMillis();
				Vector v = pl.simpleLookup(pattern);
				long t2 = System.currentTimeMillis();

				for (int c = 0; c < v.size(); c++)
					System.out.println(v.elementAt(c));
				System.out.println("--- Search time: " + (t2 - t1));

				System.out.println("Enter your pattern, or blank to quit.");
				s = inReader.readLine();
			}

		}
		catch (ScrabaidException se) {
			System.out.println(se.getMessage());
			se.printStackTrace();
		}
		catch (IOException ioe) {
			ioe.printStackTrace();
		}
	}
	private static void scrabbleSearchInteractive() {
		try {
			BufferedReader inReader = new BufferedReader(new InputStreamReader(System.in));
			PatternLookup pl = PatternLookup.getInstance();

			System.out.println("Enter your 15 character row, or blank to quit.");
			System.out.println("012345678901234");
			String s = inReader.readLine();
			while (s.length() > 0) {
				Pattern pattern = new Pattern(s);

				System.out.println("Enter your tray of characters, or blank to quit.");
				String tray = inReader.readLine();
				int numBlanks = 0;
				for (int q = 0; q < tray.length(); q++)
					if (tray.charAt(q) == ' ')
						numBlanks++;

				long t1 = System.currentTimeMillis();
				Vector v = pl.scrabbleSearch(s, tray.toCharArray(), numBlanks);
				long t2 = System.currentTimeMillis();

				for (int c = 0; c < v.size(); c++)
					System.out.println(v.elementAt(c));
				System.out.println("--- Search time: " + (t2 - t1));


				System.out.println("Enter your 15 character row, or blank to quit.");
				System.out.println("012345678901234");
				s = inReader.readLine();
			}

		}
		catch (ScrabaidException se) {
			System.out.println(se.getMessage());
			se.printStackTrace();
		}
		catch (IOException ioe) {
			ioe.printStackTrace();
		}
	}
	private static void scrabbleSearchInitialInteractive() {
		try {
			BufferedReader inReader = new BufferedReader(new InputStreamReader(System.in));
			PatternLookup pl = PatternLookup.getInstance();

			System.out.println("Enter your tray of characters, or blank to quit.");
			String tray = inReader.readLine();
			while (tray.length() > 0) {
				int numBlanks = 0;
				for (int q = 0; q < tray.length(); q++)
					if (tray.charAt(q) == ' ')
						numBlanks++;

				long t1 = System.currentTimeMillis();
				Vector v = pl.scrabbleSearchInitial(tray.toCharArray(), numBlanks);
				long t2 = System.currentTimeMillis();

				for (int c = 0; c < v.size(); c++)
					System.out.println(v.elementAt(c));
				System.out.println("--- Search time: " + (t2 - t1));


				System.out.println("Enter your tray of characters, or blank to quit.");
				tray = inReader.readLine();
			}

		}
		catch (ScrabaidException se) {
			System.out.println(se.getMessage());
			se.printStackTrace();
		}
		catch (IOException ioe) {
			ioe.printStackTrace();
		}
	}
}
