package org.mandarax.reference;

/*
 * 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
 */
import java.util.*;
import org.mandarax.kernel.*;

/**
 * Default implementation of a knowledge base managing clause sets
 * in anordered container (like a list). Moving clause sets up and down
 * (assigning higher/lower priority) is supported here.
 * <br>
 * This class has been completely redesigned in version 2.1
 * @author <A HREF="mailto:mandarax@jbdietrich.com">Jens Dietrich</A>
 * @version 3.3.1 <9 August 2004>
 * @since 1.2
 */
public final class AdvancedKnowledgeBase extends AbstractKnowledgeBase implements ExtendedKnowledgeBase {

	// the container. change this to a sorted collection !
	private java.util.Vector container = new java.util.Vector();

	// indexed container
	private java.util.Hashtable indexed = new java.util.Hashtable();

	// the feature descriptor
	private static KnowledgeBaseFeatureDescriptions featureDescriptions = null;

	// the comparator
	private Comparator comparator = null;


	/**
	 * Constructor.
	 */
	public AdvancedKnowledgeBase() {
		super();
	}
	/**
     * Set a comperator.
     * @param comp a comparator
     */
    public synchronized void setComparator(java.util.Comparator comp) {
    	comparator = comp;
    	
    	// apply new sorting
    	resort();
    }
    /**
     * Resort all entries. Does nothing if the comparator is null.
     */
    public synchronized void resort() {
     	if (comparator!=null) {   	
	    	for (Iterator keys = getKeys().iterator();keys.hasNext();) {
	    		resort(keys.next());
	    	}
    	}   
    }
    /**
     * Resort all entries for a particular key. Does nothing if the comparator is null.
     * @param key a key (a predicate)
     */
    public void resort(Object key){
    	if (comparator!=null) {
 			List clauseSets = (List)indexed.get(key);
			Collections.sort(clauseSets,comparator);   
    	}
    }
    /**
     * Get the comperator.
     * @param comp a comparator
     */
    public synchronized Comparator getComparator() {
    	return comparator;
    }
    /**
     * Indicates whether the (manual) move operations are enabled.
     * @return a boolean
     */
    public synchronized boolean isMoveEnabled() {
    	return comparator==null;
    }
    
	/**
	 * Add a clause set.
	 * @param c org.mandarax.kernel.ClauseSet
	 */
	public synchronized void add(ClauseSet c) {
		int position = container.size();
		Object[] keys = getKeys(c);
		container.add(c);

		for (int i = 0; i < keys.length; i++) {
			List list = getOrAddIndexed(keys[i]);
			list.add(c);
			// resort !!
			// note that we can not take a SortedSet - we want to be able to store 
			// two clause sets which are equal !
			resort(keys[i]);
		}

		c.addClauseSetChangeListener(this);
		fireKnowledgeBaseChanged(KnowledgeBaseChangeEvent.CLAUSE_ADDED, c);
	}
	/**
	 * Iterate over all clauses
	 * @return org.mandarax.util.ClauseIterator
	 */
	public synchronized org.mandarax.util.ClauseIterator clauses() {
		return new org.mandarax.util.MultipleClauseSetIterator(getClauseSets());
	}
	/**
	 * Iterate over all clauses found for a certain key.
	 * @return org.mandarax.util.ClauseIterator
	 * @param query org.mandarax.kernel.Clause
	 * @param additionalParameter java.lang.Object
	 */
	public synchronized org.mandarax.util.ClauseIterator clauses(Clause query, Object additionalParameter) {
		Object key = query.getKey();

		if (key instanceof org.mandarax.kernel.Predicate) {
			List list = new ArrayList(getOrAddIndexed(key));			
			return new org.mandarax.util.MultipleClauseSetIterator(list, query, additionalParameter);
		}
		else {
			return new org.mandarax.util.MultipleClauseSetIterator(new ArrayList(0));
		}
	}
	/**
	 * Handle a clause set change event.
	 * @param e org.mandarax.kernel.ClauseSetChangeEvent
	 */
	public void clauseSetChanged(ClauseSetChangeEvent e) {

		// if the predicate has changed, register changed clause
		if ((e.getType() == ClauseSetChangeEvent.KEY_CHANGED) && (e.getSource() instanceof ClauseSet)) {
			ClauseSet cs = (ClauseSet) e.getSource();

			remove(cs, getKeyArray(e.getOldValue()));
			add(cs);
		}
	}
	/**
	 * Dump the content. For testing only!
	 */
	public void dump() {
		System.out.println("---------------------------------");
		System.out.println("Dumping container ");

		for (Iterator it = container.iterator(); it.hasNext();) {
			System.out.println(it.next());
		}

		System.out.println("");

		for (Enumeration en = indexed.keys(); en.hasMoreElements();) {
			Object key = en.nextElement();

			System.out.println("KEY IS : " + key.toString());

			for (Iterator it = getOrAddIndexed(key).iterator(); it.hasNext();) {
				System.out.println(it.next());
			}
		}
	}
	/**
	 * Get a list containing all clause sets.
	 * Note that the collection returned is a read only copy of the
	 * internal collection of clause sets.
	 * @return java.util.List
	 */
	public synchronized java.util.List getClauseSets() {
		List list = new ArrayList();
		for (Iterator iter = getKeys().iterator();iter.hasNext();) {
			Object key = iter.next();
			list.addAll(getOrAddIndexed(key));
		}
		return list;
	}

	/**
	 * Get a list containing all clause sets that have the key.
	 * New in 2.2 - return copy of the original list.
	 * @param the key object
	 * @return a list containing all clause sets
	 */
	public synchronized java.util.List getClauseSets(Object key) {
		List list = new ArrayList();
		list.addAll(getOrAddIndexed(key));
		return list;
	}
	/**
	 * Get the feature descriptions.
	 * @return org.mandarax.kernel.KnowledgeBaseFeatureDescriptions
	 */
	public KnowledgeBaseFeatureDescriptions getFeatureDescriptions() {
		if (featureDescriptions == null) {
			featureDescriptions = new KnowledgeBaseFeatureDescriptions() {
				protected void initialize() {
					super.initialize();
					this.supported.add(KnowledgeBaseFeatureDescriptions.AUTO_PRIORITIES);
				}
			};
		}

		return featureDescriptions;
	}
	/**
	 * If the object is an array, return it. otherwise
	 * wrap it by an one element array..
	 * @return an array of keys
	 * @param an object
	 */
	private Object[] getKeyArray(Object obj) {
		if (obj != null) {
			if (obj.getClass().isArray()) {
				return (Object[]) obj;
			}
			else {
				Object[] keys = new Object[1];
				keys[0] = obj;
				return keys;
			}
		}
		else {
			return new Object[0];
		}
	}
	/**
	 * Get the keys.
	 * @return the keys
	 */
	public java.util.Collection getKeys() {
		return indexed.keySet();
	}
	/**
	 * Retrieve the keys for a clause set.
	 * @return a array of objects (predicates)
	 * @param cs a clause set
	 */
	private Object[] getKeys(ClauseSet cs) {
		return getKeyArray(cs.getKey());
	}
	/**
	 * Either get or add an indexed container for the clause c.
	 * @return a container 
	 * @param predicate java.lang.Object
	 */
	private List getOrAddIndexed(Object k) {
		List v = (List) indexed.get(k);
		if (v == null) {
			v = new ArrayList();
			indexed.put(k, v);
		}
		return v;
	}
	/**
	 * Move a clause set down. (= assign a lower priority)
	 * @param cs org.mandarax.kernel.ClauseSet
	 */
	public synchronized void moveDown(ClauseSet cs) {
		
		if (this.isMoveEnabled()) {
			Object[] keys = getKeys(cs);
			List list = null;
			int position = -1;
			for (int i = 0; i < keys.length; i++) {
				list = (List)getOrAddIndexed(keys[i]);
				position = list.indexOf(cs);
				if (position<(list.size()-1)) {
					list.remove(position);
					list.add(position+1,cs);								
				}
			}
		}
		else LOG_KB_MOVE.warn("Move operation disabled - clauses are arranged by comparator");
	}
	/**
	 * Move a clause set to the bottom. (= assign the lowest priority)
	 * @param cs org.mandarax.kernel.ClauseSet
	 */
	public synchronized void moveToBottom(ClauseSet cs) {
		if (this.isMoveEnabled()) {
			Object[] keys = getKeys(cs);
			List list = null;
			int position = -1;
			for (int i = 0; i < keys.length; i++) {
				list = (List)getOrAddIndexed(keys[i]);
				position = list.indexOf(cs);
				if (position<(list.size()-1)) {
					list.remove(position);
					list.add(cs);								
				}
			}
		}
		else LOG_KB_MOVE.warn("Move operation disabled - clauses are arranged by comparator");
	}
	/**
	 * Move a clause set to the top. (= assign the highest priority)
	 * @param cs org.mandarax.kernel.ClauseSet
	 */
	public synchronized void moveToTop(ClauseSet cs) {
		if (this.isMoveEnabled()) {
			Object[] keys = getKeys(cs);
			List list = null;
			int position = -1;
			for (int i = 0; i < keys.length; i++) {
				list = (List)getOrAddIndexed(keys[i]);
				position = list.indexOf(cs);
				if (position>0) {
					list.remove(position);
					list.add(0,cs);								
				}
			}
		}
		else LOG_KB_MOVE.warn("Move operation disabled - clauses are arranged by comparator");			
	}
	/**
	 * Move a clause set up. (= assign a higher priority)
	 * @param cs org.mandarax.kernel.ClauseSet
	 */
	public synchronized void moveUp(ClauseSet cs) {
		if (this.isMoveEnabled()) {
			Object[] keys = getKeys(cs);
			List list = null;
			int position = -1;
			for (int i = 0; i < keys.length; i++) {
				list = (List)getOrAddIndexed(keys[i]);
				position = list.indexOf(cs);
				if (position>0) {
					list.remove(position);
					list.add(position-1,cs);								
				}
			}
		}
		else LOG_KB_MOVE.warn("Move operation disabled - clauses are arranged by comparator");			
	}
	/**
	 * Remove a clause set.
	 * @return boolean - indicating whether the object has been found (true) or not (false)
	 * @param c org.mandarax.kernel.ClauseSet
	 */
	public synchronized boolean remove(ClauseSet c) {
		boolean result = remove(c, getKeys(c));
		fireKnowledgeBaseChanged(KnowledgeBaseChangeEvent.CLAUSE_REMOVED, c);
		return result;
	}
	/**
	 * Remove a clause set.
	 * @return boolean - indicating whether the object has been found (true) or not (false)
	 * @param c org.mandarax.kernel.ClauseSet
	 */
	private boolean remove(ClauseSet c, Object keys[]) {
		boolean success = container.remove(c);
		for (int i = 0; i < keys.length; i++) {
			Collection clauses = getOrAddIndexed(keys[i]);
			success = success && clauses.remove(c);
		}
		// unregister listener
		c.removeClauseSetChangeListener(this);

		return success;
	}
	/**
	 * Remove all clauses.
	 */
	public synchronized void removeAll() {
		indexed = new Hashtable();		
		// remove all listeners
		for (Iterator it = container.iterator(); it.hasNext();) {
			((ClauseSet) it.next()).removeClauseSetChangeListener(this);
		}
		container = new Vector();
	}

}
