# ============================================================================
#                         N O D E
# ============================================================================

class Node:

    def __init__(self, key):
        self.left = None     # mandatory
        self.right = None    # mandatory
        self.key = key       # mandatory
        # optional
        self.tag = ' ' # one character for demonstation purposes here

# ============================================================================
#                         B I N A R Y   T R E E
# ============================================================================

class BinaryTree:

    # .......Constructor ...........................................
    # Create initial binary tree with one node -- the root
    def __init__( self, key = 0 ):
        self.root = Node( key )

    # ........................................................................
    #       S E R V I C E     F U N C T O N S
    # ......IMPORTED..........................................................
    # The following BinaryTree methods are part of the present class BinaryTree,
    # they are stored separately in imported files, to improve readability of this file,
    from _treebuild_   import randomTree, addNode, addNodes
    from _treedisplay_ import display
    # def randomTree( self, node, depth ):
    # def addnode( self, parentKey, nodeKey ):
    # def addNodes( self, nodePairs ):          # pair == (parentKey, nodeKey)
    # def display( self ):
    # For purely technical reasons, other private functions are additionally imported
    from _treebuild_   import _addnoder
    from _treedisplay_ import _setXcoord, _countNodes
    # .......................................................................


    # ........................................................................
    #   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)
    # .............................................................. .........

    # Returns a list of all keys in the leaves of the *subtree* below node
    def keysInLeaves( self, node ):
        if node == None: return []

        if node.left == None and node.right == None:
           return [node.key]
        else:
           return self.keysInLeaves(node.left) + self.keysInLeaves(node.right)

    # =================================
    #    End of class BinaryTree
    # =================================


# ............................................................................
#                M A I N   P R O G R A M
# ............................................................................
t = BinaryTree( )

print( "Random tree" )
t.randomTree( t.root, 3 )  # 2nd param is maximum depth of the tree

print( "Display ")
t.display()

t.addNode( 68, 69 )
t.display()


t.addNodes( [ [44,52], [52,53], [53,54], [54,55], [55,56], [56,57], [54,59] ] )
t.display()

keys = t.keysInLeaves( t.root )
print( 'Keys in the leaves:', keys )





# Additional notes for the curious:

# 1. Empty tree
# By default, in these examples, and to avoid later tedious repetitive checks,
# the root node is always present in the tree.
# It is constructed with default key value 0, which is typically changed later.
# Thus, in these examples we mostly do not work with an empty tree.
# An empty tree is a sensible concept (like e.g. empty string or empty list)
# and it is used normally in practice. However, in the introductory examples
# it may not play that important role, functions would need to check for it and handle it
# separately and that would increase non-essential code and probably obscure other fundaments.

# 2. Tree "self-awareness"
# It is usual, when defining a tree structure (class, object), to equip 
# the tree definition with additional variables which store some important tree
# characteristics and which are appropriately updated each time the tree
# is changed or manipulated. For example, size = number of nodes, is a basic
# characterics of a particular tree (similar to length of a list etc.).
# When asked for its size, the tree does not have to run a separate calculation,
# it just returns the contents of the size variable.
# There are more such structural informations about a tree, worth storing, like
# its depth, number of leaves, etc.
# In these examples, to keep the code simple, we do not include such information
# in the tree definition. It is not vitally important on this level and it
# it is only a matter of coding, not of a concept, to include it.






























