/**
 * @(#)Dictionary.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.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.TreeSet;
import java.util.Vector;

import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.ProgressMonitor;
import javax.swing.ProgressMonitorInputStream;

import sun.audio.AudioPlayer;
import sun.audio.AudioStream;

/**
 * This class contains methods for operating on dictionaries.
 * All variables, methods are static.
 * Methods <code>loadFromFile</code>, <code>addWords</code> and <code>lookup</code>
 * are key to the Scrabble game's operation.
 * 
 * @author 3GP01 - Hardev Bhamra, Deepak Chandarana, James Tompkin
 * @version 2.0
 * @see ScrabbleBuilder
 */

public class Dictionary 
{
	static ResourceBundle messages = Scrabble.scrabble.getI18n().getCurrentResourceBundle(); //localisation message bundle
	
	/**
	 * String passed to GUI relaying information about what the dictionary is.
	 */
	public static String langForGUI;
	
	private static List lettersAttrib = new ArrayList();
	
	public static List getListOfLetters() 
	{
		return lettersAttrib;
	}
	
	// dictionary is split into sets of words containing
	// each letter, and with length from 2 to 15
	
	private static Map dictionary = new HashMap();
	
	// maps "a" to hasA, etc. hasA[i-2] is set of 
	// words containing "a", with length i
	
	private static Set[] allwords = new TreeSet[14];
	
	private static void initialiseDictionary() 
	{
		for (int i = 0; i < lettersAttrib.size(); i++) 
		{ 	// System.out.println("setting up dictionary: " + i); 
			Set[] hasChar = new TreeSet[14];
			for (int j = 0; j < 14; j++) 
			{
				hasChar[j] = new TreeSet();
			}
			dictionary.put(lettersAttrib.get(i).toString().substring(6, 7),	hasChar);
		}
		for (int k = 0; k < 14; k++) 
		{
			allwords[k] = new TreeSet();
		}
	}
	
	//used to copy a user selected dictionary into the resources/dictionaries/ folder
	public static void copyDictToDictionaries(File file, String name) 
	{
		try 
		{
			FileChannel source = new FileInputStream(file).getChannel();
			FileChannel destination = new FileOutputStream(
					"resources/dictionaries/" + name).getChannel(); //$NON-NLS-1$
			
			destination.transferFrom(source, 0, source.size());
			
			source.close();
			destination.close();
		} 
		catch (IOException e) 
		{
			displayExceptionMessage(name);
		}
	}
	
	public static File loadOtherDict() 
	{
		File startingpoint = new File("input"); //$NON-NLS-1$
		File file;
		JFileChooser fc = new JFileChooser();
		
		fc.setFileFilter(new TXTFilter());
		fc.setAcceptAllFileFilterUsed(false);
		fc.setCurrentDirectory(startingpoint);
		fc.setDialogTitle(messages.getString("Dictionary.0")); //$NON-NLS-1$
		int returnVal = fc.showOpenDialog(null);
		if (returnVal == JFileChooser.APPROVE_OPTION) 
		{
			file = fc.getSelectedFile();
			return file;
		} //get string path from file
		else 
		{
			System.err.println("Load aborted"); //$NON-NLS-1$
			System.exit(0); //quit application if dictionary load is cancelled
			return null;
		}
		
	}
	
	public static void loadFromFile(String lang, String file) //input string containing file path
	{
		BufferedReader br = null;
		Vector res = new Vector();
		String line = ""; //$NON-NLS-1$
		boolean eof = false;
		
		if (file == null) //if path == null, bring up JFileChooser
		{
			File startingpoint = new File("input"); //$NON-NLS-1$
			JFileChooser fc = new JFileChooser();
			fc.setFileFilter(new TXTFilter());
			fc.setAcceptAllFileFilterUsed(false);
			fc.setCurrentDirectory(startingpoint);
			fc.setDialogTitle(messages.getString("Dictionary.0")); //$NON-NLS-1$
			int returnVal = fc.showOpenDialog(null);
			if (returnVal == JFileChooser.APPROVE_OPTION) 
			{
				file = fc.getSelectedFile().getAbsolutePath();
			} //get string path from file
			else 
			{
				System.err.println("Load aborted"); //$NON-NLS-1$
				System.exit(0); //quit application if dictionary load is cancelled
				return;
			}
		}
		
		File file2 = new File(file); //new file from path name
		URL url = null;
		try 
		{
			url = file2.toURL(); //new url from file
		}
		catch (MalformedURLException e1) 
		{
			displayExceptionMessage(url.toString());
		}
		
		try //create ProgressMonitorInputStream on file-as-url to display loading bar
		{
			ProgressMonitorInputStream pmis = new ProgressMonitorInputStream(
					null,
					messages.getString("Dictionary.9") + file, url.openStream()); //$NON-NLS-1$
			br = new BufferedReader(new InputStreamReader(pmis));
			ProgressMonitor pm = pmis.getProgressMonitor();
			pm.setMillisToDecideToPopup(0);
		} //load dictionary from string location
		catch (FileNotFoundException e) 
		{
			displayExceptionMessage(file);
			return;
		} 
		catch (IOException ioe) 
		{ 
			displayExceptionMessage(file);
		}
		
		boolean prep = false;
		while (!prep) 
		{
			try 
			{
				line = br.readLine();
				
				if (line.equals("^^")) //$NON-NLS-1$
				{
					langForGUI = br.readLine(); //line after this is the language of the dictionary
				} 
				else if (line.equals("$$")) //between $ and £, the letters are defined, with scores/symbols/ascii, all that stuff //$NON-NLS-1$
				{
					while (!line.equals("**")) //$NON-NLS-1$
					{
						line = br.readLine();
						if (line.equals("**")) //$NON-NLS-1$
						{
							break;
						}
						lettersAttrib.add(line); //add to lettersAttribute list (containing all attributes) - this is deconstructed in ScrabbleBuilder
					}
					prep = true;
					initialiseDictionary(); //initialise dictionary from letters
				}
				
			} catch (IOException e2) { //e2.printStackTrace();
				displayExceptionMessage(line);
			}
		}
		
		while (!eof) 
		{
			try 
			{
				line = br.readLine();
				
				if (line == null) 
				{
					eof = true;
				}
				addWords(line);
			} 
			catch (IOException e) 
			{
				displayExceptionMessage(line);
			}
		}
		
		//copies loaded dictionary to resources/dictionaries so that on future dictionary selections, dictionary is added to list
		if (lang.equals(messages.getString("Dictionary.19")) && file2 != null) //$NON-NLS-1$
		{
			try {
				FileChannel source = new FileInputStream(file2).getChannel();
				FileChannel destination = new FileOutputStream(
						"resources/dictionaries/" + file2.getName()).getChannel(); //$NON-NLS-1$
				destination.transferFrom(source, 0, source.size());
				source.close();
				destination.close();
			} 
			catch (IOException e) 
			{
				displayExceptionMessage(file2.toString());
			}
		}
	}
	
	public String toString() {
		String result = ""; //$NON-NLS-1$
		for (int i = 0; i < lettersAttrib.size(); i++) 
		{
			String lett = lettersAttrib.get(i).toString();
			Set[] lettwords = (Set[]) dictionary.get(lett);
			for (int j = 0; j < lettwords.length; j++) 
			{
				result = result + lett + " " + j + "\n   " + //$NON-NLS-1$ //$NON-NLS-2$
				lettwords[j] + "\n"; //$NON-NLS-1$
			}
		}
		return result;
	}
	
	//speed up ths method, this is the great work done by the dict loader
	//takes 5 seconds to load british english on 4thfloorlab computers
	public static void addWords(String line) 
	{ 	// line is a series of words
		if (line == null) 
		{
			return;
		}
		
		line = line.toLowerCase();
		int len = line.length(); //get length of lowercase word
		
		for (int i = 0; i < len; i++) //loop through to length of word
		{
			if (len > 15 || len <= 1) 
			{
				break;
			} //if dictionary word length is >/< smaller than scrabble board
			Set[] wordsWithc = (Set[]) dictionary.get(line.charAt(i) + ""); //new array of sets, created to contain character at i //$NON-NLS-1$
			if (wordsWithc != null) //if set contains no letters
			{
				wordsWithc[len - 2].add(line); //add into set lowercase word at length-2
				allwords[len - 2].add(line); //add into allwords lowercase word at length-2
			}
		}
		
	}
	
	public static boolean lookup(Word word) 
	{	// checks if word is in dictionary 
		String wd = "" + word; //$NON-NLS-1$
		int len = wd.length();
		String lett1 = "" + wd.charAt(0); //$NON-NLS-1$
		// System.out.print("Letter: " + lett1 + " length " + len); 
		Set[] words1 = (Set[]) dictionary.get(lett1);
		// System.out.println(" Dictionary: " + words1[len]); 
		if (words1 == null) 
		{
			return false;
		}
		return words1[len - 2].contains(wd);
	}
	
	public static Set lookup(List letts, int i) 
	{ // returns all words of length i that contain
		// all letters in letts -- letts.size() > 0 needed
		Set result = new TreeSet();
		if (i < 2) 
		{
			return result;
		}
		
		String lett1 = (String) letts.get(0);
		if (lett1.equals(" ")) //$NON-NLS-1$
		{
			result.addAll(allwords[i - 2]);
		}
		else 
		{
			Set[] words1 = (Set[]) dictionary.get(lett1);
			if (words1 != null) {
				result.addAll(words1[i - 2]);
			} //union
		}
		
		for (int j = 1; j < letts.size(); j++) 
		{
			String lett = (String) letts.get(j);
			if (lett.equals(" ")) //$NON-NLS-1$
			{
				continue;
			}
			Set[] words = (Set[]) dictionary.get(lett);
			if (words != null) 
			{
				Set wordslengthi = words[i - 2];
				result.retainAll(wordslengthi); //intersection
			} 
			else 
			{
				result = new HashSet();
			}
		}
		return result;
	}
	
	public static Set getList(String ch, int i) 
	{
		Set[] words = (Set[]) dictionary.get(ch);
		return words[i - 2];
	}
	
	/**
	 * Displays a exception box containing general dictionary load failure messages,
	 *  with sound effect, to notify user of a failed dictionary load.  The system
	 * is closed if any exception is triggered, as the dictionary will most likely 
	 * be in a half-read state.
	 * 
	 * @param line A simple hashCode is generated from this input, purely for 'horrible error effect'.
	 */
	public static void displayExceptionMessage(String line) 
	{
		int error = line.hashCode();
		
		//play a sound, as per deepak's request
		try {
			InputStream in = new FileInputStream("resources/sounds/error.wav");
			AudioStream as = new AudioStream(in);
			AudioPlayer.player.start(as);
		}
		catch (FileNotFoundException fnf) 
		{ 	//no need to resolve this exception
			//if exception is triggered, audio will not play
			//fnf.printStackTrace();
		}
		catch (IOException ioe) 
		{ 	//no need to resolve this exception
			//if exception is triggered, audio will not play
			//ioe.printStackTrace();
		}
		
		JOptionPane.showMessageDialog(null,
				messages.getString("Dictionary.15") + error + System.currentTimeMillis() + ")." + //$NON-NLS-1$ //$NON-NLS-2$
				messages.getString("Dictionary.17"), //$NON-NLS-1$
				messages.getString("Dictionary.18"), //$NON-NLS-1$
				JOptionPane.ERROR_MESSAGE);
		System.exit(0);
	}
	
	public static void main(String[] args) 
	{	// initialiseDictionary();
		loadFromFile("Russian", "resources/dictionaries/Russian.txt"); //$NON-NLS-1$ //$NON-NLS-2$
		System.out.println(getList("d", 3)); //$NON-NLS-1$
		System.out.println(getList("d", 2)); //$NON-NLS-1$
		Vector v1 = new Vector();
		v1.add("d"); //$NON-NLS-1$
		v1.add("o"); //$NON-NLS-1$
		v1.add("m"); //$NON-NLS-1$
		
		System.out.println(lookup(v1, 3));
		// Vector v2 = new Vector(); 
		// v2.add("o"); 
		// System.out.println(d.lookup(v2,2));
		// System.out.println(lookup(new Word(0,0,0,4,"verve"))); 
		// System.out.println(lookup(new Word(0,0,0,1,"ar"))); 
	}
}