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

  /**
   * The root of the tree.
   */
  BSTNode 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 BST(GComparator compare) {
    this.compare = compare;
    this.root = null;
  } // BST(GComparator)


  // +---------+-------------------------------------------------
  // | 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)

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

  /**
   * Delete an element from the subtree rooted at hereWeAre.  Return
   * the modified subree.
   */
  BSTNode delete(Object deleteMe, BSTNode hereWeAre) 
  {
/*
   Deletion strategy: 
     Shove the right subtree at the rightmost point 
     in the left subtree.
   Observation:
     This strategy will probably lead to unbalanced trees.
     But hey, maybe we'll be lucky.
 */
    // 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 one: No left subtree.  Return the right subtree.
      if (hereWeAre.left == null) return hereWeAre.right;
      // Special case two: No right subtree.  Return the left subtree.
      if (hereWeAre.right == null) return hereWeAre.left;
      // Special case three: No subtrees. Handled by special cases above.
      // Default case: Two subtrees
      putAtRight(hereWeAre.left, hereWeAre.right);
      return hereWeAre.left;
    }
    // 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);
    }
    return hereWeAre;
  } // delete(Object, BSTNode)

  /**
   * Add addMe to the rightmost position in hereWeAre.  Return
   * the modified tree.
   */
  void putAtRight(BSTNode hereWeAre, BSTNode addMe)
  {
     while (hereWeAre.right != null)
       hereWeAre = hereWeAre.right;
     hereWeAre.right = addMe;
/*
     // Base case: We've run out of tree.  
     if (hereWeAre == null) return addMe;
     // Normal case
     hereWeAre.right = putAtRight(hereWeAre.right, addMe);
     return hereWeAre;
 */
  } // putAtRight(BSTNode, BSTNode)

  /**
   * Find the rightmost value in the tree rooted at hereWeAre.
   * Shove that in putItHere, delete the corresponding node,
   * and return the modified tree.
   */
  BSTNode grabAndDeleteRightMost(BSTNode hereWeAre, BSTNode putItHere)
  {
    // If hereWeAre is null, it turns out there was no rightmost
    // child.  Simply return null.
    if (hereWeAre == null)
      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 (hereWeAre.right == null) {
      putItHere.contents = hereWeAre.contents;
      return hereWeAre.left;
    }
    else {
      hereWeAre.right = grabAndDeleteRightMost(hereWeAre.right, putItHere);
      return hereWeAre;
    }
  } // grabAndDeleteRightMost(BSTNode, BSTNode)

  /**
   * 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)

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

  void dump(BSTNode subroot, String indent) {
    if (subroot != null) {
      System.out.println(indent + subroot.contents);
      dump(subroot.left, indent + "L ");
      dump(subroot.right, indent + "R ");
    }
  } // dump(BSTNode, String)
} // class BST
