# -*- coding: utf-8 -*-

class InvalidBTreeException(Exception):
  pass

class NonFullSplitException(Exception):
  pass
  
class BTree :
  def __init__(self,chld,elem,content,degree=2,isLeaf=True,isRoot=True):
    ''' Default: 2-3-4 Tree; t >= 2 degree; t-1 <= num.of.keys <= 2t-1, t<= #children <=2t'''
    global MaxVALUE
    self.isRoot = isRoot
    self.isLeaf = isLeaf
    self.degree = degree
    self.children = chld
    self.keys = elem
    self.content = content
    
  def checkNode() :
    if len(self.keys)>=2*self.degree : 
      raise InvalidBTreeException, "This is not a valid BTree node!.. too children"
    if ((not self.isRoot) and len(self.keys)<self.degree-1) : 
      raise InvalidBTreeException, "This is not a valid BTree node!.. too few children"

  def isFull(self):
    return (2*self.degree-1)==len(self.keys)

  def height(self) :
    if self.isLeaf : return 1
    else : return 1 + self.children[0].height()

  def numNodes(self) :
    if self.isLeaf : return 1
    else : return sum([c.numNodes() for c in self.children])

  def getkeys(self) :
    if self.isLeaf :
      li = self.keys[:]
    else :
      li = self.keys[:]
      for c in self.children :
        li += c.getkeys()
    return li

  def getListNodes(self,L) :
    if self.isLeaf :
      L.append(self)
    else :
      for c in self.children :
        c.getListNodes(L)
      L.append(self)
    
  def search(self,key):
    ''' If k is in the B-tree, B-TREE-SEARCH returns the ordered pair (y, i)
        consisting of a node y and an index i such that key_i[y] = k.
        Otherwise, the value NIL is returned. '''
    # Find smallest index such that key < self.keys[i]
    i=0
    while i<len(self.keys) and key>self.keys[i] : i+=1
    # Did we find the key?
    if i<len(self.keys) and key==self.keys[i] :
      return self, i
    # Are we in a leaf? If so, search ends unsuccessfully..
    elif self.isLeaf :
      return None, None
    # Recursively try on self.children at same index i
    else :
      return self.children[i].search(key)


  def split_child(self, i, y) : 
    '''split the i-th child of self: y, because it is full; after this operation self will have y and new as children and the i-th key in self'''
    elem_chld  = []
    elem_keys  = []
    elem_content = []
    if not y.isLeaf :
      elem_chld   = y.children[y.degree:]
      y.children  = y.children[:y.degree]

    elem_keys  = y.keys[y.degree:]
    elem_content = y.content[y.degree:]

    new_key_in_self   = y.keys[y.degree-1]
    new_content_in_self = y.content[y.degree-1]

    y.keys   = y.keys[:y.degree-1]
    y.content = y.content[:y.degree-1]
    
    new = BTree(elem_chld, elem_keys, elem_content, y.degree, y.isLeaf, y.isRoot)
            
    self.children = self.children[:i+1] + [new]         + self.children[i+1:]
    self.keys    = self.keys[:i]    + [new_key_in_self]   + self.keys[i:]
    self.content = self.content[:i]  + [new_content_in_self] + self.content[i:]

  def insert(self, key, content):
    """
As with binary search trees, we search for the leaf position at which to
insert the new key. With a B-tree, however, we cannot simply create a new leaf node and
insert it, as the resulting tree would fail to be a valid B-tree.
Instead, we insert the new key into an existing leaf node. Since we cannot insert a key into a leaf node that is full, we
introduce an operation that splits a full node y (having 2t - 1 keys) around its median key
keyt[y] into two nodes having t - 1 keys each.
The median key moves up into y's parent to identify the dividing point between the two new trees. But if y's parent is also full, it must be
split before the new key can be inserted, and thus this need to split full nodes can propagate
all the way up the tree.
As with a binary search tree, we can insert a key into a B-tree in a single pass down the tree
from the root to a leaf. To do so, we do not wait to find out whether we will actually need to
split a full node in order to do the insertion. Instead, as we travel down the tree searching for
the position where the new key belongs, we split each full node we come to along the way
(including the leaf.
  """
    if self.isRoot and self.isFull() :
      self.isRoot = False
      if (len(self.children) > 0) :
        self.isLeaf = False
      else :
        self.isLeaf = True
        
      new_root = BTree([self], [], [], self.degree, False, True)

      new_root.split_child(0, self)
      new_root.insertNonFull(key, content)
      return new_root
    else :
      self.insertNonFull(key, content)
      return self

  def insertNonFull(self, key, content) :
    '''
The auxiliary recursive procedure B-TREE-INSERT-NONFULL inserts a key into node self,
which is assumed to be nonfull when the procedure is called. The operation of B-TREEINSERT
and the recursive operation of B-TREE-INSERT-NONFULL guarantee that this
assumption is true.
'''      
    if self.isLeaf :
      i = 0;
      while i < len(self.keys) and key > self.keys[i] : i +=1
      self.keys[:] = self.keys[:i] + [key] + self.keys[i:]
      self.content[:] = self.content[:i] + [content] + self.content[i:]  
    else :
      i = 0;
      while i < len(self.keys) and key > self.keys[i] : i +=1
      if self.children[i].isFull() :
        self.split_child(i, self.children[i])
        if key > self.keys[i] : i = i + 1
      self.children[i].insertNonFull(key, content)

  def printtree(self,prefix=''):
    print prefix, self.keys, ' (content = ', self.content,')'
    for i in self.children :
      i.printtree(prefix+'  ')



  def delete(self, key):
   """
Assume that procedure B-TREE-DELETE is asked to delete the key k from the subtree rooted
at x. This procedure is structured to guarantee that whenever B-TREE-DELETE is called
recursively on a node x, the number of keys in x is at least the minimum degree t. Note that
this condition requires one more key than the minimum required by the usual B-tree
conditions, so that sometimes a key may have to be moved into a child node before recursion
descends to that child. This strengthened condition allows us to delete a key from the tree in
one downward pass without having to "back up" (with one exception, which we'll explain).


1. If the key k is in node x and x is a leaf, delete the key k from x.
2. If the key k is in node x and x is an internal node, do the following.
a. If the child y that precedes k in node x has at least t keys, then find the
predecessor k′ of k in the subtree rooted at y. Recursively delete k′, and replace
k by k′ in x. (Finding k′ and deleting it can be performed in a single downward
pass.) 
b. Symmetrically, if the child z that follows k in node x has at least t keys, then
find the successor k′ of k in the subtree rooted at z. Recursively delete k′, and
replace k by k′ in x. (Finding k′ and deleting it can be performed in a single
downward pass.)
c. Otherwise, if both y and z have only t - 1 keys, merge k and all of z into y, so
that x loses both k and the pointer to z, and y now contains 2t - 1 keys. Then,
free z and recursively delete k from y.
3. If the key k is not present in internal node x, determine the root ci[x] of the appropriate
subtree that must contain k, if k is in the tree at all. If ci[x] has only t - 1 keys, execute
step 3a or 3b as necessary to guarantee that we descend to a node containing at least t
keys. Then, finish by recursing on the appropriate child of x.
a. If ci[x] has only t - 1 keys but has an immediate sibling with at least t keys,
give ci[x] an extra key by moving a key from x down into ci[x], moving a key
from ci[x]'s immediate left or right sibling up into x, and moving the
appropriate child pointer from the sibling into ci[x].
b. If ci[x] and both of ci[x]'s immediate siblings have t - 1 keys, merge ci[x] with
one sibling, which involves moving a key from x down into the new merged
node to become the median key for that node.



Since most of the keys in a B-tree are in the leaves, we may expect that in practice, deletion
operations are most often used to delete keys from leaves. The B-TREE-DELETE procedure
then acts in one downward pass through the tree, without having to back up. When deleting a
key in an internal node, however, the procedure makes a downward pass through the tree but
may have to return to the node from which the key was deleted to replace the key with its
predecessor or successor (cases 2a and 2b).
"""      # in the current node "self" there are at lest "degree" keys
   if self.isLeaf :
     i=0
     while i<len(self.keys) and key>self.keys[i] : i+=1
     if i < len(self.keys) and key == self.keys[i] : # key is in the current Leaf node
       self.keys[:] = self.keys[:i]+self.keys[i+1:]
     return self if self.isRoot else None 
   else : # self is an internal node
     i=0
     while i<len(self.keys) and key>self.keys[i] : i+=1
     if i < len(self.keys) and key == self.keys[i] : # key is in the current internal node
       k = self.keys[i]
       y = self.children[i] # the child of self that precedes key
       z = self.children[i+1]

       if len(y.keys) >= self.degree :   # 2.a
         print '2.a'
         k_1 = y.keys[len(y.keys)-1]
         self.keys[i] = k_1

         self.children[i] = y
         a=self.children[i].delete(k_1)
         return a if a and a.isRoot else self if self.isRoot else None

       elif len(z.keys) >= self.degree : # 2.b
         print '2.b'
         k_1 = z.keys[0]
         self.keys[i] = k_1

         self.children[i+1] = z
         a=self.children[i+1].delete(k_1)
         return a if a and a.isRoot else self if self.isRoot else None

       else :             # 2.c
           print '2.c'
           self.keys[:] = self.keys[:i]+self.keys[i+1:]
           self.children[:] = self.children[:i]+self.children[i+1:]
           y.keys[:] = y.keys[:]+ [k] + z.keys[:]
           if not y.isLeaf :
             y.children[:] = y.children[:]+z.children[:]

           self.children[i] = y
           a=self.children[i].delete(k)
           return a if a and a.isRoot else self if self.isRoot else None

     else : # key is not in the current internal node
       cix = self.children[i]
       if len(cix.keys) == self.degree-1 :
         #execute steps 3.a or 3.b
         cix_left, cix_right = None, None
         if i > 0 : cix_left = self.children[i-1]
         if i < len(self.keys) : cix_right = self.children[i+1]

         if cix_left != None and len(cix_left.keys) >= self.degree : # 3.a.1
           # give ci[x] an extra key by moving a key from x down into ci[x]
           # i.e. move a key from ci[x]'s immediate left or right sibling
           #    up into x, and moving the appropriate child pointer from
           #    the sibling into ci[x].

           print '3.a.1'
           cix.keys[:] = cix.keys[:] + [ self.keys[i] ]
           cix.children[:] = cix.children[:] + cix.children[i+1]

           self.keys[i] = self.keys[i-1]
           self.keys[i-1] = cix_left.keys[len(cix_left.keys)-1]

           self.children[i+1] = self.children[i]
           if not cix_left.isLeaf :
             self.children[i] = cix_left.children[len(cix_left.children)-1]

           cix_left.keys[:] = cix_left.keys[:-1]
           if not cix_left.isLeaf :
             cix_left.children[:] = cix_left.children[:-1]

           self.children[i-1] = cix_left

         else :
           if cix_right != None and len(cix_right.keys) >= self.degree : # 3.a.2
             print '3.a.2'
             cix.keys[:] = cix.keys[:] + [ self.keys[i] ]
             if not cix_right.isLeaf :
               cix.children[:] = cix.children[:] + [ cix_right.children[0] ]

             self.keys[i] = cix_right.keys[0]

             cix_right.keys[:] = cix_right.keys[1:]
             if not cix_right.isLeaf :
               cix_right.children[:] = cix_right.children[1:]

             self.children[i+1] = cix_right

           else : # 3.b              
             if cix_left != None :
               print '3.b left'
               cix.keys[:] = cix_left.keys[:] + [self.keys[i-1] ]+ cix.keys[:]
               if not cix.isLeaf :
                 cix.children[:] = cix_left.children[:] + cix.children[:]
               self.keys[:] = self.keys[:i-1] + self.keys[i:]
               self.children[:] = self.children[:i-1]+self.children[i:]
               if i >= len(self.children) : i = len(self.children)-1;

             elif cix_right != None :
               print '3.b right'
               cix.keys[:] = cix.keys[:] + [self.keys[i] ]+ cix_right.keys[:]
               if not cix.isLeaf :
                 cix.children[:] = cix.children[:] + cix_right.children[i]
               self.keys[:] = self.keys[:i] + self.keys[i+1:]
               self.children[:] = self.children[:i+1]+self.children[i+2:] 

       if len(self.keys) > 0 :
         self.children[i] = cix
         a=self.children[i].delete(key)
         return a if a and a.isRoot else self if self.isRoot else None
       else :
         cix.isRoot = True
         a = cix.delete(key)
         return a if a and a.isRoot else cix


def fillBtree() :
  import random
  MaxVALUE = 1000
  bt = BTree([],[6,8],[random.randrange(MaxVALUE), random.randrange(MaxVALUE)], 2)
  bt=bt.insert(5, random.randrange(MaxVALUE))
  bt=bt.insert(3, random.randrange(MaxVALUE))
  bt=bt.insert(9, random.randrange(MaxVALUE))
  bt=bt.insert(10, random.randrange(MaxVALUE))
  bt=bt.insert(11, random.randrange(MaxVALUE))
  bt=bt.insert(12, random.randrange(MaxVALUE))
  bt=bt.insert(13, random.randrange(MaxVALUE))
  bt=bt.insert(14, random.randrange(MaxVALUE))
  bt=bt.insert(15, random.randrange(MaxVALUE))
  bt.printtree()
  return bt

if __name__ == '__main__' :
	fillBtree()
