package eu.dnetlib.enabling.tools;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.ws.wsaddressing.W3CEndpointReference;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Required;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService;
import eu.dnetlib.enabling.tools.registration.ServiceNameResolver;
import eu.dnetlib.miscutils.collections.EnsureCollection;
import eu.dnetlib.soap.cxf.StandaloneCxfEndpointReferenceBuilder;

/**
 * Enumerates all services of a given type.
 *
 * @author marko
 *
 * @param <T>
 *            service class
 */
public class DynamicServiceEnumerator<T> implements ServiceEnumerator<T> {

	/**
	 * logger.
	 */
	private static final Log log = LogFactory.getLog(DynamicServiceEnumerator.class); // NOPMD by marko on 11/24/08 5:02 PM

	/**
	 * service class
	 */
	private Class<T> clazz;

	/**
	 * lookup locator.
	 */
	private ServiceLocator<ISLookUpService> lookUpLocator;

	/**
	 * service name resolver.
	 */
	@Resource
	private ServiceNameResolver serviceNameResolver; // NOPMD

	/**
	 * build epr.
	 */
	@Resource
	private StandaloneCxfEndpointReferenceBuilder eprBuilder;

	/**
	 * default constructor, useful for spring based instantiation.
	 */
	public DynamicServiceEnumerator() {
		// default
	}

	/**
	 * Build a dynamic service enumerator for a given class
	 *
	 * @param clazz
	 *            class
	 */
	public DynamicServiceEnumerator(final Class<T> clazz) {
		super();
		this.clazz = clazz;
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see eu.dnetlib.enabling.tools.ServiceEnumerator#getServices()
	 */
	@Override
	public List<ServiceRunningInstance<T>> getServices() {
		final String serviceName = serviceNameResolver.getName(clazz);
		log.debug("searching for service: " + serviceName);

		final String xquery = "for $x in collection('/db/DRIVER/ServiceResources')//RESOURCE_PROFILE[.//RESOURCE_TYPE/@value/string() = '"
				+ serviceName
				+ "ResourceType'] return <service><id>{$x//RESOURCE_IDENTIFIER/@value/string()}</id><url>{$x//PROTOCOL[@name = 'SOAP']/@address/string()}</url><properties>{$x//SERVICE_PROPERTIES/PROPERTY}</properties></service>";
		log.debug(xquery);

		final XPathFactory factory = XPathFactory.newInstance();
		final XPath xpath = factory.newXPath();

		final DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();

		try {
			final DocumentBuilder docBuilder = docFactory.newDocumentBuilder();

			final List<String> services = lookUpLocator.getService().quickSearchProfile(xquery);
			final List<ServiceRunningInstance<T>> instances = new ArrayList<ServiceRunningInstance<T>>();

			for (final String source : EnsureCollection.list(services)) {
				final Document doc = docBuilder.parse(new InputSource(new StringReader(source)));
				final String url = xpath.evaluate("//url", doc);
				final String serviceId = xpath.evaluate("//id", doc);

				final NodeList propElements = (NodeList) xpath.evaluate("//PROPERTY", doc, XPathConstants.NODESET);
				Map<String, String> props = new HashMap<String, String>();

				for (int i = 0; i < propElements.getLength(); i++) {
					Element propElement = (Element) propElements.item(i);
					props.put(propElement.getAttribute("key"), propElement.getAttribute("value"));
				}

				final W3CEndpointReference epr = eprBuilder.getEndpointReference(url, null, null, url + "?wsdl", null, null);

				instances.add(new ServiceRunningInstance<T>(epr, serviceId, url, props));
			}

			return instances;
		} catch (final ISLookUpException e) {
			throw new IllegalStateException("cannot locate service " + serviceName, e);
		} catch (final XPathExpressionException e) {
			throw new IllegalStateException("cannot locate service " + serviceName, e);
		} catch (final SAXException e) {
			throw new IllegalStateException("cannot locate service " + serviceName, e);
		} catch (final IOException e) {
			throw new IllegalStateException("cannot locate service " + serviceName, e);
		} catch (final ParserConfigurationException e) {
			throw new IllegalStateException("cannot locate service " + serviceName, e);
		}
	}

	public Class<T> getClazz() {
		return clazz;
	}

	@Required
	public void setClazz(final Class<T> clazz) {
		this.clazz = clazz;
	}

	public ServiceLocator<ISLookUpService> getLookUpLocator() {
		return lookUpLocator;
	}

	public void setLookUpLocator(final ServiceLocator<ISLookUpService> lookUpLocator) {
		this.lookUpLocator = lookUpLocator;
	}

	public ServiceNameResolver getServiceNameResolver() {
		return serviceNameResolver;
	}

	public void setServiceNameResolver(final ServiceNameResolver serviceNameResolver) {
		this.serviceNameResolver = serviceNameResolver;
	}

	public StandaloneCxfEndpointReferenceBuilder getEprBuilder() {
		return eprBuilder;
	}

	public void setEprBuilder(StandaloneCxfEndpointReferenceBuilder eprBuilder) {
		this.eprBuilder = eprBuilder;
	}

}
