/*
 * Copyright (C) 1999-2004 <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
 */
package org.mandarax.kernel.meta;


import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Iterator;
import java.util.Vector;

import org.apache.commons.collections.iterators.SingletonIterator;
import org.mandarax.kernel.AbstractPropertiesSupport;
import org.mandarax.kernel.CachedClauseSet;
import org.mandarax.kernel.Clause;
import org.mandarax.kernel.ClauseSetChangeEvent;
import org.mandarax.kernel.ClauseSetChangeListener;
import org.mandarax.kernel.ConstantTerm;
import org.mandarax.kernel.Fact;
import org.mandarax.kernel.LogicFactory;

/**
 * Abstract clause set based on the reflect mechanism. The idea is to consider java methods with a boolean
 * return type as predicates and to analyse objects in order to build facts for tupels of objects
 * where the invokation of the method with these obects as parameter yields true.
 * Note that there is a boolean property to enable a cache mechanism. If the cache is switched off
 * (the default), any call to <code>clauses()</code> builds a new clause iterator and therefore may
 * perform a new fetch operation. Otherwise the result
 * is stored and the clause set will not note further changes in the object model.
 * There is also a flag to negate the clause set. If this flag is false (the default), a fact
 * is built from the objects where invokation of the method (the JPredicate) returns <code>true</code>.
 * If the set is negated, then a fact is built from objects where the invokation returns <code>false</code>;
 * @author <A HREF="mailto:mandarax@jbdietrich.com">Jens Dietrich</A>
 * @version 3.3.1 <9 August 2004>
 * @since 1.0
 * @todo replace clauses(Clauses,Object) ! parameters are only passed with the constructor in the subclass!!
 * @version 3.3.1 <9 August 2004>
 */
abstract class AbstractClauseSet extends AbstractPropertiesSupport implements CachedClauseSet {

    protected boolean          cacheEnabled            = false;
    protected Method           method                  = null;
    protected JPredicate       predicate               = null;
    protected Collection       cache                   = null;
    protected boolean          negated                 = false;
    protected transient Vector clauseSetChangeListener = new Vector ();

    /**
     * Constructor.
     */
    protected AbstractClauseSet() {
        super ();
    }

    /**
     * Constructor.
     * @param isNegated true if the set should be negated, false otherwise
     */
    protected AbstractClauseSet(boolean isNegated) {
        this ();

        negated = isNegated;
    }

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

    /**
     * Build a fact.
     * @return a fact
     * @param target the object receiving the metod call
     * @param parameters the parameters of the method call
     */
    protected Clause buildFact(Object target, Object[] parameters) {
        try {
            LogicFactory logicFactory = LogicFactory.getDefaultFactory ();
            Object       result       = method.invoke (target, parameters);

            if(((Boolean) result).booleanValue () != negated) {

                // build terms
                ConstantTerm[] terms =
                    new ConstantTerm[parameters.length + 1];

                terms[0] = logicFactory.createConstantTerm (target);

                for(int i = 0; i < parameters.length; i++) {
                    terms[i + 1] =
                        logicFactory.createConstantTerm (parameters[i]);
                }

                // build and return the fact
                Fact aFact = logicFactory.createFact (predicate, terms);

                return aFact;
            }
        } catch(Throwable t) {
            System.err.println ("Error building fact: " + t.getMessage ());
        }

        return null;
    }

    /**
     * Build the collection of facts.
     * @return a collection of facts
     */
    protected abstract Collection buildFacts();

    /**
     * Check the method.
     * @param aMethod a method
     * @throws java.lang.IllegalAccessException Thrown if the return type of the method is not boolean or if the number of parameters does not equal 1.
     * @throws java.lang.IllegalArgumentException Thrown if the method is not public.
     */
    protected void checkMethod(Method aMethod)
            throws NullPointerException, IllegalAccessException,
                   IllegalArgumentException {
        if(aMethod.getParameterTypes ().length != 1) {
            throw new IllegalArgumentException (
                "The number of parameter sets does not fit");
        }

        if( !Modifier.isPublic (aMethod.getModifiers ())) {
            throw new IllegalAccessException ("Public method expected");
        }

        if(aMethod.getReturnType () != Boolean.TYPE) {
            throw new IllegalArgumentException (
                "Method returning a boolean expected");
        }
    }

    /**
     * Get an iterator for clauses.
     * @return a clause iterator
     */
    public org.mandarax.util.ClauseIterator clauses() {
        Collection coll = null;

        if(isCacheEnabled ()) {
            if(cache == null) {
                cache = buildFacts ();
            }

            coll = cache;
        } else {
            coll = buildFacts ();
        }

        return new org.mandarax.util.SingleClauseSetIterator (coll);
    }

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

    /**
     * Fire a clause set change event
     * @param e an event
     */
    protected void fireClauseSetChangeEvent(ClauseSetChangeEvent e) {
        ClauseSetChangeListener l;

        for(Iterator it =
                clauseSetChangeListener.iterator (); it.hasNext (); ) {
            l = (ClauseSetChangeListener) it.next ();

            l.clauseSetChanged (e);
        }
    }

    /**
     * Get a key for indexing. Here this is the predicate shared by all clauses in the set.
     * @return the key object
     */
    public Object getKey() {
        return predicate;
    }

    /**
     * Validate the parameters provided to set up the set.
     * @param aMethod the method to be evaluated
     * @throws java.lang.IllegalAccessException Thrown if the method is not public.
     * @throws java.lang.IllegalArgumentException Thrown if the return type of the method is not boolean or if the number of argument sets does not match.
     * @throws java.lang.NullPointerException Thrown if one of the arguments is null.
     */
    protected void initialize(Method aMethod)
            throws NullPointerException, IllegalAccessException,
                   IllegalArgumentException {
        checkMethod (aMethod);

        method    = aMethod;
        predicate = new JPredicate (aMethod, negated);
    }

    /**
     * Validate the parameters provided to set up the set.
     * @param aPredicate the JPredicate
     * @throws java.lang.IllegalAccessException Thrown if the underlying method is not public.
     * @throws java.lang.IllegalArgumentException Thrown if the return type of the underlying method is not boolean or if the number of argument sets does not match.
     * @throws java.lang.NullPointerException Thrown if one of the arguments is null.
     */
    protected void initialize(JPredicate aPredicate)
            throws NullPointerException, IllegalAccessException,
                   IllegalArgumentException {
        checkMethod (aPredicate.getMethod ());

        method    = aPredicate.getMethod ();
        predicate = aPredicate;
    }

    /**
     * Indicates whether the cache is enabled.
     * @return true if the cache is enabled, false otherwise
     */
    public boolean isCacheEnabled() {
        return cacheEnabled;
    }

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

    /**
     * Reset the set. In particular, this includes cleaning the cache.
     */
    public void reset() {
        cache = null;
    }

    /**
     * Enable / disable the cache property.
     * @param flag true if the cache should be enabled and false if the cache should be disabled
     */
    public void setCacheEnabled(boolean flag) {
        cacheEnabled = flag;
    }

    /**
     * Convert the object to a string.
     * @return the string representation of this object
     */
    public String toString() {
        return predicate.toString () + " - facts";
    }
	/**
	 * Get an iterator iterating over the predicates contained in this clause set.
	 * @return an iterator
	 */
	public Iterator predicates() {
		return new SingletonIterator(predicate);
	}
}
