package nz.util;

import java.util.*;

/**
 * This Collection class provides a Set interface implemented
 * by a data structure called a Trie.  As such, it is
 * efficient for certain set operations, like contains().  
 * <b>Note that this implementation is not synchronized.</b> If multiple
 * threads access this set concurrently, and at least one of the threads
 * modifies the set, you <b>must</b> supply external
 * synchronization.  Otherwise, the internal data structures
 * almost certainly will be become corrupted.
 * <p>
 * Because the trie data structure is wholly dependent
 * on the nature of strings, this Collection stores all 
 * objects as their String representation; in other words,
 * you can put any kind of object you want to into this 
 * collection, but you can only get Strings out of it 
 * (insertion is done using the toString() operator.)  The
 * most efficient operation of this class can be obtained
 * when you put char[] array objects into it.  Note that
 * when you put a char[] array into the set, you still get
 * a String out.
 * <p>
 * The TrieStringSet Iterator does not support the remove
 * operation, however it is safe to remove the item just
 * returned by the iterator or any previous item.  If you
 * the next item to be iterated over, then the iterator
 * will return it anyway.  See the description of the clear()
 * method for more remarks about mixing operations.
 * <p>
 * Note that the Trie is naturally a sorted data structure,
 * although it always sorts in numerical order of its
 * character values, which is almost always not the same
 * as dictionary order.  That is why this class implements
 * Set rather than SortedSet.  However, you can count on
 * this numerical-sort behavior.  The iterator will ALWAYS
 * deliver results in this order, shorter strings first, etc.
 * <p>
 * TrieStringSet is fully serializable, although the internal
 * structure of the set practically guarantees that the
 * serialized output will be quite large, and that reading
 * the results will be slow.  It is almost always faster to
 * get all the strings out with toArray(), serialize that to
 * a file, and later load the strings back into a 
 * TrieStringSet.
 * <p>
 * While the Trie data structure is slower than a good tree
 * or hash table for just about everything, and uses more
 * memory, it can do some operations very quickly.  For
 * example, in heavily query-oriented applications, the
 * TrieStringSet outperforms the Java TreeSet and approaches
 * the performance of the Java HashSet; it can even exceed
 * the performance of HashSet in applications where char[]
 * arrays are used instead of Strings.  Also,
 * the trie can do some useful string set operations efficiently
 * that the other structures cannot do at all: prefix checking.
 * In this class, the operation to check whether the given
 * string is a prefix of any string in the set is implemented
 * by the containsPrefix() method.
 * <p>
 * Note that there are a lot more things that trie data
 * structures can do well, under the right circumstances.
 * In this particular case, the TrieStringSet is kept slow
 * by the requirement to handle unicode characters in a
 * reasonable way.
 * <p>
 * The guts of a trie use an array to hold things.  Since
 * the Java character set is unicode, and thus quite large,
 * a simple sparse array implementation is used for this
 * purpose.  Note that the current implementation will
 * be more wasteful for strings composed mostly of
 * characters in upper unicode pages.  Of course, it is
 * pretty darn wasteful now, actually.
 * <p>
 *
 * @see java.util.Set
 * @see nom.ziring.util.PagedArray
 * @author Neal Ziring, 2/2000
 */

public class TrieStringSet 
    extends java.util.AbstractSet 
    implements java.io.Serializable
{
    /* this value seems to give good performance for ASCII strings */
    private static final int PA_SIZE = 32;

    class TSSPagedArray extends PagedArray implements java.io.Serializable {
	boolean leaf;
	TSSPagedArray(int p, boolean l) {
	    super(65536, p, 0);
	    leaf = l;
	}
    }
    private final TSSPagedArray EMPTYLEAF = new TSSPagedArray(PA_SIZE, true);

    private int setSize = 0;
    private TSSPagedArray root;

    /**
     * Return the number of (unique) Strings in this TrieStringSet.
     * This operation is very cheap, because a count is kept during
     * insertion, so that this method can simply return the value
     * of an internal counter.
     */
    public int size() { return setSize; }

    /**
     * Create a new, empty TrieStringSet.
     */
    public TrieStringSet() {
	super();
	setSize = 0;
	root = new TSSPagedArray(PA_SIZE,false);
    }

    /**
     * Create a new TrieStringSet that contains all the
     * elements from an array.  For adding from any other
     * kind of collection, use the addAll(Collection c) method.
     *
     * @param list An array of String values
     */
    public TrieStringSet(String [] list) {
	this();
	for(int i = 0; i < list.length; i++) {
	    if (list[i] != null) add(list[i]);
	}
    }

    /**
     * Add a string or char array to this TrieStringSet.  This 
     * function will throw IllegalArgumentException of the object
     * o is null.
     *
     * @param o String object to be added to the set.
     */
    public boolean add(Object o) {
	if (o == null) throw new IllegalArgumentException("Cannot add null");
	if (o instanceof char[]) {
	    char [] a = (char [])o;
	    return addInternal(root, a, 0, a.length); 
	}
	else {
	    String s = o.toString();
	    return addInternal(root, s, 0, s.length());
	}
    }

    protected boolean addInternal(TSSPagedArray p, String s, int c, int maxC)
    {
	if (c == maxC) { 
	    if (p.leaf == false) {
		setSize++;
		p.leaf = true; 
		return true;
	    }
	    else return false;
	}
	TSSPagedArray p2;
	int ch = (int)(s.charAt(c));
	p2 = (TSSPagedArray)(p.get(ch));
	if (p2 == null) {
	    if (c == (maxC - 1)) {
		setSize += 1;
		p.put(ch, EMPTYLEAF);
		return true;
	    }
	    else {
		p2 = new TSSPagedArray(PA_SIZE,false);
		p.put(ch, p2);
	    }
	}
	if (p2 == EMPTYLEAF) {
	    if (c == (maxC - 1)) return false;  /* string already in set! */
	    p2 = new TSSPagedArray(PA_SIZE,true);
	    p.put(ch,p2);
	}
	return addInternal(p2, s, c + 1, maxC);
    }

    protected boolean addInternal(TSSPagedArray p, char [] a, int c, int maxC)
    {
	if (c == maxC) { 
	    if (p.leaf == false) {
		setSize++;
		p.leaf = true; 
		return true;
	    }
	    else return false;
	}
	TSSPagedArray p2;
	int ch = (int)(a[c]);
	p2 = (TSSPagedArray)(p.get(ch));
	if (p2 == null) {
	    if (c == (maxC - 1)) {
		setSize += 1;
		p.put(ch, EMPTYLEAF);
		return true;
	    }
	    else {
		p2 = new TSSPagedArray(PA_SIZE,false);
		p.put(ch, p2);
	    }
	}
	if (p2 == EMPTYLEAF) {
	    if (c == (maxC - 1)) return false;  /* string already in set! */
	    p2 = new TSSPagedArray(PA_SIZE,true);
	    p.put(ch,p2);
	}
	return addInternal(p2, a, c + 1, maxC);
    }

    
    /**
     * Clear out this TrieStringSet.  After this call,
     * the set will be empty.  All outstanding iterators
     * will still work, but will iterate over the
     * old elements, not any new ones that are added after
     * this operation.
     */
    public void clear() {
	setSize = 0;
	root = new TSSPagedArray(PA_SIZE,false);
    }

    /**
     * Check whether this TrieStringSet contains a particular
     * element.  This operation is pretty fast; it is linear
     * in the length of the string (it is not affected by the
     * number of elements added to the set).  The input parameter
     * can be a String or a char array (char[]), or anything
     * that delivers a reliable toString() value.  
     * 
     * @param o String or char[] object that might be in this set
     */
    public boolean contains(Object o) {
	TSSPagedArray p, p2;
	int ch, c, maxC;
	if (o instanceof char[] ) {
	    char [] a = (char [])o;
	    for(p = root, c = 0, maxC = a.length; p != null && c < maxC; p = p2, c++) {
		ch = (int)(a[c]);
		p2 = (TSSPagedArray)(p.get(ch));
	    }
	}
	else {
	    String s = o.toString();
	    for(p = root, c = 0, maxC = s.length(); p != null && c < maxC; p = p2, c++) {
		ch = (int)(s.charAt(c));
		p2 = (TSSPagedArray)(p.get(ch));
	    }
	}
	return ((p==null)?(false):(p.leaf));
    }


    /**
     * Check whether this TrieStringSet contains a particular
     * element.  This operation is pretty fast; it is linear
     * in the length of the string (it is not affected by the
     * number of elements added to the set). 
     *
     * @param a A char array
     * @param n How many elements of the char array to use, positive
     */
    public boolean contains(char [] a, int n) {
	if (a == null || n < 1) throw new IllegalArgumentException("Bad input");
	TSSPagedArray p = internalContains(root, a, 0, n);
	return ((p==null)?(false):(p.leaf));
    }

    /**
     * Check whether the supplied string or char array is in the set,
     * or is a prefix of any string in the set.  This operation is
     * just as fast as normal contains(), and is one thing that
     * the TrieStringSet can do well that other Set collection
     * classes cannot.
     * <p>
     * Example: if the set contained the strings "keyboard",
     * "mouse", and "monitor", then containsPrefix("key") would
     * return true.
     * 
     * @param o String or char[] prefix to look for in the set
     */
    public boolean containsPrefix(Object o) {
	TSSPagedArray p;
	if (o instanceof char[] ) {
	    char [] a = (char [])o;
	    p = internalContains(root, a, 0, a.length);
	}
	else {
	    String s = o.toString();
	    p = internalContains(root, s, 0, s.length());
	}
	return (p!=null);
    }

    protected TSSPagedArray internalContains(TSSPagedArray p, String s, int c, int maxC)
    {
	int ch;
	TSSPagedArray p2;
	for( ; p != null && c < maxC; p = p2, c++) {
	    ch = (int)(s.charAt(c));
	    p2 = (TSSPagedArray)(p.get(ch));
	}
	return p;
    }



    protected TSSPagedArray internalContains(TSSPagedArray p, char []s, int c, int maxC)
    {
	int ch;
	TSSPagedArray p2;
	for( ; p != null && c < maxC; p = p2, c++) {
	    ch = (int)(s[c]);
	    p2 = (TSSPagedArray)(p.get(ch));
	}
	return p;
    }


    /**
     * The toArray(Object []a) method is not supported.
     * Use the toArray() method instead.
     *
     * @throws ArrayStoreException always
     */
    public Object[] toArray(Object [] a) {
	throw new ArrayStoreException("Unsupported operation");
    }

    /**
     * Remove an element from the TrieStringSet.
     * This operation is fairly fast, but memory
     * that might have been used by deleted items
     * is never reclaimed until the entire set is
     * empty.  To clear all the items quickly, use
     * clear().
     * 
     * @param o An object purportedly in this set
     */
    public boolean remove(Object o) {
	boolean ret = false;
	String s = o.toString();
	if (s.length() == 0) { 
	    ret = root.leaf;
	    if (root.leaf) setSize--;
	    root.leaf = false;
	}
	else {
	    TSSPagedArray p = internalContains(root, s, 0, s.length() - 1);
	    if (p != null) {
		int ch = (int)(s.charAt(s.length() - 1));
		TSSPagedArray p2 = (TSSPagedArray)(p.get(ch));
		if (p2 == EMPTYLEAF) {
		    p.put(ch, null);
		    setSize--;
		    ret = true;
		}
		else {
		    ret = p2.leaf;
		    if (p2.leaf) setSize--;
		    p2.leaf = false;
		}
	    }
	}
	return ret;
    }

    
	

    class TrieIterator implements Iterator {
	class TISItem {
	    public TISItem(TSSPagedArray p0, int n0, String s0) {
		p = p0; n = n0; s = s0;
	    }
	    TSSPagedArray p;
	    int           n; /* -1 mean this node, not an index under it */
	    String        s;
	}

	String nextret = null;
	TISItem  current;
	Stack    itemStack;

	TrieIterator(TSSPagedArray r) {
	    current = new TISItem(r, -1, "");
	    itemStack = new Stack();
	    nextret = findFirstUnder();
	}

	public void remove() {
	    throw new UnsupportedOperationException("Remove not supported");
	}

	public boolean hasNext() {
	    return (nextret != null);
	}

	public Object next() {
	    if (nextret == null) throw new NoSuchElementException("End of set");
	    Object ret = nextret;
	    nextret = findFirstUnder();
	    return ret;
	}
		
	private String findFirstUnder() {
	    String ret = null;
	    while(ret == null) {
		if (current.n == -1) {
		    if  (current.p.leaf) {
			ret = current.s;
		    }
		    current.n++;
		}
		else {
		    int i = current.n;
		    TSSPagedArray ntx = (TSSPagedArray)(current.p.get(i));
		    if (ntx == null) {
			i = current.p.next(i);
		    }
		    if (i < 0) {
			// pop
			if (itemStack.isEmpty()) {
			    // then we are done
			    break;
			}
			else {
			    current = (TISItem)(itemStack.pop());
			    current.n += 1;
			}
		    }
		    else {
			// push
			current.n = i;
			itemStack.push(current);
			current = new TISItem((TSSPagedArray)(current.p.get(i)), 
					      -1, current.s + (char)(i));
		    }
		}
	    }
	    return ret;
	}
    }
	
    /**
     * Create and return a new Iterator for this TrieStringSet.
     * The Iterator that this returns does not support the remove()
     * operation, and its next() operation will always return a
     * String.
     */
    public Iterator iterator() {
	return new TrieIterator(root);
    }
}


