# -*- coding: UTF-8 -*-
#    This file is part of arena.
#
#    arena is free software: you can redistribute it and/or modify it
#    under the terms of the GNU General Public License as published by the
#    Free Software Foundation; either version 3 of the License or, at your
#    option, any later version.
#
#    arena is distributed in the hope that it will be useful, but
#    WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABLITY
#    or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
#    License for more details.
#
#    You should have received a copy of the GNU General Public License along
#    with arena.  If not, see <http://www.gnu.org/licenses/>.

## @file
#  This file defines a class to represent a node in a hierarchical, 
#  doubly-linked list.

from Library.Base.Classwork import AnObject
from Library.String.Utilities import IsPrintable

## A class that iterates over a node
class ANodeIterator(AnObject):
    ## Constructor
    def __init__(Self, ThisNode):
        AnObject.__init__(Self)
        
        Self._Next = ThisNode
        
    ## Initializer
    def init(Self):
        result = AnObject.init(Self)
        
        if result:
            # Establish private attributes
            # The next node to return
            Self._Next = None
            
        return result
        
    ## Return the next node
    def next(Self):
        if Self._Next is None:
            raise StopIteration
            
        Result = Self._Next
        Self._Next = Self._Next.Next()
        return Result
        
    # Properties ---------------------------------------------------------------
    ## Return an instance of the iterator
    def __iter__(Self):
        return Self

## A node
class ANode(AnObject, IsPrintable):
    ## Constructor
    def __init__(Self, **values):
        AnObject.__init__(Self)
        
        Self._values.update(values)
        
    ## Initializer
    def init(Self):
        result = AnObject.init(Self)
        
        if result:
            # Establish private attributes
            # Refers to the previous node in the list
            Self._Previous = None
            # Refers to the next node in the list
            Self._Next = None
            # Refers to the first child of the node
            Self._Child = None
            # Refers to the parent of the node
            Self._Parent = None
            # Stores a list of keys and values associated with the node
            Self._values = {}
            
        return result
        
    ## Destructor
    def __del__(Self):
        # Destroy any children
        if Self._Child is not None:
            del Self._Child
            Self._Child = None
            
        # Destroy any younger siblings
        if Self._Next is not None:
            del Self._Next
            Self._Next = None
            
        AnObject.__del__(Self)
        
    ## Construct a new node as a child of another node
    @classmethod
    def asChildOf(Self, ThisNode, **values):
        NewNode = ANode(**values)
        
        if ThisNode is not None:
            ThisNode.adopt(NewNode)
            
        return NewNode
        
    ## Insert this node before the specified node.
    def insertBefore(Self, ThisNode):
        Self._Next = ThisNode
        
        if ThisNode is not None:
            Self._Previous = ThisNode._Previous
            ThisNode._Previous = Self
            
    ## Insert this node after the specified node.
    def insertAfter(Self, ThisNode):
        Self._Previous = ThisNode
        
        if ThisNode is not None:
            Self._Next = ThisNode._Next
            ThisNode._Next = Self
            
    ## Prepend the specified node to this node.
    def prepend(Self, ThisNode):
        if ThisNode is not None:
            ThisNode._Next = Self
            ThisNode._Previous = Self._Previous
            Self._Previous = ThisNode
            
    ## Append the specified node to this node.
    def append(Self, ThisNode):
        if ThisNode is not None:
            ThisNode._Previous = Self
            ThisNode._Next = Self._Next
            Self._Next = ThisNode
            
    ## Adopt the specified node as a child
    def adopt(Self, ThisNode):
        if ThisNode is not None:
            ThisNode._Parent = Self
            
            if Self._Child is not None:
                Self._Child.prepend(ThisNode)
                
            Self._Child = ThisNode
            
    ## Remove the specified node and its children from the list
    def detach(Self):
        if Self._Previous is not None:
            Self._Previous._Next = Self._Next
            
        if Self._Next is not None:
            Self._Next._Previous = Self._Previous
            
    # IsPrintable implementation -----------------------------------------------
    ## Return a string representation of the node
    def toString(Self):
        return Self.name()
        
    ## Print a string representation of the node and its children to the
    #  specified stream
    def printTo(Self, Dest):
        result = 0
        
        if Dest is not None:
            # Write the node first
            result = Dest.write(str(Self))    
            
            # Write any children
            if Self._Child is not None:
                result += Self._Child.printTo(Dest)
                
            # Write any younger siblings
            if Self._Next is not None:
                result += Self._Next.printTo(Dest)
                
        return result            
            
    # Properties ---------------------------------------------------------------
    ## Retrieve the node in the list before the current node.
    def Previous(Self):
        return Self._Previous
        
    ## Retrieve the node in the list after the current node
    def Next(Self):
        return Self._Next
        
    ## Retrieve the first child of the node
    def Child(Self):
        return Self._Child
        
    ## Retrieve the parent of the node
    def Parent(Self):
        return Self._Parent
        
    ## Calculate the number of children owned by the node
    def census(Self):
        result = 0
        
        if Self._Child is not None:
            NextNode = Self._Child
            result = 1
            
            while NextNode is not None:
                NextNode = NextNode._Next
                
                if NextNode is not None:
                    result += 1
                
        return result
        
    ## Retrieve the specified item from the list of node values
    def __getitem__(Self, key):
        return Self._values[key]
        
    ## Set the specified item in the list of node values
    def __setitem__(Self, key, value):
        Self._values[key] = value
        
    ## Determine whether the specified item exists in the list of node values
    def __contains__(Self, key):
        return key in Self._values
        
    ## Update the values in the list of node values
    def update(Self, **values):
        Self._values.update(**values)
        
    ## Determine the "length" of the node -- the number of "younger siblings"
    #  it contains.
    #
    #  This routine iterates through the nodes connected to its \p Next
    #  property and counts each node encountered.  When used on the first node
    #  in a sequence, this value will produce the total number of nodes in the
    #  list.  
    #
    #  This routine will always return a value of at least 1 because it counts
    #  the node on which the method is called.
    def __len__(Self):
        result = 1
        
        if Self._Next is not None:
            NextNode = Self._Next
            result += 1
            
            while NextNode is not None:
                NextNode = NextNode._Next
                
                if NextNode is not None:
                    result += 1
                    
        return result
        
    ## Iterates over the node and its younger siblings
    def __iter__(Self):
        return ANodeIterator(Self)
