package OntologyEntityFinder;

import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;

import uk.ac.shef.wit.simmetrics.similaritymetrics.JaroWinkler;
import edu.mit.jwi.IDictionary;
import edu.mit.jwi.item.POS;
import edu.sussex.nlp.jws.JWS;
import edu.sussex.nlp.jws.Lin;
import edu.sussex.nlp.jws.WuAndPalmer;

/**
 * Utility Class for string operations
 * @author Simeon Polfliet
 * (simeon.polfliet@ensimag.imag.fr)
 */
public class StringOperation {

	/** counter for performance evaluation */
	public static int counter = 0;
	/** Store the words already computed to avoid useless computations */
	private static Hashtable<String, Double> alreadyComp = new Hashtable<String, Double>();
	/** Similarity constructor and measures */
	private static JWS ws;
	private static Lin lin;
	private static WuAndPalmer wup;
	private static JaroWinkler jarowin = new JaroWinkler();
	private static boolean useWordNet;
	/** list to modify the result of the split */
	private static LinkedList<String> discardWord;
	private static Hashtable<String, String> convertWord;

	/**
	 * Standard constructor
	 * @param useWN will use WordNet Similarity if true and only String similarity if false
	 */
	public StringOperation(boolean useWN){
		discardWord = new LinkedList<String>();
		convertWord = new Hashtable<String, String>();
		discardWord.add("es");
		discardWord.add("ia");
		discardWord.add("er");
		discardWord.add("ed");
		discardWord.add("en");
		discardWord.add("ee");
		discardWord.add("al");
		discardWord.add("rep");
		discardWord.add("con");
		discardWord.add("ic");
		discardWord.add("pe");
		discardWord.add("ting");
		discardWord.add("indus");
		
		convertWord.put("update", "date");
		convertWord.put("num", "number");
		
		if (useWN){
			ws = new JWS("WordNet", "3.0");
			lin = ws.getLin();
			wup = ws.getWuAndPalmer();
		}
		useWordNet = useWN;
	}

	/**
	 * Split the words using a test based on their presence in the WordNet dictionary
	 * @param s The string to split
	 * @return the list of words
	 */
	private static LinkedList<String> splitWN(String s){
		/** The result set */
		LinkedList<String> res = new LinkedList<String>();
		String str;

		if (s.length() <= 1){
			return res;
		}
		if (s.length() < 5){
			res.add(s);
			return res;
		}
		IDictionary dico = ws.getDictionary();

		int i = s.length();
		while (dico.getIndexWord(s.substring(0, i), POS.NOUN) == null && i > 2){
			i--;
		}

		if (i > 2){
			res.add(s.substring(0, i));
			res.addAll(splitWN(s.substring(i)));
		} else {
			res.add(s);
		}

		Iterator<String> it = res.iterator();
		while(it.hasNext()){
			str = it.next();
			if (dico.getIndexWord(str, POS.NOUN) == null || discardWord.contains(str)){
				res.clear();
				res.add(s);
				break;
			}
		}

		return res;
	}

	/**
	 * Splits a string into a list of words
	 * @param s the string to split
	 * @return list of words
	 */
	public static LinkedList<String> split(String s){
		/** The result list */
		LinkedList<String> res = new LinkedList<String>();
		/** Intermediate lists */
		LinkedList<String> tmp = new LinkedList<String>();
		LinkedList<String> tmp2 = new LinkedList<String>();
		String st;
		/** boolean set to true if the last character was a lower case one */
		boolean lowCase;
		int start;

		/* we first split the string by removing all special characters */
		String[] r = s.split("[\\s\\d.\\\\/\\-_:#\"@,;!?`()}{\\]\\[']");
		for (int i = 0; i < r.length; i++){
			if (r[i].length() > 1){
				tmp.add(r[i]);
			}
		}

		/* Then we do additional splitting when there is a change of case 
		   in the character sequence */
		Iterator<String> it = tmp.iterator();
		while (it.hasNext()){
			start = 0;
			lowCase = false;
			st = it.next();
			for (int i = 0; i < st.length(); i++){
				if (Character.isUpperCase(st.charAt(i))){
					if (lowCase){
						lowCase = false;
						res.add(st.substring(start, i).toLowerCase());
						start = i;
					}
				} else {
					lowCase = true;
				}				
			}
			if(start < st.length() - 1){
				res.add(st.substring(start, st.length()).toLowerCase());
			}
		}

		/* Then we do additional splitting based on WordNet */
		if (useWordNet){
			tmp.clear();
			it = res.iterator();
			while (it.hasNext()){
				st = it.next();
				tmp.addAll(splitWN(st));
			}
			res = tmp;
		}
		
		it = res.iterator();
		while (it.hasNext()){
			st = it.next();
			if (convertWord.containsKey(st)){
				tmp2.add(convertWord.get(st));
			} else {
				tmp2.add(st);
			}
		}
		res = tmp2;

		return res;
	}

	/**
	 * Compare two words
	 * @param word1 the word to compare with word2
	 * @param word2 the word to compare with word1
	 * @return the similarity between the two words
	 */
	public static double compareWord(String word1, String word2){

		double res = 0;

		/* If the comparison was already done, don't recompute */
		if (alreadyComp.containsKey(word1 + "/" + word2)){
			return alreadyComp.get(word1 + "/" + word2);
		} 

		if (useWordNet){
			res = (lin.max(word1, word2, "n") + wup.max(word1, word2, "n"))/2.0;
		}
		/* here res is > 0 only if the two words exists in the wordNet dictionary */

		if (res <= 0){
			res = (jarowin.getSimilarity(word1, word2));
		}

		alreadyComp.put(word1 + "/" + word2, res);
		alreadyComp.put(word2 + "/" + word1, res);
		return res;
	}

	/**
	 * Remove all the special characters from the string
	 * @param str the string to simplify
	 * @return the result string
	 */
	public static String removeSpe(String str){
		return str.replaceAll("[\\s\\d.\\\\/\\-_:#\"@,;!?`()}{\\]\\[']", "").toLowerCase();
	}

	/**
	 * Get the substring between the two delimiters characters if startDelimiter is true
	 * or between the end of the string that must be contained and the delimiter character otherwise.
	 * If the string mustContain is not contained in the string str, the result is an empty string
	 * Used for the skos hack.
	 * @param str the input string
	 * @param mustContain the string that must be contained in str
	 * @param delimiter the delimiter character
	 * @param startDelimiter determine where the substring should start
	 * @return the substring 
	 */
	public static String getSubStringWithCondition(String str, String mustContain, String delimiter, boolean startDelimiter){

		if (str.contains(mustContain) && str.contains(delimiter)){
			int pos = str.indexOf(mustContain) + mustContain.length();
			int start;

			if (startDelimiter)
				start = str.indexOf(delimiter, pos) + 1;
			else
				start = pos;

			int end = str.indexOf(delimiter, start);
			return str.substring(start, end);
		} else {
			return "";
		}
	}
}
