import random
import queue

# This file demonstrates simple manipulation
# of a binary tree data type.
# Each node contains an extra attribute "mark", which
# may used for demonstrative purposes.


# ............................................................................
#                                 N O D E
# ............................................................................

class Node:
    def __init__(self, key = 0):
        self.left = None
        self.right = None
        self.key = key
        self.xcoord = -1
        self.mark = ' ' # one character

    def print(self):
        print( "[" + str(self.key) + "]", end = "" )
        print( str(self.xcoord)+", ", end = "")


# ............................................................................
#                         B I N A R Y   T R E E
# ............................................................................

# Many methods in this demonstration are recursive.
# Recursive approach is discussed in the next lesson(s).
# BFS method itself is not recursive.
# However, it is often very convenient
# # to express other tree management method in recursive way.


class BinaryTree:
    def __init__(self, key ):
        self.root = Node( key )

    # recursive
    def setMarks(self, node, aMark):
        if node == None: return
        node.mark = aMark
        self.setMarks(node.left, aMark)
        self.setMarks(node.right, aMark)

    # recursive
    def  rndTree(self, node, depth):
        node.key = 10+random.randrange(90)
        if depth <= 0:  return node

        if random.randrange(0, 10) < 6:
            childNode = Node ( )  # empty node
            node.left = self.rndTree( childNode, depth-1 )

        if random.randrange(0, 10) < 6:
            childNode = Node ( )  # empty node
            node.right = self.rndTree( childNode, depth-1 )
        return node

    # recursive
    def count(self, node):
        if (node == None): return 0
        return 1 + self.count(node.left) + self.count(node.right)

    # recursive
    def depth (self, node):
        if (node == None): return -1
        return 1 + max(self.depth(node.left), self.depth(node.right))

    # calculates x coord = node order of in Inorder traversal
    def setXcoord(self, node, x_coord):
        if node == None: return x_coord
        node.xcoord = self.setXcoord(node.left, x_coord) + 1
        #print(node.key, node.setXcoord)
        return self.setXcoord(node.right, node.xcoord)


    def display(self):
        qu = queue.Queue()
        prevDepth = -1
        prevEndX = -1
        # in the queue store pairs(node, its depth)
        qu.put( (self. root, 0) )
        while not qu.empty():

            node, nodeDepth = qu.get()

            LbranchSize = RbranchSize = 0
            if node.left != None:
                LbranchSize = (node.xcoord - node.left.xcoord)
                qu.put( (node.left, nodeDepth+1) )
            if node.right != None:
                RbranchSize = (node.right.xcoord - node.xcoord)
                qu.put( (node.right, nodeDepth+1) )

            LspacesSize = (node.xcoord - LbranchSize) - 1  # if first on a line
            if prevDepth == nodeDepth:                  # not first on line
                LspacesSize -= prevEndX

            # print the node, branches, leading spaces
            if prevDepth < nodeDepth and prevDepth > -1 : print() # next depth occupies new line
            nodelen = 3
            print( " "*nodelen*LspacesSize, end = '' )
            print( "_"*nodelen*LbranchSize, end = ''  )
            #print( "." + ("%2d"%node.key) + node.mark+".", end = '' )
            print( node.mark + ("%2d"%node.key), end = ''  )
            print( "_"*nodelen*RbranchSize, end = ''  )

            # used in the next run of the loop:
            prevEndX = node.xcoord + RbranchSize
            prevDepth = nodeDepth
        # end of queue processing

        N = self.countNodes( self.root )
        print("\n"+ '-'*N*nodelen) # finish the last line of the tree

    def displayMarked(self, nodesList, char):
        self.setMarks(self.root, ' ')
        for quNode in nodesList:
            quNode.mark = '*'
        self.display()

    # recursive
    def countNodes(self, node):
        if node == None: return 0
        return 1 + self.countNodes( node.left ) + self.countNodes( node.right )

    # recursive
    def depth2 (self, node):
        if (node == None): return -1
        node.mark = '*'
        self.display()
        return 1 + max(self.depth2(node.left), self.depth2(node.right))

    # ........................................................................
    #    B F S   method
    # ........................................................................

    def BFS(self):
        qu = queue.Queue()
        qu.put( self.root )
        k = 0
        while not qu.empty():
            # list queue contents
            print( "Queue:",  [t.key for t in qu.queue]  )
            # display tree and highlight nodes in the queue
            self.displayMarked( qu.queue, '*')

            # process
            node = qu.get()
            print( "processed node:", node.key,)
            if node.left != None: qu.put( node.left )
            if node.right != None: qu.put( node.right )


# also try: seed 1025 depth 3   38 3 (1038)

random.seed( 1025 )
t = BinaryTree( 0 )

print( " rnd tree 2 " )
t.rndTree( t.root, 4 )

print( "set x coord" )
t.setXcoord(t.root, 0)

print( "Display ")
t.display()

print( "BFS" )
t.BFS()


exit()



'''

# A small overview of tree related concepts

Node
    A node is a structure which may contain a value
    or condition, or represent a separate data structure.
Root
    The top node in a tree, the prime ancestor.
Child
    A node directly connected to another node
    when moving away from the root, an immediate descendant.
Parent
    The converse notion of a child,
    an immediate ancestor.
Siblings
    A group of nodes with the same parent.
Neighbor
    Parent or child.
Descendant
    A node reachable by repeated proceeding
    from parent to child. Also known as subchild.
Ancestor
    A node reachable by repeated proceeding from child to parent.
Leaf / External node (not common)
    A node with no children.
Branch node / Internal node
    A node with at least one child.
Degree
    For a given node, its number of children. A leaf is necessarily
    degree zero. The degree of a tree is the degree of its root.
Degree of tree
    The degree of the root.
Edge
    The connection between one node and another.
Path
    A sequence of nodes and edges connecting a node with a descendant.
Distance
    The number of edges along the shortest path between two nodes.
Depth
    The distance between a node and the root.
Level
    1 + the number of edges between a node and the root, i.e. (Depth + 1)
Height
    The number of edges on the longest path between a node and a descendant leaf.
Width
    The number of nodes in a level.
Breadth
    The number of leaves.
Height of tree
    The height of the root node or the maximum level of any node in the tree
Forest
    A set of disjoint trees.
Sub Tree
    A tree T is a tree consisting of a node in T and all of its descendants in T.
Ordered Tree
    A rooted tree in which an ordering is specified for the children of each vertex.
Size of a tree
    Number of nodes in the tree.

'''
