package com.limegroup.gnutella.util;

import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Comparator;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedMap;

/**
 * A PATRICIA Trie.
 * 
 * PATRICIA = Practical Algorithm to Retrieve Information Coded in Alphanumeric
 * 
 * A PATRICIA Trie is a compressed Trie.  Instead of storing all data at the
 * edges of the Trie (and having empty internal nodes), PATRICIA stores data
 * in every node.  This allows for very efficient traversal, insert, delete,
 * predecessor, successor, prefix, range, and 'select' operations.  All operations
 * are performed at worst in O(K) time, where K is the number of bits in the
 * largest item in the tree.  In practice, operations actually take O(A(K))
 * time, where A(K) is the average number of bits of all items in the tree.
 * 
 * Most importantly, PATRICIA requires very few comparisons to keys while
 * doing any operation.  While performing a lookup, each comparison
 * (at most K of them, described above) will perform a single bit comparison
 * against the given key, instead of comparing the entire key to another key.
 * 
 * The Trie can return operations in lexographical order using the 'traverse',
 * 'prefix', 'submap', or 'iterator' methods.  The Trie can also scan for items
 * that are 'bitwise' (using an XOR metric) by the 'select' method.  Bitwise
 * closeness is determined by the KeyAnalyzer returning true or false for
 * a bit being set or not in a given key.
 * 
 * This PATRICIA Trie supports both variable length & fixed length keys.
 * Some methods, such as getPrefixedBy(...) are suited only to variable
 * length keys, whereas getPrefixedByBits(...) is suited to fixed-size keys.
 * 
 * See http://www.csse.monash.edu.au/~lloyd/tildeAlgDS/Tree/PATRICIA/ for
 * more information.
 * 
 * Any methods here that take an Object may throw a ClassCastException if
 * the method is expecting an instance of K (and it isn't K).
 * 
 * @author Roger Kapsi
 * @author Sam Berlin
 */
public class PatriciaTrie extends AbstractMap implements SortedMap, Serializable {
    
    private static final long serialVersionUID = 110232526181493307L;

    /** The root element of the Trie. */
    private final TrieEntry root = new TrieEntry(null, null, -1);
    
    /** The current size (total number of elements) of the Trie. */
    private int size = 0;
    
    /** The number of times this has been modified (to fail-fast the iterators). */
    private transient int modCount = 0;
    
    /** The keyAnalyzer used to analyze bit values of keys. */
    private final KeyAnalyzer keyAnalyzer;
    
    /** Constructs a new PatriciaTrie using the given keyAnalyzer. */
    public PatriciaTrie(KeyAnalyzer keyAnalyzer) {
        this.keyAnalyzer = keyAnalyzer;
    }
    
    /** Returns the KeyAnalyzer that constructed the trie. */
    public KeyAnalyzer getKeyAnalyzer() {
        return keyAnalyzer;
    }

    /** Returns the KeyAnalyzer as a comparator. */
    public Comparator comparator() {
        return keyAnalyzer;
    }
    
    /** Clears the Trie (i.e. removes all emelemnts). */
    public void clear() {
        root.key = null;
        root.bitIndex = -1;
        root.value = null;
        
        root.parent = null;
        root.left = root;
        root.right = null;
        root.predecessor = root;
        
        size = 0;
        incrementModCount();
    }
    
    /** Returns true if the Trie is empty */
    public boolean isEmpty() {
        return size == 0;
    }
    
    /** Returns the number items in the Trie */
    public int size() {
        return size;
    }
   
    /** Increments both the size and mod counter. */
    private void incrementSize() {
        size++;
        incrementModCount();
    }
    
    /** Decrements the size and increments the mod counter. */
    private void decrementSize() {
        size--;
        incrementModCount();
    }
    
    /** Increments the mod counter. */
    private void incrementModCount() {
        modCount++;
    }
    
    /**
     * Adds a new <key, value> pair to the Trie and if a pair already
     * exists it will be replaced. In the latter case it will return
     * the old value.
     */
    public Object put(Object key, Object value) {
        if (key == null) {
            throw new NullPointerException("Key cannot be null");
        }
        
        int keyLength = length(key);
        
        // The only place to store a key with a length
        // of zero bits is the root node
        if (keyLength == 0) {
            if (root.isEmpty()) {
                incrementSize();
            } else {
                incrementModCount();
            }
            return root.setKeyValue(key, value);
        }
        
        TrieEntry found = getNearestEntryForKey(key, keyLength);
        if (key.equals(found.key)) {
            if (found.isEmpty()) { // <- must be the root
                incrementSize();
            } else {
                incrementModCount();
            }
            return found.setKeyValue(key, value);
        }
        
        int bitIndex = bitIndex(key, found.key);
        if (isValidBitIndex(bitIndex)) { // in 99.999...9% the case
            /* NEW KEY+VALUE TUPLE */
            TrieEntry t = new TrieEntry(key, value, bitIndex);
            addEntry(t, keyLength);
            incrementSize();
            return null;
        } else if (isNullBitKey(bitIndex)) {
            // A bits of the Key are zero. The only place to
            // store such a Key is the root Node!
            
            /* NULL BIT KEY */
            if (root.isEmpty()) {
                incrementSize();
            } else {
                incrementModCount();
            }
            return root.setKeyValue(key, value);
            
        } else if (isEqualBitKey(bitIndex)) {
            // This is a very special and rare case.
            
            /* REPLACE OLD KEY+VALUE */
            if (found != root) {
                incrementModCount();
                return found.setKeyValue(key, value);
            }
        }
        
        throw new IndexOutOfBoundsException("Failed to put: " + key + " -> " + value + ", " + bitIndex);
    }
    
    /** Adds the given entry into the Trie. */
    private TrieEntry addEntry(TrieEntry toAdd, int keyLength) {
        TrieEntry current = root.left;
        TrieEntry path = root;
        while(true) {
            if(current.bitIndex >= toAdd.bitIndex || current.bitIndex <= path.bitIndex) {
                toAdd.predecessor = toAdd;
                
                if (!isBitSet(toAdd.key, keyLength, toAdd.bitIndex)) {
                    toAdd.left = toAdd;
                    toAdd.right = current;
                } else {
                    toAdd.left = current;
                    toAdd.right = toAdd;
                }
               
                toAdd.parent = path;
                if (current.bitIndex >= toAdd.bitIndex) {
                    current.parent = toAdd;
                }
                
                // if we inserted an uplink, set the predecessor on it
                if(current.bitIndex <= path.bitIndex) {
                    current.predecessor = toAdd;
                }
         
                if (path == root || !isBitSet(toAdd.key, keyLength, path.bitIndex))
                    path.left = toAdd;
                else
                    path.right = toAdd;
                return toAdd;
            }
                
            path = current;
            if(!isBitSet(toAdd.key, keyLength, current.bitIndex))
                current = current.left;
            else
                current = current.right;
        }
    }
    
    public Set entrySet() {
        Set es = entrySet;
        return (es != null ? es : (entrySet = new EntrySet()));
    }
    
    /**
     * Returns the Value whose Key equals our lookup Key
     * or null if no such key exists.
     */
    public Object get(Object k) {
        TrieEntry entry = getEntry(k);
        return entry != null ? entry.getValue() : null;
    }

    /**
     * Returns the entry associated with the specified key in the
     * PatriciaTrie.  Returns null if the map contains no mapping
     * for this key.
     * 
     * This may throw ClassCastException if the object is not of type K.
     */
    TrieEntry getEntry(Object k) {
        Object key = k;
        if(key == null)
            return null;
        
        int keyLength = length(key);
        TrieEntry entry = getNearestEntryForKey(key, keyLength);
        return !entry.isEmpty() && key.equals(entry.key) ? entry : null;
    }
    
    /**
     * Returns the nearest entry for a given key.  This is useful
     * for finding knowing if a given key exists (and finding the value
     * for it), or for inserting the key.
     * 
     * The actual get implementation. This is very similar to
     * selectR but with the exception that it might return the
     * root Entry even if it's empty.
     */
    private TrieEntry getNearestEntryForKey(Object key, int keyLength) {
        TrieEntry current = root.left;
        TrieEntry path = root;
        while(true) {
            if(current.bitIndex <= path.bitIndex)
                return current;
            
            path = current;
            if(!isBitSet(key, keyLength, current.bitIndex))
                current = current.left;
            else
                current = current.right;
        }
    }
    
    /**
     * Returns the Value whose Key has the longest prefix
     * in common with our lookup key.
     */
    public Object select(Object key) {
        int keyLength = length(key);
        TrieEntry[] result = new TrieEntry[1];
        if (!selectR(root.left, -1, key, keyLength, result)) {
            TrieEntry e = result[0];
            return e.getValue();
        }
        return null;
    }
    
    /**
     * This is eqivalent to the other selectR() method but without
     * its overhead because we're selecting only one best matching
     * Entry from the Trie.
     */
    private boolean selectR(TrieEntry h, int bitIndex, 
            final Object key, final int keyLength, final TrieEntry[] result) {
        
        if (h.bitIndex <= bitIndex) {
            // If we hit the root Node and it is empty
            // we have to look for an alternative best
            // mathcing node.
            if (!h.isEmpty()) {
                result[0] = h;
                return false;
            }
            return true;
        }

        if (!isBitSet(key, keyLength, h.bitIndex)) {
            if (selectR(h.left, h.bitIndex, key, keyLength, result)) {
                return selectR(h.right, h.bitIndex, key, keyLength, result);
            }
        } else {
            if (selectR(h.right, h.bitIndex, key, keyLength, result)) {
                return selectR(h.left, h.bitIndex, key, keyLength, result);
            }
        }
        return false;
    }
    
    public Map.Entry select(Object key, Cursor cursor) {
        int keyLength = length(key);
        TrieEntry[] result = new TrieEntry[]{ null };
        selectR(root.left, -1, key, keyLength, cursor, result);
        return result[0];
    }

    private boolean selectR(TrieEntry h, int bitIndex, 
            final Object key, 
            final int keyLength,
            final Cursor cursor,
            final TrieEntry[] result) {

        if (h.bitIndex <= bitIndex) {
            if(!h.isEmpty()) {
                int ret = cursor.select(h);
                switch(ret) {
                case Cursor.REMOVE:
                    throw new UnsupportedOperationException("cannot remove during select");
                case Cursor.EXIT:
                    result[0] = h;
                    return false; // exit
                case Cursor.REMOVE_AND_EXIT:
                    TrieEntry entry = new TrieEntry(h.getKey(), h.getValue(), -1);
                    result[0] = entry;
                    removeEntry(h);
                    return false;
                case Cursor.CONTINUE:
                    // fallthrough.
                }
            }
            return true; // continue
        }

        if (!isBitSet(key, keyLength, h.bitIndex)) {
            if (selectR(h.left, h.bitIndex, key, keyLength, cursor, result)) {
                return selectR(h.right, h.bitIndex, key, keyLength, cursor, result);
            }
        } else {
            if (selectR(h.right, h.bitIndex, key, keyLength, cursor, result)) {
                return selectR(h.left, h.bitIndex, key, keyLength, cursor, result);
            }
        }
        
        return false;
    }

    /**
     * Returns a view of this Trie of all elements that are
     * prefixed by the given key.
     * 
     * In a fixed-keysize Trie, this is essentially a 'get' operation.
     * 
     * For example, if the trie contains 'Lime', 'LimeWire', 
     * 'LimeRadio', 'Lax', 'Later', 'Lake', and 'Lovely', then
     * a lookup of 'Lime' would return 'Lime', 'LimeRadio', and 'LimeWire'.
     * 
     * The view that this returns is optimized to have a very efficient
     * Iterator.  The firstKey, lastKey & size methods must iterate
     * over all possible values in order to determine the results.  This
     * information is cached until the Patricia tree changes.  All other
     * methods (except Iterator) must compare the given key to the prefix
     * to ensure that it is within the range of the view.  The Iterator's
     * remove method must also relocate the subtree that contains the
     * prefixes if the entry holding the subtree is removed or changes.
     * Changing the subtree takes O(K) time.
     * 
     * @param key
     * @return
     */
    public SortedMap getPrefixedBy(Object key) {
        return getPrefixedByBits(key, 0, keyAnalyzer.length(key));
    }

    /**
     * Returns a view of this Trie of all elements that are
     * prefixed by the length of the key.
     * 
     * Fixed-keysize Tries will not support this operation
     * (because all keys will be the same length).
     * 
     * For example, if the trie contains 'Lime', 'LimeWire', 
     * 'LimeRadio', 'Lax', 'Later', 'Lake', and 'Lovely', then
     * a lookup of 'LimePlastics' with a length of 4 would
     * return 'Lime', 'LimeRadio', and 'LimeWire'.
     * 
     * The view that this returns is optimized to have a very efficient
     * Iterator.  The firstKey, lastKey & size methods must iterate
     * over all possible values in order to determine the results.  This
     * information is cached until the Patricia tree changes.  All other
     * methods (except Iterator) must compare the given key to the prefix
     * to ensure that it is within the range of the view.  The Iterator's
     * remove method must also relocate the subtree that contains the
     * prefixes if the entry holding the subtree is removed or changes.
     * Changing the subtree takes O(K) time.
     *  
     * @param key
     * @param length
     * @return
     */
    public SortedMap getPrefixedBy(Object key, int length) {
        return getPrefixedByBits(key, 0, length * keyAnalyzer.bitsPerElement());
    }

    /**
     * Returns a view of this Trie of all elements that are prefixed
     * by the key, starting at the given offset and for the given length.
     * 
     * Fixed-keysize Tries will not support this operation
     * (because all keys are the same length).
     *
     * For example, if the trie contains 'Lime', 'LimeWire', 
     * 'LimeRadio', 'Lax', 'Later', 'Lake', and 'Lovely', then
     * a lookup of 'The Lime Plastics' with an offset of 4 and a 
     * length of 4 would return 'Lime', 'LimeRadio', and 'LimeWire'.
     * 
     * The view that this returns is optimized to have a very efficient
     * Iterator.  The firstKey, lastKey & size methods must iterate
     * over all possible values in order to determine the results.  This
     * information is cached until the Patricia tree changes.  All other
     * methods (except Iterator) must compare the given key to the prefix
     * to ensure that it is within the range of the view.  The Iterator's
     * remove method must also relocate the subtree that contains the
     * prefixes if the entry holding the subtree is removed or changes.
     * Changing the subtree takes O(K) time.
     * 
     * @param key
     * @param offset
     * @param length
     * @return
     */
    public SortedMap getPrefixedBy(Object key, int offset, int length) {
        return getPrefixedByBits(key, offset * keyAnalyzer.bitsPerElement(), length * keyAnalyzer.bitsPerElement());
    }

    /**
     * Returns a view of this Trie of all elements that are prefixed
     * by the number of bits in the given Key.
     * 
     * Fixed-keysize Tries can support this operation as a way to do
     * lookups of partial keys.  That is, if the Trie is storing IP
     * addresses, you can lookup all addresses that begin with
     * '192.168' by providing the key '192.168.X.X' and a length of 16
     * would return all addresses that begin with '192.168'.
     * 
     * The view that this returns is optimized to have a very efficient
     * Iterator.  The firstKey, lastKey & size methods must iterate
     * over all possible values in order to determine the results.  This
     * information is cached until the Patricia tree changes.  All other
     * methods (except Iterator) must compare the given key to the prefix
     * to ensure that it is within the range of the view.  The Iterator's
     * remove method must also relocate the subtree that contains the
     * prefixes if the entry holding the subtree is removed or changes.
     * Changing the subtree takes O(K) time.
     * 
     * @param key
     * @param length
     * @return
     */
    public SortedMap getPrefixedByBits(Object key, int bitLength) {
        return getPrefixedByBits(key, 0, bitLength);
    }
    
    /**
     * Returns a view of this map, with entries containing only those that
     * are prefixed by a value whose bits matches the bits between 'offset'
     * and 'length' in the given key.
     * 
     * The view that this returns is optimized to have a very efficient
     * Iterator.  The firstKey, lastKey & size methods must iterate
     * over all possible values in order to determine the results.  This
     * information is cached until the Patricia tree changes.  All other
     * methods (except Iterator) must compare the given key to the prefix
     * to ensure that it is within the range of the view.  The Iterator's
     * remove method must also relocate the subtree that contains the
     * prefixes if the entry holding the subtree is removed or changes.
     * Changing the subtree takes O(K) time.
     * 
     * @param key
     * @param offset
     * @param length
     * @return
     */
    private SortedMap getPrefixedByBits(Object key, int offset, int length) {
        int offsetLength = offset + length;
        if (offsetLength > length(key)) {
            throw new IllegalArgumentException(offset + " + " + length + " > " + length(key));
        }
        
        if(offsetLength == 0)
            return this;
        
        return new PrefixSubMap(key, offset, length);
    }
    
    /**
     * Returns true if this trie contains the specified Key
     * 
     * This may throw ClassCastException if the object is not
     * of type K.
     */
    public boolean containsKey(Object k) {
        Object key = k;
        if(key == null)
            return false;
        
        int keyLength = length(key);
        TrieEntry entry = getNearestEntryForKey(key, keyLength);
        return !entry.isEmpty() && key.equals(entry.key);
    }
    
    /** Returns true if this Trie contains the specified value. */
    public boolean containsValue(Object o) {
        for(Iterator iter = values().iterator(); iter.hasNext();)
            if(valEquals(iter.next(), o))
                return true;
        return false;
    }
    
    
    /**
     * Removes a Key from the Trie if one exists
     * 
     * This may throw ClassCastException if the object is not of type K.
     * 
     * @param key the Key to delete
     * @return Returns the deleted Value
     */
    public Object remove(Object k) {
        Object key = k;
        if(key == null)
            return null;
        
        int keyLength = length(key);        
        TrieEntry current = root.left;
        TrieEntry path = root;
        while(true) {
            if(current.bitIndex <= path.bitIndex) {
                if(!current.isEmpty() && key.equals(current.key))
                    return removeEntry(current);
                else
                    return null;
            }
            
            path = current;
            if(!isBitSet(key, keyLength, current.bitIndex))
                current = current.left;
            else
                current = current.right;
        }
    }
    
    /**
     * Removes a single entry from the Trie.
     * 
     * If we found a Key (Entry h) then figure out if it's
     * an internal (hard to remove) or external Entry (easy 
     * to remove)
     */
    private Object removeEntry(TrieEntry h) {
        if (h != root) {
            if (h.isInternalNode()) {
                removeInternalEntry(h);
            } else {
                removeExternalEntry(h);
            }
        }
        
        decrementSize();
        return h.setKeyValue(null, null);
    }
    
    /**
     * Removes an external entry from the Trie.
     * 
     * If it's an external Entry then just remove it.
     * This is very easy and straight forward.
     */
    private void removeExternalEntry(TrieEntry h) {
        if (h == root) {
            throw new IllegalArgumentException("Cannot delete root Entry!");
        } else if (!h.isExternalNode()) {
            throw new IllegalArgumentException(h + " is not an external Entry!");
        } 
        
        TrieEntry parent = h.parent;
        TrieEntry child = (h.left == h) ? h.right : h.left;
        
        if (parent.left == h) {
            parent.left = child;
        } else {
            parent.right = child;
        }
        
        // either the parent is changing, or the predecessor is changing.
        if (child.bitIndex > parent.bitIndex) {
            child.parent = parent;
        } else {
            child.predecessor = parent;
        }
        
    }
    
    /**
     * Removes an internal entry from the Trie.
     * 
     * If it's an internal Entry then "good luck" with understanding
     * this code. The Idea is essentially that Entry p takes Entry h's
     * place in the trie which requires some re-wireing.
     */
    private void removeInternalEntry(TrieEntry h) {
        if (h == root) {
            throw new IllegalArgumentException("Cannot delete root Entry!");
        } else if (!h.isInternalNode()) {
            throw new IllegalArgumentException(h + " is not an internal Entry!");
        } 
        
        TrieEntry p = h.predecessor;
        
        // Set P's bitIndex
        p.bitIndex = h.bitIndex;
        
        // Fix P's parent, predecessor and child Nodes
        {
            TrieEntry parent = p.parent;
            TrieEntry child = (p.left == h) ? p.right : p.left;
            
            // if it was looping to itself previously,
            // it will now be pointed from it's parent
            // (if we aren't removing it's parent --
            //  in that case, it remains looping to itself).
            // otherwise, it will continue to have the same
            // predecessor.
            if(p.predecessor == p && p.parent != h)
                p.predecessor = p.parent;
            
            if (parent.left == p) {
                parent.left = child;
            } else {
                parent.right = child;
            }
            
            if (child.bitIndex > parent.bitIndex) {
                child.parent = parent;
            }
        };
        
        // Fix H's parent and child Nodes
        {         
            // If H is a parent of its left and right childs 
            // then change them to P
            if (h.left.parent == h) {
                h.left.parent = p;
            }
            
            if (h.right.parent == h) {
                h.right.parent = p;
            }
            
            // Change H's parent
            if (h.parent.left == h) {
                h.parent.left = p;
            } else {
                h.parent.right = p;
            }
        };
        
        // Copy the remaining fields from H to P
        //p.bitIndex = h.bitIndex;
        p.parent = h.parent;
        p.left = h.left;
        p.right = h.right;
        
        // Make sure that if h was pointing to any uplinks,
        // p now points to them.
        if(isValidUplink(p.left, p))
            p.left.predecessor = p;
        if(isValidUplink(p.right, p))
            p.right.predecessor = p;
            
    }
    
    /**
     * Returns the node lexographically before the given node (or null if none).
     * 
     * This follows four simple branches:
     *  - If the uplink that returned us was a right uplink:
     *      - If predecessor's left is a valid uplink from predecessor, return it.
     *      - Else, follow the right path from the predecessor's left.
     *  - If the uplink that returned us was a left uplink:
     *      - Loop back through parents until we encounter a node where 
     *        node != node.parent.left.
     *          - If node.parent.left is uplink from node.parent:
     *              - If node.parent.left is not root, return it.
     *              - If it is root & root isEmpty, return null.
     *              - If it is root & root !isEmpty, return root.
     *          - If node.parent.left is not uplink from node.parent:
     *              - Follow right path for first right child from node.parent.left   
     * 
     * @param start
     * @return
     */
    private TrieEntry previousEntry(TrieEntry start) {
        if(start.predecessor == null)
            throw new IllegalArgumentException("must have come from somewhere!");
        
        if(start.predecessor.right == start) {
            if(isValidUplink(start.predecessor.left, start.predecessor)) {
                return start.predecessor.left;
            } else {
                return followRight(start.predecessor.left);
            }
        } else {
            TrieEntry node = start.predecessor;
            while(node.parent != null && node == node.parent.left)
                node = node.parent;
            if(node.parent == null) // can be null if we're looking up root.
                return null;
            if(isValidUplink(node.parent.left, node.parent)) {
                if(node.parent.left == root) {
                    if(root.isEmpty())
                        return null;
                    else
                        return root;
                } else {
                    return node.parent.left;
                }
            } else {
                return followRight(node.parent.left);
            }
        }
    }
    
    /**
     * Returns the entry lexographically after the given entry.
     * If the given entry is null, returns the first node.
     */
    private TrieEntry nextEntry(TrieEntry node) {
        if(node == null) {
            return firstEntry();
        } else {
            return nextEntryImpl(node.predecessor, node, null);
        }
    }
    
    /**
     * Returns the entry lexographically after the given entry.
     * If the given entry is null, returns the first node.
     * 
     * This will traverse only within the subtree.  If the given node
     * is not within the subtree, this will have undefined results.
     */
    private TrieEntry nextEntryInSubtree(TrieEntry node, TrieEntry parentOfSubtree) {
        if(node == null) {
            return firstEntry();
        } else {
            return nextEntryImpl(node.predecessor, node, parentOfSubtree);
        }
    }
    
    /**
     * Scans for the next node, starting at the specified point, and using 'previous'
     * as a hint that the last node we returned was 'previous' (so we know not to return
     * it again).  If 'tree' is non-null, this will limit the search to the given tree.
     * 
     * The basic premise is that each iteration can follow the following steps:
     * 
     * 1) Scan all the way to the left.
     *   a) If we already started from this node last time, proceed to Step 2.
     *   b) If a valid uplink is found, use it.
     *   c) If the result is an empty node (root not set), break the scan.
     *   d) If we already returned the left node, break the scan.
     *   
     * 2) Check the right.
     *   a) If we already returned the right node, proceed to Step 3.
     *   b) If it is a valid uplink, use it.
     *   c) Do Step 1 from the right node.
     *   
     * 3) Back up through the parents until we encounter find a parent
     *    that we're not the right child of.
     *    
     * 4) If there's no right child of that parent, the iteration is finished.
     *    Otherwise continue to Step 5.
     * 
     * 5) Check to see if the right child is a valid uplink.
     *    a) If we already returned that child, proceed to Step 6.
     *       Otherwise, use it.
     *    
     * 6) If the right child of the parent is the parent itself, we've
     *    already found & returned the end of the Trie, so exit.
     *    
     * 7) Do Step 1 on the parent's right child.
     */
    private TrieEntry nextEntryImpl(TrieEntry start, TrieEntry previous, TrieEntry tree) {
        TrieEntry current = start;

        // Only look at the left if this was a recursive or
        // the first check, otherwise we know we've already looked
        // at the left.
        if(previous == null || start != previous.predecessor) {
            while(!current.left.isEmpty()) {
                // stop traversing if we've already
                // returned the left of this node.
                if(previous == current.left) {
                    break;
                }
                
                if(isValidUplink(current.left, current)) {
                    return current.left;
                }
                
                current = current.left;
            }
        }
        
        // If there's no data at all, exit.
        if(current.isEmpty()) {
            return null;
        }
        
        // If we've already returned the left,
        // and the immediate right is null,
        // there's only one entry in the Trie
        // which is stored at the root.
        //
        //  / ("")   <-- root
        //  \_/  \
        //       null <-- 'current'
        //
        if(current.right == null)
            return null;
        
        // If nothing valid on the left, try the right.
        if(previous != current.right) {
            // See if it immediately is valid.
            if(isValidUplink(current.right, current)) {
                return current.right;
            }
            
            // Must search on the right's side if it wasn't initially valid.
            return nextEntryImpl(current.right, previous, tree);
        }
        
        // Neither left nor right are valid, find the first parent
        // whose child did not come from the right & traverse it.
        while(current == current.parent.right) {
            // If we're going to traverse to above the subtree, stop.
            if(current == tree)
                return null;
            
            current = current.parent;
        }

        // If we're on the top of the subtree, we can't go any higher.
        if(current == tree)
            return null;
        
        
        // If there's no right, the parent must be root, so we're done.
        if(current.parent.right == null) {
            return null;
        }

        
        // If the parent's right points to itself, we've found one.
        if(previous != current.parent.right && isValidUplink(current.parent.right, current.parent)) {
            return current.parent.right;
        }
        
        // If the parent's right is itself, there can't be any more nodes.
        if(current.parent.right == current.parent) {
            return null;
        }
        
        // We need to traverse down the parent's right's path.
        return nextEntryImpl(current.parent.right, previous, tree);
    }
    
    /** Returns each entry as a string. */
    public String toString() {
//        StringBuilder buffer = new StringBuilder();
        StringBuffer buffer = new StringBuffer();
        buffer.append("Trie[").append(size()).append("]={\n");
        for(Iterator i = newEntryIterator(); i.hasNext(); ) {
            buffer.append("  ").append(i.next().toString()).append("\n");
        }
        buffer.append("}\n");
        return buffer.toString();
    }
    
    public Map.Entry traverse(Cursor cursor) {
        TrieEntry entry = nextEntry(null);
        while(entry != null) {
            TrieEntry current = entry;
            int ret = cursor.select(current);
            entry = nextEntry(current);
            switch(ret) {
            case Cursor.EXIT:
                return current;
            case Cursor.REMOVE:
                removeEntry(current);
                break; // out of switch, stay in while loop
            case Cursor.REMOVE_AND_EXIT:
                Map.Entry value = new TrieEntry(current.getKey(), current.getValue(), -1);
                removeEntry(current);
                return value;
            case Cursor.CONTINUE: // do nothing.
            }
        }
        
        return null;
    }
    
    /** Returns true if 'next' is a valid uplink coming from 'from'. */
    private boolean isValidUplink(TrieEntry next, TrieEntry from) {            
        return next != null && next.bitIndex <= from.bitIndex && !next.isEmpty();
    }
    
    /** Returns true if bitIndex is a valid index */
    private static boolean isValidBitIndex(int bitIndex) {
        return 0 <= bitIndex && bitIndex <= Integer.MAX_VALUE;
    }
    
    /** Returns true if bitIndex is a NULL_BIT_KEY */
    private static boolean isNullBitKey(int bitIndex) {
        return bitIndex == KeyAnalyzer.NULL_BIT_KEY;
    }
    
    /** Returns true if bitIndex is a EQUAL_BIT_KEY */
    private static boolean isEqualBitKey(int bitIndex) {
        return bitIndex == KeyAnalyzer.EQUAL_BIT_KEY;
    }
    
    /** Returns the length of the key, or 0 if the key is null. */
    private int length(Object key) {
        if (key == null) {
            return 0;
        }
        
        return keyAnalyzer.length(key);
    }
    
    /**
     * Returns whether or not the given bit on the 
     * key is set, or false if the key is null
     */
    private boolean isBitSet(Object key, int keyLength, int bitIndex) {
        if (key == null) { // root's might be null!
            return false;
        }
        return keyAnalyzer.isBitSet(key, keyLength, bitIndex);
    }
    
    /**
     * Utility method for calling
     * keyAnalyzer.bitIndex(key, 0, length(key), foundKey, 0, length(foundKey))
     */
    private int bitIndex(Object key, Object foundKey) {
        return keyAnalyzer.bitIndex(key, 0, length(key), foundKey, 0, length(foundKey));
    }
    
    /** The actual Trie nodes. */
    private static class TrieEntry implements Map.Entry, Serializable {
        
        private static final long serialVersionUID = 4596023148184140013L;
        
        private Object key;
        private Object value;
        
        /** The index this entry is comparing. */
        private int bitIndex;
        
        /** The parent of this entry. */
        private TrieEntry parent;
        /** The left child of this entry. */
        private TrieEntry left;
        /** The right child of this entry. */
        private TrieEntry right;
        /** The entry who uplinks to this entry. */ 
        private TrieEntry predecessor;
        
        private TrieEntry(Object key, Object value, int bitIndex) {
            this.key = key;
            this.value = value;
            
            this.bitIndex = bitIndex;
            
            this.parent = null;
            this.left = this;
            this.right = null;
            this.predecessor = this;
        }
        
        public boolean equals(Object o) {
            if(o == this) {
                return true;
            } else if(o instanceof Map.Entry) {
                Map.Entry e = (Map.Entry)o;
                Object k1 = getKey();
                Object k2 = e.getKey();
                if (k1 == k2 || (k1 != null && k1.equals(k2))) {
                    Object v1 = getValue();
                    Object v2 = e.getValue();
                    if (v1 == v2 || (v1 != null && v1.equals(v2))) 
                        return true;
                }
                return false;
            } else {
                return false;
            }
        }
        
        /**
         * Whether or not the entry is storing a key.
         * Only the root can potentially be empty, all other
         * nodes must have a key.
         */
        public boolean isEmpty() {
            return key == null;
        }
        
        public Object getKey() {
            return key;
        }
        
        public Object getValue() {
            return value;
        }
        
        public Object setValue(Object value) {
            Object o = this.value;
            this.value = value;
            return o;
        }
        
        /** 
         * Replaces the old key and value with the new ones.
         * Returns the old vlaue.
         */
        private Object setKeyValue(Object key, Object value) {
            this.key = key;
            return setValue(value);
        }
        
        /** Neither the left nor right child is a loopback */
        private boolean isInternalNode() {
            return left != this && right != this;
        }
        
        /** Either the left or right child is a loopback */
        private boolean isExternalNode() {
            return !isInternalNode();
        }

        public String toString() {
//            StringBuilder buffer = new StringBuilder();
            StringBuffer buffer = new StringBuffer();
            
            if (bitIndex == -1) {
                buffer.append("RootEntry(");
            } else {
                buffer.append("Entry(");
            }
            
            buffer.append("key=").append(getKey()).append(" [").append(bitIndex).append("], ");
            buffer.append("value=").append(getValue()).append(", ");
            //buffer.append("bitIndex=").append(bitIndex).append(", ");
            
            if (parent != null) {
                if (parent.bitIndex == -1) {
                    buffer.append("parent=").append("ROOT");
                } else {
                    buffer.append("parent=").append(parent.getKey()).append(" [").append(parent.bitIndex).append("]");
                }
            } else {
                buffer.append("parent=").append("null");
            }
            buffer.append(", ");
            
            if (left != null) {
                if (left.bitIndex == -1) {
                    buffer.append("left=").append("ROOT");
                } else {
                    buffer.append("left=").append(left.getKey()).append(" [").append(left.bitIndex).append("]");
                }
            } else {
                buffer.append("left=").append("null");
            }
            buffer.append(", ");
            
            if (right != null) {
                if (right.bitIndex == -1) {
                    buffer.append("right=").append("ROOT");
                } else {
                    buffer.append("right=").append(right.getKey()).append(" [").append(right.bitIndex).append("]");
                }
            } else {
                buffer.append("right=").append("null");
            }
            buffer.append(", ");
            
            if(predecessor != null) {
                if(predecessor.bitIndex == -1) {
                    buffer.append("predecessor=").append("ROOT");
                } else {
                    buffer.append("predecessor=").append(predecessor.getKey()).append(" [").append(predecessor.bitIndex).append("]");
                }
            }
            
            buffer.append(")");
            return buffer.toString();
        }
    }
    
    /** Analyzes keys on a bit level. */
    public static interface KeyAnalyzer extends Comparator, Serializable {
        
        /** Returned by bitIndex if key's bits are all 0 */
        public static final int NULL_BIT_KEY = -1;
        
        /** 
         * Returned by bitIndex if key and found key are
         * equal. This is a very very specific case and
         * shouldn't happen on a regular basis
         */
        public static final int EQUAL_BIT_KEY = -2;
        
        /** 
         * Returns the length of the Key in bits. 
         */
        public int length(Object key);
        
        /** Returns whether or not a bit is set */
        public boolean isBitSet(Object key, int keyLength, int bitIndex);
        
        /**
         * Returns the n-th different bit between key and found.
         * This starts the comparison in key at 'keyStart' and goes
         * for 'keyLength' bits, and compares to the found key
         * starting at 'foundStart' and going for 'foundLength' bits.
         */
        public int bitIndex(Object key, int keyStart, int keyLength,
                            Object found, int foundStart, int foundLength);
        
        /**
         * Returns the number of bits per element in the key.
         * This is only useful for variable-length keys, such as Strings.
         */
        public int bitsPerElement();
        
        /**
         * Determines whether or not the given prefix (from offset to length)
         * is a prefix of the given key.
         */
        public boolean isPrefix(Object prefix, int offset, int length, Object key);
    }
    
    /** An iterator that stores a single TrieEntry. */
    private class SingletonIterator implements Iterator {
        private final TrieEntry entry;
        private int hit = 0;
        
        public SingletonIterator(TrieEntry entry) {
            this.entry = entry;
        }
        
        public boolean hasNext() {
            return hit == 0;
        }

        public Object next() {
            if(hit != 0)
                throw new NoSuchElementException();
            hit++;
            return entry;
        }

        public void remove() {
            if(hit != 1)
                throw new IllegalStateException();
            hit++;
            PatriciaTrie.this.removeEntry(entry);
        }
        
    }
    
    /** An iterator for the entries. */
    private abstract class NodeIterator implements Iterator {
        protected int expectedModCount = modCount;   // For fast-fail 
        protected TrieEntry next; // the next node to return
        protected TrieEntry current; // the current entry we're on
        
        // Starts iteration from the beginning.
        protected NodeIterator() {
            next = PatriciaTrie.this.nextEntry(null);
        }
        
        // Starts iteration at the given entry.
        protected NodeIterator(TrieEntry firstEntry) {
            next = firstEntry;
        }
        
        public boolean hasNext() {
            return next != null;
        }
        
        TrieEntry nextEntry() { 
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            TrieEntry e = next;
            if (e == null) 
                throw new NoSuchElementException();
            
            next = findNext(e);
            current = e;
            return e;
        }
        
        protected TrieEntry findNext(TrieEntry prior) {
            return PatriciaTrie.this.nextEntry(prior);
        }
        
        public void remove() {
            if (current == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            
            TrieEntry node = current;
            current = null;
            PatriciaTrie.this.removeEntry(node);
            
            expectedModCount = modCount;
        }
    }

    private class ValueIterator extends NodeIterator {
        public Object next() {
            return nextEntry().value;
        }
    }

    private class KeyIterator extends NodeIterator {
        public Object next() {
            return nextEntry().getKey();
        }
    }

    private class EntryIterator extends NodeIterator {
        public Object next() {
            return nextEntry();
        }
    }
    
    /** An iterator for iterating over a prefix search. */
    private class PrefixEntryIterator extends NodeIterator {
        // values to reset the subtree if we remove it.
        protected final Object prefix; 
        protected final int offset;
        protected final int length;
        protected boolean lastOne;
        
        protected TrieEntry subtree; // the subtree to search within
        
        // Starts iteration at the given entry & search only within the given subtree.
        PrefixEntryIterator(TrieEntry startScan, Object prefix, int offset, int length) {
            subtree = startScan;
            next = PatriciaTrie.this.followLeft(startScan);
            this.prefix = prefix;
            this.offset = offset;
            this.length = length;
        }

        public Object next() {
            Map.Entry entry = nextEntry();
            if(lastOne)
                next = null;
            return entry;
        }
        
        protected TrieEntry findNext(TrieEntry prior) {
            return PatriciaTrie.this.nextEntryInSubtree(prior, subtree);
        }
        
        public void remove() {
            // If the current entry we're removing is the subtree
            // then we need to find a new subtree parent.
            boolean needsFixing = false;
            int bitIdx = subtree.bitIndex;
            if(current == subtree)
                needsFixing = true;
            
            super.remove();
            
            // If the subtree changed its bitIndex or we
            // removed the old subtree, get a new one.
            if(bitIdx != subtree.bitIndex || needsFixing)
                subtree = subtree(prefix, offset, length);
            
            // If the subtree's bitIndex is less than the
            // length of our prefix, it's the last item
            // in the prefix tree.
            if(length >= subtree.bitIndex)
                lastOne = true;
        }
        
    }
    
    /** An iterator for submaps. */
    private class SubMapEntryIterator extends NodeIterator {
        private final Object firstExcludedKey;

        SubMapEntryIterator(TrieEntry first, TrieEntry firstExcluded) {
            super(first);
            firstExcludedKey = 
              (firstExcluded == null ? null : firstExcluded.key);
        }

        public boolean hasNext() {
            return next != null && next.key != firstExcludedKey;
        }

        public Object next() {
            if (next == null || next.key == firstExcludedKey)
                throw new NoSuchElementException();
            return nextEntry();
        }
    }

    Iterator newKeyIterator()   {
        return new KeyIterator();
    }
    
    Iterator newValueIterator()   {
        return new ValueIterator();
    }
    
    Iterator newEntryIterator()   {
        return new EntryIterator();
    }
    
    /**
     * Each of these fields are initialized to contain an instance of the
     * appropriate view the first time this view is requested.  The views are
     * stateless, so there's no reason to create more than one of each.
     */
    private transient volatile Set               keySet = null;
    private transient volatile Collection        values = null;
    private transient volatile Set  entrySet = null;
    
    private class EntrySet extends AbstractSet {
        public Iterator iterator() {
            return newEntryIterator();
        }
        
        public boolean contains(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            
            TrieEntry candidate = getEntry(((Map.Entry)o).getKey());
            return candidate != null && candidate.equals(o);
        }
        
        public boolean remove(Object o) {
            int size = size();
            PatriciaTrie.this.remove(o);
            return size != size();
        }
        
        public int size() {
            return size;
        }
        
        public void clear() {
            PatriciaTrie.this.clear();
        }
    }
    
    /**
     * Returns a set view of the keys contained in this map.  The set is
     * backed by the map, so changes to the map are reflected in the set, and
     * vice-versa.  The set supports element removal, which removes the
     * corresponding mapping from this map, via the <tt>Iterator.remove</tt>,
     * <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt>, and
     * <tt>clear</tt> operations.  It does not support the <tt>add</tt> or
     * <tt>addAll</tt> operations.
     *
     * @return a set view of the keys contained in this map.
     */
    public Set keySet() {
        Set ks = keySet;
        return (ks != null ? ks : (keySet = new KeySet()));
    }

    private class KeySet extends AbstractSet {
        public Iterator iterator() {
            return newKeyIterator();
        }
        public int size() {
            return size;
        }
        public boolean contains(Object o) {
            return containsKey(o);
        }
        public boolean remove(Object o) {
            int size = size();
            PatriciaTrie.this.remove(o);
            return size != size();
        }
        public void clear() {
            PatriciaTrie.this.clear();
        }
    }
    
    /**
     * Returns a collection view of the values contained in this map.  The
     * collection is backed by the map, so changes to the map are reflected in
     * the collection, and vice-versa.  The collection supports element
     * removal, which removes the corresponding mapping from this map, via the
     * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
     * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> operations.
     * It does not support the <tt>add</tt> or <tt>addAll</tt> operations.
     *
     * @return a collection view of the values contained in this map.
     */
    public Collection values() {
        Collection vs = values;
        return (vs != null ? vs : (values = new Values()));
    }
    
    /** Test two values for equality.  Works with null values. */
    private static boolean valEquals(Object o1, Object o2) {
        return (o1==null ? o2==null : o1.equals(o2));
    }

    private class Values extends AbstractCollection {
        public Iterator iterator() {
            return newValueIterator();
        }
        public int size() {
            return size;
        }
        public boolean contains(Object o) {
            return containsValue(o);
        }
        public void clear() {
            PatriciaTrie.this.clear();
        }
        public boolean remove(Object o) {
            for(Iterator i =  iterator(); i.hasNext(); ) {
                Object v = i.next();
                if(valEquals(v, o)) {
                    i.remove();
                    return true;
                }
            }
            return false;
        }
    }
    
    /**
     * Returns the first entry the Trie is storing.
     * 
     * This is implemented by going always to the left until
     * we encounter a valid uplink.  That uplink is the first key.
     */
    private TrieEntry firstEntry() {
        // if Trie is empty, no first node.
        if(isEmpty())
            return null;
        
        return followLeft(root);
    }
    
    /** Goes left through the tree until it finds a valid node. */
    private TrieEntry followLeft(TrieEntry node) {
        while(true) {
            TrieEntry child = node.left;
            // if we hit root and it didn't have a node, go right instead.
            if(child.isEmpty())
                child = node.right;
            
            if(child.bitIndex <= node.bitIndex)
                return child;
            
            node = child;
        }
    }
    
    /**
     * Returns the last entry the Trie is storing.
     * 
     * This is implemented by going always to the right until
     * we encounter a valid uplink.  That uplink is the last key.
     */
    private TrieEntry lastEntry() {
        return followRight(root.left);
    }
    
    /** Traverses down the right path until it finds an uplink. */
    protected TrieEntry followRight(TrieEntry node) {
        // if Trie is empty, no last entry.
        if(node.right == null)
            return null;
        
        // Go as far right as possible, until we encounter an uplink.
        while(node.right.bitIndex > node.bitIndex)
            node = node.right;
        
        return node.right;
    }

    public Object firstKey() {
        return firstEntry().getKey();
    }

    public SortedMap headMap(Object toKey) {
        return new SubMap(null, toKey);
    }

    public Object lastKey() {
        TrieEntry entry = lastEntry();
        if(entry != null)
            return entry.getKey();
        else
            return null;
    }

    
    public SortedMap subMap(Object fromKey, Object toKey) {
        return new SubMap(fromKey, toKey);
    }

    public SortedMap tailMap(Object fromKey) {
        return new SubMap(fromKey, null);
    } 
    
    /**
     * Returns an entry strictly higher than the given key,
     * or null if no such entry exists.
     */
    protected TrieEntry higherEntry(Object key) {
        // TODO: Cleanup so that we don't actually have to add/remove from the
        //       tree.  (We do it here because there are other well-defined 
        //       functions to perform the search.)
        int keyLength = length(key);
        
        if (keyLength == 0) {
            if(!root.isEmpty()) {
                // If data in root, and more after -- return it.
                if(size() > 1) {
                    return nextEntry(root);
                } else { // If no more after, no higher entry.
                    return null;
                }
            } else {
                // Root is empty & we want something after empty, return first.
                return firstEntry();
            }
        }
        
        TrieEntry found = getNearestEntryForKey(key, keyLength);
        if (key.equals(found.key))
            return nextEntry(found);
        
        int bitIndex = bitIndex(key, found.key);
        if (isValidBitIndex(bitIndex)) {
            TrieEntry added = new TrieEntry(key, null, bitIndex);
            addEntry(added, keyLength);
            incrementSize(); // must increment because remove will decrement
            TrieEntry ceil = nextEntry(added);
            removeEntry(added);
            modCount -= 2; // we didn't really modify it.
            return ceil;
        } else if (isNullBitKey(bitIndex)) {
            if (!root.isEmpty())
                return firstEntry();
            else if(size() > 1)
                return nextEntry(firstEntry());
            else
                return null;
        } else if (isEqualBitKey(bitIndex)) {
            return nextEntry(found);
        }

        // we should have exited above.
        throw new IllegalStateException("invalid lookup: " + key);
    }
    
    /**
     * Returns a key-value mapping associated with the least key greater
     * than or equal to the given key, or null if there is no such key.
     */
    protected TrieEntry ceilingEntry(Object key) {
        // Basically:
        // Follow the steps of adding an entry, but instead...
        //
        // - If we ever encounter a situation where we found an equal
        //   key, we return it immediately.
        //
        // - If we hit an empty root, return the first iterable item.
        //
        // - If we have to add a new item, we temporarily add it,
        //   find the successor to it, then remove the added item.
        //
        // These steps ensure that the returned value is either the
        // entry for the key itself, or the first entry directly after
        // the key.
        
        // TODO: Cleanup so that we don't actually have to add/remove from the
        //       tree.  (We do it here because there are other well-defined 
        //       functions to perform the search.)
        int keyLength = length(key);
        
        if (keyLength == 0) {
            if(!root.isEmpty())
                return root;
            else
                return firstEntry();
        }
        
        TrieEntry found = getNearestEntryForKey(key, keyLength);
        if (key.equals(found.key))
            return found;
        
        int bitIndex = bitIndex(key, found.key);
        if (isValidBitIndex(bitIndex)) {
            TrieEntry added = new TrieEntry(key, null, bitIndex);
            addEntry(added, keyLength);
            incrementSize(); // must increment because remove will decrement
            TrieEntry ceil = nextEntry(added);
            removeEntry(added);
            modCount -= 2; // we didn't really modify it.
            return ceil;
        } else if (isNullBitKey(bitIndex)) {
            if (!root.isEmpty())
                return root;
            else
                return firstEntry();
        } else if (isEqualBitKey(bitIndex)) {
            return found;
        }

        // we should have exited above.
        throw new IllegalStateException("invalid lookup: " + key);
    }
    
    /**
     * Returns a key-value mapping associated with the greatest key
     * strictly less than the given key, or null if there is no such key.
     */
    protected TrieEntry lowerEntry(Object key) {
        // Basically:
        // Follow the steps of adding an entry, but instead...
        //
        // - If we ever encounter a situation where we found an equal
        //   key, we return it's previousEntry immediately.
        //
        // - If we hit root (empty or not), return null.
        //
        // - If we have to add a new item, we temporarily add it,
        //   find the previousEntry to it, then remove the added item.
        //
        // These steps ensure that the returned value is always just before
        // the key or null (if there was nothing before it).
        
        // TODO: Cleanup so that we don't actually have to add/remove from the
        //       tree.  (We do it here because there are other well-defined 
        //       functions to perform the search.)
        int keyLength = length(key);
        
        if (keyLength == 0) {
            return null; // there can never be anything before root.
        }
        
        TrieEntry found = getNearestEntryForKey(key, keyLength);
        if (key.equals(found.key))
            return previousEntry(found);
        
        int bitIndex = bitIndex(key, found.key);
        if (isValidBitIndex(bitIndex)) {
            TrieEntry added = new TrieEntry(key, null, bitIndex);
            addEntry(added, keyLength);
            incrementSize(); // must increment because remove will decrement
            TrieEntry prior = previousEntry(added);
            removeEntry(added);
            modCount -= 2; // we didn't really modify it.
            return prior;
        } else if (isNullBitKey(bitIndex)) {
            return null;
        } else if (isEqualBitKey(bitIndex)) {
            return previousEntry(found);
        }

        // we should have exited above.
        throw new IllegalStateException("invalid lookup: " + key);
    }
    
    /**
     * Returns a key-value mapping associated with the greatest key
     * less than or equal to the given key, or null if there is no such key.
     */
    protected TrieEntry floorEntry(Object key) {        
        // TODO: Cleanup so that we don't actually have to add/remove from the
        //       tree.  (We do it here because there are other well-defined 
        //       functions to perform the search.)
        int keyLength = length(key);
        
        if (keyLength == 0) {
            if(!root.isEmpty())
                return root;
            else
                return null;
        }
        
        TrieEntry found = getNearestEntryForKey(key, keyLength);
        if (key.equals(found.key))
            return found;
        
        int bitIndex = bitIndex(key, found.key);
        if (isValidBitIndex(bitIndex)) {
            TrieEntry added = new TrieEntry(key, null, bitIndex);
            addEntry(added, keyLength);
            incrementSize(); // must increment because remove will decrement
            TrieEntry floor = previousEntry(added);
            removeEntry(added);
            modCount -= 2; // we didn't really modify it.
            return floor;
        } else if (isNullBitKey(bitIndex)) {
            if (!root.isEmpty())
                return root;
            else
                return null;
        } else if (isEqualBitKey(bitIndex)) {
            return found;
        }

        // we should have exited above.
        throw new IllegalStateException("invalid lookup: " + key);
    }
    
    
    /**
     * Finds the subtree that contains the prefix.
     * 
     * This is very similar to getR but with the difference that
     * we stop the lookup if h.bitIndex > keyLength.
     */
    private TrieEntry subtree(Object prefix, int offset, int length) {
        TrieEntry current = root.left;
        TrieEntry path = root;
        while(true) {
            if(current.bitIndex <= path.bitIndex || length < current.bitIndex)
                break;
            
            path = current;
            if(!isBitSet(prefix, length+offset, current.bitIndex+offset)) {
                current = current.left;
            } else {
                current = current.right;
            }
        }        

        // Make sure the entry is valid for a subtree.
        TrieEntry entry = current.isEmpty() ? path : current;
        
        // If entry is root, it can't be empty.
        if (entry.isEmpty())
            return null;
        
        int offsetLength = offset + length;
        
        // if root && length of root is less than length of lookup,
        // there's nothing.
        // (this prevents returning the whole subtree if root has an empty
        //  string and we want to lookup things with "\0")
        if(entry == root && length(entry.getKey()) < offsetLength)
            return null;
        
        // Found key's length-th bit differs from our key
        // which means it cannot be the prefix...
        if(isBitSet(prefix, offsetLength, offsetLength) != 
          isBitSet(entry.key, length(entry.key), length)) {
            return null;
        }
        
        // ... or there are less than 'length' equal bits
        int bitIndex = keyAnalyzer.bitIndex(prefix, offset, length,
                                            entry.key, 0, length(entry.getKey()));
        if (bitIndex >= 0 && bitIndex < length)
            return null;
        
        return entry;
    }
    
    /** A submap used for prefix views over the Trie. */
    private class PrefixSubMap extends SubMap {
        protected final Object prefix;
        protected final int offset;
        protected final int length;        
        private transient int keyModCount = 0;
        protected int size;
        
        PrefixSubMap(Object prefix, int offset, int length) {
            this.prefix = prefix;
            this.offset = offset;
            this.length = length;
            fromInclusive = false;
        }
        
        public Object firstKey() {
            fixup();
            TrieEntry e;
            if(fromKey == null) {
                e = firstEntry();
            } else {
                e = higherEntry(fromKey);
            }
            
            Object first = e != null ? e.getKey() : null;
            if (e == null || !keyAnalyzer.isPrefix(prefix, offset, length, first))
                throw new NoSuchElementException();
            return first;
        }

        public Object lastKey() {
            fixup();
            TrieEntry e;
            if(toKey == null) {
                e = lastEntry();
            } else {
                e = lowerEntry(toKey);
            }
            
            Object last = e != null ? e.getKey() : null;
            if (e == null || !keyAnalyzer.isPrefix(prefix, offset, length, last))
                throw new NoSuchElementException();
            return last;
        }
        
        protected boolean inRange(Object key) {
            return keyAnalyzer.isPrefix(prefix, offset, length, key);
        }

        protected boolean inRange2(Object key) {
            return keyAnalyzer.isPrefix(prefix, offset, length, key);
        }
        
        protected boolean inToRange(Object key, boolean forceInclusive) {
            return keyAnalyzer.isPrefix(prefix, offset, length, key);
        }
        
        protected boolean inFromRange(Object key, boolean forceInclusive) {
            return keyAnalyzer.isPrefix(prefix, offset, length, key);
        }
        
        private void fixup() {
            // The trie has changed since we last
            // found our toKey / fromKey
            if(modCount != keyModCount) {
                Iterator iter = entrySet().iterator();
                size = 0;
                
                Map.Entry entry = null;
                if(iter.hasNext()) {
                    entry = (Map.Entry)iter.next();
                    size = 1;
                }
                
                fromKey = entry == null ? null : entry.getKey();
                if(fromKey != null) {
                    TrieEntry prior = previousEntry((TrieEntry)entry);
                    fromKey = prior == null ? null : prior.getKey();
                }
                
                toKey = fromKey;
                
                while(iter.hasNext()) {
                    size++;
                    entry = (Map.Entry)iter.next();
                }
                
                toKey = entry == null ? null : entry.getKey();
                
                if(toKey != null) {
                    entry = nextEntry((TrieEntry)entry);
                    toKey = entry == null ? null : entry.getKey();
                }
                
                keyModCount = modCount;
            }
        }
        
        protected Set newSubMapEntrySet() {
            return new PrefixEntrySetView();
        }

        private class PrefixEntrySetView extends SubMap.EntrySetView {
            private TrieEntry prefixStart;
            private int iterModCount = 0;
            
            public int size() {
                fixup();
                return PrefixSubMap.this.size;
            }

            public Iterator iterator() {
                if(modCount != iterModCount) {
                    prefixStart = subtree(prefix, offset, length);
                    iterModCount = modCount;
                }
                
                if(prefixStart == null) {
                    return EmptyIterator.emptyIterator();
                } else if(length >= prefixStart.bitIndex){
                    return new SingletonIterator(prefixStart);
                } else {
                    return new PrefixEntryIterator(prefixStart, prefix, offset, length);
                }
            }
        }
    }
    
    private class SubMap extends AbstractMap implements SortedMap, java.io.Serializable {

        // TODO: add serialVersionUID
        
        /** The key to start from, null if the beginning. */
        protected Object fromKey;
        
        /** The key to end at, null if till the end. */
        protected Object toKey;
        
        /** Whether or not the 'from' is inclusive. */
        protected boolean fromInclusive;
        
        /** Whether or not the 'to' is inclusive. */
        protected boolean toInclusive;
        
        /**
         * Constructs a blank SubMap -- this should ONLY be used
         * by subclasses that wish to lazily construct their
         * fromKey or toKey
         */
        protected SubMap() {}

        SubMap(Object fromKey, Object toKey) {
            if(fromKey == null && toKey == null)
                throw new IllegalArgumentException("must have a from or to!");
            if(fromKey != null && toKey != null && keyAnalyzer.compare(fromKey, toKey) > 0)
                throw new IllegalArgumentException("fromKey > toKey");
            this.fromKey = fromKey;
            this.toKey = toKey;
            fromInclusive = true;
        }
        
        public boolean isEmpty() {
            return entrySet().isEmpty();
        }

        public boolean containsKey(Object key) {
            return inRange(key) && PatriciaTrie.this.containsKey(key);
        }
       
        public Object remove(Object key) {
            if(!inRange(key))
                return null;
            return PatriciaTrie.this.remove(key);
        }

        public Object get(Object key) {
            if (!inRange( key))
                return null;
            return PatriciaTrie.this.get(key);
        }

        public Object put(Object key, Object value) {
            if (!inRange(key))
                throw new IllegalArgumentException("key out of range");
            return PatriciaTrie.this.put(key, value);
        }

        public Comparator comparator() {
            return keyAnalyzer;
        }
 
        public Object firstKey() {
            TrieEntry e;
            if(fromKey == null) {
                e = firstEntry();
            } else {
                if(fromInclusive)
                    e = ceilingEntry(fromKey);
                else
                    e = higherEntry(fromKey);
            }
            
            Object first = e != null ? e.getKey() : null;
            if (e == null || toKey != null && !inToRange(first, false))
                throw new NoSuchElementException();
            return first;
        }

        public Object lastKey() {
            TrieEntry e;
            if(toKey == null) {
                e = lastEntry();
            } else {
                if(toInclusive)
                    e = floorEntry(toKey);
                else
                    e = lowerEntry(toKey);
            }
            
            Object last = e != null ? e.getKey() : null;
            if (e == null || fromKey != null && !inFromRange(last, false))
                throw new NoSuchElementException();
            return last;
        }

        private transient Set entrySet;

        public Set entrySet() {
            if(entrySet == null)
                entrySet = newSubMapEntrySet();
            return entrySet;
        }
        
        protected Set newSubMapEntrySet() {
            return new EntrySetView();
        }

        class EntrySetView extends AbstractSet {
            private transient int size = -1, sizeModCount;

            public int size() {
                if (size == -1 || sizeModCount != PatriciaTrie.this.modCount) {
                    size = 0;  sizeModCount = PatriciaTrie.this.modCount;
                    Iterator i = iterator();
                    while (i.hasNext()) {
                        size++;
                        i.next();
                    }
                }
                return size;
            }

            public boolean isEmpty() {
                return !iterator().hasNext();
            }

            public boolean contains(Object o) {
                if (!(o instanceof Map.Entry))
                    return false;
                Map.Entry entry = (Map.Entry) o;
                Object key = entry.getKey();
                if (!inRange(key))
                    return false;
                TrieEntry node = getEntry(key);
                return node != null && 
                       valEquals(node.getValue(), entry.getValue());
            }

            public boolean remove(Object o) {
                if (!(o instanceof Map.Entry))
                    return false;
                Map.Entry entry = (Map.Entry) o;
                Object key = entry.getKey();
                if (!inRange(key))
                    return false;
                TrieEntry node = getEntry(key);
                if (node!=null && valEquals(node.getValue(),entry.getValue())){
                    removeEntry(node);
                    return true;
                }
                return false;
            }

            public Iterator iterator() {
                return new SubMapEntryIterator(
                    (fromKey == null ? firstEntry() : ceilingEntry(fromKey)),
                    (toKey   == null ? null         : ceilingEntry(toKey)));
            }
        }

        public SortedMap subMap(Object fromKey, Object toKey) {
            if (!inRange2(fromKey))
                throw new IllegalArgumentException("fromKey out of range");
            if (!inRange2(toKey))
                throw new IllegalArgumentException("toKey out of range");
            return new SubMap(fromKey, toKey);
        }

        public SortedMap headMap(Object toKey) {
            if (!inRange2(toKey))
                throw new IllegalArgumentException("toKey out of range");
            return new SubMap(fromKey, toKey);
        }

        public SortedMap tailMap(Object fromKey) {
            if (!inRange2(fromKey))
                throw new IllegalArgumentException("fromKey out of range");
            return new SubMap(fromKey, toKey);
        }

        protected boolean inRange(Object key) {
            return (fromKey == null || inFromRange(key, false)) &&
                   (toKey   == null || inToRange(key, false));
        }

        // This form allows the high endpoint (as well as all legit keys)
        protected boolean inRange2(Object key) {
            return (fromKey == null || inFromRange(key, false)) &&
                   (toKey   == null || inToRange(key, true));
        }
        
        protected boolean inToRange(Object key, boolean forceInclusive) {
            int ret = keyAnalyzer.compare(key, toKey);
            if(toInclusive || forceInclusive)
                return ret <= 0;
            else
                return ret < 0;
        }
        
        protected boolean inFromRange(Object key, boolean forceInclusive) {
            int ret = keyAnalyzer.compare(key, fromKey);
            if (fromInclusive || forceInclusive)
                return ret >= 0;
            else
                return ret > 0;
        }
    }
    
    public static interface Cursor {
     
        public static final int EXIT = 0;
        public static final int CONTINUE = 1;
        public static final int REMOVE = 2;
        public static final int REMOVE_AND_EXIT = 3;
        /**
         * Notification that the trie is currently looking at the given entry.
         * Return EXIT to finish the trie operation, CONTINUE to look at the
         * next entry, REMOVE to remove the entry and continue iterating, or
         * REMOVE_AND_EXIT to remove the entry and stop iterating. 
         * Not all operations support REMOVE.
         * 
         * @param entry
         * @return
         */
        public int select(Map.Entry entry);
    }
    
    public static class EmptyIterator extends UnmodifiableIterator {
        /** A constant EmptyIterator. */
        public final static Iterator EMPTY_ITERATOR = new EmptyIterator();

        public static Iterator emptyIterator() {
            return EMPTY_ITERATOR;
        }

        // inherits javadoc comment
        public boolean hasNext() {
            return false;
        }

        // inherits javadoc comment
        public Object next() {
            throw new NoSuchElementException();
        }
    }
}
