package eu.dnetlib.pid.service;

import java.io.StringReader;
import java.util.Collection;

import javax.xml.ws.wsaddressing.W3CEndpointReference;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.socialhistoryservices.pid.PidType;
import org.springframework.beans.factory.annotation.Required;

import com.google.common.collect.Lists;

import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService;
import eu.dnetlib.enabling.resultset.MappedResultSetFactory;
import eu.dnetlib.enabling.tools.AbstractBaseService;
import eu.dnetlib.enabling.tools.ServiceLocator;
import eu.dnetlib.miscutils.cache.EhCache;
import eu.dnetlib.pid.service.rmi.PIDAssignerException;
import eu.dnetlib.pid.service.rmi.PIDAssignerService;
import eu.dnetlib.pid.service.rmi.PIDBridgeService;

/**
 * Implementation of the PIDAssignerService. Reads PID assignement rules from a profile with a given identifier and
 * creates a UnaryFunction that uses the read rules to assign PIDs.
 * 
 * @author alessia
 * 
 */
public class PIDAssignerServiceImpl extends AbstractBaseService implements PIDAssignerService {
	private static final Log log = LogFactory.getLog(PIDAssignerServiceImpl.class); // NOPMD by marko on 11/24/08 5:02 PM

	private MappedResultSetFactory mappedResultSetFactory;
	private ServiceLocator<ISLookUpService> lookupLocator;
	private ServiceLocator<PIDBridgeService> pidBridgeLocator;
	private EhCache<String, PidType> pidCache = new EhCache<String, PidType>();
	private String defaultResolveURL = "";

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.pid.service.rmi.PIDAssignerService#assignPIDs(javax.xml.ws.wsaddressing.W3CEndpointReference,
	 *      java.lang.String)
	 */
	@Override
	public W3CEndpointReference assignPIDs(W3CEndpointReference epr, String ruleId, String namingAuthority) throws PIDAssignerException {
		this.pidCache.clear();
		if ((ruleId == null) || (ruleId.isEmpty()))
			throw new PIDAssignerException("Invalid ruleId: id is empty");
		if (epr == null)
			throw new PIDAssignerException("Passed epr is empty");
		if (namingAuthority == null || namingAuthority.isEmpty())
			throw new PIDAssignerException("Passed naming authority is null or empty");
		Collection<PIDAssignmentRule> rules = this.readRuleProfile(ruleId);
		CachedPIDUnaryFunction function = new CachedPIDUnaryFunction(rules);
		//PIDUnaryFunction function = new PIDUnaryFunction(rules);

		function.setNamingAuthority(namingAuthority);
		function.setPidBridgeService(pidBridgeLocator.getService());
		function.setUnavailableURL(defaultResolveURL);
		function.setPidCache(pidCache);
		return mappedResultSetFactory.createMappedResultSet(epr, function);
	}

	private Collection<PIDAssignmentRule> readRuleProfile(String ruleId) throws PIDAssignerException {
		log.info("Reading PID rule profile id: " + ruleId);
		Collection<PIDAssignmentRule> rules = Lists.newArrayList();
		try {
			String profile = lookupLocator.getService().getResourceProfileByQuery(
					"//RESOURCE_PROFILE[.//RESOURCE_IDENTIFIER/@value/string() = '" + ruleId + "']");
			SAXReader reader = new SAXReader();
			Document doc = reader.read(new StringReader(profile));
			int countRule = 0;
			for (Object o : doc.selectNodes("//RULE")) {
				PIDAssignmentRule currentRule = new PIDAssignmentRule();
				Node node = (Node) o;

				String baseNodes = node.valueOf("./@baseNodes");
				String localID = node.valueOf("./@localID");
				String resolveURL = node.valueOf("./@resolveURL");
				String target = node.valueOf("./@target");

				currentRule.setBaseNodesXPath(baseNodes);
				currentRule.setLocalIDXPath(localID);
				currentRule.setResolveURLXPath(resolveURL);
				currentRule.setTargetXPath(target);

				for (Object locatt : node.selectNodes("./locatt")) {
					Node locattNode = (Node) locatt;
					PIDLocAttRule locattRule = new PIDLocAttRule(locattNode.valueOf("./@resolveURL"), locattNode.valueOf("./@target"));
					currentRule.getLocattRules().put(locattNode.valueOf("./@view"), locattRule);
				}
				countRule++;
				log.debug("Rule number: " + countRule + " --> " + currentRule);
				rules.add(currentRule);
			}
			return rules;
		} catch (Exception e) {
			throw new PIDAssignerException("Error obtaing rule " + ruleId, e);
		}
	}

	public MappedResultSetFactory getMappedResultSetFactory() {
		return mappedResultSetFactory;
	}

	@Required
	public void setMappedResultSetFactory(MappedResultSetFactory mappedResultSetFactory) {
		this.mappedResultSetFactory = mappedResultSetFactory;
	}

	@Required
	public void setLookupLocator(ServiceLocator<ISLookUpService> lookupLocator) {
		this.lookupLocator = lookupLocator;
	}

	public ServiceLocator<ISLookUpService> getLookupLocator() {
		return lookupLocator;
	}

	public ServiceLocator<PIDBridgeService> getPidBridgeLocator() {
		return pidBridgeLocator;
	}

	@Required
	public void setPidBridgeLocator(ServiceLocator<PIDBridgeService> pidBridgeLocator) {
		this.pidBridgeLocator = pidBridgeLocator;
	}

	@Required
	public void setDefaultResolveURL(String defaultResolveURL) {
		this.defaultResolveURL = defaultResolveURL;
	}

	public String getDefaultResolveURL() {
		return defaultResolveURL;
	}

	public EhCache<String, PidType> getPidCache() {
		return pidCache;
	}

	public void setPidCache(EhCache<String, PidType> pidCache) {
		this.pidCache = pidCache;
	}

}
