package ws.prova.reference;

/*
 * Copyright (C) 1999-2005 <a href="mailto:mandarax@jbdietrich.com">Jens Dietrich</a>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

import org.apache.commons.collections.iterators.SingletonIterator;
import org.mandarax.kernel.*;
import org.mandarax.reference.Complex;

import java.util.*;

/**
 * Reference implementation for (simple) facts. In older versions (prior to 1.9)
 * there was no separate Query interface/class - facts were used instead.
 * In order to facilitate maintanance of older applications, this class still implements
 * Query. Some newer features, in particular DerivationEventListeners, are not supported.
 * For queries, QueryImpl should be used instead.
 *
 * @author <A HREF="mailto:mandarax@jbdietrich.com">Jens Dietrich</A>
 * @author <A HREF="mailto:a.kozlenkov@city.ac.uk">Alex Kozlenkov</A>
 * @version 3.3.1 <9 August 2004>
 * @see org.mandarax.reference.QueryImpl
 * @since 1.1
 *        Prova re-integration modifications
 */
public class FactImpl extends Complex implements Fact, Query {

	private List posLiterals = null;
	private static List negLiterals = new Vector();
	private Predicate predicate = null;
//	private transient Vector clauseSetChangeListener = new Vector();
	private ClauseSet container = this;
	// utility vars for the query interface
	private Fact[] facts = {this};
	private String name = null;
	private Properties delegate = null;
	private int containsVariables = -1;
	private List vars;

	/**
	 * Constructor.
	 */
	public FactImpl() {
		super();
	}

	/**
	 * Constructor.
	 *
	 * @param p the predicate
	 */
	FactImpl(Predicate p) {
		super();
		setPredicate(p);
	}

	/**
	 * Constructor.
	 *
	 * @param p the predicate
	 * @param t the terms
	 */
	public FactImpl(Predicate p, Term[] t) {
		super();
		setPredicate(p);
		for (int i = 0; i < t.length; i++) {
			setTerm(t[i], i);
		}
	}

	/**
	 * Initialise the array of variables
	 *
	 * @param vars
	 */
	public void setVars(List vars) {
		this.vars = vars;
	}

	/**
	 * Add a clause set listener.
	 *
	 * @param l a listener
	 */
	public void addClauseSetChangeListener(ClauseSetChangeListener l) {
//		clauseSetChangeListener.add(l);
	}

	/**
	 * Apply a set of replacements to a clause. Returns a new clause!
	 *
	 * @param r a collection of replacements
	 * @return the fact (clause) resulting from the application of the replacements
	 */
	public Clause apply(Collection r) {		
		return applyToFact(r);
	}

	/**
	 * Apply a single replacement to a fact. Returns a new clause!
	 *
	 * @param r the replacement
	 * @return the fact (clause) resulting from the application of the replacemen
	 */
	public Clause apply(Replacement r) {		
		return applyToFact(r);
	}

	/**
	 * Apply a set of replacements to a fact. Returns a new fact!
	 *
	 * @param r a collection of replacement
	 * @return the fact resulting from the application of the replacement
	 */
	public Fact applyToFact(Collection r) {		
		Term[] t = this.getTerms();
		Term newTerm = null;

		// create a new fact
		FactImpl f = new FactImpl();
		copyStructure(f);
		for (int i = 0; i < t.length; i++) {
			newTerm = t[i];
			for (Iterator it = r.iterator(); it.hasNext();) {
				newTerm = newTerm.apply((Replacement) it.next());
			}
			f.setTerm(newTerm, i);
		}

		return f;
	}

	/**
	 * Apply a single replacement to a fact. Returns a new fact!
	 *
	 * @param r a replacement
	 * @return the fact (clause) resulting from the application of the replacement
	 */
	public Fact applyToFact(Replacement r) {
		Term[] t = getTerms();
		// create a new fact
		FactImpl f = new FactImpl();
		copyStructure(f);
		for (int i = 0; i < t.length; i++) {
			f.setTerm(t[i].apply(r), i);
		}
		return f;
	}

	/**
	 * Whether the fact contains variables
	 *
	 * @return boolean
	 */
	public boolean isBound() {
		if (containsVariables >= 0)
			return containsVariables == 0;

		Term[] tt = this.getTerms();
		for (int i = 0; i < tt.length; i++) {
			if (tt[i].containsVariables()) {
				containsVariables = 1;
				return false;
			}
		}
		containsVariables = 0;
		return true;
	}

	/**
	 * Get an iterator for clauses.
	 *
	 * @return a clause iterator
	 */
	public org.mandarax.util.ClauseIterator clauses() {
		return new org.mandarax.util.SingleClauseIterator(this);
	}

	/**
	 * Get an iterator for clauses. The parameters are ignored and <code>clauses()</code> is called!
	 *
	 * @param query               a query
	 * @param additionalParameter an additional parameter
	 * @return a clause iterator
	 */
	public org.mandarax.util.ClauseIterator clauses(Clause query, Object additionalParameter) {
		return clauses();
	}

	/**
	 * Copy the structure, i.e. length of the terms, types of the terms and methods,
	 *
	 * @param f the fact that takes the structure from this object
	 */
	protected void copyStructure(FactImpl f) {
		f.types = types;
		f.terms = new Term[terms.length];
		f.predicate = predicate;
	}

	/**
	 * Check whether objects are equal.
	 *
	 * @param obj the object to compare this object with
	 * @return true if the objects are equal, false otherwise
	 */
	public boolean equals(Object obj) {
		if ((obj != null) && (obj instanceof FactImpl)) {
			FactImpl f = (FactImpl) obj;
			boolean result = (predicate == null)
					? f.predicate == null
					: predicate.equals(f.predicate);
			Term[] terms1 = terms;
			Term[] terms2 = f.terms;
			if (terms1 == null)
				result = result && terms2 == null;
			else {
				for (int i = 0; i < terms.length; i++) {
					result = result
							&& (((terms[i] == null) && (terms2[i] == null))
							|| (terms1[i].equals(terms2[i])));

					if (!result) {
						return false;
					}
				}
			}
			return result;
		}
		return false;
	}

	/**
	 * Fire a clause set change event
	 *
	 * @param e the event
	 */
	protected void fireClauseSetChangeEvent(ClauseSetChangeEvent e) {
//		ClauseSetChangeListener l;
//
//		for( Iterator it = clauseSetChangeListener.iterator(); it.hasNext(); ) {
//			l = (ClauseSetChangeListener) it.next();
//			l.clauseSetChanged(e);
//		}
	}

	/**
	 * Get the object constructing the complex.
	 *
	 * @return the predicate of this fact
	 */
	protected Constructor getConstructor() {
		return getPredicate();
	}

	/**
	 * Get the clause set containing the clause. This method is in particular useful for analyzing derivations,
	 * since derivations show only the clauses used, not the clause set generating this clauses (e.g., AutoFacts). On the other
	 * hand, knowledge bases contain often clause sets. So it could be hard to find the clause sets in the knowledge base
	 * that caused a certain result. For some clauses such as facts, the container is just the fact itself.
	 *
	 * @return a clause set
	 */
	public ClauseSet getContainer() {
		return container;
	}

	/**
	 * Get a key for fast access. The key is usually the predicate, but subclasses
	 * might want to use different keys. Note that keys are mainly used by inference engines and
	 * knowledge bases to improve performance and inference engine and knowledge bases
	 * must know about the semantics of keys to use them correctly!
	 *
	 * @return the key object
	 */
	public Object getKey() {
		return getPredicate();
	}

	/**
	 * Get the negative literals.
	 *
	 * @return the collection of negative literals, this is an empty collection!
	 */
	public List getNegativeLiterals() {
		return negLiterals;
	}

	/**
	 * Get the positive literals.
	 *
	 * @return the collection of positive literals, this is a singleton containing only this object
	 */
	public List getPositiveLiterals() {
		if (posLiterals == null) {
			posLiterals = new Vector(1);
			posLiterals.add(this);
		}

		return posLiterals;
	}

	/**
	 * Get the predicate.
	 *
	 * @return the predicate
	 */
	public Predicate getPredicate() {
		return predicate;
	}

	/**
	 * Get the hash code of a fact.
	 *
	 * @return the hash value
	 */
	public int hashCode() {
		int i = (predicate == null)
				? 0
				: predicate.hashCode();

		if (terms.length == 0) {
			return i;
		}

		if (terms.length == 1) {
			return i ^ ((terms[0] == null)
					? 0
					: terms[0].hashCode());
		} else {
			return i ^ ((terms[0] == null)
					? 0
					: terms[0].hashCode()) ^ ((terms[terms.length - 1]
					== null)
					? 0
					: terms[terms.length - 1]
					.hashCode());
		}
	}

	/**
	 * Indicates whether the clause is atomic.
	 *
	 * @return a boolean
	 */
	public boolean isAtomic() {
		return true;
	}

	/**
	 * Indicates whether the clause is the empty clause.
	 *
	 * @return false
	 */
	public boolean isEmpty() {
		return false;
	}

	/**
	 * Indicates whether the object (usually a term or a clause set) can be performed
	 * using the java semantics.
	 *
	 * @return a boolean
	 */
	public boolean isExecutable() {
		if ((predicate != null) && predicate.isExecutable()) {
			Term[] t = getTerms();
			for (int i = 0; i < t.length; i++) {
				if (!t[i].isExecutable()) {
					return false;
				}
			}

			return true;
		}

		return false;
	}

	/**
	 * Remove a clause set listener.
	 *
	 * @param l a listener
	 */
	public void removeClauseSetChangeListener(ClauseSetChangeListener l) {
//		clauseSetChangeListener.remove(l);
	}

	/**
	 * Resolve a fact: if supported, the predicate
	 * should be performed using its semantic. E.g., for a fact
	 * like <i>isFather(Max,Jens)</i> the function <i>isFather</i> is performed
	 * for the objects (constant terms) <i>Max</i> and <i>Jens</i> and the result (e.g., <i>true</i>) is returned.
	 * Note that not all predicates support this (since not all predicates
	 * have a semantic), and the operation can also fail
	 * in case one term is variable. In these cases we throw appropriate exceptions.
	 *
	 * @param session a session object
	 * @return the result of resolving the term
	 * @throws UnsupportedOperationException
	 * @throws IllegalArgumentException
	 */
	public Object resolve(Session session) throws UnsupportedOperationException, IllegalArgumentException {
		return predicate.perform(terms, session);
	}

	/**
	 * Set a container.
	 *
	 * @param aContainerthe new container
	 */
	public void setContainer(ClauseSet aContainer) {
		container = aContainer;
	}

	/**
	 * Set a new predicate.
	 *
	 * @param p the predicate
	 */
	public void setPredicate(Predicate p) {
		Predicate oldPredicate = predicate;
		predicate = p;
		terms = new Term[p.getStructure().length];
		types = p.getStructure();
		if ((oldPredicate == null)
				? p != null
				: !(oldPredicate.equals(p))) {
			fireClauseSetChangeEvent(new ClauseSetChangeEvent(this,
					oldPredicate, p, ClauseSetChangeEvent.KEY_CHANGED));
		}
	}

	/**
	 * Get the facts.
	 *
	 * @return an array of facts
	 * @see org.mandarax.kernel.Query
	 */
	public Fact[] getFacts() {
		return facts;
	}

	/**
	 * Set the facts. Not supported by facts, throws an exception!
	 * Please use QueryImpl for queries!
	 *
	 * @param facts an array of facts
	 * @see org.mandarax.kernel.Query
	 */
	public void setFacts(Fact[] facts) {
		throw new UnsupportedOperationException("FactImpl does not support setFacts(). Please use QueryImpl for queries!");
	}

	/**
	 * Get the common name of this query, i.e., something like "Find the oncle of a person".
	 *
	 * @return a string
	 */
	public String getName() {
		return name;
	}

	/**
	 * Set the common name of this query.
	 *
	 * @param a name
	 */
	public void setName(String queryName) {
		name = queryName;
	}

	/**
	 * Indicates whether the fact is negated (negation as failure)
	 *
	 * @return always false
	 * @see org.mandarax.reference.PrerequisiteImpl
	 */
	public boolean isNegatedAF() {
		return false;
	}

	/**
	 * Set a property.
	 *
	 * @param key   the key
	 * @param value the value
	 * @return the previous value of the specified key in this property list, or null if it did not have one.
	 */
	public Object setProperty(String key, String value) {
		if (delegate == null) delegate = new Properties();
		return delegate.setProperty(key, value);
	}

	/**
	 * Get a property.
	 *
	 * @param key the property key
	 * @return the respective value. The method returns null if the property is not found.
	 */
	public String getProperty(String key) {
		if (delegate == null) delegate = new Properties();
		return delegate.getProperty(key);
	}

	/**
	 * Returns an enumeration of all the keys in this property list, including distinct
	 * keys in the default property list if a key of the same name has not already been
	 * found from the main properties list.
	 *
	 * @return an enumeration of all the keys in this property list, including the keys in
	 *         the default property list
	 */
	public Enumeration propertyNames() {
		if (delegate == null) delegate = new Properties();
		return delegate.propertyNames();
	}

	/**
	 * Remove a property.
	 *
	 * @param key the property key
	 * @return the value to which the key had been mapped, or null if the key did not have a mapping.
	 */
	public Object removeProperty(String key) {
		if (delegate != null) return delegate.remove(key);
		return null;
	}

	/**
	 * Get the properties as one "properties" instance.
	 *
	 * @return a properties instance
	 */
	public Properties getProperties() {
		if (delegate == null) delegate = new Properties();
		return delegate;
	}

	/**
	 * Set the properties. Not required by the interface, but useful for bean (introspection-) based
	 * tools.
	 *
	 * @param properties the properties
	 */
	public void setProperties(Properties properties) {
		delegate = properties;
	}

	/**
	 * Get an iterator iterating over the predicates contained in this clause set.
	 *
	 * @return an iterator
	 */
	public Iterator predicates() {
		return new SingletonIterator(predicate);
	}

	/**
	 * Get the registered derivation event listeners.
	 *
	 * @return an array of listeners
	 */
	public DerivationEventListener[] getDerivationEventListeners() {
		throw new UnsupportedOperationException("Please use QueryImpl instead");
	}

	/**
	 * Set the derivation event listeners.
	 *
	 * @param listeners listeners
	 */
	public void setDerivationEventListeners(DerivationEventListener[] listeners) {
		throw new UnsupportedOperationException("Please use QueryImpl instead");
	}

	public Fact applyToFactMinimally(Collection r, Map complexReplacements) {
		// TODO Auto-generated method stub
		return null;
	}
}
