'''
This file implements the Heap data structure as a subclass of the BinaryTree.
The book implements Heaps using an *implicit* tree with an *explicit* vector implementation,
so the code in the book is likely to be less helpful than the code for the other data structures.
The book's implementation is the traditional implementation because it has a faster constant factor
(but the same asymptotics).
'''

from containers.BinaryTree import BinaryTree


class Heap(BinaryTree):
    '''
    FIXME:
    Heap is currently not a subclass of BinaryTree.
    You should make the necessary changes in the class declaration line above
    and in the constructor below.
    '''

    def __init__(self, xs=None):
        '''
        FIXME:
        If xs is a list (i.e. xs is not None),
        then each element of xs needs to be inserted into the Heap.
        '''
        super().__init__()
        self.numel = 0
        self.tree_list = [float('-inf')]
        if xs is not None:
            self.insert_list(list(xs))

    def __repr__(self):
        '''
        Notice that in the BinaryTree class,
        we defined a __str__ function,
        but not a __repr__ function.
        Recall that the __repr__ function should return a string that can be used to recreate a valid instance of the class.
        Thus, if you create a variable using the command Heap([1,2,3])
        it's __repr__ will return "Heap([1,2,3])"

        For the Heap, type(self).__name__ will be the string "Heap",
        but for the AVLTree, this expression will be "AVLTree".
        Using this expression ensures that all subclasses of Heap will have a correct implementation of __repr__,
        and that they won't have to reimplement it.
        '''
        return type(self).__name__ + '(' + str(self.to_list('inorder')[1:]) + ')'

    def is_heap_satisfied(self):
        '''
        Whenever you implement a data structure,
        the first thing to do is to implement a function that checks whether
        the structure obeys all of its laws.
        This makes it possible to automatically test whether insert/delete functions
        are actually working.
        '''
        if self.root:
            return Heap._is_heap_satisfied(self.root)
        return True

    @staticmethod
    def _is_heap_satisfied(node):
        '''
        FIXME:
        Implement this method.
        '''
        if node is None:
            return True
        ret = True
        if node.left:
            if node.left.value < node.value:
                return False
            else:
                ret &= Heap._is_heap_satisfied(node.left)
        if node.right:
            if node.right.value < node.value:
                return False
            else:
                ret &= Heap._is_heap_satisfied(node.right)
        return ret

    def insert(self, value):
        '''
        Inserts value into the heap.

        FIXME:
        Implement this function.

        HINT:
        The pseudo code is
        1. Find the next position in the tree using the binary representation of the total number of nodes
            1. You will have to explicitly store the size of your heap in a variable (rather than compute it) to maintain the O(log n) runtime
            1. See https://stackoverflow.com/questions/18241192/implement-heap-using-a-binary-tree for hints
        1. Add `value` into the next position
        1. Recursively swap value with its parent until the heap property is satisfied

        HINT:
        Create a @staticmethod helper function,
        following the same pattern used in the BST and AVLTree insert functions.
        '''
        self.numel += 1
        binary = "{0:b}".format(self.numel)[1:]
        self.tree_list.append(value)
        self.do_swaps(binary)

    def do_swaps(self, binary):
        if binary == '':
            return
        if not self.is_heap_satisfied():
            idx = int(binary)
            idx_2 = int(binary[:-1])
            if self.tree_list[idx] < self.tree_list[idx_2]:
                tmp = self.tree_list[idx]
                self.tree_list[idx] = self.tree_list[idx_2]
                self.tree_list[idx_2] = tmp
        if not self.is_heap_satisfied():
            self.do_swaps(binary[:-1])

    def do_down_swaps(self, binary):
        if not self.is_heap_satisfied():
            idx = int(binary)
            while (idx * 2) <= self.numel:
                mc = self.min_child(idx)
                if self.tree_list[idx] > self.tree_list[mc]:
                    tmp = self.heap_tree[idx]
                    self.heap_tree[idx] = self.heap_tree[mc]
                    self.heap_tree[mc] = tmp
                idx = mc

    def min_child(self, idx):
        if idx * 2 + 1 > self.numel:
            return idx * 2
        else:
            if self.tree_list[idx * 2] < self.tree_list[idx * 2 + 1]:
                return idx * 2
            else:
                return idx * 2 + 1

    def to_list(self, order):
        return self.tree_list

    def insert_list(self, xs):
        '''
        Given a list xs, insert each element of xs into self.

        FIXME:
        Implement this function.
        '''
        for x in xs:
            self.insert(x)

    def find_smallest(self):
        '''
        Returns the smallest value in the tree.

        FIXME:
        Implement this function.
        '''
        return min(self.tree_list[1:])

    def remove_min(self):
        '''
        Removes the minimum value from the Heap.
        If the heap is empty, it does nothing.

        FIXME:
        Implement this function.

        HINT:
        The pseudocode is
        1. remove the bottom right node from the tree
        2. replace the root node with what was formerly the bottom right
        3. "trickle down" the root node: recursively swap it with its largest child until the heap property is satisfied

        HINT:
        I created two @staticmethod helper functions: _remove_bottom_right and _trickle.
        It's possible to do it with only a single helper (or no helper at all),
        but I personally found dividing up the code into two made the most sense.
        '''
        retval = self.find_smallest()
        idx = self.tree_list.index(retval)
        self.tree_list[idx] = self.tree_list[self.numel]
        self.numel -= 1
        self.tree_list.pop()
        self.do_down_swaps(idx)
        return retval
