/**
 * Binary search trees.
 */
public class NewBST
{
  // +--------+--------------------------------------------------
  // | Fields |
  // +--------+

  /**
   * The root of the tree.
   */
  NewBSTNode root;
  
  /**
   * The comparator used to decide where things go.
   */
  GComparator compare;


  // +--------------+--------------------------------------------
  // | Constructors |
  // +--------------+

  /**
   * Create a new binary search tree that uses compare to compare
   * elements.
   */
  public NewBST(GComparator compare) {
    this.compare = compare;
    this.root = null;
  } // NewBST(GComparator)


  // +------------------+----------------------------------------
  // | Exported Methods |
  // +------------------+

  /**
   * Add a new element to the tree.
   */
  public void add(Object newElement) {
    this.root = add(newElement, this.root);
  } // add(Object newElement)

  /**
   * Delete an element from the tree.
   */
  public void delete(Object deleteMe) {
    this.root = delete(deleteMe, this.root);
  } // delete(Object)

  /**
   * Dump the tree to the screen.
   */
  public void dump() {
    dump(this.root, "");
  } // dump()


  // +----------------------+------------------------------------
  // | Local Helper Methods |
  // +----------------------+

  /**
   * Add an element at the subtree rooted at hereWeAre.  Return
   * the modified subtree.
   */
  NewBSTNode add(Object newElement, NewBSTNode hereWeAre) {
    if (hereWeAre == null) {
      hereWeAre = new NewBSTNode(newElement);
    }
    else if (compare.mayPrecede(newElement, hereWeAre.contents)) {
      hereWeAre.left = add(newElement, hereWeAre.left);
    }
    else {
      hereWeAre.right = add(newElement, hereWeAre.right);
    }
    hereWeAre.updateHeight();
    return hereWeAre;
  } // add(Object)

  /**
   * Delete an element from the subtree rooted at hereWeAre.  Return
   * the modified subree.
   */
  NewBSTNode delete(Object deleteMe, NewBSTNode hereWeAre) 
  {
    // Deletion strategy: Find the rightmost node in the left subtree,
    // delete it, and put the value here.
    // Base case: Not found, ran off of the tree.
    if (hereWeAre == null) {
      return null;
    }
    // Base case: Found!
    else if (this.same(deleteMe, hereWeAre.contents)) {
      // Special case: No left subtree.  Return the right subtree.
      if (hereWeAre.left == null) return hereWeAre.right;
      // Normal case: Left subtree.  Do funky stuff.
      hereWeAre.left = getAndDeleteRightmost(hereWeAre.left, hereWeAre);
    }
    // Recursive case: Should be in left subtree.
    else if (this.compare.mayPrecede(deleteMe, hereWeAre.contents)) {
      hereWeAre.left = delete(deleteMe, hereWeAre.left);
    }
    // Recursive case: Should be in right subtree.
    else {
      hereWeAre.right = delete(deleteMe, hereWeAre.right);
    }

    // A little bit of clean up and we're done.
    hereWeAre.updateHeight();
    return hereWeAre;
  } // delete(Object, NewBSTNode)

  /**
   * Dump the subtree rooted at subroot.  Indent that subtree by indent.
   */
  void dump(NewBSTNode subroot, String indent) {
    if (subroot != null) {
      System.out.println(indent + subroot.contents 
                         + " [height " + subroot.height +"]");
      dump(subroot.left, indent + "L ");
      dump(subroot.right, indent + "R ");
    }
  } // dump(NewBSTNode, String)

  /**
   * Find the rightmost value in the tree rooted at hereWeAre.
   * Shove that in putItHere, delete the corresponding node,
   * and return the modified tree.
   */
  NewBSTNode getAndDeleteRightmost(NewBSTNode hereWeAre, NewBSTNode putItHere)
  {
    // If hereWeAre is null, it turns out there was no rightmost
    // child.  Simply return null.
    if (null == hereWeAre)
      return null;
    // If hereWeAre has no right children, it is the rightmost node,
    // so shove its contents in putItHere and return its left child
    else if (null == hereWeAre.right) {
      putItHere.contents = hereWeAre.contents;
      return hereWeAre.left;
    }
    else {
      hereWeAre.right = getAndDeleteRightmost(hereWeAre.right, putItHere);
      hereWeAre.updateHeight();
      return hereWeAre;
    }
  } // getAndDeleteRightmost(NewBSTNode, NewBSTNode)

  /**
   * Determine if two elements are the same using the comparator.
   * Assume that if x may precede y and y may precede x, then
   * x is the same as y.
   */
  boolean same(Object alpha, Object beta) {
    return compare.mayPrecede(alpha, beta) 
           && compare.mayPrecede(beta, alpha);
  } // same(Object, Object)

} // class NewBST
