public class LLRB<Key extends Comparable<Key>, Value>
{
   private static final boolean RED = true;
   private static final boolean BLACK = false;
   private Node root;

   private class Node
   {
      private Key key;
      private Value val;
      private Node left, right;
      private boolean color;         // true == RED, false == BLACK

      Node(Key key, Value val)
      {
         this.key = key;
         this.val = val;
         this.color = RED;
      }

      public String toString()
      {
         return "["+key+","+val+"]";
      }
   }

   public Value get(Key key)
   {
      Node x = root;

      while (x != null)
      {
         int cmp = key.compareTo(x.key);

         if (cmp == 0) 
            return x.val;
         else if (cmp < 0) 
            x = x.left;
         else if (cmp > 0) 
            x = x.right;
      }
      return null;
   }

   /* ==============================================
      Main insert method
      ============================================== */
   public void put(Key key, Value value)
   {
      root = insert(root, key, value);
      root.color = BLACK;
   }

   /* ==============================================
      HELPER insert method (recursive)

          h = root node of the subtree

      Insert (key, value) in subtree at root h
      Return the new tree after insertion
      ============================================== */
   private Node insert(Node h, Key key, Value value)
   {
      if (h == null) 
         return new Node(key, value);

      /* -------------------------------------
         This is the BST insert alg.
         ------------------------------------- */

      int cmp = key.compareTo(h.key);

      if (cmp < 0) 
         h.left = insert(h.left, key, value);   // insert (key, value) in left
      else if (cmp > 0)
         h.right = insert(h.right, key, value); // insert (key, value) in right
      else
         h.val = value;  // Found, update value

      if (isRed(h.right) && !isRed(h.left))   // This forces Left Leaning RB
         h = rotateLeft(h);
      if (isRed(h.left) && isRed(h.left.left)) 
         h = rotateRight(h);
      if (isRed(h.left) && isRed(h.right)) 
         colorFlip(h);

      return h;
   }


   private Node fixUp(Node h)
   {
      /* ==============================
	 Fix RB tree (on the way UP)
	 ============================== */

      /* ------------------------------------------------
              h                   x
             / \       ===>      / \
           y(B) x(R)          h(R*) b
               / \             / \
              a   b          y(B) a
         ------------------------------------------------ */
      if (isRed(h.right) && !isRed(h.left))   // This forces Left Leaning RB
         h = rotateLeft(h);

      /* ------------------------------------------------
                  h                   x(R)
                 / \       ===>      / \
               x(R) y              a(R) h(R*)
              / \                      / \
            a(R) b                    b   y
         ------------------------------------------------ */
      if (isRed(h.left) && isRed(h.left.left)) 
         h = rotateRight(h);

      /* ------------------------------------------------
                  h(B)                h(R)
                 / \       ===>      / \
               x(R) y(R)           x(B) y(B)
         ------------------------------------------------ */
      if (isRed(h.left) && isRed(h.right)) 
         colorFlip(h);

      return h;
   }



   public void delete(Key key)
   {
      root = delete(root, key);
      root.color = BLACK;
   }

   private Node delete(Node h, Key key)
   {
      if (key.compareTo(h.key) < 0)
      {
         if (!isRed(h.left) && !isRed(h.left.left))
            h = moveRedLeft(h);
         h.left = delete(h.left, key);
      }
      else
      {
         if (isRed(h.left))
            h = rotateRight(h);

         if (key.compareTo(h.key) == 0 && (h.right == null))
            return null;

         if (!isRed(h.right) && !isRed(h.right.left))
            h = moveRedRight(h);

         if (key.compareTo(h.key) == 0)
         {
            Node minNode = null;
/*
            minNode = min( h.right );  // Node with min in right subtree
*/
            h.val = minNode.val;
            h.key = minNode.key;

            h.right = deleteMin(h.right);
         }
         else 
            h.right = delete(h.right, key);
      }

      return fixUp(h);
   }


   public void deleteMin()
   {
      root = deleteMin(root);
      root.color = BLACK;
   }

   private Node deleteMin(Node h)
   {
         if (h.left == null) 
            return null;

         if (!isRed(h.left) && !isRed(h.left.left))
            h = moveRedLeft(h);

         h.left = deleteMin(h.left);
         return fixUp(h);
   }


   /* ====================================================
      Auxiliary methods 
      ==================================================== */
   Node rotateLeft(Node h)
   {
      printTree();
      System.out.println("*** rotateLeft(" + h + ")");

      Node x = h.right;
      h.right = x.left;
      x.left = h;
      x.color = h.color;
      h.color = RED;
      return x;
   }

   Node rotateRight(Node h)
   {
      printTree();
      System.out.println("*** rotateRight(" + h + ")");

      Node x = h.left;
      h.left = x.right;
      x.right = h;
      x.color = h.color;
      h.color = RED;
      return x;
   }

   void colorFlip(Node h)
   {
      printTree();
      System.out.println("*** flipColor(" + h + ")");

      h.color = !h.color;
      h.left.color = !h.left.color;
      h.right.color = !h.right.color;
   }

   /* =======================================
      Auxiliary methods for delete
      ======================================= */
   private Node moveRedLeft(Node h)
   {
      colorFlip(h);
      if (isRed(h.right.left))
      {
         h.right = rotateRight(h.right);
         h = rotateLeft(h);
         colorFlip(h);
      }
      return h;
   }

   private Node moveRedRight(Node h)
   {
      colorFlip(h);
      if (isRed(h.left.left))
      {
         h = rotateRight(h);
         colorFlip(h);
      }
      return h;
   }

   boolean isRed(Node x)
   {
      if ( x == null )
         return false;      // external nodes are black

      return x.color;
   }


   /* ==========================
      Print tree
      ========================== */
   public void printnode(Node x, int h)
   {
      String color;

      color = x.color?"(R)":"(B)";

      for (int i = 0; i < h; i++)
         System.out.print("               ");

      System.out.println("[" + x.key + "," + x.val + "]" + color);
   }

   void printTree()
   {
      System.out.println("================================================");
      showR( root, 0 );
      System.out.println("================================================");
   }

   public void showR(Node t, int h)
   {
      if (t == null)
         return;

      showR(t.right, h+1);
      printnode(t, h);
      showR(t.left, h+1);
   }
}