public class Trie {
    private TrieNode root = null;

    public void insert(String word) {
	root = insert(word, root);
    }

    private TrieNode insert(String word, TrieNode t) {
	TrieNode newNode, tmpNode;
	boolean ends = false;
	char first;

	if (word.length() == 0) {
	    return t;
	} else if (word.length() == 1) {
	    ends = true;
	}
	first = word.charAt(0);

	if (t == null) {
	    // If we have an empty tree we can just append the node to 
	    // this branch.
	    newNode = new TrieNode(first, ends);
	    tmpNode = insert(word.substring(1), null);
	    newNode.setChild(tmpNode);
	    return newNode;
	} else if (first < t.getLetter()) {
	    // The node should be before this sibling, so we "squeeze"
	    // it in, and point out the sibling.
	    newNode = new TrieNode(first, ends, t, null);
	    tmpNode = insert(word.substring(1), null);
	    newNode.setChild(tmpNode);
	    return newNode;
	} else if (first > t.getLetter()) {
	    // We have not yet come to the place where this node should
	    // be appended to the tree.
	    tmpNode = insert(word, t.getSibling());
	    t.setSibling(tmpNode);
	    return t;	    
	}
	// A node with the current letter already exists. Continue with
	// appending the rest of the word to this subtree.
	if (ends) {
	    // Do not forget to mark this node as an end node if it is the
	    // final letter of the word.
	    t.setEnd(ends);
	} else {
	    // Otherwise just append the rest of the word to this subtree.
	    tmpNode = insert(word.substring(1), t.getChild());
	    t.setChild(tmpNode);		
	}
	return t;
    }
    
    public boolean exists(String word) {
	return exists(word, root);
    }

    private boolean exists(String word, TrieNode t) {
	char first;
	if (word.length() == 0) {
	    return false;
	}
	first = word.charAt(0);

	if (t == null) {
	    // If we have an empty tree so the word cannot exist in the tree.
	    return false;
	} else if (first < t.getLetter()) {
	    // The node should be before this sibling, so we should already have
	    // found it.
	    return false;
	} else if (first > t.getLetter()) {
	    // We have not yet come to the place where this node should
	    // be appended to the tree.
	    return exists(word, t.getSibling());	    
	} 
	// The initial letters match...
	if (word.length() == 1) {
	     // ...and this is the final letter of so the boolean say if 
	    // we have inserted this word into our tree or not.
	    return t.getEnd();
	}
	// ...and this is NOT the final letter of the word so let us 
	// match the rest.
	return exists(word.substring(1), t.getChild());
    }

    public boolean existsVerbose(String word) {
	// Exactly the same as exists but also writes out the node id of 
	// the matched nodes during the search.
	boolean retval = existsVerbose(word, root);
	System.out.println();
	return retval;
    }

    private boolean existsVerbose(String word, TrieNode t) {
	char first;
	if (word.length() == 0) {
	    return false;
	}
	first = word.charAt(0);

	if (t == null) {
	    return false;
	} else if (first < t.getLetter()) {
	    return false;
	} else if (first > t.getLetter()) {
	    return existsVerbose(word, t.getSibling());	    
	}
	System.out.print(t.getId() + " ");
	if (word.length() == 1) {
	    return t.getEnd();
	}
	return existsVerbose(word.substring(1), t.getChild());
    }

    public void printTrie() {
	printTrie(root, "");
    }

    private void printTrie(TrieNode t, String prefix) {
	String word;
	if (t != null) {
	    word = prefix + t.getLetter();

	    // Is this a word? Print the word
	    if (t.getEnd()) {
		System.out.println(word);
	    }
	    
	    // Children should be printed. They share the same WORD as prefix
	    if (t.getChild() != null) {
		printTrie(t.getChild(), word);
	    }
	    
	    // Siblings should be printed. They share the same PREFIX
	    if (t.getSibling() != null) {
		printTrie(t.getSibling(), prefix);
	    }
	}
    }

    public void printWordsStartingWith(String s) {
	printWordsStartingWith(s, root, s);
    }

    private void printWordsStartingWith(String s, TrieNode t, String prefix) {
	// This is nothing new really. First we do the same as
	// exists(s) to find the prefix string s. Then we start
	// printTrie from that node with s as prefix. We must supply
	// both the string s where one letter is "shaved off" at the
	// time and prefix which will be passed to printTrie.

	char first;
	if (s.length() == 0) {
	    // The searchstring s is empty so the tree from this point
	    // on is the one we want to print.
	    printTrie(t, prefix);
	} else {
	    // We have to continue to search for the subtree. First
	    // pick out the initial letter of the searchstring s
	    first = s.charAt(0);

	    if (t == null) {
		// If we have an empty tree so s cannot exist in the tree.
		return;
	    } else if (first < t.getLetter()) {
		// The node should be before this sibling, so we should already have
		// found it.
		return;
	    } else if (first > t.getLetter()) {
		// We have not yet come to the place where this node should
		// be appended to the tree.
		printWordsStartingWith(s, t.getSibling(), prefix);	    
	    }  else {
		// The initial letters match and this is NOT the final
		// letter of the word so let us match the rest.
		printWordsStartingWith(s.substring(1), t.getChild(), prefix);
	    }
	}
    }
}
