# -*- 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 generic class for data source access.

from Library.Base.Events import ALoggingEventfulObject
from Library.IO.Logging.Logs import AMessageWithTimeStamp

## The events generated by ADataSource
dsEvents = {
  # Before a connection to the data source is made
  "connect": None,
  # Before the connection to the data source is severed
  "disconnect": None,
  # Before counting the number of records returned by a data source operation,
  "count": None,
  # Before data is fetched from the data source
  "fetch": None,
  # Before data is inserted into the data source
  "insert": None,
  # Before existing data in the data source is updated
  "update": None,
  # Before existing data in the data source is deleted
  "delete": None,
  # Before the list of categories in the data source is retrieved
  "fetchCategories": None
}

# A generic data source eror
class ADataSourceError(AMessageWithTimeStamp):
    pass

## The abstract interface to a data source.   
#    
#  This class defines several routines that are meant to be implemented by
#  descendant classes; as a result, this class should not be instantiated 
#  directly.
class ADataSource(ALoggingEventfulObject):
    ## Construct a new data source.
    def __init__(Self, **params):
        ALoggingEventfulObject.__init__(Self, **params)

    ## Initializer        
    def init(Self):
        result = ALoggingEventfulObject.init(Self)
        
        if result == True:
            # Establish private attributes
            # The events generated by the data source
            Self._events.update(dsEvents)
                        
        return result
        
    ## Connect to the data source.
    #    
    #  This routine should be implemented by descendant classes. A status
    #  code should be returned to indicate success or failure.  Zero (0) is
    #  assumed to mean that a connection was successfully made.
    #
    #  This method generates the \p connect event.
    def connect(Self, **params):
        result = -1
        
        # Generate the "connect" event
        Event = Self.generate("connect", params=params)
        
        if Event.handled():
            result = Event.result()
        
        del Event
            
        return result
        
    ## Disconnect from the data source.
    #    
    #  This routine should be implemented by descendant classes.  It should
    #  close the data source connection and release any associated resources.
    #
    #  This method generates the \p disconnect event.
    def disconnect(Self):
        # Generate the "disconnect" event
        Self.generate("disconnect")

        Self._handle = None
        
    ## Count the number of records that would be returned by the specified
    #  fetch operation.
    #
    #  This routine should be implemented by descendant classes.  It should
    #  return the number of records that would be returned if the paramters
    #  provided to it were used in a call to ADataSource.fetch().
    #
    #  The parameters supplied to this function have the same meaning as those
    #  supplied to ADataSource.fetch()
    #
    #  This method generates the \p count event.
    def count(Self, categoryName, **params):
        result = 0
        
        Event = Self.generate("count", categoryName=categoryName, params=params)
        if Event.handled():
            result = Event.result()
            
        del Event
        
        return result        
        
    ## Fetch data from the data source.
    #
    #  This routine should be implemented by descendant classes.  It should
    #  search the data source for the specified data and return an iterable
    #  object that contains or retrieves each record fetched by this method.
    #
    #  Note that only the \p categoryName parameter is required: this is the
    #  name of the category (or table) within the data source that will be
    #  searched.
    #
    #  The \p params passed to this method may include:
    #  - \p fieldNames -- A list() of which fields are to be returned.  If this
    #                     parameter is blank or if it is omitted, all fields
    #                     should be returned.
    #  - \p criteria -- A list() of filter criteria to apply to the search; only
    #                   those records that meet the criteria should be returned
    #  - \p sort -- A list() of fields and sort order indicators; the data 
    #               returned should be sorted based on this criteria    
    #  - \p limit -- The maximum number of records to return
    #  - \p offset -- How many records to skip before returning results.
    #
    #  This method should generate an error event if the data retrieval fails.
    #
    #  This method generates the \p fetch event.
    def fetch(Self, categoryName, **params):
        result = None
        
        Event = Self.generate("fetch", categoryName=categoryName, params=params)
        if Event.handled():
            result = Event.result()
            
        del Event
        
        return result
        
    ## Insert data into the data source.
    #
    #  This routine should be implemented by descendant classes.  It should
    #  insert the specified data into the data source and return a value that
    #  indicates success (0 or greater) or failure (less than zero).
    #
    #  The \p categoryName parameter is required; it specifies the category
    #  (or table) within the data source where the new data is to be stored.
    #
    #  Within \p params, the following parameters are expected:
    #  - \p fieldsValues -- A dictionary object that contains the names of 
    #                       fields and their associated values.
    #
    #  Other parameters may also be supplied within \p params, but these will
    #  have meaning only to descendant classes or event handlers attached to
    #  this method.
    #
    #  This method generates the \p insert event.
    #
    #  This method should return the unique id associated with the record
    #  upon success (a positive integer value greater than zero); or an error
    #  code (a negative integer value less than zero) on failure.
    def insert(Self, categoryName, **params):
        result = -1
        
        Event = Self.generate("insert", categoryName=categoryName, 
            params=params)
            
        if Event.handled():
            result = Event.result()
            
        del Event
        
        return result
        
    ## Update data in the data source.
    #
    #  This routine should be implemented by descendant classes.  It should
    #  update the specified data in the data source, using the specified 
    #  criteria to determine which record to update, and return a value that
    #  indicates success (0 or greater) or failure (less than zero).
    #
    #  The \p categoryName parameter is required; it specifies the category
    #  (or table) within the data source where the data to update may be found.
    #
    #  Within \p params, the following parameters are expected:
    #  - \p fieldsValues -- A dictionary object that contains the names of 
    #                       fields and their associated values.
    #  - \p criteria -- A list() of criteria that is used to determine which
    #                   record will be updated.
    #
    #  Other parameters may also be supplied within \p params, but these will
    #  have meaning only to descendant classes or event handlers attached to
    #  this method.
    #
    #  This method generates the \p update event.
    def update(Self, categoryName, **params):
        result = -1
        
        Event = Self.generate("update", categoryName=categoryName,
            params=params)
            
        if Event.handled():
            result = Event.result()
            
        del Event
        
        return result
        
    ## Delete data from the data source.
    #
    #  This routine should be implemented by descendant classes.  It should
    #  delete the specified data from the data source, using the specified
    #  criteria to determine which records to delete, and return a value that
    #  indicates success (0 or greater) or failure (less than zero).
    #
    #  The \p categoryName parameter is required; it specifies the category
    #  (or table) within the data source where the data to be deleted may be
    #  found.
    #
    #  Within \p params, the following parameters are expected:
    #  - \p criteria -- A list() of criteria that is used to determine which
    #                   record will be updated.
    #
    #  Other parameters may also be supplied within \p params, but these will
    #  have meaning only to descendant classes or event handlers attached to
    #  this method.
    #
    #  This method generates the \p delete event.
    def delete(Self, categoryName, **params):
        result = -1
        
        Event = Self.generate("delete", categoryName=categoryName,
            params=params)
            
        if Event.handled():
            result = Event.result()
            
        del Event
        
        return result
        
    ## Retrieve a list of all categories (or tables) in the data source.
    #
    #  This method should return a list or tuple that contains the name of
    #  every category present in the data source.  If there are no categories,
    #  it should return \p None.
    #
    #  This method generates the \p fetchCategories event.
    def categories(Self):
        result = None
        
        Event = Self.generate("fetchCategories")
        if Event.handled():
            result = Event.result()
            
        del Event

        return result
