package eu.dnetlib.enabling.tools.registration;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;

import javax.xml.transform.dom.DOMResult;
import javax.xml.ws.Endpoint;
import javax.xml.ws.wsaddressing.W3CEndpointReference;

import org.antlr.stringtemplate.StringTemplate;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Required;

import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpDocumentNotFoundException;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService;
import eu.dnetlib.enabling.is.registry.rmi.ISRegistryException;
import eu.dnetlib.enabling.is.registry.rmi.ISRegistryService;
import eu.dnetlib.enabling.locators.UniqueServiceLocator;
import eu.dnetlib.enabling.tools.HNMLocator;
import eu.dnetlib.enabling.tools.NullHNMLocator;
import eu.dnetlib.soap.EndpointReferenceBuilder;

/**
 * This class takes care of registering a service.
 * 
 * TODO: merge the implementation
 * 
 * @author marko
 * 
 */
public class ServiceRegistrator {

	/**
	 * second.
	 */
	private static final int SECOND = 0;

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

	/**
	 * locator.
	 */
	private UniqueServiceLocator serviceLocator;

	/**
	 * epr builder.
	 */
	private EndpointReferenceBuilder<Endpoint> eprBuilder;

	/**
	 * component which finds an hnm profile.
	 */
	private HNMLocator hnmLocator = new NullHNMLocator();

	/**
	 * Key-value pairs with service specific parameters which will appear in service profile.
	 */
	private Map<String, String> serviceProperties = new HashMap<String, String>();

	/**
	 * Key-value pairs [name - endpoint] of extra protocols available in service profiles.
	 */
	private Map<String, String> extraProtocols = new HashMap<String, String>();

	/**
	 * this bean helps us resolve service names from service instances.
	 */
	private ServiceNameResolver serviceNameResolver = new InterfaceServiceNameResolver(); // NOPMD

	/**
	 * Register a service schema for a given service.
	 * 
	 * @param service
	 *            service
	 */
	public void registerServiceSchema(final Object service) {
		registerServiceSchema(serviceNameResolver.getName(service));
	}

	/**
	 * Register a service schema for a particular service.
	 * 
	 * @param serviceName
	 *            service name
	 */
	public void registerServiceSchema(final String serviceName) {
		try {
			final InputStream schemaStream = getClass().getResourceAsStream("ServiceProfileSchemaTemplate.st");
			if (schemaStream == null) { throw new IllegalStateException("cannot find service profile schema template"); }

			final StringWriter schemaBuffer = new StringWriter();
			IOUtils.copy(schemaStream, schemaBuffer);
			final StringTemplate schema = new StringTemplate(schemaBuffer.toString());

			final String resourceType = serviceName + "ResourceType";
			schema.setAttribute("resourceType", resourceType);

			if (serviceLocator == null) {
				log.error("************* SERVICE LOCATOR IS NULL:" + serviceName);
				return;
			}
			final ISRegistryService registry = serviceLocator.getService(ISRegistryService.class, true);
			if (registry == null) {
				log.error("************* REGISTRY SERVICE IS NULL");
				return;
			}

			registry.addResourceType(resourceType, schema.toString());
		} catch (final ISRegistryException e) {
			throw new IllegalStateException("cannot register service profile schema", e);
		} catch (final IOException e) {
			throw new IllegalStateException("cannot read service profile schema template", e);
		}
	}

	/**
	 * register a given service.
	 * 
	 * @param service
	 *            infers the service name from the class or annotations
	 * @param endpoint
	 *            jaxws endpoint
	 * @return service profile id
	 */
	public String registerService(final Object service, final Endpoint endpoint) {
		return registerService(serviceNameResolver.getName(service), endpoint);
	}

	/**
	 * register a service with a given service name. Return null if the service cannot be registered, for example because it's blocked if
	 * the HNM is missing.
	 * 
	 * @param serviceName
	 *            service name
	 * @param endpoint
	 *            jaxws endpoint
	 * @return service profile id, or null if the service cannot be registered because blocked
	 */
	public String registerService(final String serviceName, final Endpoint endpoint) {
		return registerService(serviceName, eprBuilder.getEndpointReference(endpoint));
	}

	/**
	 * register a service with a given service name.
	 * 
	 * @param serviceName
	 *            service name
	 * @param epr
	 *            w3c endpoint reference
	 * @return service profile id
	 */
	public String registerService(final String serviceName, final W3CEndpointReference epr) {

		ensureSchemaExists(serviceName);

		final DOMResult result = new DOMResult();
		epr.writeTo(result);

		try {
			final InputStream templateStream = getClass().getResourceAsStream("ServiceProfileTemplate.st");
			if (templateStream == null) { throw new IllegalStateException("cannot find service profile template"); }

			final StringWriter buffer = new StringWriter();
			IOUtils.copy(templateStream, buffer);

			final StringTemplate templ = new StringTemplate(buffer.toString());

			final String resourceType = serviceName + "ResourceType";

			final String address = result.getNode().getChildNodes().item(0).getChildNodes().item(0).getTextContent();
			final String hnmId = hnmLocator.getHNMForUrl(address);

			// skip registration if there is no HNM yet.
			if (hnmId == null) {
				log.warn(String.format("skipping %s service registration, can't find NHM service'", serviceName));
				return null;
			}

			templ.setAttribute("resourceType", resourceType);
			templ.setAttribute("serviceName", serviceName);
			templ.setAttribute("address", address);
			templ.setAttribute("protocols", getExtraProtocols());
			templ.setAttribute("parentId", hnmId);
			templ.setAttribute("properties", serviceProperties);

			log.debug("template: " + templ.toString());

			final String res = serviceLocator.getService(ISRegistryService.class, true).insertProfileForValidation(resourceType, templ.toString());
			Thread.sleep(SECOND);
			return res;
		} catch (final IOException e) {
			throw new IllegalStateException("cannot load service profile template", e);

		} catch (final ISRegistryException e) {
			throw new IllegalStateException("cannot register service profile", e);
		} catch (final InterruptedException e) {
			throw new IllegalStateException("cannot wait for register service profile", e);
		}
	}

	/**
	 * Check that the service schema for this service already exists, and create it if it doesn't.
	 * 
	 * @param serviceName
	 *            service name
	 */
	protected void ensureSchemaExists(final String serviceName) {
		try {
			serviceLocator.getService(ISLookUpService.class).getResourceTypeSchema(serviceName);
			//serviceLocator.getService(ISLookUpService.class).getResourceProfileByQuery(
			//		"//*[local-name() = 'complexType' and @name = 'RESOURCE_TYPEType']//*[local-name() = 'enumeration' and @value = '" + serviceName
			//				+ "ResourceType']");
			log.warn("schema for " + serviceName + " appears to exist");
		} catch (final ISLookUpDocumentNotFoundException e) {
			log.warn("registering schema for " + serviceName);
			registerServiceSchema(serviceName);
		} catch (final ISLookUpException e) {
			throw new IllegalStateException(e);
		}
	}

	/**
	 * validate the registered profile.
	 * 
	 * TODO: XXX: huge hack. used right now to automatically register the blackboard. It won't work anymore once the services are validated
	 * from the GUI
	 * 
	 * @param profId
	 *            profile id
	 * @param endpoint
	 *            service endpoint (hook for possible IS_SN automated registrations)
	 * @return new id.
	 */
	public String validateProfile(final String profId, final Endpoint endpoint) {
		try {
			Thread.sleep(SECOND);
		} catch (final InterruptedException e) {
			throw new IllegalStateException("interrupted", e);
		}

		try {
			return serviceLocator.getService(ISRegistryService.class, true).validateProfile(profId);
		} catch (final ISRegistryException e) {
			throw new IllegalStateException("cannot validate service profile", e);
		}
	}

	/**
	 * validate the registered profile.
	 * 
	 * @param profId
	 *            old profile identifier
	 * @return new profile identifier
	 */
	public String validateProfile(final String profId) {
		return validateProfile(profId, null);
	}

	public EndpointReferenceBuilder<Endpoint> getEprBuilder() {
		return eprBuilder;
	}

	@Required
	public void setEprBuilder(final EndpointReferenceBuilder<Endpoint> eprBuilder) {
		this.eprBuilder = eprBuilder;
	}

	public HNMLocator getHnmLocator() {
		return hnmLocator;
	}

	public void setHnmLocator(final HNMLocator hnmLocator) {
		this.hnmLocator = hnmLocator;
	}

	public ServiceNameResolver getServiceNameGen() {
		return serviceNameResolver;
	}

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

	public ServiceNameResolver getServiceNameResolver() {
		return serviceNameResolver;
	}

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

	public Map<String, String> getServiceProperties() {
		return serviceProperties;
	}

	public void setServiceProperties(final Map<String, String> serviceProperties) {
		this.serviceProperties = serviceProperties;
	}

	public Map<String, String> getExtraProtocols() {
		return extraProtocols;
	}

	public void setExtraProtocols(final Map<String, String> extraProtocols) {
		this.extraProtocols = extraProtocols;
	}

	public UniqueServiceLocator getServiceLocator() {
		return serviceLocator;
	}

	@Required
	public void setServiceLocator(final UniqueServiceLocator serviceLocator) {
		this.serviceLocator = serviceLocator;
	}
}
