package eu.dnetlib.parthenos.publisher;

import java.io.StringReader;
import java.util.Map;
import java.util.Map.Entry;
import javax.xml.transform.sax.SAXSource;

import com.google.common.collect.Maps;
import net.sf.saxon.s9api.*;
import net.sf.saxon.s9api.Serializer.Property;
import org.springframework.stereotype.Component;
import org.xml.sax.InputSource;

/**
 * Created by alessia on 10/03/17.
 */
@Component
public class SaxonHelper {

	private Processor xmlProcessor = new Processor(false);


	public Helper help(){
		return new Helper();
	}

	public class Helper{
		private Serializer serializer;

		Helper() {
			this.serializer = xmlProcessor.newSerializer();
			serializer.setOutputProperty(Property.METHOD, "xml");
			serializer.setOutputProperty(Property.INDENT, "yes");
		}

		public Helper setSerializerProperty(Property p, String value){
			serializer.setOutputProperty(p, value);
			return this;
		}

		public XdmNode parseXML(final String xmlSource) throws SaxonApiException {
			SAXSource source = new SAXSource(new InputSource(new StringReader(xmlSource)));
			DocumentBuilder docBuilder = xmlProcessor.newDocumentBuilder();
			return docBuilder.build(source);
		}

		/**
		 * Evaluate the given xpath on the given XML source.
		 *
		 * @param xmlSource  XML source string
		 * @param xpath      the xpath to evaluate
		 * @param namespaces the map of namespaces to be declared to evaluate the xpath
		 * @return an XdmItem resulting from the evaluation of the xpath. Returns null if nothing matches.
		 */
		public XdmItem evaluateSingle(final String xmlSource, final String xpath, final Map<String, String> namespaces) throws SaxonApiException {
			XPathSelector xpathSelector = prepareXPathSelector(xpath, namespaces);
			return evaluateSingle(xmlSource, xpathSelector);
		}

		/**
		 * Applies the given xpath selector on the given XML source.
		 *
		 * @param xmlSource  XML source string
		 * @param xpathSelector    the configured xpath  selector to apply
		 * @return an XdmItem resulting from the evaluation of the xpath. Returns null if nothing matches.
		 */
		public XdmItem evaluateSingle(final String xmlSource, final XPathSelector xpathSelector) throws SaxonApiException {
			XdmNode xdmNode = parseXML(xmlSource);
			xpathSelector.setContextItem(xdmNode);
			return xpathSelector.evaluateSingle();
		}

		/**
		 * Get an XPathSelector for the given xpath and namespaces.
		 * @param xpath the xpath
		 * @param namespaces the map of namespaces to be declared to evaluate the xpath
		 * @return XPathSelector
		 * @throws SaxonApiException
		 */
		public XPathSelector prepareXPathSelector(final String xpath,final Map<String, String> namespaces) throws SaxonApiException {
			XPathCompiler compiler = xmlProcessor.newXPathCompiler();
			for (Entry<String, String> ns : namespaces.entrySet()) {
				compiler.declareNamespace(ns.getKey(), ns.getValue());
			}
			XPathExecutable xpathExecutable = compiler.compile(xpath);
			XPathSelector xpathSelector = xpathExecutable.load();
			return xpathSelector;
		}

		/**
		 * Get an XPathSelector for the given xpath and namespaces.
		 * @param xpath the xpath
		 * @param nsPrefix namespace prefix
		 * @param ns namespace URI
		 * @return XPathSelector
		 * @throws SaxonApiException
		 */
		public XPathSelector prepareXPathSelector(final String xpath,final String nsPrefix, final String ns) throws SaxonApiException {
			Map<String, String> map = Maps.newHashMap();
			map.put(nsPrefix, ns);
			return prepareXPathSelector(xpath, map);
		}

		/**
		 *  Evaluate the given xpath on the given XML source.
		 * @param xmlSource  XML source string
		 * @param xpath      the xpath to evaluate
		 * @param nsPrefix namespace prefix
		 * @param ns namespace URI
		 * @return an XdmItem resulting from the evaluation of the xpath. Returns null if nothing matches.
		 * @throws SaxonApiException
		 *
		 */
		public XdmItem evaluateSingle(final String xmlSource, final String xpath, final String nsPrefix, final String ns) throws SaxonApiException {
			Map<String, String> map = Maps.newHashMap();
			map.put(nsPrefix, ns);
			return evaluateSingle(xmlSource, xpath, map);
		}
		/**
		 * Evaluate the given xpath on the given XML source.
		 *
		 * @param xmlSource  XML source string
		 * @param xpath      the xpath to evaluate. xpath must evaluate to an xdmNode
		 * @param namespaces the map of namespaces to be declared to evaluate the xpath
		 * @return the XML serialization as string of the XdmItem resulting from the evaluation of the xpath. Returns null if nothing matches.
		 * @throws ClassCastException if xpath is not evaluated to a node.
		 */
		public String evaluateSingleAsString(final String xmlSource, final String xpath, final Map<String, String> namespaces) throws SaxonApiException {
			XdmItem item = evaluateSingle(xmlSource, xpath, namespaces);
			if(item != null){
				XdmNode node = (XdmNode) item;
				return serializer.serializeNodeToString(node);
			}
			else return null;
		}


		/**
		 * Applies the given xpath selector on the given XML source.
		 *
		 * @param xmlSource  XML source string
		 * @param xpathSelector    the configured xpath  selector to apply
		 * @return the XML serialization as string of the XdmItem resulting from the evaluation of the xpath. Returns null if nothing matches.
		 * @throws ClassCastException if xpath is not evaluated to a node.
		 */
		public String evaluateSingleAsString(final String xmlSource, final XPathSelector xpathSelector) throws SaxonApiException {
			XdmItem item = evaluateSingle(xmlSource, xpathSelector);
			if(item != null){
				XdmNode node = (XdmNode) item;
				return serializer.serializeNodeToString(node);
			}
			else return null;
		}

		/**
		 * Evaluate the given xpath on the given XML source.
		 *
		 * @param xmlSource  XML source string
		 * @param xpath      the xpath to evaluate. xpath must evaluate to an xdmNode
		 * @param nsPrefix namespace prefix
		 * @param ns namespace URI
		 * @return the XML serialization as string of the XdmItem resulting from the evaluation of the xpath. Returns null if nothing matches.
		 * @throws ClassCastException if xpath is not evaluated to a node.
		 */
		public String evaluateSingleAsString(final String xmlSource, final String xpath, final String nsPrefix, final String ns) throws SaxonApiException {
			Map<String, String> map = Maps.newHashMap();
			map.put(nsPrefix, ns);
			return evaluateSingleAsString(xmlSource, xpath, map);
		}


	}





}
