"""binary tree code and examples
"""

from csc148_queue import Queue
from typing import Union


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

    def __init__(self, value: object, left: Union['BinaryTree', None]=None,
                 right: Union['BinaryTree', None]=None) -> None:
        """
        Create BinaryTree self with value and children left and right.

        """
        self.value, self.left, self.right = value, left, right

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

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

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

        >>> 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="") -> str:
        """
        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: object) -> bool:
        """
        Return whether tree rooted at self contains value.

        >>> t = BinaryTree(5, BinaryTree(7), BinaryTree(9))
        >>> 7 in t
        True
        >>> t = BinaryTree(5, BinaryTree(7), None)
        >>> 3 in t
        False
        """
        if self.left is None and self.right is None:
            return self.value == value
        elif self.left is None:
            return self.value == value or value in self.right
        elif self.right is None:
            return self.value == value or value in self.left
        else:
            return (self.value == value
                    or value in self.left
                    or value in self.right)

        # another solution
        # 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))


def contains(node: Union[BinaryTree, None], value: object) -> bool:
    """
    Return whether tree rooted at self contains value.

    >>> t = BinaryTree(5, BinaryTree(7), BinaryTree(9))
    >>> contains(t,5)
    True
    >>> contains(t,2)
    False
    >>> t1 = BinaryTree(5, BinaryTree(7,BinaryTree(3)), None)
    >>> contains(t1,1)
    False
    """
    if node.left is None and node.right is None:
        return node.value == value
    elif node.left is None:
        return node.value == value or contains(node.right, value)
    elif node.right is None:
        return node.value == value or contains(node.left, value)
    else:
        return (node.value == value
                or contains(node.left, value)
                or contains(node.right, value))

    # another solution
    # if node.left is None and node.right is None:
    #     return node.value == value
    # else:
    #     return (node.value == value
    #             or (node.left is not None and contains(node.left,value))
    #             or (node.right is not None and contains(node.right,value)))
    # another solution


def evaluate(b: BinaryTree) -> Union[float, object]:
    """
    Evaluate the expression rooted at b.  If b is a leaf,
    return its float value.  Otherwise, evaluate b.left and
    b.right and combine them with b.value.

    Assume:  -- b is a non-empty binary tree
             -- interior nodes contain value in {"+", "-", "*", "/"}
             -- interior nodes always have two children
             -- leaves contain float value

    >>> b = BinaryTree(3.0)
    >>> evaluate(b)
    3.0
    >>> b = BinaryTree("*", BinaryTree(3.0), BinaryTree(4.0))
    >>> evaluate(b)
    12.0
    """
    if b.left is None and b.right is None:
        return b.value
    else:
        return eval("{} {} {}".format(evaluate(b.left),
                                      b.value,
                                      evaluate(b.right)))

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