"""binary tree code and examples
"""


from csc148_queue import Queue


class BinaryTree:
    """
    A Binary Tree, i.e. arity 2.
    """

    def __init__(self, value, left=None, right=None):
        """
        Create BinaryTree self with value and children left and right.

        @param BinaryTree self: this binary tree
        @param object value: value of this node
        @param BinaryTree|None left: left child
        @param BinaryTree|None right: right child
        @rtype: None
        """
        self.value, self.left, self.right = value, left, right

    def __eq__(self, other):
        """
        Return whether BinaryTree self is equivalent to other.

        @param BinaryTree self: this binary tree
        @param Any other: object to check equivalence to self
        @rtype: bool

        >>> BinaryTree(7).__eq__("seven")
        False
        >>> b1 = BinaryTree(7, BinaryTree(5))
        >>> b1.__eq__(BinaryTree(7, BinaryTree(5), None))
        True
        """
        return (type(self) == type(other) and
                self.value == other.value and
                (self.left, self.right) == (other.left, other.right))

    def __repr__(self):
        """
        Represent BinaryTree (self) as a string that can be evaluated to
        produce an equivalent BinaryTree.

        @param BinaryTree self: this binary tree
        @rtype: str

        >>> BinaryTree(1, BinaryTree(2), BinaryTree(3))
        BinaryTree(1, BinaryTree(2, None, None), BinaryTree(3, None, None))
        """
        return "BinaryTree({}, {}, {})".format(repr(self.value),
                                               repr(self.left),
                                               repr(self.right))

    def __str__(self, indent=""):
        """
        Return a user-friendly string representing BinaryTree (self)
        inorder.  Indent by indent.

        >>> b = BinaryTree(1, BinaryTree(2, BinaryTree(3)), BinaryTree(4))
        >>> print(b)
            4
        1
            2
                3
        <BLANKLINE>
        """
        right_tree = (self.right.__str__(
            indent + "    ") if self.right else "")
        left_tree = self.left.__str__(indent + "    ") if self.left else ""
        return (right_tree + "{}{}\n".format(indent, str(self.value)) +
                left_tree)

    def __contains__(self, value):
        """
        Return whether tree rooted at self contains value.

        @param BinaryTree self: binary tree to search for value
        @param object value: value to search for
        @rtype: bool

        >>> t = BinaryTree(5, BinaryTree(7), BinaryTree(9))
        >>> t.__contains__(7)
        True
        """
        # if self.left is None and self.right is None:
        #     return self.value == value
        # else:
        #     return (self.value == value
        #             or (self.left is not None and self.left.contains(value))
        #             or (self.right is not None and self.right.contains(value)))
        return (self.value == value
                or (self.left is not None and value in self.left)
                or (self.right is not None and value in self.right))


# assume binary search tree order property
def bst_contains(node, value):
    """
    Return whether tree rooted at node contains value.

    Assume node is the root of a Binary Search Tree

    @param BinaryTree|None node: node of a Binary Search Tree
    @param object value: value to search for
    @rtype: bool

    >>> bst_contains(None, 5)
    False
    >>> bst_contains(BinaryTree(7, BinaryTree(5), BinaryTree(9)), 5)
    True
    """
    if node is None:
        return False
    elif value > node.value:
        return bst_contains(node.right, value)
    elif value < node.value:
        return bst_contains(node.left, value)
    else:
        return True

def insert(node, value):
    """
    Insert value in BST rooted at node if necessary, and return new root.

    Assume node is the root of a Binary Search Tree.

    @param BinaryTree|None node: root of a binary search tree.
    @param object value: value to insert into BST, if necessary.
    @rtype: BinaryTree

    >>> b = BinaryTree(5)
    >>> b1 = insert(b, 3)
    >>> print(b1)
    5
        3
    <BLANKLINE>
    """
    if node is None:
        node = BinaryTree(value)
    elif value > node.value:
        node.right = insert(node.right, value)
    elif value < node.value:
        node.left = insert(node.left, value)
    return node

def find_max(node):
    """
    Find and return subnode with maximum data.

    Assume node is the root of a binary search tree.

    @param BinaryTree node: binary tree node to begin search from
    @rtype: BinaryTree

    >>> find_max(BinaryTree(5, BinaryTree(3), BinaryTree(7)))
    BinaryTree(7, None, None)
    """
    return find_max(node.right) if node.right is not None else node


def delete(node, value):
    """
    Delete data from binary search tree rooted at node, if it exists,
    and return root of resulting tree.

    @param BinaryTree|None node: tree to delete data from
    @param object value: data to delete
    @rtype: BinaryTree|None

    >>> b = BinaryTree(8)
    >>> b = insert(b, 4)
    >>> b = insert(b, 2)
    >>> b = insert(b, 6)
    >>> b = insert(b, 12)
    >>> b = insert(b, 14)
    >>> b = insert(b, 10)
    >>> b = delete(b, 12)
    >>> print(b)
            14
        10
    8
            6
        4
            2
    <BLANKLINE>
    >>> b = delete(b, 14)
    >>> print(b)
        10
    8
            6
        4
            2
    <BLANKLINE>
    """
    # 1. If this node is None, return that
    if node is None:
        pass
    # 2. If value is more than node.value, delete it from right child and
    #     return this node
    elif value > node.value:
        node.right = delete(node.right, value)
    # 3. If value is less than node.value, delete it from left child
    #     and return this node
    elif value < node.value:
        node.left = delete(node.left, value)
    # 4. If node with value has fewer than two children,
    #     and you know one is None, return the other one
    elif node.left is None:
        node = node.right
    elif node.right is None:
        node = node.left
    # 5. If node with value has two non-None children,
    #     replace value with that of its largest child in the left subtree,
    #     and delete that child, and return this node
    else:
        node.value = find_max(node.left).value
        node.left = delete(node.left, node.value)
    return node

if __name__ == "__main__":
    import doctest
    doctest.testmod()
