/*
 * This source code is made available under the terms of the
 * Common Public License Version 1.0 distibuted along with the
 * source code package.
 */
package de.uka.cmtm.serviceregistry.query.protege.dig;

import java.io.IOException;
import java.io.StringWriter;
import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.serialize.XMLSerializer;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import de.uka.cmtm.serviceregistry.query.protege.ParameterConformClasses;
import de.uka.cmtm.serviceregistry.query.protege.ProfileIndividualInfo;
import de.uka.cmtm.serviceregistry.query.protege.ProfileIndividualLocator;
import de.uka.cmtm.serviceregistry.query.protege.ProfileIndividualParameter;
import edu.stanford.smi.protegex.owl.inference.dig.exception.DIGReasonerException;
import edu.stanford.smi.protegex.owl.inference.dig.reasoner.DefaultDIGReasoner;
import edu.stanford.smi.protegex.owl.inference.dig.translator.DIGQueryResponse;
import edu.stanford.smi.protegex.owl.inference.dig.translator.DIGVocabulary;
import edu.stanford.smi.protegex.owl.inference.dig.translator.DefaultDIGTranslator;
import edu.stanford.smi.protegex.owl.inference.protegeowl.DefaultProtegeOWLReasoner;
import edu.stanford.smi.protegex.owl.inference.protegeowl.ProtegeOWLReasoner;
import edu.stanford.smi.protegex.owl.inference.protegeowl.ReasonerManager;
import edu.stanford.smi.protegex.owl.model.OWLClass;
import edu.stanford.smi.protegex.owl.model.OWLDatatypeProperty;
import edu.stanford.smi.protegex.owl.model.OWLIndividual;
import edu.stanford.smi.protegex.owl.model.OWLModel;
import edu.stanford.smi.protegex.owl.model.OWLObjectProperty;
import edu.stanford.smi.protegex.owl.model.RDFResource;

/**
 * This is a ProfileIndividualLocator making use of the DIG query interface
 * 
 * @author tilmann
 */
public class DigProfileIndividualLocator implements ProfileIndividualLocator {

	private static final Log log = LogFactory
			.getLog(DigProfileIndividualLocator.class);

	/**
	 * ID for dig queries
	 */
	private static final String PROFILES_QUERY_ID = "profiles";

	private final String upperServiceOntologyPrefix;

	private final String topOntologyPrefix;

	private final OWLObjectProperty preconditionProperty;

	private final OWLObjectProperty effectProperty;

	private final OWLObjectProperty logicalInputProperty;

	private final OWLObjectProperty logicalOutputProperty;

	private final OWLObjectProperty userRoleProperty;

	private final OWLDatatypeProperty textDescriptionProperty;

	private final OWLClass serviceProfileClass;

	private ProtegeOWLReasoner owlReasoner;

	private DefaultDIGReasoner digReasoner;

	private DefaultDIGTranslator digTranslator;

	/**
	 * Used to collect conform classes
	 */
	private Map<OWLClass, Collection<OWLClass>> ancestors = new HashMap<OWLClass, Collection<OWLClass>>();

	/**
	 * Used to collect conform classes
	 */
	private Map<OWLClass, Collection<OWLClass>> descendants = new HashMap<OWLClass, Collection<OWLClass>>();

	/**
	 * Used to check if all required properties are found
	 * 
	 * TODO: Maybe we can drop this if we have DIG 2.0 queries
	 */
	private ProfileIndividualParameter requiredClasses;

	/**
	 * Used to check if all required properties are found
	 * 
	 * TODO: Maybe we can drop this if we have DIG 2.0 queries
	 */
	private Map<OWLClass, Collection<OWLClass>> preconditionAncestors = new HashMap<OWLClass, Collection<OWLClass>>();

	/**
	 * Used to check if all required properties are found
	 * 
	 * TODO: Maybe we can drop this if we have DIG 2.0 queries
	 */
	private Map<OWLClass, Collection<OWLClass>> logicalInputAncestors = new HashMap<OWLClass, Collection<OWLClass>>();

	/**
	 * The parameters used during individual search
	 */
	private ProfileIndividualParameter piParameter;

	/**
	 * Holds conform classes for the search parameter
	 */
	private ParameterConformClasses conformClasses;

	/**
	 * The OWLModel searched
	 */
	private OWLModel owlModel;

	/**
	 * Used to check interface constraints.
	 */
	private int expectedSearchLevel = 0;

	/**
	 * Creates a new DigProfileIndividualLocator
	 * 
	 * @param owlModel
	 *            the OWLModel to be searched
	 */
	public DigProfileIndividualLocator(OWLModel owlModel,
			String digReasonerUrl, String upperServiceOntologyUrl,
			String topOntologyUrl) {

		this.owlModel = owlModel;

		RDFResource upperServiceOntology = null;
		RDFResource topOntology = null;

		try {

			// Connect to DIG-Reasoner
			ReasonerManager reasonerManager = ReasonerManager.getInstance();
			owlReasoner = reasonerManager.getReasoner(owlModel);
			owlReasoner.setURL(digReasonerUrl);

			if (owlReasoner.isConnected()) {
				log.info("Connected to " + owlReasoner.getIdentity().getName());

				log.debug("Synchronizing ontology with reasoner");
				((DefaultProtegeOWLReasoner) owlReasoner)
						.synchronizeReasoner(null);
				digReasoner = (DefaultDIGReasoner) owlReasoner.getDIGReasoner();
				digTranslator = (DefaultDIGTranslator) digReasoner
						.getTranslator();
			} else
				throw new IllegalStateException("OWL-Reasoner not available.");

			// Look up upper and top ontologies
			upperServiceOntology = owlModel.getOWLOntologyByURI(new URI(
					upperServiceOntologyUrl));
			log.trace("Found upper ontology: "
					+ (upperServiceOntology == null ? "none"
							: upperServiceOntology.getURI()));

			topOntology = owlModel.getOWLOntologyByURI(new URI(topOntologyUrl));
			log.trace("Found top ontology: "
					+ (topOntology == null ? "none" : topOntology.getURI()));

		} catch (Exception e) {
			throw new IllegalArgumentException(
					"Cannot synchronize ontology with reasoner.", e);
		}

		// Look up upper and top ontology prefixes
		if (upperServiceOntology != null)
			upperServiceOntologyPrefix = upperServiceOntology
					.getNamespacePrefix()
					+ ":";
		else
			throw new IllegalArgumentException("Upper ontology not present.");

		log.trace("Found upper ontology prefix: " + upperServiceOntologyPrefix);

		if (topOntology != null)
			topOntologyPrefix = topOntology.getNamespacePrefix() + ":";
		else
			throw new IllegalArgumentException("Top ontology not present.");

		log.trace("Found top ontology prefix: " + topOntologyPrefix);

		// Look up several upper ontology properties and classes
		preconditionProperty = owlModel
				.getOWLObjectProperty(upperServiceOntologyPrefix
						+ "precondition");
		if (preconditionProperty == null)
			throw new IllegalArgumentException(
					"preconditionProperty not present.");
		log.trace("Found preconditionProperty: "
				+ preconditionProperty.getName());

		effectProperty = owlModel
				.getOWLObjectProperty(upperServiceOntologyPrefix + "effect");
		if (effectProperty == null)
			throw new IllegalArgumentException("effectProperty not present.");
		log.trace("Found effectProperty: " + effectProperty.getName());

		logicalInputProperty = owlModel
				.getOWLObjectProperty(upperServiceOntologyPrefix
						+ "logicalInput");
		if (logicalInputProperty == null)
			throw new IllegalArgumentException(
					"logicalInputProperty not present.");
		log.trace("Found logicalInputProperty: "
				+ logicalInputProperty.getName());

		logicalOutputProperty = owlModel
				.getOWLObjectProperty(upperServiceOntologyPrefix
						+ "logicalOutput");
		if (logicalOutputProperty == null)
			throw new IllegalArgumentException(
					"logicalOutputProperty not present.");
		log.trace("Found logicalOutputProperty: "
				+ logicalOutputProperty.getName());

		userRoleProperty = owlModel
				.getOWLObjectProperty(upperServiceOntologyPrefix + "userRole");
		if (userRoleProperty == null)
			throw new IllegalArgumentException("userRoleProperty not present.");
		log.trace("Found userRoleProperty: " + userRoleProperty.getName());

		textDescriptionProperty = owlModel
				.getOWLDatatypeProperty(upperServiceOntologyPrefix
						+ "textDescription");
		if (textDescriptionProperty == null)
			throw new IllegalArgumentException(
					"textDescriptionProperty not present.");
		log.trace("Found textDescriptionProperty: "
				+ textDescriptionProperty.getName());

		serviceProfileClass = owlModel
				.getOWLNamedClass(upperServiceOntologyPrefix + "ServiceProfile");
		if (serviceProfileClass == null)
			throw new IllegalArgumentException(
					"serviceProfileClass not present.");
		log
				.trace("Found serviceProfileClass: "
						+ serviceProfileClass.getName());
	}

	/**
	 * Look up property values for a given service profile individual and store
	 * them in a ProfileIndividualInfo
	 * 
	 * @param profile
	 *            the ServiceProfile individual
	 * @return the complete service profile including the properties
	 */
	private ProfileIndividualInfo gatherProfileInfo(OWLIndividual profile) {
		ProfileIndividualInfo piInfo = new ProfileIndividualInfo();

		piInfo.setProfile(profile);
		Set<OWLIndividual> set;

		piInfo.setDescription(profile
				.getPropertyValueLiteral(textDescriptionProperty));

		set = new HashSet<OWLIndividual>();
		set.addAll(profile.getPropertyValues(preconditionProperty));
		piInfo.setPreconditions(set);

		set = new HashSet<OWLIndividual>();
		set.addAll(profile.getPropertyValues(effectProperty));
		piInfo.setEffects(set);

		set = new HashSet<OWLIndividual>();
		set.addAll(profile.getPropertyValues(logicalInputProperty));
		piInfo.setLogicalInputs(set);

		set = new HashSet<OWLIndividual>();
		set.addAll(profile.getPropertyValues(logicalOutputProperty));
		piInfo.setLogicalOutputs(set);

		set = new HashSet<OWLIndividual>();
		set.addAll(profile.getPropertyValues(userRoleProperty));
		piInfo.setUserRoles(set);

		return piInfo;
	}

	/**
	 * Get the maximum search level that can be used plus one.
	 * 
	 * @return the number of search levels available
	 */
	public int getAvailableSearchLevels() {
		return 7;
	}

	/**
	 * This method can be used to search for services profile individuals
	 * suitable to the previously set searchParameters. The resulting list may
	 * also contain individuals that do not perfectly match the parameters. This
	 * is indicated with a relevance below 1. How exact profile individuals are
	 * matched can be specified wiht the searchLevel parameter. Subsequent calls
	 * for a single search parameter must start with search level 0 and may
	 * increase searchlevel only by one each time up to the maximum that can be
	 * obtained by getAvailableSearchLevels.
	 * 
	 * @param searchLevel
	 *            the search level to be applied
	 * @return a list of search results
	 * @throws IOException
	 */
	public List<ProfileIndividualInfo> findProfileIndividuals(int searchLevel)
			throws IOException {

		// Check input parameters
		if (searchLevel >= getAvailableSearchLevels())
			throw new IllegalArgumentException("SearchLevel " + searchLevel
					+ " not supported.");

		if (piParameter == null)
			throw new IllegalStateException(
					"The search parameter must be set prior to a call to findProfileIndividuals");

		// TODO: Maybe we can drop this if we have DIG 2.0 queries
		if (searchLevel != expectedSearchLevel)
			throw new IllegalStateException("Wrong search level: Given "
					+ searchLevel + " Expected " + expectedSearchLevel);
		expectedSearchLevel = (expectedSearchLevel + 1)
				% getAvailableSearchLevels();

		// Prepare result list and search parameter
		List<ProfileIndividualInfo> results = new LinkedList<ProfileIndividualInfo>();
		ProfileIndividualParameter mySearchParameter = piParameter.clone();

		// Create catom element
		if (mySearchParameter.getCategory() == null)
			mySearchParameter.setCategory(serviceProfileClass);

		try {
			// Select step for searchLevel. Steps depend on each other so order
			// does matter.
			// TODO: Maybe we can drop this if we have DIG 2.0 queries
			switch (searchLevel) {
			case 0: {
				Document askDoc = digTranslator.createAsksDocument(owlReasoner
						.getReasonerKnowledgeBaseURI());

				// Initialize values to check found individuals lateron
				requiredClasses = mySearchParameter.clone();
				// preconditionAncestors.clear();
				// logicalInputAncestors.clear();

				// Create DIG query
				createIndividualsQuery(mySearchParameter, askDoc, false, false,
						false, false, false, false, true);

				Document resultDoc = performRequest(askDoc);

				// Convert and look up resulting profile individuals
				gatherResults(resultDoc, results);

				// Remove found profile individuals that should not have been
				// found.
				// TODO: Maybe we can drop this if we have DIG 2.0 queries
				removeExcessiveMatches(results, false, false, false, false,
						false, false, true);
				break;
			}
			case 1: {
				Document askDoc = digTranslator.createAsksDocument(owlReasoner
						.getReasonerKnowledgeBaseURI());

				createIndividualsQuery(mySearchParameter, askDoc, true, true,
						true, true, false, false, true);

				// Query for ancestors to get conform classes
				createAncestorQueries(mySearchParameter.getPreconditions(),
						askDoc);

				// Queries for descendants to get conform classes
				createDescendantQueries(mySearchParameter.getEffects(), askDoc);
				createDescendantQueries(mySearchParameter.getUserRoles(),
						askDoc);
				if (piParameter.getCategory() != null)
					createDescendantQueries(Collections
							.singletonList(mySearchParameter.getCategory()),
							askDoc);

				Document resultDoc = performRequest(askDoc);

				// Convert and look up resulting profile individuals
				gatherResults(resultDoc, results);

				// fetch conform classes
				fillAncestors(mySearchParameter.getPreconditions(),
						conformClasses.getPreconditions());
				fillDescendants(mySearchParameter.getEffects(), conformClasses
						.getEffects());
				fillDescendants(mySearchParameter.getUserRoles(),
						conformClasses.getUserRoles());
				if (piParameter.getCategory() != null)
					fillDescendants(Collections.singletonList(mySearchParameter
							.getCategory()), conformClasses.getCategories());

				// TODO: Maybe we can drop this if we have DIG 2.0 queries
				removeExcessiveMatches(results, true, true, true, true, false,
						false, true);
				break;
			}
			case 2: {
				Document askDoc = digTranslator.createAsksDocument(owlReasoner
						.getReasonerKnowledgeBaseURI());

				createIndividualsQuery(mySearchParameter, askDoc, true, true,
						true, true, true, true, true);

				// Query for ancestors to get conform classes
				createAncestorQueries(mySearchParameter.getLogicalInputs(),
						askDoc);

				// Query for descendants to get conform classes
				createDescendantQueries(mySearchParameter.getLogicalOutputs(),
						askDoc);

				Document resultDoc = performRequest(askDoc);

				// Convert and look up resulting profile individuals
				gatherResults(resultDoc, results);

				// fetch conform classes
				fillAncestors(mySearchParameter.getLogicalInputs(),
						conformClasses.getLogicalInputs());
				fillDescendants(mySearchParameter.getLogicalOutputs(),
						conformClasses.getLogicalOutputs());

				// TODO: Maybe we can drop this if we have DIG 2.0 queries
				removeExcessiveMatches(results, true, true, true, true, true,
						true, true);
				break;
			}
			case 3: {
				Document askDoc = digTranslator.createAsksDocument(owlReasoner
						.getReasonerKnowledgeBaseURI());

				createIndividualsQuery(mySearchParameter, askDoc, true, true,
						true, true, true, true, false);

				Document resultDoc = performRequest(askDoc);

				gatherResults(resultDoc, results);

				removeExcessiveMatches(results, true, true, true, true, true,
						true, false);
				break;
			}
			case 4: {
				Document askDoc = digTranslator.createAsksDocument(owlReasoner
						.getReasonerKnowledgeBaseURI());

				// Remove property values not taken into account
				mySearchParameter.getUserRoles().clear();
				requiredClasses = mySearchParameter.clone();

				createIndividualsQuery(mySearchParameter, askDoc, true, true,
						true, true, true, true, false);

				Document resultDoc = performRequest(askDoc);

				gatherResults(resultDoc, results);

				removeExcessiveMatches(results, true, true, true, true, true,
						true, false);
				break;
			}
			case 5: {
				Document askDoc = digTranslator.createAsksDocument(owlReasoner
						.getReasonerKnowledgeBaseURI());

				// Remove property values not taken into account
				mySearchParameter.getUserRoles().clear();
				mySearchParameter.setCategory(serviceProfileClass);
				requiredClasses = mySearchParameter.clone();

				createIndividualsQuery(mySearchParameter, askDoc, true, true,
						true, true, true, true, false);

				Document resultDoc = performRequest(askDoc);

				gatherResults(resultDoc, results);

				removeExcessiveMatches(results, true, true, true, true, true,
						true, false);
				break;
			}
			case 6: {
				Document askDoc = digTranslator.createAsksDocument(owlReasoner
						.getReasonerKnowledgeBaseURI());

				// Remove property values not taken into account
				mySearchParameter.setCategory(serviceProfileClass);
				mySearchParameter.getUserRoles().clear();
				mySearchParameter.getPreconditions().clear();
				mySearchParameter.getEffects().clear();
				requiredClasses = mySearchParameter.clone();

				createIndividualsQuery(mySearchParameter, askDoc, true, true,
						true, true, true, true, false);

				Document resultDoc = performRequest(askDoc);

				gatherResults(resultDoc, results);

				removeExcessiveMatches(results, true, true, true, true, true,
						true, false);
				break;
			}
			}
		} catch (DIGReasonerException e) {
			IOException ioe = new IOException(
					"Problem during communication with DIG Reasoner.");
			ioe.initCause(e);
			throw ioe;
		}
		log.info("Profiles found in level " + searchLevel + ": "
				+ results.size());
		return results;
	}

	/**
	 * Fill all found descendants of the given classes into the given set
	 * 
	 * @param classes
	 *            the classes of which descendants will be looked for
	 * @param classesDescendants
	 *            the descendants set to be filled
	 */
	private void fillDescendants(Collection<OWLClass> classes,
			Set<OWLClass> classesDescendants) {
		for (OWLClass owlClass : classes) {
			classesDescendants.addAll(descendants.get(owlClass));
		}
	}

	/**
	 * Fill all found ancestors of the given classes into the given set
	 * 
	 * @param classes
	 *            the classes of which ancestors will be looked for
	 * @param classesAncestors
	 *            the ancestors set to be filled
	 */
	private void fillAncestors(Collection<OWLClass> classes,
			Set<OWLClass> classesAncestors) {
		for (OWLClass owlClass : classes) {
			classesAncestors.addAll(ancestors.get(owlClass));
		}
	}

	/**
	 * Convert DIG response and look up property values of service profile
	 * individual
	 * 
	 * @param resultDoc
	 *            the DIG response
	 * @param results
	 *            the newly created collection of ProfileIndividualInfo
	 * @throws DIGReasonerException
	 */
	private void gatherResults(Document resultDoc,
			Collection<ProfileIndividualInfo> results)
			throws DIGReasonerException {

		Iterator<DIGQueryResponse> iter = digTranslator
				.getDIGQueryResponseIterator(owlModel, resultDoc);

		while (iter.hasNext()) {
			final DIGQueryResponse response = iter.next();
			String responseId = response.getID();
			if (PROFILES_QUERY_ID.equals(responseId)) {
				for (OWLIndividual profile : (Collection<OWLIndividual>) response
						.getIndividuals()) {

					// look up property values
					results.add(gatherProfileInfo(profile));
				}
			} else if (responseId.startsWith("ancestors:")) {
				String className = responseId
						.substring(responseId.indexOf(":") + 1);
				OWLClass owlClass = owlModel.getOWLNamedClass(className);
				ancestors.put(owlClass, response.getConcepts());

				// TODO: Maybe we can drop this if we have DIG 2.0 queries
				if (piParameter.getPreconditions().contains(owlClass)) {
					preconditionAncestors.put(owlClass, response.getConcepts());
					// log.debug("Found precondition ancestors for " +
					// owlClass.getName() + " : " + response.getConcepts());
				}

				// TODO: Maybe we can drop this if we have DIG 2.0 queries
				if (piParameter.getLogicalInputs().contains(owlClass)) {
					logicalInputAncestors.put(owlClass, response.getConcepts());
					// log.debug("Found input ancestors for " +
					// owlClass.getName() + " : " + response.getConcepts());
				}

			} else if (responseId.startsWith("descendants:")) {
				String className = responseId
						.substring(responseId.indexOf(":") + 1);
				OWLClass owlClass = owlModel.getOWLNamedClass(className);
				descendants.put(owlClass, response.getConcepts());
			}
		}
	}

	/**
	 * Perform DIG request
	 * 
	 * @param askDoc
	 *            DIG request document
	 * @return DIG response document
	 * @throws DIGReasonerException
	 */
	private Document performRequest(Document askDoc)
			throws DIGReasonerException {

		log.trace("Sending dig request:");
		logDocument(askDoc);

		Document resultDoc = digReasoner.performRequest(askDoc);

		log.trace("Received dig response:");
		logDocument(resultDoc);

		return resultDoc;
	}

	/**
	 * Checks if required classes are present in the classes presented by given
	 * individuals.
	 * 
	 * @param individuals
	 *            the individuals
	 * @param classes
	 *            the required classes
	 * @param allRequired
	 *            if true all classes must be present if false it is sufficient
	 *            if a single class is present
	 * @return true if requirements met
	 * 
	 * TODO: Maybe we can drop this if we have DIG 2.0 queries
	 */
	private boolean requiredClassesPresent(
			Collection<OWLIndividual> individuals,
			Collection<OWLClass> classes, boolean allRequired) {

		// determine present classes
		Collection<OWLClass> foundClasses = new HashSet<OWLClass>();
		for (OWLIndividual individual : individuals) {
			foundClasses.add((OWLClass) individual.getRDFType());
		}

		if (allRequired)
			return foundClasses.containsAll(classes);
		else {
			for (OWLClass owlClass : classes) {
				if (foundClasses.contains(owlClass))
					return true;
			}
			return false;
		}
	}

	/**
	 * Checks if required classes are present in the classes presented by given
	 * individuals. For every class in the the classes collection either the
	 * class or one of its super classes in the superClases map must be present.
	 * 
	 * @param individuals
	 *            the individuals
	 * @param classes
	 *            the required classes
	 * @param allRequired
	 *            if true all classes must be present if false it is sufficient
	 *            if a single class is present
	 * @return true if requirements met
	 * 
	 * TODO: Maybe we can drop this if we have DIG 2.0 queries
	 */
	private boolean requiredClassesPresent(
			Collection<OWLIndividual> individuals,
			Collection<OWLClass> classes,
			Map<OWLClass, Collection<OWLClass>> superClassesMap,
			boolean allRequired) {

		// Determine presented classes
		Collection<OWLClass> foundClasses = new HashSet<OWLClass>();
		for (OWLIndividual individual : individuals) {
			foundClasses.add((OWLClass) individual.getRDFType());
		}

		boolean allPresent = true;
		for (OWLClass owlClass : classes) {

			// First check if class present
			boolean setMemberPresent = foundClasses.contains(owlClass);

			// Otherwise check if at least one super class present
			if (!setMemberPresent && superClassesMap.containsKey(owlClass))
				for (OWLClass superClass : superClassesMap.get(owlClass)) {
					if (foundClasses.contains(superClass)) {
						setMemberPresent = true;
						break;
					}
				}

			allPresent = allPresent && setMemberPresent;
			if (setMemberPresent && !allRequired)
				return true;
			if (!allPresent && allRequired)
				return false;
		}
		if (allPresent)
			return true;
		return false;
	}

	/**
	 * Remove the exceeding matches found due to limitations of DIG 1.1
	 * 
	 * @param piInfos
	 *            the found matches
	 * @param conformCategory
	 *            consider conform service category
	 * @param conformUserRoles
	 *            consider conform user roles
	 * @param conformPreconditions
	 *            consider conform precondition
	 * @param conformEffects
	 *            consider conform effects
	 * @param conformInputs
	 *            consider conform logical inputs
	 * @param conformOutputs
	 *            consider conform logical outputs
	 * @param allRequired
	 *            if true all required classes must be present otherwise a
	 *            single match is sufficient
	 * 
	 * TODO: Maybe we can drop this if we have DIG 2.0 queries
	 */
	private void removeExcessiveMatches(
			Collection<ProfileIndividualInfo> piInfos, boolean conformCategory,
			boolean conformUserRoles, boolean conformPreconditions,
			boolean conformEffects, boolean conformInputs,
			boolean conformOutputs, boolean allRequired) {

		Iterator<ProfileIndividualInfo> iter = piInfos.iterator();
		profiles: while (iter.hasNext()) {
			ProfileIndividualInfo piInfo = iter.next();

			// Remove individuals presenting subclasses of category
			if (!conformCategory
					&& requiredClasses.getCategory() != null
					&& !requiredClasses.getCategory().equals(
							piInfo.getProfile().getRDFType())) {

				if (log.isTraceEnabled())
					log.trace("Removing " + piInfo.getProfile().getName()
							+ ": Wrong category");

				iter.remove();
				continue profiles;
			}

			// Remove individuals presenting subclasses of user Role
			if (!conformUserRoles
					&& !requiredClassesPresent(piInfo.getUserRoles(),
							requiredClasses.getUserRoles(), allRequired)) {

				if (log.isTraceEnabled())
					log.trace("Removing " + piInfo.getProfile().getName()
							+ ": Wrong userRoles");

				iter.remove();
				continue profiles;
			}

			// Always remove individuals presenting subclasses of logical inputs
			if (!requiredClassesPresent(piInfo.getLogicalInputs(),
					requiredClasses.getLogicalInputs(), logicalInputAncestors,
					allRequired)) {

				if (log.isTraceEnabled())
					log.trace("Removing " + piInfo.getProfile().getName()
							+ ": Wrong logicalInputs");

				iter.remove();
				continue profiles;
			}

			// Remove individuals presenting subclasses of logical outputs
			if (!conformOutputs
					&& !requiredClassesPresent(piInfo.getLogicalOutputs(),
							requiredClasses.getLogicalOutputs(), allRequired)) {

				if (log.isTraceEnabled())
					log.trace("Removing " + piInfo.getProfile().getName()
							+ ": Wrong logicalOutputs");

				iter.remove();
				continue profiles;
			}

			// Always remove individuals presenting subclasses of preconditions
			if (!requiredClassesPresent(piInfo.getPreconditions(),
					requiredClasses.getPreconditions(), preconditionAncestors,
					allRequired)) {

				if (log.isTraceEnabled())
					log.trace("Removing " + piInfo.getProfile().getName()
							+ ": Wrong preconditions");

				iter.remove();
				continue profiles;
			}

			// Remove individuals presenting subclasses of effects
			if (!conformEffects
					&& !requiredClassesPresent(piInfo.getEffects(),
							requiredClasses.getEffects(), allRequired)) {

				if (log.isTraceEnabled())
					log.trace("Removing " + piInfo.getProfile().getName()
							+ ": Wrong effects");

				iter.remove();
				continue profiles;
			}

		}
	}

	/**
	 * Create ancestors query in DIG request document
	 * 
	 * @param classes
	 *            collection of classes for which to ask for ancestors
	 * @param askDoc
	 *            DIG request document
	 * @throws DIGReasonerException
	 */
	private void createAncestorQueries(Collection<OWLClass> classes,
			Document askDoc) throws DIGReasonerException {

		for (OWLClass owlClass : classes) {
			if (!ancestors.containsKey(owlClass)) {
				Element ancElement = digTranslator.createQueryElement(askDoc,
						DIGVocabulary.Ask.ANCESTORS, "ancestors:"
								+ owlClass.getName());
				askDoc.getDocumentElement().appendChild(ancElement);

				digTranslator.translateToDIG(owlClass, askDoc, ancElement);
			}
		}
	}

	/**
	 * Create descendant query in DIG request document
	 * 
	 * @param classes
	 *            collection of classes for which to ask for ancestors
	 * @param askDoc
	 *            DIG request document
	 * @throws DIGReasonerException
	 */
	private void createDescendantQueries(Collection<OWLClass> classes,
			Document askDoc) throws DIGReasonerException {

		for (OWLClass owlClass : classes) {
			if (!descendants.containsKey(owlClass)) {
				Element ancElement = digTranslator.createQueryElement(askDoc,
						DIGVocabulary.Ask.DESCENDANTS, "descendants:"
								+ owlClass.getName());
				askDoc.getDocumentElement().appendChild(ancElement);

				digTranslator.translateToDIG(owlClass, askDoc, ancElement);
			}
		}
	}

	/**
	 * Create individuals query in DIG request document
	 * 
	 * @param askDoc
	 *            DIG request document
	 * @param searchParameter
	 *            the parameter to search for
	 * @param conformCategory
	 *            consider conform service category
	 * @param conformUserRoles
	 *            consider conform user roles
	 * @param conformPreconditions
	 *            consider conform precondition
	 * @param conformEffects
	 *            consider conform effects
	 * @param conformInputs
	 *            consider conform logical inputs
	 * @param conformOutputs
	 *            consider conform logical outputs
	 * @param allRequired
	 *            if true all required classes must be present otherwise a
	 *            single match is sufficient
	 * @throws DIGReasonerException
	 */
	private Element createIndividualsQuery(
			ProfileIndividualParameter searchParameter, Document askDoc,
			boolean conformCategory, boolean conformUserRoles,
			boolean conformPreconditions, boolean conformEffects,
			boolean conformInputs, boolean conformOutputs, boolean allRequired)
			throws DIGReasonerException {

		// Create individuals element
		Element indElement = digTranslator.createQueryElement(askDoc,
				DIGVocabulary.Ask.INSTANCES, PROFILES_QUERY_ID);
		askDoc.getDocumentElement().appendChild(indElement);

		// Create and element
		Element andElement = askDoc.createElement(DIGVocabulary.Language.AND);
		indElement.appendChild(andElement);

		Element toAppend = andElement;

		digTranslator.translateToDIG(searchParameter.getCategory(), askDoc,
				toAppend);

		// Limit preconditions if not conform search
		if (!conformPreconditions)
			for (OWLClass owlClass : searchParameter.getPreconditions()) {
				Element someElement = askDoc
						.createElement(DIGVocabulary.Language.SOME);
				toAppend.appendChild(someElement);

				digTranslator.translateToDIG(preconditionProperty, askDoc,
						someElement);
				digTranslator.translateToDIG(owlClass, askDoc, someElement);

			}

		// Create nested or element if not all required
		if (!searchParameter.getEffects().isEmpty() && !allRequired) {
			Element orElement = askDoc.createElement(DIGVocabulary.Language.OR);
			toAppend.appendChild(orElement);
			toAppend = orElement;
		}
		// Limit effects
		for (OWLClass owlClass : searchParameter.getEffects()) {
			Element someElement = askDoc
					.createElement(DIGVocabulary.Language.SOME);
			toAppend.appendChild(someElement);

			digTranslator.translateToDIG(effectProperty, askDoc, someElement);
			digTranslator.translateToDIG(owlClass, askDoc, someElement);
		}
		toAppend = andElement;

		// Limit logical inputs if not conform search
		if (!conformInputs)
			for (OWLClass owlClass : searchParameter.getLogicalInputs()) {
				Element someElement = askDoc
						.createElement(DIGVocabulary.Language.SOME);
				toAppend.appendChild(someElement);

				digTranslator.translateToDIG(logicalInputProperty, askDoc,
						someElement);
				digTranslator.translateToDIG(owlClass, askDoc, someElement);
			}

		// Create nested or element if not all required
		if (!searchParameter.getLogicalOutputs().isEmpty() && !allRequired) {
			Element orElement = askDoc.createElement(DIGVocabulary.Language.OR);
			toAppend.appendChild(orElement);
			toAppend = orElement;
		}
		// Limit logical outputs
		for (OWLClass owlClass : searchParameter.getLogicalOutputs()) {
			Element someElement = askDoc
					.createElement(DIGVocabulary.Language.SOME);
			toAppend.appendChild(someElement);

			digTranslator.translateToDIG(logicalOutputProperty, askDoc,
					someElement);
			digTranslator.translateToDIG(owlClass, askDoc, someElement);
		}
		toAppend = andElement;

		// Create nested or element if not all required
		if (!searchParameter.getUserRoles().isEmpty() && !allRequired) {
			Element orElement = askDoc.createElement(DIGVocabulary.Language.OR);
			toAppend.appendChild(orElement);
			toAppend = orElement;
		}
		// Limit user roles
		for (OWLClass owlClass : searchParameter.getUserRoles()) {
			Element someElement = askDoc
					.createElement(DIGVocabulary.Language.SOME);
			toAppend.appendChild(someElement);

			digTranslator.translateToDIG(userRoleProperty, askDoc, someElement);
			digTranslator.translateToDIG(owlClass, askDoc, someElement);
		}
		return indElement;
	}

	/**
	 * Trace log a dom document
	 * 
	 * @param doc
	 *            the dom document
	 */
	private void logDocument(Document doc) {
		if (log.isTraceEnabled()) {
			try {
				OutputFormat format = new OutputFormat();
				format.setIndent(4);
				format.setIndenting(true);
				format.setPreserveSpace(false);
				XMLSerializer serializer = new XMLSerializer(format);
				StringWriter writer = new StringWriter();
				serializer.setOutputCharStream(writer);
				serializer.serialize(doc);
				writer.close();
				log.trace(writer.getBuffer());
			} catch (IOException e) {
				log.warn("Error while creating log output.", e);
			}
		}
	}

	/**
	 * Sets the search parameter for subsequent calls to findProfileIndividuals
	 * 
	 * @param searchParameter
	 *            the roles the individuals should have assigned
	 */
	public void setProfileIndividualParameter(
			ProfileIndividualParameter searchParameter) {
		this.piParameter = searchParameter;
		expectedSearchLevel = 0;
		this.conformClasses = new ParameterConformClasses();
	}

	/**
	 * Get Conform Classes taken into account by the last search step executed.
	 * 
	 * @return a java bean containing conform classes
	 */
	public ParameterConformClasses getConformClasses() {
		return conformClasses;
	}
}
