""" Tree class and functions
"""
from csc148_queue import Queue
from typing import List, Any


class Tree:
    """
    A bare-bones Tree ADT that identifies the root with the entire tree.

    === Attributes ===
    value - value  of root node
    children - root nodes of children
    """
    value: object
    children: List["Tree"]

    def __init__(self, value: object, children: List["Tree"]=None) -> None:
        """
        Create Tree self with content value and 0 or more children
        """
        self.value = value
        # copy children if not None
        # NEVER have a mutable default parameter...
        self.children = children[:] if children is not None else []

    def __repr__(self) -> str:
        """
        Return representation of Tree (self) as string that
        can be evaluated into an equivalent Tree.

        >>> t1 = Tree(5)
        >>> t1
        Tree(5)
        >>> t2 = Tree(7, [t1])
        >>> t2
        Tree(7, [Tree(5)])
        """
        # Our __repr__ is recursive, because it can also be called
        # via repr...!
        return "Tree({}, {})".format(self.value, self.children)

    def __eq__(self, other: Any) -> bool:
        """
        Return whether this Tree is equivalent to other.

        >>> t1 = Tree(5)
        >>> t2 = Tree(5, [])
        >>> t1 == t2
        True
        >>> t3 = Tree(5, [t1])
        >>> t2 == t3
        False
        """
        return (type(self) is type(other) and
                self.value == other.value and
                self.children == other.children)

    def __str__(self, indent: int=0) -> str:
        """
        Produce a user-friendly string representation of Tree self,
        indenting each level as a visual clue.

        >>> t = Tree(17)
        >>> print(t)
        17
        >>> t1 = Tree(19, [t, Tree(23)])
        >>> print(t1)
           23
        19
           17
        >>> t3 = Tree(29, [Tree(31), t1])
        >>> print(t3)
              23
           19
              17
        29
           31
        """
        root_str = indent * " " + str(self.value)
        mid = len(self.children) // 2
        left_str = [c.__str__(indent + 3)
                    for c in self.children][: mid]
        right_str = [c.__str__(indent + 3)
                     for c in self.children][mid:]
        return "\n".join(right_str + [root_str] + left_str)

    def count_nodes(self) -> int:
        """
        Return the number of nodes in self.

        >>> t = descendants_from_list(Tree(19), [1, 2, 3, 4, 5, 6, 7], 3)
        >>> t.count_nodes()
        8
        """
        return 1 + sum([c.count_nodes() for c in self.children])
        pass

    def is_leaf(self) -> bool:
        """Return whether Tree self is a leaf

        >>> Tree(5).is_leaf()
        True
        >>> Tree(5,[Tree(7)]).is_leaf()
        False
        """
        return len(self.children) == 0

    def __contains__(self, v: object) -> bool:
        """
        Return whether Tree self contains v.

        >>> t = Tree(17)
        >>> t.__contains__(17)
        True
        >>> t = descendants_from_list(Tree(19), [1, 2, 3, 4, 5, 6, 7], 3)
        >>> t.__contains__(5)
        True
        >>> t.__contains__(18)
        False
        """
        return self.value == v or any([v in tree
                                    for tree in self.children])
        pass

    def height(self) -> int:
        """
        Return length of longest path, + 1, in tree rooted at self.

        >>> t = Tree(5)
        >>> t.height()
        1
        >>> t = descendants_from_list(Tree(7), [0, 1, 3, 5, 7, 9, 11, 13], 3)
        >>> t.height()
        3
        """

    def flatten(self) -> list:
        """ Return a list of all values in tree rooted at self.

        >>> t = Tree(5)
        >>> t.flatten()
        [5]
        >>> t = descendants_from_list(Tree(7), [0, 1, 3, 5, 7, 9, 11, 13], 3)
        >>> L = t.flatten()
        >>> L.sort()
        >>> L == [0, 1, 3, 5, 7, 7, 9, 11, 13]
        True
        """
        if self.is_leaf():
            return [self.value]
        else:
            return ([self.value]
                    + sum([c.flatten()
                           for c in self.children], []))


# helpful helper function
def descendants_from_list(t: Tree, list_: list, arity: int) -> Tree:
    """
    Populate Tree t's descendants from list_, filling them
    in in level order, with up to arity children per node.
    Then return t.

    >>> descendants_from_list(Tree(0), [1, 2, 3, 4], 2)
    Tree(0, [Tree(1, [Tree(3, []), Tree(4, [])]), Tree(2, [])])
    """
    q = Queue()
    q.add(t)
    list_ = list_.copy()
    while not q.is_empty():  # unlikely to happen
        new_t = q.remove()
        for i in range(0, arity):
            if len(list_) == 0:
                return t  # our work here is done
            else:
                new_t_child = Tree(list_.pop(0))
                new_t.children.append(new_t_child)
                q.add(new_t_child)
    return t
