package eu.dnetlib.enabling.tools;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.Comparator;
import java.util.List;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.xml.ws.wsaddressing.W3CEndpointReference;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Required;

import edu.emory.mathcs.backport.java.util.Collections;
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.soap.cxf.StandaloneCxfEndpointReferenceBuilder;

/**
 * Locates a service through dynamic service discovery.
 *
 * @author marko
 *
 * @param <T>
 */
public class DynamicServiceLocator<T> implements ServiceLocator<T> {

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

	/**
	 * service interface.
	 */
	private Class<T> clazz;

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

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

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

	/**
	 * service resolver. used to create proxies for discovered services.
	 */
	private ServiceResolver serviceResolver;

	/**
	 * delegate the score computation to this component. By default use a DefaultServiceLocatorLocationScorer.
	 */
	private DynamicServiceLocatorLocationScorer scorer;

	/**
	 * By default use a DefaultServiceLocatorLocationScorer if no scorer is defined.
	 */
	@PostConstruct
	protected void init() {
		if (scorer == null) {
			final DefaultServiceLocatorLocationScorer tmp = new DefaultServiceLocatorLocationScorer();
			tmp.setEprBuilder(eprBuilder);
			scorer = tmp;
		}
	}

	/**
	 * {@inheritDoc}
	 *
	 * @see eu.dnetlib.enabling.tools.ServiceLocator#getService()
	 */
	public T getService() {
		final String serviceName = serviceNameResolver.getName(clazz);
		log.debug("searching for service: " + serviceName);

		final String xquery = "collection('/db/DRIVER/ServiceResources')//RESOURCE_PROFILE[.//RESOURCE_TYPE/@value/string() = '" + serviceName
				+ "ResourceType']//PROTOCOL[@name = 'SOAP']/@address/string()";
		log.debug(xquery);
		try {
			final List<String> candidates = lookUpLocator.getService().quickSearchProfile(xquery);
			if (candidates == null || candidates.isEmpty())
				throw new IllegalStateException("cannot locate service " + serviceName + ", no matching service profile found");

			Collections.sort(candidates, new Comparator<String>() {
				public int compare(final String o1, final String o2) {
					try {
						final Integer u1Score = computeScore(new URL(o1));
						final Integer u2Score = computeScore(new URL(o2));
						return -u1Score.compareTo(u2Score);
					} catch (MalformedURLException e) {
						log.warn("ignoring service with malformed url", e);
						return 0;
					}
				}
			});
			log.debug(candidates);

			final String address = candidates.get(0);
			final W3CEndpointReference epr = eprBuilder.getEndpointReference(address, null, null, address + "?wsdl", null, null);
			return serviceResolver.getService(clazz, epr);

		} catch (ISLookUpException e) {
			throw new IllegalStateException("cannot locate service " + serviceName, e);
		}
	}

	/**
	 * compute the score for a given service url.
	 *
	 * @param url
	 *            url to be scored
	 * @return score
	 * @throws MalformedURLException
	 *             happens
	 */
	protected int computeScore(final URL url) throws MalformedURLException {
		return scorer.score(url);
	}

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

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

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

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

	public ServiceNameResolver getServiceNameResolver() {
		return serviceNameResolver;
	}

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

	public StandaloneCxfEndpointReferenceBuilder getEprBuilder() {
		return eprBuilder;
	}

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

	@Required
	public ServiceResolver getServiceResolver() {
		return serviceResolver;
	}

	public void setServiceResolver(final ServiceResolver serviceResolver) {
		this.serviceResolver = serviceResolver;
	}

	public DynamicServiceLocatorLocationScorer getScorer() {
		return scorer;
	}

	public void setScorer(final DynamicServiceLocatorLocationScorer scorer) {
		this.scorer = scorer;
	}

}
