#!/usr/bin/env python
# -*- 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 and implements a class that acts much like a Python
#  dictionary object, with a few additional features.

from Library.Memory.BinaryTrees import ABinaryTree
from Library.Memory.StringTrees import AStringLeaf
from Library.String.Utilities import String
from Library.Base.Exceptions import AnException

## The default delimiters to use when imploding dictionaries
dDefaultItemsDelimiter = ", "
dDefaultKeysDelimiter = "="

## The format used to construct a string representation of a dictionary entry
deStringRepresentation = "\t{key}: {value}\n"

## A single entry in a dictionary, which matches a string key to a value.
class ADictionaryEntry(AStringLeaf):
    ## Constructor
    def __init__(Self, thisName, thisValue):
        """ Construct a new dictionary entry. """
        
        AStringLeaf.__init__(Self, thisName)
        Self.value = thisValue
    
    ## Initializer   
    def init(Self):
        result = AStringLeaf.init(Self)
        
        if result:
            # Establish public attributes
            # The value of the entry
            Self.value = None
            
        return result
                    
    # IsPrintable implementation -----------------------------------------------
    ## Construct a string representation of the dictionary entry.
    def toString(Self):
        return deStringRepresentation.format(key=Self._key,
          value=str(Self.value))
          
## A binary tree class that acts much like a Python dictionary object.
class ADictionary(ABinaryTree):
    ## Constructor
    def __init__(Self):
        ABinaryTree.__init__(Self)
        
    ## Initializer
    def init(Self):
        return ABinaryTree.init(Self)
        
    ## Construct a string containing a list of the items in the tree,
    #  with the keys and values delimited by the specified delimiter, and
    #  the key/value pairs delimited by the specified delimiter.
    def implode(Self, pairsDelimiter=dDefaultItemsDelimiter,
      keysDelimiter=dDefaultKeysDelimiter):
        result = ""
        
        if len(Self._items) > 0:
            for Item in Self._items.itervalues():
                if len(result) > 0:
                    result += pairsDelimiter
                    
                result += Item.name() + keysDelimiter + str(Item.value)

        return result

    ## Construct a string containing a list of the keys in the tree,
    #  with the keys separated by the specified delimiter.
    def implodeKeys(Self, itemsDelimiter=dDefaultItemsDelimiter):
        result = ""
        
        if len(Self._items) > 0:
            for Item in Self._items.itervalues():
                if len(result) > 0:
                    result += itemsDelimiter
                    
                result += Item.name()

        return result

    ## Construct a string containing a list of the values in the tree,
    #  with the values separated by the specified delimiter. 
    def implodeValues(Self, itemsDelimiter=dDefaultItemsDelimiter):
        result = ""
        
        if len(Self._items) > 0:
            for Item in Self._items.itervalues():
                if len(result) > 0:
                    result += itemsDelimiter
                    
                result += str(Item.value)

        return result

    ## Expand a string that contains a list of key/value pairs into
    #  binary tree keys and values.
    #
    #  The string is assumed to contain one or more items separated by the
    #  specified item delimiter, and with key/value pairs separated by the
    #  specified key delimiter.
    def explode(Self, thisString, itemDelimiter=dDefaultItemsDelimiter,
      keyDelimiter=dDefaultKeysDelimiter):
        if len(thisString) > 0:
            # Expand the items in the string
            items = String.explode(thisString, list, itemDelimiter)
            if len(items) > 0:
                # Expand the items into key/value pairs
                for item in items:
                    key, sep, value = item.partition(keyDelimiter)
                    Self.insert(ADictionaryEntry(key, value))
                    
    ## Updates the dictionary to include the specified items.
    #    
    #  /p theseItems must be a dictionary object, the keys of which will be
    #  used to specify token strings, and the values of which will be used
    #  to match those token strings to internal parser values. 
    #    
    #  The number of items that were successfully added to the set is
    #  returned.
    def update(Self, **theseItems):
        result = 0
        
        if len(theseItems) > 0:
            for key, value in theseItems.iteritems():
                NewEntry = ADictionaryEntry(key, value)
                if Self.insert(NewEntry):
                    result += 1
                    
        return result  
        
    ## Delete the specified item from the dictionary.
    def delete(Self, thisKey):
        result = False
            
        if thisKey in Self._items:
            ThisItem = Self._items[thisKey]
            del ThisItem
            
            del Self._items[thisKey]
            result = True
            
        return result                          
                    
    #Properties ----------------------------------------------------------------
    ## Retrieve the value of the specified dictionary item.
    #
    #  This routine is provided for convenience, so that access to the items in
    #  the tree can be programmed as though the tree was a native Python
    #  dictionary object.
    def __getitem__(Self, key):
        ThisItem = ABinaryTree.__getitem__(Self, key)
        return ThisItem.value

    ## Set the value of the specified field.
    #
    #  This routine is provided for convenience, so that access to the items in
    #  the tree can be programmed as though the tree was a native Python
    #  dictionary object.
    def __setitem__(Self, key, value):            
        ThisItem = Self.fetch(key)
        
        if ThisItem is not None:
            ThisItem.value = value
            
        else:
            ThisItem = ADictionaryEntry(key, value)
            if Self.insert(ThisItem) == False:
                raise AnException("failed to insert item named {} with value {}",
                  str(key), str(value))

    ## Iterate over the items in the dictionary.  This method allows an instance
    #  of ADictionary to be used in a Python <tt>for..in</tt> statement.
    def __iter__(Self):
        return ADictionaryIterator(Self._Root)
