package eu.dnetlib.functionality.modular.ui.repositories;

import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.dom4j.Document;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.ui.ModelMap;

import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gson.Gson;

import eu.dnetlib.enabling.datasources.common.Api;
import eu.dnetlib.enabling.datasources.common.Datasource;
import eu.dnetlib.enabling.datasources.common.LocalDatasourceManager;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService;
import eu.dnetlib.enabling.locators.UniqueServiceLocator;
import eu.dnetlib.functionality.modular.ui.ModuleEntryPoint;
import eu.dnetlib.miscutils.collections.Pair;

public class RepoApisEntryPointController extends ModuleEntryPoint {

	@Resource
	private UniqueServiceLocator serviceLocator;

	@Resource
	private RepoUIUtils repoUIUtils;

	private String compatibilityLevelsVocabulary;

	private String validatorAddress;
	private String validatorServiceAddress;

	@Autowired
	private LocalDatasourceManager<Datasource<?, ?>, Api<?>> dsManager;

	public class RepoHIWorkflow implements Comparable<RepoHIWorkflow> {

		private final String id;
		private final String name;
		private final Set<String> ifaceTypes;
		private final Set<String> compliances;
		private List<Pair<String, String>> fields;

		/**
		 * Instantiates a new repo hi workflow.
		 *
		 * @param id
		 *            the id
		 * @param name
		 *            the name
		 * @param ifaceTypes
		 *            the iface types
		 * @param compliances
		 *            the compliances
		 * @param fields
		 *            the fields
		 */
		public RepoHIWorkflow(final String id, final String name, final Set<String> ifaceTypes, final Set<String> compliances,
				final List<Pair<String, String>> fields) {
			super();
			this.id = id;
			this.name = name;
			this.ifaceTypes = ifaceTypes;
			this.compliances = compliances;
			this.fields = fields;
		}

		public String getId() {
			return id;
		}

		public String getName() {
			return name;
		}

		public Set<String> getIfaceTypes() {
			return ifaceTypes;
		}

		public Set<String> getCompliances() {
			return compliances;
		}

		@Override
		public int compareTo(final RepoHIWorkflow o) {
			return getName().compareTo(o.getName());
		}

		/**
		 * @return the fields
		 */
		public List<Pair<String, String>> getFields() {
			return fields;
		}

		/**
		 * @param fields
		 *            the fields to set
		 */
		public void setFields(final List<Pair<String, String>> fields) {
			this.fields = fields;
		}

	}

	private RepoHIWorkflow parseQuery(final String input) {
		final SAXReader reader = new SAXReader();
		try {
			final Document doc = reader.read(new StringReader(input));

			final String id = doc.valueOf("//id");
			final String name = doc.valueOf("//name");
			final String type = doc.valueOf("//types");
			final String compliance = doc.valueOf("//compliances");
			final Set<String> ifcTypes = Sets.newHashSet(Splitter.on(",").omitEmptyStrings().trimResults().split(type));
			final Set<String> compliances = Sets.newHashSet(Splitter.on(",").omitEmptyStrings().trimResults().split(compliance));

			final List<Pair<String, String>> fields = new ArrayList<>();
			for (final Object o : doc.selectNodes(".//FIELD")) {
				final Node currentNode = (Node) o;
				final String key = currentNode.valueOf("@name");
				final String value = currentNode.getText();
				fields.add(new Pair<>(key, value));
			}
			return new RepoHIWorkflow(id, name, ifcTypes, compliances, fields);
		} catch (final Exception e) {
			return null;
		}
	}

	private List<RepoHIWorkflow> listRepoHIWorkflows() throws ISLookUpException {

		final String xquery =
				"for $x in collection('/db/DRIVER/WorkflowDSResources/WorkflowDSResourceType') where $x//WORKFLOW_TYPE='REPO_HI' return <result> <id>{$x//RESOURCE_IDENTIFIER/@value/string()}</id> <name>{$x//WORKFLOW_NAME/text()}</name> <types>{$x//PARAM[@name='expectedInterfaceTypologyPrefixes']/text()}</types> <compliances>{$x//PARAM[@name='expectedCompliancePrefixes']/text()}</compliances> {$x//FIELD} </result>";
		final List<RepoHIWorkflow> list = Lists.newArrayList();
		for (final String s : serviceLocator.getService(ISLookUpService.class).quickSearchProfile(xquery)) {
			final RepoHIWorkflow repoHiInfo = parseQuery(s);
			if (repoHiInfo != null) {
				list.add(repoHiInfo);
			}
		}
		Collections.sort(list);
		return list;
	}

	@Override
	protected void initialize(final ModelMap map, final HttpServletRequest request, final HttpServletResponse response) throws Exception {
		final Gson gson = new Gson();
		map.addAttribute("availableRepohiWfs", gson.toJson(listRepoHIWorkflows()));
		map.addAttribute("compatibilityLevels", gson.toJson(repoUIUtils.fetchVocabularyTerms(getCompatibilityLevelsVocabulary())));
		map.addAttribute("browseFields", gson.toJson(dsManager.listBrowsableFields()));
		map.addAttribute("validatorAddress", getValidatorAddress());
		map.addAttribute("validatorServiceAddress", getValidatorServiceAddress());
	}

	public String getCompatibilityLevelsVocabulary() {
		return compatibilityLevelsVocabulary;
	}

	@Required
	public void setCompatibilityLevelsVocabulary(final String compatibilityLevelsVocabulary) {
		this.compatibilityLevelsVocabulary = compatibilityLevelsVocabulary;
	}

	public String getValidatorAddress() {
		return validatorAddress;
	}

	@Required
	public void setValidatorAddress(final String validatorAddress) {
		this.validatorAddress = validatorAddress;
	}

	public String getValidatorServiceAddress() {
		return validatorServiceAddress;
	}

	@Required
	public void setValidatorServiceAddress(final String validatorServiceAddress) {
		this.validatorServiceAddress = validatorServiceAddress;
	}

}
