package eu.dnetlib.data.utility.cleaner;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.springframework.beans.factory.annotation.Required;

import com.google.common.collect.Lists;

import eu.dnetlib.data.utility.cleaner.rmi.CleanerException;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService;
import eu.dnetlib.enabling.tools.ServiceLocator;
import eu.dnetlib.miscutils.datetime.DateUtils;

/**
 * @author michele
 * 
 *         Vocabulary rules must be declared in a CleanerDS profile, for each vocabulary must be present the relative
 *         VocabularyDS profile:
 * 
 *         <RULE xpath="..." vocabularies="VOC1" /> <RULE xpath="..." vocabularies="VOC1, VOC2, VOC3" />
 */

public class VocabularyRule extends XPATHCleaningRule {

	private static final long VOCABULARIES_EXPIRATION_TIME = 3600000; // 1 hour

	private List<String> vocabularies;
	private ServiceLocator<ISLookUpService> lookupLocator;

	// Normal fields
	private long lastupdate = 0;

	private static final Log log = LogFactory.getLog(VocabularyRule.class); // NOPMD by marko on 11/24/08 5:02 PM

	private static final Log logVocabularies = LogFactory.getLog("VOCABULARY_RULES");

	private List<Synonym> vocabularyTerms = new ArrayList<Synonym>();

	@Override
	public List<Map<String, String>> applyXpathRule(final Document doc, final String context) throws CleanerException {
		verifyVocabulary();
		return super.applyXpathRule(doc, context);
	}

	@Override
	protected String calculateNewValue(final String oldValue, final String context, final String id) throws CleanerException {
		log.debug("calculating new value " + oldValue + " context " + context);

		if (vocabularyTerms.isEmpty()) {
			log.warn("Vocabulary terms is void, vocabularies: " + this.vocabularies);
		}

		String newValue = null;
		for (final Synonym s : vocabularyTerms) {
			if (context == null) {
				if (s.getSynonym().equalsIgnoreCase(oldValue)) {
					newValue = s.getTerm();
				}
			} else {
				if (s.getSynonym().equalsIgnoreCase(oldValue) && context.equals(s.getContext())) {
					newValue = s.getTerm();
				}
			}
		}

		if (newValue == null) {
			log.debug("Synonym " + oldValue + " not found in vocabulary");
			return oldValue;
		}

		if (logVocabularies.isInfoEnabled() && !newValue.equals(oldValue)) {
			logVocabularies.info("[" + oldValue + "] => [" + newValue + "], " + "VOCAB: " + this.vocabularies + ", " + "RECORD: " + id + ", " + "XPATH: "
					+ this.getXpath());
		}
		return newValue;
	}

	private void verifyVocabulary() throws CleanerException {
		synchronized (this) {
			if (DateUtils.now() - lastupdate < VOCABULARIES_EXPIRATION_TIME) {
				return;
			}

			final List<Synonym> newVocabularyTerms = Lists.newArrayList();

			for (final String vocabulary : vocabularies) {
				try {
					final String query = "for $x in collection('/db/DRIVER/VocabularyDSResources/VocabularyDSResourceType')"
							+ "//RESOURCE_PROFILE[.//VOCABULARY_NAME='" + vocabulary.trim() + "']//SYNONYM "
							+ "return concat($x/@term,'|-:-|',$x/@encoding,'|-:-|',$x/../../@english_name), "
							+ "for $x in collection('/db/DRIVER/VocabularyDSResources/VocabularyDSResourceType')"
							+ "//RESOURCE_PROFILE[.//VOCABULARY_NAME='" + vocabulary.trim() + "']//TERM "
							+ "return concat($x/@english_name,'|-:-|ALL|-:-|',$x/@english_name)";

					for (final String s : lookupLocator.getService().quickSearchProfile(query)) {
						log.debug("SYNONYM : " + s);
						final String[] arr = s.split("\\|-:-\\|");
						if (arr[0] == null || arr[0].isEmpty()) {
							continue;
						}
						newVocabularyTerms.add(new Synonym(arr[0], arr[1], arr[2]));
					}

					log.info("VOCABULARY " + vocabulary.trim() + " - terms size " + newVocabularyTerms.size());
				} catch (final Exception e) {
					throw new CleanerException("Error obtaining vocabulary " + vocabulary, e);
				}
			}
			lastupdate = DateUtils.now();
			vocabularyTerms = newVocabularyTerms;
		}
	}

	@Override
	protected Map<String, String> verifyValue(final String value, final String context, final String id) throws CleanerException {
		if (vocabularyTerms.isEmpty()) {
			log.warn("Vocabulary terms is void, vocabularies: " + this.vocabularies);
		}

		for (final Synonym s : vocabularyTerms) {
			if (context == null) {
				if (s.getTerm().equalsIgnoreCase(value)) {
					return null;
				}
			} else {
				if (s.getTerm().equalsIgnoreCase(value) && context.equals(s.getContext())) {
					return null;
				}
			}
		}

		if (logVocabularies.isInfoEnabled()) {
			logVocabularies.info("[" + value + "] is INVALID, " + "VOCAB: " + this.vocabularies + ", " + "RECORD: " + id + ", " + "XPATH: "
					+ this.getXpath());
		}
		final Map<String, String> error = new HashMap<String, String>();
		error.put("term", value);
		error.put("vocabularies", this.vocabularies.toString().replaceAll("\\[", "").replaceAll("\\]", ""));
		error.put("xpath", this.getXpath());
		return error;
	}

	public List<Synonym> getVocabularyTerms() {
		return vocabularyTerms;
	}

	public void setVocabularyTerms(final List<Synonym> vocabularyTerms) {
		this.vocabularyTerms = vocabularyTerms;
	}

	@Required
	public void setVocabularies(final List<String> vocabularies) {
		this.vocabularies = vocabularies;
	}

	@Required
	public void setLookupLocator(final ServiceLocator<ISLookUpService> lookupLocator) {
		this.lookupLocator = lookupLocator;
	}

}
