# -*- 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 that manages a list of data retrieved from a
#  data source.

from Library.Base.Events import ALoggingEventfulObject
from Library.String.Utilities import IsPrintable, String
from UserDict import DictMixin

## The events generated by ADataList
dlEvents = {
    ## Before data is fetched from the list
    "fetch": None,
    ## Before data is appended to the list
    "append": None,
    ## Before data is removed from the list
    "remove": None
}

## The default delimiter used to separate items when a string representation of
#  the data list is generated.
dlDefaultItemsDelimiter = ", "

## The error message used when an attempt is made to retrieve a non-existent
#  item from a data row.
dlErrorRetrievingItem = "Failed to retrieve item {item} from {classname}"

## A list of data retrieved from a database.
#
## This class is used to organize and maintain a list of values that is
#  retrieved from a database.  The method of obtaining the list is left up to
#  descendant classes, but it will generally represent a single column retrieved
#  by means of a query.  As such, this class stores the column values as keys
#  within the list and the primary key for each row that contains the column 
#  value is stored as a subordinate value to that key.  In this way, the list
#  can be searched for a specific value (using Python's 'in' operator) while
#  making updates to the source easy, since the primary key of each value is
#  associated with the value.
#
#  This class is meant to be overridden by descendant classes which provide more
#  specific functionality (such as the ability to actually retrieve the list 
#  from a database).  This class should not be directly instantiated.
class ADataList(ALoggingEventfulObject, DictMixin):
    
    ## Construct a new data list instance.
    #
    #  In addition to the parameters expected by 
    #  ALoggingEventfulObject.__init__(), this routine expects that \p params
    #  will contain the following items:
    #  - \p DataSource -- The data source from which the list will operate.
    def __init__(Self, **params):
        ALoggingEventfulObject.__init__(Self, **params)
        
        if "DataSource" in Self._params:
            Self._Source = Self._params["DataSource"]
            del Self._params["DataSource"]

    ## Initialize a new data list instance.
    #
    #  The routine sets the value of \p ADataList.delimiter to
    #  that specified by #dlDefaultItemsDelimiter.  This delimiter will be used
    #  to separate values when the list is converted to a string by a call to
    #  ADataList.toString().
    def init(Self):
        result = ALoggingEventfulObject.init(Self)

        if result:
            # Establish private attributes
            ## The data source from which the list will be retrieved
            Self._Source = None
            ## Stores the list
            Self._items = {}
            ## The events generated by the data list
            Self._events.update(dlEvents)
            
            # Establish public attributes
            ## The delimiter used to separate items in the list when generating
            ## a string representation of the list
            Self.delimiter = dlDefaultItemsDelimiter

        return result

    ## Fetch the list from the data source.
    #
    #  This method should be implemented by descendant classes.  It should 
    #  return an integer value that indicates how many values were successfully
    #  retrieved from the data source. 
    #
    #  This method generates the \p fetch event.
    #
    #  @return A return value of less than zero (0) indicates a failure of some 
    #  kind.  A return value of zero may not mean that an error has occurred; 
    #  only that data matching the query was not found.  The meaning of return
    #  values less than zero is left to descendant classes to define.
    def fetch(Self):
        result = 0
        
        Event = Self.generate("fetch")
        if Event.handled():
            result = Event.result()
            
        del Event
        
        return result

    ## Append the specified \p item to the list.
    #
    #  This method should be implemented by descendant classes which will, among
    #  other things, update the list as it is stored in the data source.  An
    #  integer value should be returned to indicate whether or not the item was
    #  successfully added to the list.  
    #
    #  This method generates the \p append event.
    #
    #  @return A return value of zero (0) indicates that the item was 
    #  successfully appended to the list; any other value indicates an error of
    #  some kind occurred.  The meaning of such a return value is left to 
    #  descendant classes to define.
    def append(Self, item):
        result = 0
        
        Event = Self.generate("append")
        if Event.handled():
            result = Event.result()
            
        del Event
        
        return result

    ## Remove the specified \p item from the list.
    #
    #  This method should be implemented by descendant classes which will, among
    #  other things, remove the specified item from the data source.  An integer
    #  value should be returned to indicate whether or not the removal 
    #  succeeded.
    #
    #  This method generates the \p remove event.
    #
    #  @return A return value of zero (0) indicates that the item was removed
    #  successfully from the list; any other value indicates an error of some
    #  kind occurred.  The meaning of a such a return value is left to 
    #  descendant classes to define.
    def remove(Self, item):
        result = 0
        
        Event = Self.generate("remove", item=item)
        if Event.handled():
            result = Event.result()
            
        del Event
        
        return result

    # IsPrintable implementation -----------------------------------------------
    ## Construct a string representation of the list.
    #
    #  This method calls String.implode() to condense the items in the list into
    #  a string of delimited values.  If the list is empty, an empty string will
    #  be returned.
    def toString(Self):
        if len(Self._items) > 0:
            return String.implode(Self._items.keys(), Self.delimiter)

        return ""

    # Properties ---------------------------------------------------------------
    ## Retrieve the data source used by the list.
    #
    #  This will be the class instance that represents the database or other
    #  data source from which the values in the list were retrieved.
    def DataSource(Self):
        return Self._Source

    ## Retrieve the specified value from the list.
    #
    #  Remember that ADataList and its descendants represent a \em column of
    #  data, not an individual record.  The values of that column are stored
    #  internally as keys in a dictionary, while the primary key of each column
    #  is stored as a subordinate value associated with that key.
    def item(Self, key):
        if key in Self._items:
            return Self._items[key]

        return None
    
    ## Retrieve the dictionary object used to store column values and primary
    #  keys.
    def items(Self):
        return Self._items

    ## Obtain the specified value from the list.
    #
    #  This routine is defined for convenience, so that ADataList and its
    #  descendants can be used as though they were Python dictionary objects.
    #
    #  If \p key is not found, a KeyError exception is raised by this routine.
    def __getitem__(Self, key):
        if key in Self._items:
            return Self._items[key]

        else:
            raise KeyError(dlErrorRetrievingItem.format(item=key,
              classname=Self.displayName()))

    ## Set the value of the specified item.
    #
    #  This routine is defined for convenience, so that ADataList and its
    #  descendants can be used as though they were Python dictionary objects.
    #
    #  Note that, unlike a Python dictionary object, new items cannot be added
    #  to the list by this method; that must be done through a call to 
    #  ADataList.append().
    def __setitem__(Self, key, value):
        if key in Self._items:
            Self._items[key] = value

    ## Delete the specified item from the list.
    #
    #  This routine is defined for convenience, so that ADataList and its
    #  descendants can be used as though they were Python dictionary objects.
    #
    #  This method simply calls ADataList.remove().
    def __delitem__(Self, key):
        Self.remove(key)

    ## Retrieve the list of keys in use by the list.
    #
    #  Remember that ADataList and its descendants store a \em column of data
    #  rather than an individual record.  The values in the column are stored 
    #  internally as keys within a Python dictionary, while the primary key 
    #  associated with each value is stored as a subordinate value to that key.  
    #  This method returns the list of values in the column, separated from 
    #  their primary keys and any other information which the data list might
    #  store about them.
    #  
    #  This method is provided for convenience, so that ADataList and its
    #  descendants can be used as though they were Python dictionary objects.
    def keys(Self):
        return Self._items.keys()
        
    # Determine whether the specified item exists in the data list
    def __contains__(Self, key):
        return key in Self._items
