package eu.dnetlib.utils;

import java.io.StringReader;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Iterator;

import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.Endpoint;
import javax.xml.ws.wsaddressing.W3CEndpointReference;
import javax.xml.ws.wsaddressing.W3CEndpointReferenceBuilder;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;

import eu.dnetlib.domain.EPR;
import eu.dnetlib.soap.EndpointReferenceBuilder;

public class EPRUtils {
	private static final String REFERENCE_PARAMETER_NS = "http://www.driver.org";
	
	private static XPathExpression ADDRESS_EXPR = null;
	private static XPathExpression REFERENCE_PARAMETER_EXPR = null;
	
	static {
		XPathFactory factory = XPathFactory.newInstance();
		XPath xpath = factory.newXPath();
		xpath.setNamespaceContext(new RSClientNSContext());

		try {
			REFERENCE_PARAMETER_EXPR = xpath.compile("//wsa:ReferenceParameters");
			ADDRESS_EXPR = xpath.compile("//wsa:Address");
	
		} catch (XPathExpressionException e) {
			throw new RuntimeException("Failed to build xpath expression.", e);
		}
	}
	
	private static Logger logger = Logger.getLogger(EPRUtils.class);
	
	public static EPR createEPR(W3CEndpointReference w3cEpr) {
		
		try {
			StringWriter writer = new StringWriter();
			w3cEpr.writeTo(new StreamResult(writer));
			EPR epr = new EPR();
			Node node = null;
			String address = null;
			
			synchronized (EPRUtils.class) {
				node = (Node) REFERENCE_PARAMETER_EXPR.evaluate(new InputSource(new StringReader(writer.toString())), XPathConstants.NODE);
				address = (String) ADDRESS_EXPR.evaluate(new InputSource(new StringReader(writer.toString())));	
			}
			
			if (node != null && node.getChildNodes() != null) {
				for (int i = 0; i < node.getChildNodes().getLength(); i++) {
					Node child = node.getChildNodes().item(i);
					
					if (child.getNamespaceURI() != null && 
							child.getNamespaceURI().equals(REFERENCE_PARAMETER_NS)) {
						String name = child.getLocalName();
						String value = child.getTextContent();
						
						if (logger.isDebugEnabled())
							logger.debug("Found parameter " + name + ": " + value);
						
						epr.setParameter(name, value);
					}
				}
			}
			
			epr.setAddress(address);
			epr.setEpr(writer.toString());
			
			return epr;
		} catch (XPathExpressionException e) {
			logger.error("Error parsing epr", e);
			e.printStackTrace();
		}
		
		return null;
	}
	
	public static W3CEndpointReference createW3CEPR(EPR epr) {
		W3CEndpointReference w3cEpr = null;
		
		if (epr.getEpr() != null) {
			StringReader reader = new StringReader(epr.getEpr());
			
			w3cEpr = new W3CEndpointReference(new StreamSource(reader));
		} else {
			StringWriter writer = new StringWriter();

			w3cEpr = buildW3CEpr(epr);
			w3cEpr.writeTo(new StreamResult(writer));
			
			epr.setEpr(writer.toString());
		}
		
		return w3cEpr;
	}
	
	public static W3CEndpointReference createW3CEPR(EndpointReferenceBuilder<Endpoint> builder, Endpoint endpoint) {
		W3CEndpointReference w3cepr = builder.getEndpointReference(endpoint);
		
		return w3cepr;
	}
	
	public static EPR createEPR(EndpointReferenceBuilder<Endpoint> builder,
			Endpoint endpoint) {
		EPR epr = null;
		
		if (logger.isDebugEnabled()) {
			logger.debug("Creating epr for " + endpoint.getImplementor().getClass());
		}
		
		epr = createEPR(createW3CEPR(builder, endpoint));
		
		if (logger.isDebugEnabled()) {
			logger.debug("Created epr: " + epr);
		}

		return epr;
	}
	
	public static String eprToXml(final EPR epr) {
		/************************************************************************************************
		 * WARNING: Avoid using cached XML string because it can not be parsed again safely. If the		*
		 * W3C EPR generated by parsing the cached XML string is serialized again back to XML, the		*
		 * builder keeps adding namespaces (basically the same namespace URI with just different		*
		 * namespace prefix). As a consequence															*
		 * 																							 	*
		 * EPRUtils.eprToXml(epr).equals(EPRUtils.eprToXml(EPRUtils.xmlToEpr(EPRUtils.eprToXml(epr))))	*
		 * 																								*
		 * would always be false for any given EPR epr.													*
		 * **********************************************************************************************/
		epr.setEpr(null);
		
		final StringWriter xml = new StringWriter();
		createW3CEPR(epr).writeTo(new StreamResult(xml));
		return xml.toString();
	}
	
	public static EPR xmlToEpr(final String xml) {
		return createEPR((W3CEndpointReference) W3CEndpointReference.readFrom(new StreamSource(new StringReader(xml))));
	}
	
	private static W3CEndpointReference buildW3CEpr(EPR epr) {
		try {
			W3CEndpointReferenceBuilder builder = new W3CEndpointReferenceBuilder();
			Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
			
			builder.address(epr.getAddress());
			builder.serviceName(new QName(REFERENCE_PARAMETER_NS, "serviceName"));
			
			for (String name:epr.getParameterNames()) {
				Element element = doc.createElementNS(REFERENCE_PARAMETER_NS, name);
				element.setTextContent(epr.getParameter(name));
				
				builder.referenceParameter(element);
			}
			
			return builder.build();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		}

		return null;
	}
}

class RSClientNSContext implements NamespaceContext {
	
	HashMap<String, String> prefixes = null;

	public String getNamespaceURI(String prefix) {
		if (prefixes == null) {
			prefixes = new HashMap<String, String>();
			
			prefixes.put("wsa", "http://www.w3.org/2005/08/addressing");
			prefixes.put("driver", "http://www.driver.org");
			prefixes.put("wsaw", "http://www.w3.org/2006/05/addressing/wsdl");
			prefixes.put("wsdli", "http://www.w3.org/2005/08/wsdl-instance");
		}

		if (prefix == null) {
			throw new NullPointerException("Null prefix");
		} else {
			prefix = prefixes.get(prefix);
			
			if (prefix == null) {
				return XMLConstants.DEFAULT_NS_PREFIX;
			} else {
				return prefix;
			}
		}
	}

	public String getPrefix(String namespaceURI) {
		throw new UnsupportedOperationException();
	}

	public Iterator<String> getPrefixes(String namespaceURI) {
		throw new UnsupportedOperationException();
	}
}