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 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.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.
	 * TODO: obsolete, replace by full enumerator injection
	 */
	@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;

	/**
	 * service enumerator.
	 */
	private ServiceEnumerator<T> enumerator;

	/**
	 * 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;
		}

		if (enumerator == null) {
			final DynamicServiceEnumerator<T> tmp = new DynamicServiceEnumerator<T>(clazz);
			tmp.setLookUpLocator(lookUpLocator);
			tmp.setServiceNameResolver(serviceNameResolver);
			tmp.setEprBuilder(eprBuilder);
			enumerator = tmp;
		}
	}

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

		// TODO: backward compat hack
		if(enumerator == null) {
			log.warn("Enumerator is null in " + this + ". Postconstruct not called by spring, please check. Now called manually to workaround this problem");
			init();
		}


		final List<ServiceRunningInstance<T>> candidates = enumerator.getServices();
		if (candidates == null || candidates.isEmpty())
			throw new IllegalStateException("cannot locate service " + serviceName + ", no matching service profile found");

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

		return serviceResolver.getService(clazz, candidates.get(0).getEpr());
	}

	/**
	 * 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;
	}

}
