import random
import queue

# This file demonstrates a simple manipulation with a
# binary tree data type.
# Technically, a tree discussed here is a so-called rooted tree.
# each node in a binary tree has at most two children,
# left and right one, if they exist.

# Optionally, see more data structure examples at
# https://xlinux.nist.gov/dads/

# ............................................................................
#                                 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
# ............................................................................

class BinaryTree:
    def __init__(self, key ):
        self.root = Node( key )

    def setTags(self, node, aMark ):
        if node == None: return
        node.mark = aMark
        self.setTags( node.left, aMark )
        self.setTags( node.right, aMark )

    def processInOrder(self, node):
        if (node == None): return
        self.processInOrder(node.left)
        print(node.key, end = " ")
        #node.print()
        self.processInOrder(node.right)

    def processPreOrder(self, node):
        if (node == None): return
        print(node.key, end = " ")
        self.processPreOrder(node.left)
        self.processPreOrder(node.right)

    def processPostOrder(self, node):
        if (node == None): return
        self.processPostOrder(node.left)
        self.processPostOrder(node.right)
        print(node.key, end = " ")

    def  rndTree0(self, node, depth):
        if (depth <= 0 or random.randrange(10) > 7) :
            return None
        newnode = Node(10+random.randrange(90))
        newnode.left = Node.rndTree0(depth-1)
        newnode.right = Node.rndTree0(depth-1)
        return newnode

    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

    def count(self, node):
        if (node == None): return 0
        return 1 + self.count(node.left) + self.count(node.right)

    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.setTags( self.root, ' ' )
        for quNode in nodesList:
            quNode.mark = '*'
        self.display()


    def countNodes(self, node):
        if node == None: return 0
        return 1 + self.countNodes( node.left ) + self.countNodes( node.right )

    def depth2 (self, node):
        if (node == None): return -1
        node.mark = '*'
        self.display()
        return 1 + max(self.depth2(node.left), self.depth2(node.right))

    def BFS(self):
        self.setTags( self.root, ' ' )
        qu = queue.Queue()
        qu.put( self.root )
        while not qu.empty():
            node = qu.get() # remove front
            print( node.key, end = ' ' )
            if node.left != None: qu.put( node.left )
            if node.right != None: qu.put( node.right )
        print()

    def BFS2(self):
        qu = queue.Queue()
        qu.put( self.root )
        k = 0
        while not qu.empty():
            # display tree and highlight nodes in the queue
            self.displayMarked( qu.queue, '*')
            # process
            node = qu.get()
            if node.left != None: qu.put( node.left )
            if node.right != None: qu.put( node.right )
        #print()

    def DFS(self):
        stack = [ [self.root, 0] ] # (node, no of visits)
        while stack != []:

            self.displayMarked( [item[0] for item in stack] , '*')

            node, nodeVisits = stack[-1]    # stack top
            print( " in node ", node.key )

            if nodeVisits == 0:
                stack[-1][1] += 1 # incr no. of visits
                if node.left != None:
                    stack.append( [node.left, 0] )  # push
                continue

            if nodeVisits == 1:
                stack[-1][1] += 1 # incr no. of visits
                if node.right != None:
                    stack.append( [node.right, 0] ) # push
                continue

            if nodeVisits == 2:
                stack.pop()

    # here, the stack does not contain the path from the root to the node
    def DFS9(self):
        stack = [ self.root ] # (node, no of visits)
        while stack != []:
            self.displayMarked( stack, '*')
            node = stack.pop()    # stack pop
            print( " in node ", node.key )
            if node.right != None:
                stack.append( node.right ) # push
            if node.left != None:
                stack.append( node.left )  # push



# https://xlinux.nist.gov/dads/

# seed 1025 depth 3   38 3

random.seed( 1038 )
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 plain" )
t.BFS()

print( "BFS2" )
t.BFS2()

#exit()

print( "Inorder " )
t.processInOrder(t.root)

print( "Preorder " )
t.processPreOrder(t.root)


print( "Inorder " )
t.processInOrder(t.root)

print( "\n depth 2 ")
t.root.depth = t.depth2( t.root )

print( "BFS2" )
t.BFS2()

print( "DFS" )
t.DFS()

print( "DFS9" )
t.DFS9()


