import queue

# ............................................................................
#                                 N O D E
# ............................................................................

class Node:
    def __init__(self, key):
        self.left = None
        self.right = None
        self.key = key
        self.xcoord = -1
        self.tag = ' ' # 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:

    # ........................................................................
    #   C O N S T R U C T O R
    def __init__(self, key ):
        self.root = Node( key )

    # ........................................................................
    #   S E R V I C E   M E T H O D S    (mainly printing)
    def countNodes(self, node):
        if node == None: return 0
        return 1 + self.countNodes( node.left ) + self.countNodes( 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):
        self.setXcoord(self.root, 0)
        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.tag+".", end = '' )
            print( node.tag + ("%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

    # ........................................................................
    #   C U S T O M   F U N C T I O N (S),   T E S T E D  I N   M A I N (below)

    # add a new node to the existing tree
    # specify the key of its parent (to locate the place)
    # and the position (Left or Right) relative to the parent
    # If the specified parent and position do not exist do nothing.
    def addnode(self, nodekey, parentkey, LorR, currnode ):
        if currnode == None: return False
        if currnode.key == parentkey:
            # if can add node here do it and return true
            if LorR == 'L' and  currnode.left == None:
                currnode.left = Node( nodekey )
                return True
            if LorR == 'R' and  currnode.right == None:
                currnode.right = Node( nodekey )
                return True
        # when cannot add here, try both subtrees
        leftsuccess = self.addnode( nodekey, parentkey, LorR, currnode.left )
        if leftsuccess == True:  # do not continue to the right subtree
            return True
        rightsuccess = self.addnode( nodekey, parentkey, LorR, currnode.right )
        return rightsuccess  # whatever the success has been

    def addnodes (self, spec ):
        values = spec.split()
        for i in range( 0, len(values), 3 ):
            self.addnode( int(values[i]), int(values[i+1]), values[i+2], self.root )


    def balancedtree(self, fromval, toval ):
        self.root.key = fromval
        #  go through all dependencies, note the role of the index (BFS order of nodes)
        for index in range( 0,  (toval-fromval)//2 ):
            self.addnode( fromval+2*index+1, fromval+index, 'L', self.root )
            self.addnode( fromval+2*index+2, fromval+index, 'R', self.root )


# ............................................................................
#                M A I N   P R O G R A M
# ............................................................................


# ............................................................................
# Example 1

# create a tree with a single node -- root.
t = BinaryTree( 22 )
# apply a function to the tree
t.addnode( 11, 22, 'L', t.root )
# display the created tree
t.display()
print();print()

# ............................................................................
# Example 2

t = BinaryTree( 22 )
t.addnode( 11, 22, 'L', t.root )
t.addnodes( "33 22 R   30 33 L   40 33 R" )
t.display()
print();print()

# ............................................................................
# Example 3

t = BinaryTree( 22 )
t.balancedtree( 11, 50 )
t.display()


