package eu.dnetlib.functionality.modular.ui.workflows.util;

import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.xml.transform.stream.StreamSource;

import org.apache.commons.lang.StringUtils;
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.springframework.beans.factory.annotation.Required;

import com.google.common.base.Function;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

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.workflows.objects.AdvancedMetaWorkflowDescriptor;
import eu.dnetlib.functionality.modular.ui.workflows.objects.AtomicWorkflowDescriptor;
import eu.dnetlib.functionality.modular.ui.workflows.objects.MetaWorkflowDescriptor;
import eu.dnetlib.functionality.modular.ui.workflows.objects.NodeInfo;
import eu.dnetlib.functionality.modular.ui.workflows.objects.NodeWithUserParams;
import eu.dnetlib.functionality.modular.ui.workflows.util.functions.NodeToWorkflowParam;
import eu.dnetlib.miscutils.datetime.DateUtils;
import eu.dnetlib.miscutils.functional.xml.ApplyXslt;
import eu.dnetlib.msro.workflows.nodes.NodeStatus;
import eu.dnetlib.msro.workflows.util.WorkflowParam;
import eu.dnetlib.msro.workflows.util.WorkflowParamUI;
import eu.dnetlib.msro.workflows.util.WorkflowsConstants.WorkflowStatus;

public class ISLookupClient {

	@Resource
	private UniqueServiceLocator serviceLocator;

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

	private static final ApplyXslt metaWorkflowToHTML = new ApplyXslt(new StreamSource(
			ISLookupClient.class.getResourceAsStream("/eu/dnetlib/functionality/modular/workflows/xslt/metawf_profile2html.xslt")));

	private String categoryUisJson;

	private Map<String, List<WorkflowParamUI>> categoryUis;

	@PostConstruct
	private void init() {
		log.info("Initialing categoryUis map using JSON: " + getCategoryUisJson());
		this.categoryUis = new Gson().fromJson(getCategoryUisJson(), new TypeToken<Map<String, List<WorkflowParamUI>>>() {}.getType());
	}

	public List<String> listSimpleWorflowSections() {
		final String xquery = "distinct-values(//METAWORKFLOW_SECTION/text())";
		try {
			return serviceLocator.getService(ISLookUpService.class).quickSearchProfile(xquery);
		} catch (ISLookUpException e) {
			log.error("Error obtaining worflowSections", e);
			return Lists.newArrayList();
		}
	}

	public List<MetaWorkflowDescriptor> listMetaWorflowsForSection(final String name) {
		return listMetaWorflowsByCondition(".//RESOURCE_TYPE/@value='MetaWorkflowDSResourceType' and .//METAWORKFLOW_SECTION='" + name + "'");
	}

	public List<MetaWorkflowDescriptor> listMetaWorflowsForDatasource(final String dsId) {
		return listMetaWorflowsByCondition(".//RESOURCE_TYPE/@value='MetaWorkflowDSResourceType' and .//DATAPROVIDER/@id='" + dsId + "'");
	}

	private List<MetaWorkflowDescriptor> listMetaWorflowsByCondition(final String cond) {
		final String query = "for $x in /*[" + cond + "] order by $x//METAWORKFLOW_NAME return concat(" +
				"$x//RESOURCE_IDENTIFIER/@value, ' @@@ ', " +
				"$x//METAWORKFLOW_NAME, ' @@@ ', " +
				"$x//METAWORKFLOW_NAME/@family, ' @@@ ', " +
				"$x//CONFIGURATION/@status)";

		try {
			return Lists.transform(serviceLocator.getService(ISLookUpService.class).quickSearchProfile(query), new Function<String, MetaWorkflowDescriptor>() {

				@Override
				public MetaWorkflowDescriptor apply(final String s) {
					Iterator<String> iter = Splitter.on("@@@").trimResults().split(s).iterator();
					String id = iter.next();
					String name = iter.next();
					String family = iter.next();
					WorkflowStatus status = WorkflowStatus.valueOf(iter.next());
					return new MetaWorkflowDescriptor(id, name, status, family);
				}
			});
		} catch (ISLookUpException e) {
			return Lists.newArrayList();
		}
	}

	public AtomicWorkflowDescriptor getAtomicWorkflow(final String id) {
		final String xml = getProfile(id);
		final SAXReader reader = new SAXReader();
		try {
			final Document doc = reader.read(new StringReader(xml));

			final String name = doc.valueOf("//WORKFLOW_NAME");
			final String imageUrl = "wf_atomic_workflow.img?id=" + id + "&t=" + DateUtils.now();
			final String lastExecutionDate = doc.valueOf("//STATUS/LAST_EXECUTION_DATE");
			final String start = doc.valueOf("//CONFIGURATION/@start");
			final boolean ready = doc.selectNodes("//PARAM[@required='true' and string-length(normalize-space(.)) = 0]").isEmpty();

			return new AtomicWorkflowDescriptor(id, name, "", null, imageUrl, ready, start, lastExecutionDate, 0, 0);
		} catch (Exception e) {
			log.error("Error parsing xml: " + xml, e);
		}
		return new AtomicWorkflowDescriptor("", "", "", null, "", false, "disabled", "", 0, 0);
	}

	public String getProfile(final String id) {
		try {
			return serviceLocator.getService(ISLookUpService.class).getResourceProfile(id);
		} catch (ISLookUpException e) {
			log.error("Error finding profile: " + id, e);
			return null;
		}
	}

	public NodeInfo getNodeInfo(final String wfId, final String nodeName) {
		try {
			final String query = "/*[.//RESOURCE_IDENTIFIER/@value='" + wfId + "']//NODE[@name='" + nodeName + "']";
			final String xml = serviceLocator.getService(ISLookUpService.class).getResourceProfileByQuery(query);
			final SAXReader reader = new SAXReader();
			final Document doc = reader.read(new StringReader(xml));

			final String name = doc.valueOf("/NODE/@name");
			final String desc = doc.valueOf("//DESCRIPTION");

			@SuppressWarnings("unchecked")
			final List<WorkflowParam> params = Lists.transform(doc.selectNodes("//PARAM"), new NodeToWorkflowParam(getCategoryUis()));

			return new NodeInfo(name, desc, params);

		} catch (Exception e) {
			log.error("Error accessing node " + nodeName + " of wf " + wfId, e);
			return new NodeInfo("", "", new ArrayList<WorkflowParam>());
		}
	}

	public Set<String> getNotConfiguredNodes(final String id) {
		final String query = "for $x in (/*[.//RESOURCE_IDENTIFIER/@value='" + id + "']//NODE) "
				+ "where count($x//PARAM[@required='true' and string-length(normalize-space(.)) = 0]) > 0 " + "return $x/@name/string()";

		try {
			final List<String> list = serviceLocator.getService(ISLookUpService.class).quickSearchProfile(query);
			return Sets.newHashSet(list);
		} catch (Exception e) {
			log.error("Error executing xquery: " + query, e);
			return Sets.newHashSet();
		}
	}

	public AdvancedMetaWorkflowDescriptor getMetaWorkflow(final String id) {
		final String xml = getProfile(id);
		final Set<String> innerWfs = Sets.newHashSet();
		try {
			final SAXReader reader = new SAXReader();
			final Document doc = reader.read(new StringReader(xml));

			final String name = doc.valueOf("//METAWORKFLOW_NAME");
			final String family = doc.valueOf("//METAWORKFLOW_NAME/@family");

			final String email = doc.valueOf("//ADMIN_EMAIL");

			final boolean scheduled = Boolean.valueOf(doc.valueOf("//SCHEDULING/@enabled"));;
			final String cronExpression = doc.valueOf("//SCHEDULING/CRON");;
			final int minInterval = Integer.parseInt(doc.valueOf("//SCHEDULING/MININTERVAL"));
			final WorkflowStatus status = WorkflowStatus.valueOf(doc.valueOf("//CONFIGURATION/@status"));
			for (Object o : doc.selectNodes("//WORKFLOW")) {
				final Element wf = (Element) o;
				final String wfId = wf.valueOf("@id");
				innerWfs.add(wfId);
				try {
					final String profile = serviceLocator.getService(ISLookUpService.class).getResourceProfile(wfId);
					final Document doc2 = new SAXReader().read(new StringReader(profile));
					final String start = doc2.valueOf("//CONFIGURATION/@start");
					final String lastDate = doc2.valueOf("//STATUS/LAST_EXECUTION_DATE");
					final String lastStatus = doc2.valueOf("//STATUS/LAST_EXECUTION_STATUS");

					wf.addAttribute("start", start);

					if (!StringUtils.isEmpty(lastDate)) {
						wf.addAttribute("date", lastDate);
					}

					if (!StringUtils.isEmpty(lastStatus)) {
						wf.addAttribute("status", lastStatus);
					}

					if ("disabled".equalsIgnoreCase(start)) {
						wf.addAttribute("disabled", "true");
					}

					if (doc2.selectNodes("//PARAM[@required='true' and string-length(normalize-space(.)) = 0]").isEmpty()) {
						wf.addAttribute("configured", "true");
					} else {
						wf.addAttribute("configured", "false");
					}

				} catch (Exception e) {
					log.error("Error obtaining wf status", e);
					wf.addAttribute("status", NodeStatus.NOT_CONFIGURED.toString());
				}
			}

			// TODO delete this
			ApplyXslt metaWorkflowToHTML = new ApplyXslt(new StreamSource(
					ISLookupClient.class.getResourceAsStream("/eu/dnetlib/functionality/modular/workflows/xslt/metawf_profile2html.xslt")));

			final String html = metaWorkflowToHTML.evaluate(doc.asXML());

			return new AdvancedMetaWorkflowDescriptor(id, name, email, scheduled, cronExpression, minInterval, status, family, innerWfs, html);

		} catch (Exception e) {
			log.error("Error parsing xml: " + xml, e);
			return new AdvancedMetaWorkflowDescriptor("", "", "", false, "", 0, WorkflowStatus.MISSING, "", new HashSet<String>(), "");
		}
	}

	public Map<String, String> listRepoHiWorkflows() {
		final Map<String, String> map = Maps.newHashMap();

		final String query = "for $x in collection('/db/DRIVER/WorkflowDSResources/WorkflowDSResourceType') " + "where $x//WORKFLOW_TYPE='REPO_HI' "
				+ "return concat($x//RESOURCE_IDENTIFIER/@value, ' @@@ ', $x//WORKFLOW_NAME)";

		try {
			for (String s : serviceLocator.getService(ISLookUpService.class).quickSearchProfile(query)) {
				String[] arr = s.split("@@@");
				map.put(arr[0].trim(), arr[1].trim());
			}
		} catch (Exception e) {
			log.error("Error executing xquery: " + query, e);
		}
		return map;
	}

	public List<String> listMetaWorflowsForWfId(final String wfId) {
		final String query = "for $x in collection('/db/DRIVER/MetaWorkflowDSResources/MetaWorkflowDSResourceType') where $x//WORKFLOW/@id='" + wfId
				+ "' return $x//RESOURCE_IDENTIFIER/@value/string()";
		try {
			return serviceLocator.getService(ISLookUpService.class).quickSearchProfile(query);
		} catch (ISLookUpException e) {
			log.error("Error executing xquery: " + query, e);
			return Lists.newArrayList();
		}
	}

	public boolean isExecutable(final String metaWfId) {
		final String query = "for $x in collection('/db/DRIVER/MetaWorkflowDSResources/MetaWorkflowDSResourceType')[.//RESOURCE_IDENTIFIER/@value = '"
				+ metaWfId
				+ "']//WORKFLOW/@id \n"
				+ "for $y in collection('/db/DRIVER/WorkflowDSResources/WorkflowDSResourceType')[.//RESOURCE_IDENTIFIER/@value = $x]//PARAM[@required='true' and string-length(normalize-space(.)) = 0] \n"
				+ "return $y";
		try {
			return serviceLocator.getService(ISLookUpService.class).quickSearchProfile(query).isEmpty();
		} catch (ISLookUpException e) {
			log.error("Error executing xquery: " + query, e);
			return false;
		}
	}

	public List<String> listWfFamilies() {
		final String query = "distinct-values(for $x in collection('/db/DRIVER/MetaWorkflowDSResources/MetaWorkflowDSResourceType') "
				+ "where string-length($x//DATAPROVIDER/@id) > 0 return $x//METAWORKFLOW_NAME/@family/string())";
		try {
			return serviceLocator.getService(ISLookUpService.class).quickSearchProfile(query);
		} catch (ISLookUpException e) {
			log.error("Error executing xquery: " + query, e);
			return Lists.newArrayList();
		}
	}

	public String getDatasourceName(final String dsId) {
		final String query = "/*[.//RESOURCE_IDENTIFIER/@value='" + dsId + "']//OFFICIAL_NAME/text()";

		try {
			return serviceLocator.getService(ISLookUpService.class).getResourceProfileByQuery(query);
		} catch (ISLookUpException e) {
			log.error("Error executing xquery: " + query, e);
			return "UNKNOWN";
		}
	}

	public List<NodeWithUserParams> listWorkflowUserParams(final String wfId) {
		final List<NodeWithUserParams> res = Lists.newArrayList();

		final String xml = getProfile(wfId);
		try {
			final SAXReader reader = new SAXReader();
			final Document doc = reader.read(new StringReader(xml));

			for (Object o : doc.selectNodes("//NODE")) {
				final Element node = (Element) o;

				@SuppressWarnings("unchecked")
				final List<Node> pnodes = node.selectNodes(".//PARAM[@managedBy='user' or @category]");
				if (pnodes.size() > 0) {
					final String name = node.valueOf("@name");
					final String desc = node.valueOf("./DESCRIPTION");
					final List<WorkflowParam> params = Lists.transform(pnodes, new NodeToWorkflowParam(getCategoryUis()));
					res.add(new NodeWithUserParams(name, desc, params));
				}
			}
		} catch (Exception e) {
			log.error("Error obtaing params of wf: " + wfId, e);
		}

		return res;
	}

	public Map<String, List<WorkflowParamUI>> getCategoryUis() {
		return categoryUis;
	}

	public String getCategoryUisJson() {
		return categoryUisJson;
	}

	@Required
	public void setCategoryUisJson(final String categoryUisJson) {
		this.categoryUisJson = categoryUisJson;
	}

}
