package eu.dnetlib.enabling.tools.registration;

import static eu.dnetlib.enabling.tools.registration.ServiceRegistrationManagerImpl.State.PENDING;
import static eu.dnetlib.enabling.tools.registration.ServiceRegistrationManagerImpl.State.REGISTERED;
import static eu.dnetlib.enabling.tools.registration.ServiceRegistrationManagerImpl.State.UNKNOWN;
import static eu.dnetlib.enabling.tools.registration.ServiceRegistrationManagerImpl.State.UNREGISTERED;

import java.io.IOException;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.ws.Endpoint;
import javax.xml.xpath.XPathExpressionException;

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

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.locators.UniqueServiceLocator;
import eu.dnetlib.enabling.tools.OpaqueResource;
import eu.dnetlib.enabling.tools.StringOpaqueResource;

/**
 * Simple service registration manager implementation.
 * 
 * <p>
 * The simplest way to use of the service registration manager is to declare the following in your bean definition file:
 * </p>
 * 
 * <pre>
 * 	&lt;template:instance name=&quot;serviceRegistrationManager&quot;
 *  t:name=&quot;myServiceRegistrationManager&quot; t:service=&quot;myService&quot; t:endpoint=&quot;myServiceEndpoint&quot;
 *  t:jobScheduler=&quot;jobScheduler&quot;/&gt;
 * </pre>
 * 
 * <p>
 * The ServiceRegistrationManager requires periodic ticks from a quartz job scheduler (the 'jobScheduler' bean in the above example)
 * </p>
 * 
 * <p>
 * See the ValidatingServiceRegistrationManagerImpl class for a special implementation which performs automatic service profile validation.
 * </p>
 * 
 * @see ValidatingServiceRegistrationManagerImpl
 * @author marko
 * 
 */
public class ServiceRegistrationManagerImpl implements ServiceRegistrationManager {

	/**
	 * error message: the profile is not found. This actually happens a lot, because of the way the old ISLookUpService API was concieved.
	 */
	private static final String PROFILE_NOT_FOUND = "profile not found";

	/**
	 * error message: some error during searches for service profile state.
	 */
	private static final String ERROR_CHECK = "error checking profile";

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

	/**
	 * registration manager states.
	 * 
	 * @author marko
	 * 
	 */
	public enum State {
		/**
		 * service profile is either registered or registered and we have to check out if the profile already exists.
		 */
		UNKNOWN,
		/**
		 * service profile is not yet registered or the registration disappeared.
		 */
		UNREGISTERED, /**
		 * the service profile is registered for validation. A validation operation is awaited.
		 */
		PENDING, /**
		 * the service profile is fully registered.
		 */
		REGISTERED
	}

	/**
	 * service profile identifier.
	 */
	private String profileId;

	/**
	 * service to be registered.
	 */
	private Object service;

	/**
	 * service endpoint.
	 */
	private Endpoint endpoint;

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

	/**
	 * service registrator.
	 */
	private ServiceRegistrator registrator;

	/**
	 * service profile resource local cache.
	 */
	private OpaqueResource serviceProfile;

	/**
	 * if true the service profile schema will be always updated at boot.
	 */
	private boolean schemaUpdate = true;

	/**
	 * disable the service registration.
	 */
	private boolean disabled = false;

	/**
	 * registration state of the service profile.
	 */
	private State state = State.UNKNOWN;

	/**
	 * check if we have already a registered service profile for this service.
	 */
	void checkExisting() {
		final String uri = registrator.getEprBuilder().getAddress(endpoint) + "?wsdl";
		final String query = "for $x in //RESOURCE_PROFILE[.//RESOURCE_URI/@value='" + uri + "']"
				+ " where contains($x//RESOURCE_TYPE/@value/string(), 'Service') return $x";

		try {
			final OpaqueResource resource = new StringOpaqueResource(serviceLocator.getService(ISLookUpService.class, true).getResourceProfileByQuery(query));

			if ("PendingServiceResources".equals(resource.getResourceKind())) {
				state = State.PENDING;
			} else {
				setProfileId(resource.getResourceId());
				setServiceProfile(resource);
				state = State.REGISTERED;
			}
		} catch (final ISLookUpDocumentNotFoundException e) {
			log.debug("there is no service registered for uri: " + uri);
			state = State.UNREGISTERED;
		} catch (final ISLookUpException e) {
			log.warn(ERROR_CHECK, e);
		} catch (final XPathExpressionException e) {
			log.warn(ERROR_CHECK, e);
		} catch (final SAXException e) {
			log.warn(ERROR_CHECK, e);
		} catch (final IOException e) {
			log.warn(ERROR_CHECK, e);
		} catch (final ParserConfigurationException e) {
			log.warn(ERROR_CHECK, e);
		}
	}

	/**
	 * check that a pending service profile has been validated.
	 * 
	 * if it has been validated change the state to REGISTERED.
	 */
	void checkPending() {
		log.debug("checking pending status: " + getService());

		final String uri = serviceProfile.getResourceUri();
		final String query = "collection('')//RESOURCE_PROFILE[.//RESOURCE_URI/@value='" + uri + "']";
		try {
			final OpaqueResource resource = new StringOpaqueResource(serviceLocator.getService(ISLookUpService.class, true).getResourceProfileByQuery(query));

			if (!"PendingServiceResources".equals(resource.getResourceKind())) {
				setProfileId(resource.getResourceId());
				setServiceProfile(resource);
				state = State.REGISTERED;
			}
		} catch (final ISLookUpDocumentNotFoundException e) {
			log.debug(PROFILE_NOT_FOUND, e);
		} catch (final ISLookUpException e) {
			log.warn(ERROR_CHECK, e);
		} catch (final XPathExpressionException e) {
			log.warn(ERROR_CHECK, e);
		} catch (final SAXException e) {
			log.warn(ERROR_CHECK, e);
		} catch (final IOException e) {
			log.warn(ERROR_CHECK, e);
		} catch (final ParserConfigurationException e) {
			log.warn(ERROR_CHECK, e);
		}
	}

	/**
	 * ensure the service is registered.
	 */
	public void registerService() {
		log.debug("registering service profile: " + getService());

		setProfileId(getRegistrator().registerService(getService(), getEndpoint()));

		if (getProfileId() == null) {
			log.debug("cannot register profile, no hnm");
			return;
		}

		log.debug("Service profile registered: " + getProfileId());

		retrieveServiceProfile();
		state = State.PENDING;
		if (getProfileId() == null) throw new IllegalStateException("cannot be true, I'm dreaming");
	}

	/**
	 * register service schema.
	 */
	public void registerSchema() {
		getRegistrator().registerServiceSchema(getService());
	}

	/**
	 * maintain the cached service profile.
	 */
	private void retrieveServiceProfile() {
		log.debug("Retrieving service profile: " + getProfileId());

		try {
			setServiceProfile(new StringOpaqueResource(serviceLocator.getService(ISLookUpService.class, true).getResourceProfile(getProfileId())));
		} catch (final ISLookUpDocumentNotFoundException e) {
			log.debug(PROFILE_NOT_FOUND, e);
		} catch (final ISLookUpException e) {
			log.warn(ERROR_CHECK, e);
		} catch (final XPathExpressionException e) {
			log.warn(ERROR_CHECK, e);
		} catch (final SAXException e) {
			log.warn(ERROR_CHECK, e);
		} catch (final IOException e) {
			log.warn(ERROR_CHECK, e);
		} catch (final ParserConfigurationException e) {
			log.warn(ERROR_CHECK, e);
		}
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.enabling.tools.registration.ServiceRegistrationManager#getServiceProfile()
	 */
	@Override
	public OpaqueResource getServiceProfile() {
		retrieveServiceProfile();
		return serviceProfile;
	}

	/**
	 * called periodically to advance the state machine.
	 */
	public void tick() {
		if (disabled) return;

		synchronized (registrator) {
			if (state != REGISTERED) {
				log.debug("checking service profile registration: " + getService() + " (" + state + ")");
			}

			if (state == UNKNOWN) {
				if (schemaUpdate) {
					registerSchema();
				}
				checkExisting();
			} else if (state == UNREGISTERED) {
				registerService();
			} else if (state == PENDING) {
				checkPending();
			}

			if (state != REGISTERED) {
				log.debug("tick finished");
			}
		}
	}

	public void setProfileId(final String profileId) {
		this.profileId = profileId;
	}

	public String getProfileId() {
		return profileId;
	}

	@Override
	public Object getService() {
		return service;
	}

	@Override
	public void setService(final Object service) {
		this.service = service;
	}

	public Endpoint getEndpoint() {
		return endpoint;
	}

	@Override
	public void setEndpoint(final Endpoint endpoint) {
		this.endpoint = endpoint;
	}

	public ServiceRegistrator getRegistrator() {
		return registrator;
	}

	@Required
	public void setRegistrator(final ServiceRegistrator registrator) {
		this.registrator = registrator;
	}

	public void setServiceProfile(final OpaqueResource serviceProfile) {
		this.serviceProfile = serviceProfile;
	}

	public State getState() {
		return state;
	}

	public void setState(final State state) {
		this.state = state;
	}

	public boolean isSchemaUpdate() {
		return schemaUpdate;
	}

	public void setSchemaUpdate(final boolean schemaUpdate) {
		this.schemaUpdate = schemaUpdate;
	}

	public boolean isDisabled() {
		return disabled;
	}

	@Override
	public void setDisabled(final boolean disabled) {
		this.disabled = disabled;
	}

	public UniqueServiceLocator getServiceLocator() {
		return serviceLocator;
	}

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

}
