package eu.dnetlib.enabling.datasources;

import java.io.StringReader;
import java.text.ParseException;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import org.antlr.stringtemplate.StringTemplate;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.quartz.CronExpression;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.core.io.ClassPathResource;

import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import eu.dnetlib.enabling.datasources.rmi.BrowsableField;
import eu.dnetlib.enabling.datasources.rmi.BrowseTerm;
import eu.dnetlib.enabling.datasources.rmi.DatasourceConstants;
import eu.dnetlib.enabling.datasources.rmi.DatasourceDesc;
import eu.dnetlib.enabling.datasources.rmi.DatasourceManagerService;
import eu.dnetlib.enabling.datasources.rmi.DatasourceManagerServiceException;
import eu.dnetlib.enabling.datasources.rmi.IfaceDesc;
import eu.dnetlib.enabling.datasources.rmi.RepositoryMapEntry;
import eu.dnetlib.enabling.datasources.rmi.SearchInterfacesEntry;
import eu.dnetlib.enabling.datasources.rmi.SimpleDatasourceDesc;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpDocumentNotFoundException;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService;
import eu.dnetlib.enabling.is.registry.rmi.ISRegistryException;
import eu.dnetlib.enabling.is.registry.rmi.ISRegistryService;
import eu.dnetlib.enabling.locators.UniqueServiceLocator;
import eu.dnetlib.enabling.tools.AbstractBaseService;

public class DatasourceManagerServiceImpl extends AbstractBaseService implements DatasourceManagerService {

	private static final Log log = LogFactory.getLog(DatasourceManagerServiceImpl.class);

	private static final String REPOSITORY_RESOURCE_TYPE = "RepositoryServiceResourceType";

	private List<XmlBrowsableField> browsableFields;

	private ClassPathResource findReposQueryTmpl = new ClassPathResource("/eu/dnetlib/enabling/datasources/templates/findRepos.xquery.st");

	private ClassPathResource browseRepoApisQueryTmpl = new ClassPathResource("/eu/dnetlib/enabling/datasources/templates/browseRepoApis.xquery.st");
	private ClassPathResource findRepoApisQueryTmpl = new ClassPathResource("/eu/dnetlib/enabling/datasources/templates/findRepoApis.xquery.st");
	private ClassPathResource findReposMapQuery = new ClassPathResource("/eu/dnetlib/enabling/datasources/templates/findReposMap.xquery");
	private ClassPathResource simpleFindReposQueryTmpl = new ClassPathResource("/eu/dnetlib/enabling/datasources/templates/simpleFindRepos.xquery.st");

	@Resource
	private UniqueServiceLocator serviceLocator;

	private String environment;

	private String fixDsId(final String dsId) throws DatasourceManagerServiceException {
		if (dsId.endsWith("_UGVuZGluZ1JlcG9zaXRvcnlSZXNvdXJjZXMvUmVwb3NpdG9yeVNlcnZpY2VSZXNvdXJjZVR5cGU=")
				|| dsId.endsWith("_UmVwb3NpdG9yeVNlcnZpY2VSZXNvdXJjZXMvUmVwb3NpdG9yeVNlcnZpY2VSZXNvdXJjZVR5cGU=")) {
			return dsId;
		} else {
			try {
				return serviceLocator
						.getService(ISLookUpService.class)
						.getResourceProfileByQuery(
								"for $x in collection("
										+ "'/db/DRIVER/RepositoryServiceResources/RepositoryServiceResourceType',"
										+ "'/db/DRIVER/PendingRepositoryResources/RepositoryServiceResourceType') "
										+ "where $x//DATASOURCE_ORIGINAL_ID = '" + dsId + "' "
										+ "return $x//RESOURCE_IDENTIFIER/@value/string()");
			} catch (Exception e) {
				throw new DatasourceManagerServiceException("Invalid id: " + dsId, e);
			}
		}
	}

	@Override
	public boolean addDatasource(final DatasourceDesc ds) throws DatasourceManagerServiceException {
		try {
			final String profile = DatasourceDescToProfile.convert(ds, environment);
			serviceLocator.getService(ISRegistryService.class).registerProfile(profile);
			return true;
		} catch (Exception e) {
			log.error("Error registering profile", e);
			throw new DatasourceManagerServiceException("Error registering profile", e);
		}
	}

	@Override
	public boolean deleteDatasource(final String dsId) throws DatasourceManagerServiceException {
		return _deleteDatasource(fixDsId(dsId));
	}

	private boolean _deleteDatasource(final String dsId) throws DatasourceManagerServiceException {
		try {
			return serviceLocator.getService(ISRegistryService.class).deleteProfile(dsId);
		} catch (ISRegistryException e) {
			log.error("Error deleting profile " + dsId, e);
			throw new DatasourceManagerServiceException("Error deleting profile " + dsId, e);
		}
	}

	@Override
	public DatasourceDesc getDatasource(final String dsId) throws DatasourceManagerServiceException {
		return _getDatasource(fixDsId(dsId));
	}

	private DatasourceDesc _getDatasource(final String dsId) throws DatasourceManagerServiceException {
		try {
			final String profile = serviceLocator.getService(ISLookUpService.class).getResourceProfile(dsId);
			return ProfileToDatasourceDesc.convert(profile);
		} catch (Exception e) {
			log.error("Error accessing profile " + dsId, e);
			throw new DatasourceManagerServiceException("Error accessing profile " + dsId, e);
		}
	}

	@Override
	public List<DatasourceDesc> listAllDatasources() throws DatasourceManagerServiceException {
		return listDatasourcesUsingFilter(null, null, null, null);
	}

	@Override
	public List<DatasourceDesc> listDatasourcesUsingFilter(final String compliance,
			final String contentDescription,
			final String iisProcessingWorkflow,
			final String collectedFrom)
			throws DatasourceManagerServiceException {
		try {
			final StringTemplate st = new StringTemplate();
			st.setTemplate(IOUtils.toString(findReposQueryTmpl.getInputStream()));

			final Map<String, String> conds = Maps.newHashMap();

			if (StringUtils.isNotBlank(compliance)) {
				conds.put("//INTERFACE/@compliance", compliance);
			}
			if (StringUtils.isNotBlank(contentDescription)) {
				conds.put("//INTERFACE/@contentDescription", contentDescription);
			}
			if (StringUtils.isNotBlank(iisProcessingWorkflow)) {
				// NOT USED
			}
			if (StringUtils.isNotBlank(collectedFrom)) {
				// NOT USED
			}

			if (!conds.isEmpty()) {
				st.setAttribute("conds", conds);
			}

			final List<String> list = serviceLocator.getService(ISLookUpService.class).quickSearchProfile(st.toString());

			return Lists.transform(list, new ProfileToDatasourceDesc());
		} catch (Exception e) {
			log.error("Error listing datasources", e);
			throw new DatasourceManagerServiceException("Error listing datasources", e);
		}
	}

	@Override
	public boolean updateLevelOfCompliance(final String dsId, final String ifaceId, final String level) throws DatasourceManagerServiceException {
		return _updateLevelOfCompliance(fixDsId(dsId), ifaceId, level);
	}

	private boolean _updateLevelOfCompliance(final String dsId, final String ifaceId, final String level) throws DatasourceManagerServiceException {
		try {
			return serviceLocator.getService(ISRegistryService.class)
					.updateProfileNode(dsId, "//INTERFACE[@id = '" + ifaceId + "']/@compliance", "'" + level + "'");
		} catch (Exception e) {
			log.error("Error updating profile: " + dsId, e);
			throw new DatasourceManagerServiceException("Error updating profile: " + dsId, e);
		}
	}

	@Override
	public boolean updateBaseUrl(final String dsId, final String ifaceId, final String baseUrl) throws DatasourceManagerServiceException {
		return _updateBaseUrl(fixDsId(dsId), ifaceId, baseUrl);
	}

	private boolean _updateBaseUrl(final String dsId, final String ifaceId, final String baseUrl) throws DatasourceManagerServiceException {
		try {
			return serviceLocator.getService(ISRegistryService.class)
					.updateProfileNode(dsId, "//INTERFACE[@id = '" + ifaceId + "']/BASE_URL", "<BASE_URL>" + baseUrl + "</BASE_URL>");
		} catch (Exception e) {
			log.error("Error updating profile: " + dsId, e);
			throw new DatasourceManagerServiceException("Error updating profile: " + dsId, e);
		}
	}

	@Override
	public boolean updateActivationStatus(final String dsId, final String ifaceId, final boolean active) throws DatasourceManagerServiceException {
		return _updateActivationStatus(fixDsId(dsId), ifaceId, active);
	}

	private boolean _updateActivationStatus(final String dsId, final String ifaceId, final boolean active) throws DatasourceManagerServiceException {
		try {
			return serviceLocator.getService(ISRegistryService.class)
					.updateProfileNode(dsId, "//INTERFACE[@id = '" + ifaceId + "']/@active", "'" + active + "'");
		} catch (Exception e) {
			log.error("Error updating profile: " + dsId, e);
			throw new DatasourceManagerServiceException("Error updating profile: " + dsId, e);
		}
	}

	@Override
	public boolean updateContentDescription(final String dsId, final String ifaceId, final String desc) throws DatasourceManagerServiceException {
		return _updateContentDescription(fixDsId(dsId), ifaceId, desc);
	}

	private boolean _updateContentDescription(final String dsId, final String ifaceId, final String desc) throws DatasourceManagerServiceException {
		try {
			return serviceLocator.getService(ISRegistryService.class)
					.updateProfileNode(dsId, "//INTERFACE[@id = '" + ifaceId + "']/@contentDescription", "'" + desc + "'");
		} catch (Exception e) {
			log.error("Error updating profile: " + dsId, e);
			throw new DatasourceManagerServiceException("Error updating profile: " + dsId, e);
		}
	}

	@Override
	public boolean setIisProcessingWorkflow(final String dsId, final String ifaceId, final String wf) throws DatasourceManagerServiceException {
		throw new DatasourceManagerServiceException("NOT IMPLEMENTED");
	}

	@Override
	public boolean updateExtraField(final String dsId, final String ifaceId, final String field, final String value, final boolean preserveOriginal)
			throws DatasourceManagerServiceException {
		return _updateExtraField(fixDsId(dsId), ifaceId, field, value, preserveOriginal);
	}

	private boolean _updateExtraField(final String dsId, final String ifaceId, final String field, final String value, final boolean preserveOriginal)
			throws DatasourceManagerServiceException {
		try {
			final String profile = serviceLocator.getService(ISLookUpService.class).getResourceProfile(dsId);
			final Document doc = new SAXReader().read(new StringReader(profile));

			final Node ifcNode = doc.selectSingleNode("//INTERFACE[@id = '" + ifaceId + "']");
			final Node node = ifcNode.selectSingleNode("./INTERFACE_EXTRA_FIELD[@name = '" + field + "']");
			if (node != null) {
				node.setText(value);
			} else {
				final Element e = ((Element) ifcNode).addElement("INTERFACE_EXTRA_FIELD");
				e.addAttribute("name", field);
				e.setText(value);
			}
			return serviceLocator.getService(ISRegistryService.class).updateProfile(dsId, doc.asXML(), REPOSITORY_RESOURCE_TYPE);
		} catch (Exception e) {
			log.error("Error updating profile: " + dsId, e);
			throw new DatasourceManagerServiceException("Error updating profile: " + dsId, e);
		}
	}

	@Override
	public boolean updateAccessParam(final String dsId, final String ifaceId, final String field, final String value, final boolean preserveOriginal)
			throws DatasourceManagerServiceException {
		return _updateAccessParam(fixDsId(dsId), ifaceId, field, value, preserveOriginal);
	}

	private boolean _updateAccessParam(final String dsId, final String ifaceId, final String field, final String value, final boolean preserveOriginal)
			throws DatasourceManagerServiceException {
		try {
			final String profile = serviceLocator.getService(ISLookUpService.class).getResourceProfile(dsId);
			final Document doc = new SAXReader().read(new StringReader(profile));

			final Node node = doc.selectSingleNode("//INTERFACE[@id = '" + ifaceId + "']/ACCESS_PROTOCOL");
			((Element) node).addAttribute(field, value);

			return serviceLocator.getService(ISRegistryService.class).updateProfile(dsId, doc.asXML(), REPOSITORY_RESOURCE_TYPE);
		} catch (Exception e) {
			log.error("Error updating profile: " + dsId, e);
			throw new DatasourceManagerServiceException("Error updating profile: " + dsId, e);
		}
	}

	@Override
	public boolean deleteAccessParamOrExtraField(final String dsId, final String ifaceId, final String field) throws DatasourceManagerServiceException {
		return _deleteAccessParamOrExtraField(fixDsId(dsId), ifaceId, field);
	}

	private boolean _deleteAccessParamOrExtraField(final String dsId, final String ifaceId, final String field) throws DatasourceManagerServiceException {
		try {
			final String profile = serviceLocator.getService(ISLookUpService.class).getResourceProfile(dsId);
			final Document doc = new SAXReader().read(new StringReader(profile));

			final Node ef = doc.selectSingleNode("//INTERFACE[@id = '" + ifaceId + "']/INTERFACE_EXTRA_FIELD[@name = '" + field + "']");
			if (ef != null) {
				ef.detach();
			}
			final Node ap = doc.selectSingleNode("//INTERFACE[@id = '" + ifaceId + "']/ACCESS_PROTOCOL/@" + field);
			if (ap != null) {
				ap.detach();

			}
			return serviceLocator.getService(ISRegistryService.class).updateProfile(dsId, doc.asXML(), REPOSITORY_RESOURCE_TYPE);
		} catch (Exception e) {
			log.error("Error updating profile: " + dsId, e);
			throw new DatasourceManagerServiceException("Error updating profile: " + dsId, e);
		}
	}

	@Override
	public boolean addInterface(final String dsId, final IfaceDesc iface) throws DatasourceManagerServiceException {
		iface.setRemovable(true);
		return _addInterface(fixDsId(dsId), iface);
	}

	private boolean _addInterface(final String dsId, final IfaceDesc iface) throws DatasourceManagerServiceException {
		try {
			final String profile = serviceLocator.getService(ISLookUpService.class).getResourceProfile(dsId);
			final Document doc = new SAXReader().read(new StringReader(profile));

			final Node node = doc.selectSingleNode("//INTERFACE[@id = '" + iface.getId() + "']");
			if (node != null) {
				node.detach();
			}

			((Element) doc.selectSingleNode("//INTERFACES")).add(IfaceDescToNode.convert(iface));

			return serviceLocator.getService(ISRegistryService.class).updateProfile(dsId, doc.asXML(), REPOSITORY_RESOURCE_TYPE);
		} catch (Exception e) {
			log.error("Error updating profile: " + dsId, e);
			throw new DatasourceManagerServiceException("Error updating profile: " + dsId, e);
		}
	}

	@Override
	public boolean deleteInterface(final String dsId, final String ifaceId) throws DatasourceManagerServiceException {
		return _deleteInterface(fixDsId(dsId), ifaceId);
	}

	private boolean _deleteInterface(final String dsId, final String ifaceId) throws DatasourceManagerServiceException {
		try {
			final String profile = serviceLocator.getService(ISLookUpService.class).getResourceProfile(dsId);
			final Document doc = new SAXReader().read(new StringReader(profile));

			final Node node = doc.selectSingleNode("//INTERFACE[@id = '" + ifaceId + "']");
			if (node != null) {
				node.detach();
			}

			return serviceLocator.getService(ISRegistryService.class).updateProfile(dsId, doc.asXML(), REPOSITORY_RESOURCE_TYPE);
		} catch (Exception e) {
			log.error("Error updating profile: " + dsId, e);
			throw new DatasourceManagerServiceException("Error updating profile: " + dsId, e);
		}
	}

	@Override
	public boolean updateSQL(final String dsId, final String sql, final boolean delete) throws DatasourceManagerServiceException {
		throw new DatasourceManagerServiceException("NOT IMPLEMENTED");
	}

	@Override
	public Date findNextScheduledExecution(final String dsId, final String ifaceId) throws DatasourceManagerServiceException {
		return _findNextScheduledExecution(fixDsId(dsId), ifaceId);
	}

	private Date _findNextScheduledExecution(final String dsId, final String ifaceId) throws DatasourceManagerServiceException {
		final String xquery = "/*[.//DATAPROVIDER/@interface='" + ifaceId + "' and .//SCHEDULING/@enabled='true']//CRON/text()";
		try {
			final String cronExpression = serviceLocator.getService(ISLookUpService.class).getResourceProfileByQuery(xquery);
			final CronExpression cron = new CronExpression(cronExpression);
			return cron.getNextValidTimeAfter(new Date());
		} catch (ISLookUpDocumentNotFoundException e) {
			// When the value is not found a null value must be returned
			return null;
		} catch (ISLookUpException e) {
			log.error("Error in xquery: " + xquery, e);
			throw new DatasourceManagerServiceException("Error in xquery: " + xquery, e);
		} catch (ParseException e) {
			log.error("Error parsing cron expression", e);
			throw new DatasourceManagerServiceException("Error parsing cron expression", e);
		}
	}

	@Override
	public boolean bulkUpdateApiExtraFields(final String repoId, final String ifaceId, final Map<String, String> fields)
			throws DatasourceManagerServiceException {
		try {
			final String profile = serviceLocator.getService(ISLookUpService.class).getResourceProfile(repoId);

			final SAXReader reader = new SAXReader();
			final Document doc = reader.read(new StringReader(profile));

			final Element iface = (Element) doc.selectSingleNode("//INTERFACE[@id='" + ifaceId + "']");
			if (iface != null) {

				while (iface.selectNodes("./INTERFACE_EXTRA_FIELD").size() > 0) {
					iface.selectSingleNode("./INTERFACE_EXTRA_FIELD").detach();
				}
				for (Map.Entry<String, String> e : fields.entrySet()) {
					if (e.getValue() != null && !e.getValue().isEmpty()) {
						final Element field = iface.addElement("INTERFACE_EXTRA_FIELD");
						field.addAttribute("name", e.getKey());
						field.addText(e.getValue());
					}
				}
				serviceLocator.getService(ISRegistryService.class).updateProfile(repoId, doc.asXML(), REPOSITORY_RESOURCE_TYPE);
			} else {
				log.error("Invalid interface: " + ifaceId);
				throw new DatasourceManagerServiceException("Missing interface: " + ifaceId);
			}
		} catch (Exception e) {
			log.error("Error updating API of profile: " + repoId);
			throw new DatasourceManagerServiceException("Error updating API of profile: " + repoId, e);
		}
		return true;

	}

	@Override
	public boolean bulkUpdateApiAccessParams(final String dsId, final String ifaceId, final Map<String, String> params)
			throws DatasourceManagerServiceException {
		return _bulkUpdateApiAccessParams(fixDsId(dsId), ifaceId, params);
	}

	private boolean _bulkUpdateApiAccessParams(final String repoId, final String ifaceId, final Map<String, String> params)
			throws DatasourceManagerServiceException {
		try {
			final String profile = serviceLocator.getService(ISLookUpService.class).getResourceProfile(repoId);

			final SAXReader reader = new SAXReader();
			final Document doc = reader.read(new StringReader(profile));

			final Element accessNode = (Element) doc.selectSingleNode("//INTERFACE[@id='" + ifaceId + "']/ACCESS_PROTOCOL");
			if (accessNode != null) {
				while (accessNode.attributes().size() > 0) {
					accessNode.selectSingleNode("@*").detach();
				}
				for (Map.Entry<String, String> e : params.entrySet()) {
					if (e.getValue() != null && !e.getValue().isEmpty()) {
						if (e.getKey().equalsIgnoreCase("baseUrl")) {
							doc.selectSingleNode("//INTERFACE[@id='" + ifaceId + "']/BASE_URL").setText(e.getValue());
						} else {
							accessNode.addAttribute(e.getKey(), e.getValue());
						}
					}
				}
				serviceLocator.getService(ISRegistryService.class).updateProfile(repoId, doc.asXML(), REPOSITORY_RESOURCE_TYPE);
			} else {
				log.error("Invalid interface: " + ifaceId);
				throw new DatasourceManagerServiceException("Missing interface: " + ifaceId);
			}
		} catch (Exception e) {
			log.error("Error updating API of profile: " + repoId);
			throw new DatasourceManagerServiceException("Error updating API of profile: " + repoId, e);
		}
		return true;
	}

	@Override
	public boolean overrideCompliance(final String dsId, final String ifaceId, final String level) throws DatasourceManagerServiceException {
		return _overrideCompliance(fixDsId(dsId), ifaceId, level);
	}

	private boolean _overrideCompliance(final String repoId, final String ifaceId, final String compliance) throws DatasourceManagerServiceException {
		try {
			final String profile = serviceLocator.getService(ISLookUpService.class).getResourceProfile(repoId);

			final SAXReader reader = new SAXReader();
			final Document doc = reader.read(new StringReader(profile));

			final Element iface = (Element) doc.selectSingleNode("//INTERFACE[@id='" + ifaceId + "']");
			if (iface != null) {
				final Map<String, String> fields = Maps.newHashMap();

				if (!StringUtils.isEmpty(compliance)) {
					fields.put(DatasourceConstants.OVERRIDING_COMPLIANCE_FIELD, compliance);
				}

				while (iface.selectNodes("./INTERFACE_EXTRA_FIELD").size() > 0) {
					final Node node = iface.selectSingleNode("./INTERFACE_EXTRA_FIELD");
					final String name = node.valueOf("@name");

					if (!name.equals(DatasourceConstants.OVERRIDING_COMPLIANCE_FIELD)) {
						fields.put(node.valueOf("@name"), node.getText());
					}
					node.detach();
				}

				for (Map.Entry<String, String> e : fields.entrySet()) {
					if (e.getValue() != null && !e.getValue().isEmpty()) {
						final Element field = iface.addElement("INTERFACE_EXTRA_FIELD");
						field.addAttribute("name", e.getKey());
						field.addText(e.getValue());
					}
				}
				serviceLocator.getService(ISRegistryService.class).updateProfile(repoId, doc.asXML(), REPOSITORY_RESOURCE_TYPE);
			} else {
				log.error("Invalid interface: " + ifaceId);
				throw new DatasourceManagerServiceException("Missing interface: " + ifaceId);
			}
		} catch (Exception e) {
			log.error("Error updating API of profile: " + repoId);
			throw new DatasourceManagerServiceException("Error updating API of profile: " + repoId, e);
		}

		return true;
	}

	@Override
	public List<BrowsableField> listBrowsableFields() throws DatasourceManagerServiceException {
		return Lists.transform(getBrowsableFields(), new Function<XmlBrowsableField, BrowsableField>() {

			@Override
			public BrowsableField apply(final XmlBrowsableField f) {
				return new BrowsableField(f.getId(), f.getLabel());
			}
		});
	}

	@Override
	public List<BrowseTerm> browseField(final String f) throws DatasourceManagerServiceException {

		final List<BrowseTerm> list = Lists.newArrayList();

		try {
			final XmlBrowsableField field = findBrowseField(f);
			if (field != null) {
				final StringTemplate st = new StringTemplate(IOUtils.toString(browseRepoApisQueryTmpl.getInputStream()));
				st.setAttribute("xpath", field.getXpath());

				final Map<String, String> terms = fetchVocabularyTerms(field.getVocabulary());

				for (String s : serviceLocator.getService(ISLookUpService.class).quickSearchProfile(st.toString())) {
					final String[] arr = s.split("@-@-@");
					final String id = arr[0].trim();
					final int count = NumberUtils.toInt(arr[1].trim(), 0);
					list.add(new BrowseTerm(id, findLabel(id, terms), count));
				}
			}
		} catch (Exception e) {
			log.error("Error browsing field " + f, e);
		}

		return list;
	}

	private String findLabel(final String code, final Map<String, String> terms) {
		return terms.containsKey(code) ? terms.get(code) : code;
	}

	public Map<String, String> fetchVocabularyTerms(final String voc) throws ISLookUpException {
		final String xquery = "for $x in collection('/db/DRIVER/VocabularyDSResources/VocabularyDSResourceType')[.//VOCABULARY_NAME/@code = '"
				+ voc.trim() + "']//TERM return concat($x/@code, ' @@@ ', $x/@english_name)";

		final Map<String, String> map = Maps.newHashMap();
		for (String s : serviceLocator.getService(ISLookUpService.class).quickSearchProfile(xquery)) {
			final String[] arr = s.split("@@@");
			map.put(arr[0].trim(), arr[1].trim());
		}
		return map;
	}

	private XmlBrowsableField findBrowseField(final String id) {
		for (XmlBrowsableField f : getBrowsableFields()) {
			if (f.getId().equals(id)) { return f; }
		}
		return null;
	}

	@Override
	public List<SearchInterfacesEntry> searchInterface(final String field, final String value) throws DatasourceManagerServiceException {
		try {

			final StringTemplate st = new StringTemplate();
			st.setTemplate(IOUtils.toString(findRepoApisQueryTmpl.getInputStream()));

			if (field.equalsIgnoreCase("__search__")) {
				st.setAttribute("cond", "contains(../..//(*|@*)/lower-case(.), '" + StringEscapeUtils.escapeXml(value.toLowerCase()) + "')");
			} else {
				final XmlBrowsableField f = findBrowseField(field);
				if (f != null) {
					st.setAttribute("cond", f.getXpath() + "='" + StringEscapeUtils.escapeXml(value) + "'");
				} else {
					return Lists.newArrayList();
				}
			}

			final String query = st.toString();

			final SAXReader reader = new SAXReader();

			final List<String> list = serviceLocator.getService(ISLookUpService.class).quickSearchProfile(query);

			return Lists.transform(list, new Function<String, SearchInterfacesEntry>() {

				@Override
				public SearchInterfacesEntry apply(final String s) {
					final SearchInterfacesEntry iface = new SearchInterfacesEntry();
					try {
						final Document doc = reader.read(new StringReader(s));
						final String country = doc.valueOf("//REPO/@country");

						iface.setRepoId(doc.valueOf("//REPO/@id"));
						iface.setRepoCountry(StringUtils.isEmpty(country) ? "-" : country.toUpperCase());
						iface.setRepoName(doc.valueOf("//REPO/@name"));
						iface.setRepoPrefix(doc.valueOf("//REPO/@prefix"));

						final Node ifcNode = doc.selectSingleNode("//INTERFACE");

						iface.setId(ifcNode.valueOf("./@id"));
						iface.setActive(Boolean.valueOf(ifcNode.valueOf("./@active")));
						iface.setProtocol(ifcNode.valueOf("./ACCESS_PROTOCOL/text()"));

						final String overCompliance = ifcNode.valueOf("./INTERFACE_EXTRA_FIELD[@name='overriding_compliance']");
						if (StringUtils.isEmpty(overCompliance)) {
							iface.setCompliance(ifcNode.valueOf("@compliance"));
						} else {
							iface.setCompliance(overCompliance);
						}

						final String lastAggregationDate = ifcNode.valueOf("./INTERFACE_EXTRA_FIELD[@name='last_aggregation_date']");
						if (!StringUtils.isEmpty(lastAggregationDate)) {
							iface.setAggrDate(lastAggregationDate);
						} else {
							final String lastDownloadDate = ifcNode.valueOf("./INTERFACE_EXTRA_FIELD[@name='last_download_date']");
							if (!StringUtils.isEmpty(lastDownloadDate)) {
								iface.setAggrDate(lastDownloadDate);
							}
						}
						final String lastAggregationTotal = ifcNode.valueOf("./INTERFACE_EXTRA_FIELD[@name='last_aggregation_total']");
						if (StringUtils.isEmpty(lastAggregationTotal)) {
							final String lastDownloadTotal = ifcNode.valueOf("./INTERFACE_EXTRA_FIELD[@name='last_download_total']");
							if (StringUtils.isEmpty(lastDownloadTotal)) {
								iface.setAggrTotal(0);
							} else {
								iface.setAggrTotal(NumberUtils.toInt(lastDownloadTotal, 0));
							}
						} else {
							iface.setAggrTotal(NumberUtils.toInt(lastAggregationTotal, 0));
						}
					} catch (Exception e) {
						log.error(e);
					}
					return iface;
				}
			});
		} catch (Exception e) {
			log.error("Error searching field " + field + " - value: " + value, e);
		}
		return Lists.newArrayList();
	}

	@Override
	public List<RepositoryMapEntry> getRepositoryMap() throws DatasourceManagerServiceException {
		try {
			final SAXReader reader = new SAXReader();

			final String query = IOUtils.toString(findReposMapQuery.getInputStream());
			final List<String> list = serviceLocator.getService(ISLookUpService.class).quickSearchProfile(query);
			return Lists.transform(list, new Function<String, RepositoryMapEntry>() {

				@Override
				public RepositoryMapEntry apply(final String s) {
					final RepositoryMapEntry r = new RepositoryMapEntry();

					try {
						final Document doc = reader.read(new StringReader(s));
						r.setId(doc.valueOf("//dsId"));
						r.setName(doc.valueOf("//name"));
						r.setLat(Float.parseFloat(doc.valueOf("//lat")));
						r.setLng(Float.parseFloat(doc.valueOf("//lng")));
					} catch (Exception e) {
						log.error(e);
					}
					return r;
				}
			});
		} catch (Exception e) {
			log.error("Error obtaing repo map entries", e);
		}
		return Lists.newArrayList();
	}

	private List<XmlBrowsableField> getBrowsableFields() {
		return browsableFields;
	}

	@Required
	public void setBrowsableFields(final List<XmlBrowsableField> browsableFields) {
		this.browsableFields = browsableFields;
	}

	@Override
	public List<SimpleDatasourceDesc> simpleListDatasourcesByType(final String type) throws DatasourceManagerServiceException {
		try {
			final List<SimpleDatasourceDesc> list = Lists.newArrayList();

			final StringTemplate st = new StringTemplate(IOUtils.toString(simpleFindReposQueryTmpl.getInputStream()));
			st.setAttribute("type", type);

			for (String s : serviceLocator.getService(ISLookUpService.class).quickSearchProfile(st.toString())) {
				final SimpleDatasourceDesc r = new SimpleDatasourceDesc();
				final String[] arr = s.split("@=@");
				r.setId(arr[0].trim());
				r.setName(arr[1].trim());
				r.setOrigId(arr[2].trim());
				r.setApis(Sets.newHashSet(arr[3].replaceAll("\\s", "").split(",")));
				r.setValid("true".equals(arr[4].trim()));
				r.setTypology(type);
				list.add(r);
			}

			Collections.sort(list);

			return list;
		} catch (Exception e) {
			log.error("Error listing repos", e);
		}
		return Lists.newArrayList();
	}

	public String getEnvironment() {
		return environment;
	}

	@Required
	public void setEnvironment(final String environment) {
		this.environment = environment;
	}

}
