/**
 * 
 */
package eu.dnetlib.data.collective.transformation.engine.functions;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.dom4j.Node;

import eu.dnetlib.common.profile.Resource;
import eu.dnetlib.common.utils.XMLUtils;

/**
 * @author jochen
 *
 */
public class Vocabulary implements IVocabulary{

	private List<Term> terms;
	private Map<String, String> encodingMap;
	private Resource resource;
	private boolean isCaseSensitive = true;
	private String delimiter = null;
	private String name = null;
		
	/**
	 * @return the terms
	 */
	public List<Term> getTerms() {
		return terms;
	}

	/**
	 * @param terms the terms to set
	 */
	public void setTerms(List<Term> terms) {
		this.terms = terms;
	}

	/**
	 * @return the name
	 */
	public String getName() {
		return name;
	}

	/**
	 * @param name the name to set
	 */
	public void setName(String name) {
		this.name = name;
	}

	public String getVocabularyName(){
		return resource.getValue("//VOCABULARY_NAME");
	}
	
	/**
	 * returns the normalized, encoded String for a given key if found, otherwise a special value -depending on the vocabulary- is returned indicating that it couldn't be normalized 
	 * @param key a list of Strings to encode
	 * @return a normalized, encoded String
	 */
	@Override
	public String encoding(List<String> keys)throws ProcessingException{
		// take the first best
		for (String key: keys){
			key = key.trim();
			if (!isCaseSensitive) 
				key = key.toLowerCase();
			if (encodingMap.containsKey(key))
				return encodingMap.get(key);
		}
		if (encodingMap.containsKey("Unknown") || encodingMap.containsKey("unknown")){
			if (isCaseSensitive) return encodingMap.get("Unknown");
			else  return encodingMap.get("unknown");
		}else{
			if (isCaseSensitive) return encodingMap.get("Undetermined");
			else  return encodingMap.get("undetermined");
		}
	}
	
	class Term{
		String code;
		String name;
		List<String> synonyms = new LinkedList<String>();
		
		void addSynonym(String synonym){
			synonyms.add(synonym);
		}
		
		List<String> getSynonyms(){
			return synonyms;
		}
	}

	
	/**
	 * init the encoding with the given list of term parameters
	 * @param termList list of parameters with expected key:value pairs 'name':string, 'encoding':string, 'synonyms':list<string>
	 */
	@SuppressWarnings("unchecked")
	public void setResource(List<Map<String, ?>> aTermList){
		terms = new LinkedList<Term>();
		for (Map<String, ?> termMap : aTermList){
			Term t = new Term();
			terms.add(t);
			t.name = (String)termMap.get("name");
			t.code = (String)termMap.get("code");
			for (String synonym: (List<String>)termMap.get("synonyms"))
				t.addSynonym(synonym);
		}
		setCode();
	}
	
	/**
	 * init the encoding with term parameters from a vocabulary resource profile
	 * @param resource
	 */
	public void setResource(Resource resource) {
		this.resource = resource;
		terms = new LinkedList<Term>();
		List<Node> nodes = resource.getNodeList("//TERMS/*");
		int index = 1;
		for (Node n: nodes){
			Term t = new Term();
			terms.add(t);
			try {
				t.name = XMLUtils.getNode(n, "//TERM[" + index + "]/@english_name").getText();
				t.code = XMLUtils.getNode(n, "//TERM[" + index + "]/@code").getText();
				List<Node> nsynonyms = XMLUtils.getNodes(n, "//TERM[" + index + "]/SYNONYMS/*");
				int indexSynonyms = 1;
				for (Node nsynonym: nsynonyms){
					String synonymTerm = XMLUtils.getNode(nsynonym, "//TERM[" + index + "]//SYNONYM[" + indexSynonyms + "]/@term").getText();
					t.addSynonym(synonymTerm);
					indexSynonyms++;
				}
			} catch (Exception e) {
				throw new IllegalStateException(e);
			}
			index++;
		}
		setCode();
	}
	
	private void setCode(){
		encodingMap = new TreeMap<String, String>();
		for (Term t: terms){
			if (isCaseSensitive){ 
				encodingMap.put(t.name, t.code);
				encodingMap.put(t.code, t.code);
			}else{
				encodingMap.put(t.name.toLowerCase(), t.code);
				encodingMap.put(t.code.toLowerCase(), t.code);
			}
			if (this.delimiter != null){
				String[] splittedEncodings = t.code.split(this.delimiter);
				for (String encoding: splittedEncodings){
					if (isCaseSensitive){
						encodingMap.put(encoding, t.code);						
					}else{
						encodingMap.put(encoding.toLowerCase(), t.code);						
					}
				}
			}

			for (String synonym : t.synonyms){
				if (isCaseSensitive) encodingMap.put(synonym, t.code);
				else encodingMap.put(synonym.toLowerCase(), t.code);
			}
		}
	}

	public Resource getResource() {
		return resource;
	}

	public void setCaseSensitive(boolean isCaseSensitive) {
		this.isCaseSensitive = isCaseSensitive;
	}

	public boolean isCaseSensitive() {
		return isCaseSensitive;
	}

	/**
	 * @param delimiter the delimiter to set
	 */
	public void setDelimiter(String delimiter) {
		this.delimiter = delimiter;
	}

	/**
	 * @return the delimiter
	 */
	public String getDelimiter() {
		return delimiter;
	}

	@Override
	public List<String> encoding(List<String> aKeys, String aDefaultPattern,
			String aFilterFunction) throws ProcessingException {
		throw new ProcessingException("no implementation of filtered encoding.");
	}
	

}
