package eu.dnetlib.enabling.is.sn.resourcestate;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import javax.xml.transform.dom.DOMResult;
import javax.xml.ws.wsaddressing.W3CEndpointReference;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

//import org.apache.commons.logging.Log;
//import org.apache.commons.logging.LogFactory;
import org.apache.oro.text.perl.Perl5Util;
import org.springframework.beans.factory.annotation.Required;

import eu.dnetlib.enabling.is.sn.AbstractSubscriptionRegistry;
import eu.dnetlib.enabling.is.sn.SubscriptionRegistry;
import eu.dnetlib.enabling.is.sn.SubscriptionRequest;
import eu.dnetlib.enabling.is.sn.TopicExpressionMatchResult;

/**
 * Manage subscription for UPDATE/CREATE/DELETE resource-related topic prefixes.
 * 
 * @author marko
 * 
 */
public class ResourceStateSubscriptionRegistry extends AbstractSubscriptionRegistry implements SubscriptionRegistry {

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

	/**
	 * subscription DAO.
	 */
	private ResourceStateSubscriptionDAO subscriptionDao;

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.enabling.is.sn.SubscriptionRegistry#registerSubscription(eu.dnetlib.enabling.is.sn.SubscriptionRequest)
	 */
	@Override
	public String registerSubscription(final SubscriptionRequest subscription) {
		final TopicExpressionMatchResult prefixMatch = matchPrefix(subscription);
		if (prefixMatch == null)
			return null;

		final TopicExpressionMatchResult typeMatch = matchType(prefixMatch.getRest());
		if (typeMatch == null)
			return null; // TODO: decide whether to fail or not

		final TopicExpressionMatchResult idMatch = matchId(typeMatch.getRest());

		if (idMatch == null)
			return null; // TODO: decide whether to fail or not

		return registerSubscription(new ResourceStateSubscription(subscription, prefixMatch.getPrefix(), typeMatch.getPrefix(), idMatch.getPrefix(),
				idMatch.getRest()));
	}

	/**
	 * this registers the real subscription.
	 * 
	 * TODO: am I sure that the overload is a good thing here?
	 * 
	 * @param subscription
	 *            subscription
	 * @return subscription id (potentially changed)
	 */
	private String registerSubscription(final ResourceStateSubscription subscription) {
		// TODO: change the dao, and implement a method which finds a given subscription directly.
		final Collection<ResourceStateSubscription> similar = subscriptionDao.listSubscriptions(subscription.getPrefix(), subscription.getType(),
				subscription.getResourceId());
		for (final ResourceStateSubscription r : similar) {
			if (r != null && getAddress(subscription.getSubscriber()).equals(getAddress(r.getSubscriber()))
					&& (subscription.getXpath() == r.getXpath() || subscription.getXpath().equals(r.getXpath()))
					&& ((subscription.getResourceId() == r.getResourceId() || subscription.getResourceId().equals(r.getResourceId())))) {
				return r.getSubscriptionId();
			}
		}

		subscriptionDao.addSubscription(subscription);

		return subscription.getSubscriptionId();
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.enabling.is.sn.SubscriptionRegistry#unsubscribe(java.lang.String)
	 */
	@Override
	public boolean unsubscribe(final String subId) {
		return subscriptionDao.removeSubscription(subId);
	}

	/**
	 * Obtains the address component of the EPR.
	 * 
	 * TODO: refactor in a utility class.
	 * 
	 * @param epr
	 *            endpoint reference
	 * @return address contained in the endpoint reference
	 */
	private Object getAddress(final W3CEndpointReference epr) {
		final DOMResult dom = new DOMResult();
		epr.writeTo(dom);

		try {
			return XPathFactory.newInstance().newXPath().evaluate("//*[local-name() = 'Address']", dom.getNode());
		} catch (final XPathExpressionException e) {
			throw new IllegalStateException("cannot construct xpath expression", e);
		}
	}

	/**
	 * extract resource type name.
	 * 
	 * @param rest
	 *            topic expression without prefix
	 * @return tuple containing type name and rest of the prefix
	 */
	public TopicExpressionMatchResult matchType(final String rest) {
		final Perl5Util matcher = new Perl5Util(); // NOPMD
		if (!matcher.match("/^(\\*|[a-zA-Z_0-9]*)($|/)(.*)$/", rest))
			return null;

		return new TopicExpressionMatchResult(matcher.getMatch().group(1), matcher.getMatch().group(2 + 1));
	}

	/**
	 * extract resource id.
	 * 
	 * @param rest
	 *            topic expression without prefix
	 * @return tuple containing type name and rest of the prefix
	 */
	public TopicExpressionMatchResult matchId(final String rest) {
		final Perl5Util matcher = new Perl5Util(); // NOPMD
		if (!matcher.match("/^([^/]*)(.*)/", rest))
			return null;

		return new TopicExpressionMatchResult(matcher.getMatch().group(1), matcher.getMatch().group(2));
	}

	/**
	 * return all subscriptions matching a given prefix and a given type. Wildcard subscriptions will match any resource
	 * type.
	 * 
	 * @param prefix
	 *            prefix
	 * @param type
	 *            concrete type
	 * @param resId
	 *            resource identifier
	 * @return all matching subscriptions
	 */
	public Collection<ResourceStateSubscription> listMatchingSubscriptions(final String prefix, final String type, final String resId) {
		final Set<ResourceStateSubscription> merged = new HashSet<ResourceStateSubscription>();
		merged.addAll(subscriptionDao.listSubscriptions(prefix, type, resId));
		merged.addAll(subscriptionDao.listSubscriptions(prefix, type, "*"));
		merged.addAll(subscriptionDao.listSubscriptions(prefix, "*", "*"));
		return merged;
	}

	@Override
	protected Collection<String> getAcceptedPrefixes() {
		return Arrays.asList(new String[] { ResourceStateSubscription.PREFIX_CREATE, ResourceStateSubscription.PREFIX_DELETE,
				ResourceStateSubscription.PREFIX_UPDATE });
	}

	public ResourceStateSubscriptionDAO getSubscriptionDao() {
		return subscriptionDao;
	}

	@Required
	public void setSubscriptionDao(final ResourceStateSubscriptionDAO subscriptionDao) {
		this.subscriptionDao = subscriptionDao;
	}

}
