package eu.dnetlib.vocabularies.publisher;

import com.google.common.collect.Lists;
import eu.dnetlib.enabling.tools.SplittedQueryExecutor;
import eu.dnetlib.miscutils.collections.MappedCollection;
import eu.dnetlib.vocabularies.publisher.model.Synonym;
import eu.dnetlib.vocabularies.publisher.model.Vocabulary;
import eu.dnetlib.vocabularies.publisher.model.VocabularyTerm;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;

import java.util.List;
import java.util.stream.Collectors;

public class VocabularyRetriever {

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

	@Autowired
	private SplittedQueryExecutor queryExecutor;

	@Cacheable("vocabularies")
	public List<Vocabulary> listVocabularies() {
		log.debug("listVocabularies(): not using cache");
		String query = "for $x in collection('/db/DRIVER/VocabularyDSResources/VocabularyDSResourceType') order by $x//VOCABULARY_NAME "
				+ "return concat ($x//RESOURCE_IDENTIFIER/@value, ':-:',$x//VOCABULARY_NAME, ':-:',$x//VOCABULARY_DESCRIPTION, ':-:',$x//VOCABULARY_NAME/@code)";
		Iterable<Vocabulary> vocs = this.queryExecutor.query(Vocabulary.class, query);
		return Lists.newArrayList(vocs);
	}

	@Cacheable(value = "terms", key = "#id")
	public Vocabulary getVocabulary(final String id) throws VocabularyNotFoundException {
		log.warn(String.format("getVocabulary(%s): not using cache", id));
		// get the vocabulary info:
		String queryVoc = "let $x := /*[.//RESOURCE_IDENTIFIER/@value='"
				+ id
				+ "']"
				+ " return concat ($x//RESOURCE_IDENTIFIER/@value, ':-:',$x//VOCABULARY_NAME, ':-:',$x//VOCABULARY_DESCRIPTION, ':-:',$x//VOCABULARY_NAME/@code)";

		Vocabulary voc = getVocabularyByQuery(queryVoc);
		if (voc == null) throw new VocabularyNotFoundException("Can't find vocabulary by id: " + id);
		else return voc;
	}

	@Cacheable(value = "termsByCode", key = "#code")
	public Vocabulary getVocabularyByCode(final String code) throws VocabularyNotFoundException {
		log.warn(String.format("getVocabularyByCode(%s): not using cache", code));
		String queryVoc = "let $x := //RESOURCE_PROFILE[.//VOCABULARY_NAME/@code='"
				+ code
				+ "']"
				+ " return concat ($x//RESOURCE_IDENTIFIER/@value, ':-:',$x//VOCABULARY_NAME, ':-:',$x//VOCABULARY_DESCRIPTION, ':-:',$x//VOCABULARY_NAME/@code)";
		Vocabulary voc = getVocabularyByQuery(queryVoc);
		if (voc == null) throw new VocabularyNotFoundException("Can't find vocabulary by code: " + code);
		else return voc;
	}

	@Cacheable(value = "synonymsByCode", key = "#termCode")
	public VocabularyTerm getTermSynonyms(final String vocabularyCode, final String termCode) throws VocabularyNotFoundException {
		log.warn(String.format("getTermSynonyms(%s, %s): not using cache", vocabularyCode, termCode));
		final Vocabulary voc = getVocabularyByCode(vocabularyCode);

		final VocabularyTerm vt = voc.getTerms().stream()
				.filter(t -> t.getCode().equalsIgnoreCase(termCode))
				.findFirst()
				.get();

		String querySynonyms =
				String.format("for $x in /*[.//RESOURCE_IDENTIFIER/@value='%s']//TERM[./@code = '%s']//SYNONYM " +
						"return concat ($x/@term,':-:',$x/@encoding)", voc.getId(), termCode);

		final List<String> res = queryExecutor.performQuery(querySynonyms);

		if (res != null) {
			final List<Synonym> syn = res.stream()
					.map(s -> {
						String[] split = s.split(":-:");
						return new Synonym(split[0], split[1]);
					}).collect(Collectors.toList());

			vt.setSynonyms(syn);
		}
		return vt;
	}

	private Vocabulary getVocabularyByQuery(final String query) {
		Iterable<Vocabulary> vocs = this.queryExecutor.query(Vocabulary.class, query);
		if (vocs.iterator().hasNext()) {
			Vocabulary theVoc = vocs.iterator().next();
			if (theVoc.getId() == null) return null;
			// now the terms
			String queryTerms = "for $x in /*[.//RESOURCE_IDENTIFIER/@value='" + theVoc.getId() + "']//TERM order by $x/@english_name "
					+ "return concat ($x/@english_name,':-:',$x/@native_name,':-:',$x/@encoding,':-:',$x/@code)";
			Iterable<VocabularyTerm> terms = this.queryExecutor.query(VocabularyTerm.class, queryTerms);
			theVoc.setTerms(Lists.newArrayList(terms));
			return theVoc;
		} else return null;
	}

}
