/**
 * Copyright 2012 Lezan Hawizy, David M. Jessop, Daniel Lowe and Peter Murray-Rust
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package uk.ac.cam.ch.wwmm.chemicaltagger;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.UncheckedIOException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import nu.xom.Document;
import nu.xom.Element;
import nu.xom.Elements;
import nu.xom.Node;
import nu.xom.ParentNode;
import nu.xom.Serializer;

import org.apache.commons.io.IOUtils;
import org.jsoup.Jsoup;
import org.jsoup.safety.Safelist;

import uk.ac.cam.ch.wwmm.oscar.document.Token;

/********************************************
 * A Utils class.
 * 
 * @author lh359, pm286
 ********************************************/
public class Utils {
	
	private static Pattern exponentXMLPattern = Pattern.compile("(-?\\d+)<sup>(-?\\d+)</sup>");

	/**************************
	 * Hides Utility Class Constructor.
	 */
	private Utils(){
		
	}
	/******************************************
	 * Replaces all non-XML characters with _ .
	 * 
	 * @param text (String)
	 * @return ncName (String)
	 *******************************************/
	public static String makeNCName(String text) {
		String ncName = text;
		if (text == null) {
			ncName = "emptyName";
		} else if (ncName.trim().length() == 0) {
			ncName = "emptyName";
		} else {
			ncName = ncName.trim();
			char c = ncName.charAt(0);
			if (!Character.isLetter(c) && c != '_') {
				ncName = '_' + ncName;
			}
			ncName = ncName.replaceAll("[^A-Za-z0-9_.-]", "_");
		}
		return ncName;
	}

	/******************************************
	 * Writes out an XML document to a file.
	 * @param doc (Document)
	 * @param xmlFilename (String)
	 *******************************************/
	public static void writeXMLToFile(Document doc, String xmlFilename) {

		try {
			Serializer serializer = new Serializer(new FileOutputStream(
					xmlFilename), "UTF-8");
			serializer.write(doc);
			serializer.flush();
		} catch (IOException ex) {
			ex.printStackTrace();
		}
	}

	/******************************************
	 * Cleans up text from html characters.
	 * @param paragraph (String)
	 * @return cleanedParagraph (String)
	 *******************************************/
	public static String cleanHTMLText(String paragraph) {
		String cleanedParagraph = paragraph;	
		cleanedParagraph = convertExponentials(cleanedParagraph);
		cleanedParagraph = Jsoup.clean(cleanedParagraph,new Safelist());
		cleanedParagraph =Jsoup.parse(cleanedParagraph).text();
		return cleanedParagraph;
	}

	/**********************************
	 * Converts exponents in the format of 10<sup>16</sup> to 10 exp=16.
	 * This is mainly to avoid loss when cleaning xml tags.
	 * @param xmlExponential (String)
	 * @return nonXMLExponential (String).
	 */
	private static String convertExponentials(String xmlExponential) {
		String nonXMLExponential = xmlExponential;
		Matcher exponentialMatcher = exponentXMLPattern.matcher(xmlExponential);
		if (exponentialMatcher.find()){
		  nonXMLExponential = exponentialMatcher.replaceAll(exponentialMatcher.group(1)+"^"+exponentialMatcher.group(2));
		  
		}
		return nonXMLExponential;
	}

	/***********************************************************
	 * Loads a "sentence" file consisting of a single line of text. Has to be
	 * qualified file name e.g. uk/ac/cam/ch/wwmm/foo.txt.
	 * 
	 * @param resourceName (String)
	 * @return sentence (String)
	 **************************************************************/
	public static String readSentence(String resourceName) {
		// requires sentence with no newlines except possibly at end
		String sentence = null;
		try {
			InputStream refStream = ClassLoader
					.getSystemResourceAsStream(resourceName);

			sentence = IOUtils.toString(refStream, "UTF-8");
		} catch (IOException e) {
			throw new RuntimeException("Cannot read sentence: " + resourceName);
		}
		return sentence.trim();
	}

	/**************************************************************
	 * Returns the content of the resource as an inputstream.
	 * 
	 * @param pathName (String)
	 * @return sentence (String)
	 * @throws IOException
	 **************************************************************/
	public static String getPathAsInputStream(String pathName)
			throws IOException {

		String sentence = null;
		BufferedReader br = null;
		try {

			br = new BufferedReader(new InputStreamReader(
					new FileInputStream(new File(pathName)), "UTF-8"));
			sentence = br.readLine();
		} catch (IOException e) {
			throw new RuntimeException("Cannot read sentence: " + pathName);
		}
		finally{
			IOUtils.closeQuietly(br);
		}
		return sentence.trim();
	}

	/**************************************************************
	 * Returns the content of the resource as an inputstream.
	 * 
	 * @param context (Class)
	 * @param pathName (String)
	 * @return sentence (String)
	 * @throws IOException
	 **************************************************************/
	public static InputStream getInputStream(Class<?> context, String pathName)
			throws IOException {

		InputStream inStream = context.getResourceAsStream(pathName);
		if (inStream == null) {
			throw new IOException("File not found: " + pathName
					+ " (using context " + context.getName() + ")");
		}

		return inStream;
	}

	/***********************************
	 * Checks if a string contains a number.
	 * 
	 * @param currentString (String)
	 * @return boolean
	 ***********************************/
	public static boolean containsNumber(String currentString) {
		for (char c : currentString.toCharArray()) {
			if (Character.isDigit(c)) {
				return true;
			}
		}
		return false;
	}

	/*******************************************
	 * Overloading method for running chemicalTagger
	 * Sets runSpectraTagger to false by default.
	 * @param text (String)
	 * @return doc (Document)
	 *****************************************/
	public static Document runChemicalTagger(String text) {
		return runChemicalTagger(text, false);

	}

	/*******************************************
	 * Convenience method for running chemicalTagger
	 * Includes a flag for setting spectraTagger.
	 * @param text (String)
	 * @param runSpectraTagger (boolean)
	 * @return doc (Document)
	 *****************************************/
	public static Document runChemicalTagger(String text,
			boolean runSpectraTagger) {
		ChemistryPOSTagger chemPos = ChemistryPOSTagger.getDefaultInstance();
        //ChemistryPOSTagger chemPos = new ChemistryPOSTagger(ctTokeniser, oscarTagger, regexTagger, openNLPTagger)
		POSContainer posContainer = chemPos.runTaggers(text);
		
		//POSContainer posContainer = chemPos.runTaggers(inputSentence, prioritiseOscar, useSpectraTagger)
		ChemistrySentenceParser chemistrySentenceParser = new ChemistrySentenceParser(
				posContainer);

		chemistrySentenceParser.parseTags();
		Document doc = chemistrySentenceParser.makeXMLDocument();
		return doc;
	}
	/**************************************
	 * Loads the tags from the .tokens file generated by Antlr.
	 * @param contextClass 
	 * @return HashSet
	 */
	public static HashSet<String> loadsTagsFromFile(Class<?> contextClass) {
		HashSet<String> tagSet = new HashSet<String>();
		String pathName = "ChemicalChunker.tokens";
		InputStream inStream = contextClass.getClassLoader().getResourceAsStream(pathName);
		List<String> readlines = null;
		try {
			readlines = IOUtils.readLines(inStream);
		} catch (UncheckedIOException e) {
			throw new RuntimeException("Could not load tokens file",e);
		}
		for (String line : readlines) {
			if (line.startsWith("'")){
			 String tag = line.split("'")[1];
			 tagSet.add(tag);	
			}
		}

		IOUtils.closeQuietly(inStream);
		return tagSet;
	}
	
	/**
	 * Gets the next terminal element. This element need not be a sibling
	 * @param startingEl
	 * @return
	 */
	public static Element getNextTerminalElement(Element startingEl) {
		ParentNode parent = startingEl.getParent();
		if (parent == null || !(parent instanceof Element)){
			return null;
		}
		int index = parent.indexOf(startingEl);
		if (index +1 >=parent.getChildCount()){
			return getNextTerminalElement((Element) parent);//reached end of element
		}
		Node nextNode = parent.getChild(index+1);
		if (!(nextNode instanceof Element)){
			return null;
		}
		Element next =(Element) nextNode;
		Elements children =next.getChildElements();
		while (children.size()!=0){
			next =children.get(0);
			children =next.getChildElements();
		}
		return next;
	}

	/**
	 * Gets the previous terminal element. This element need not be a sibling
	 * @param startingEl
	 * @return
	 */
	public static Element getPreviousTerminalElement(Element startingEl) {
		ParentNode parent = startingEl.getParent();
		if (parent == null || !(parent instanceof Element)){
			return null;
		}
		int index = parent.indexOf(startingEl);
		if (index ==0) {
			return getPreviousTerminalElement((Element) parent);//reached beginning of element
		}
		Node previousNode = parent.getChild(index-1);
		if (!(previousNode instanceof Element)){
			return null;
		}
		Element previous =(Element) previousNode;
		Elements children =previous.getChildElements();
		while (children.size()!=0){
			previous =children.get(children.size()-1);
			children =previous.getChildElements();
		}
		return previous;
	}
	
	/**
	 * Gets the previous sibling if one exists or the previous sibling of the first parent element with previous siblings
	 * @param startingEl
	 * @return
	 */
	public static Element getPreviousSiblingOrParentsSibling(Element startingEl) {
		ParentNode parent = startingEl.getParent();
		if (parent == null || !(parent instanceof Element)){
			return null;
		}
		int index = parent.indexOf(startingEl);
		if (index ==0) {
			return getPreviousSiblingOrParentsSibling((Element) parent);//reached beginning of element
		}
		Node previousNode = parent.getChild(index-1);
		if (!(previousNode instanceof Element)){
			return null;
		}
		return (Element) previousNode;
	}
	
	/********************************************
	 * Writes items of a list to a file.
	 * @param sentenceList
	 * @param filename
	 * @throws IOException 
	 ********************************************/
	public static void writeListToFile(List<String> sentenceList, String filename) throws IOException {
		OutputStream os = new FileOutputStream(new File(filename));
		IOUtils.writeLines(sentenceList, null, os, "UTF-8");
		IOUtils.closeQuietly(os);
	}
	
	/**
	 * Concatenates token surfaces adding a space between them
	 * @param tokens
	 * @return
	 */
	public static String tokensToSpaceDelimitedStr(List<Token> tokens){
	    StringBuilder sb = new StringBuilder();
	    for (Token token : tokens) {
	    	sb.append(token.getSurface());
	    	sb.append(" ");
		}
	    return sb.toString().trim();
	}
	
}
