# -*- 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 several generic classes that represent
#  queries which can be executed against a data source (such as a database). 

from Library.Base.Events import AnEventfulObject, ALoggingEventfulObject
from Library.IO.Logging.Logs import AMessageWithTimeStamp
from Library.DataSource.Databases import ADataOperation
from Library.DataSource.DataRows import ADataRow
from Library.String.Utilities import IsPrintable, String
from UserDict import DictMixin

## Events generated by a AQueryIterator
qiEvents = {
    # Before a record is retrieved
    "nextRecord": None
}

## Events generated by AQuery
qryEvents = {
    # Before a record is retrieved
    "nextRecord": None
}

## The message used when a query iterator reaches the end of the query result
# set
qiEndOfRows = "no further rows in \"{query}\""

## This class allows a query to behave as an iterable object.
class AQueryIterator(AnEventfulObject):
    ## Construct a new instance of the iterator that will iterate using the
    #  specified query and return rows as instances of the specified row class.
    def __init__(Self, ThisQuery, RowClass=None, rowParams=None, **params):
        AnEventfulObject.__init__(Self, **params)
        Self._Query = ThisQuery
        
        if RowClass is not None:
            Self._RowClass = RowClass
            
            if rowParams is not None:
                Self._rowParams = rowParams

    ## Initializer
    def init(Self):
        result = AnEventfulObject.init(Self)

        if result:
            # Establish private attributes
            # The query over which the iterator will iterate
            Self._Query = None
            # The class used to construct row instances
            Self._RowClass = ADataRow
            # The parameters passed to the row class constructor
            Self._rowParams = {}
            
            # Update the events generated by the iterator
            Self._events.update(qiEvents)

        return result
 
    ## Retrieve the next row from the query.
    #
    #  This method generates the \p nextRecord event.
    def next(Self):
        Event = Self.generate("nextRecord")
        if Event.handled():
            del Event
            return
            
        del Event
        
        if Self._Query is not None:
            Row = Self._Query.nextRecord(Self._RowClass, **Self._rowParams)
            if Row is not None:
                return Row

        raise StopIteration(qiEndOfRows.format(query=str(Self._Query)))

    # Properties ---------------------------------------------------------------
    ## Retrieve a reference to the Query instance used by the iterator.
    def Query(Self):
        return Self._Query

    ## Retrieve a reference to the iterator object.  This method is necessary
    #  to allow the iterator to be used as part of a Python <tt>for..in</tt>
    #  statement.
    def __iter__(Self):
        return Self
        
    ## Retrieve the "length" of the iterator object -- the total number of
    #  rows returned by the query
    def __len__(Self):
        if Self._Query is not None:
            return Self._Query.recordCount()
            
        return 0

## A basic query executed against a data source.
#
#  Note that this class does not implement the execute() method inherited from
#  ADataOperation and so it should not be instantiated directly.
class AQuery(ADataOperation):
    ## Construct a new query instance that will operate on the specified data
    #  source.
    def __init__(Self, **params):
        ADataOperation.__init__(Self, **params)

    ## Initializer
    def init(Self):
        result = ADataOperation.init(Self)

        if result:
            # Establish private attributes
            # The number of records affected or retrieved by the query
            Self._recordCount = 0
            
            # Update the events generated by the query
            Self._events.update(qryEvents)

        return result

    ## Destroy the query instance.
    #
    #  This routine should be implemented by descendant classes.  It should
    #  free the query and release all associated resources.
    def __del__(Self):
        AnEventfulObject.__del__(Self)

    ## Execute the query.
    #
    #  This routine should be implemented by descendant classes.  It should
    #  construct the query string and submit it to the data source.  An integer
    #  value should be returned to indicate whether execution was successful
    #  (but not necessarily whether the query returned any data).  A return
    #  value of zero (0) indicates success.
    def execute(Self):
        return ADataOperation.execute(Self)

    ## Retrieve the next row from the query.
    #
    #  This routine should be implemented by descendant classes.  It should
    #  return the next record from the data generated when the data operation
    #  was executed.  This row should be returned as the specified instance
    #  of \p RowClass.  If there are no more rows, then \p None should be 
    #  returned.
    #
    #  If \p RowClass is specified, the routine should return an instance of
    #  that class.  If no class is specified, an instance of ADataRow may be
    #  returned.
    #
    #  Any parameters passed in \p params will be fed to the row class 
    #  constructor.
    #
    #  This method generates the \p nextRecord event.
    def nextRecord(Self, RowClass=None, **params):
        Result = None
      
        # Generate the "nextRecord" event
        Event = Self.generate("nextRecord")
        if Event.handled():
            Result = Event.result()
      
        del Event
          
        return Result

    # Properties ---------------------------------------------------------------
    ## Retrieve the number of rows affected or returned by the query.
    def recordCount(Self):
        return Self._recordCount

    ## Return an iterator for the query that retrieves rows as instances
    #  of the specified row class.
    def Rows(Self, RowClass=None, rowParams=None, **params):
        if RowClass is not None:
            return AQueryIterator(Self, RowClass, rowParams, **params)

        return AQueryIterator(Self, None, rowParams, **params)

    ## Return an iterator for the query.
    #
    #  The iterator returned by this method returns instances of ADataRow.
    #  To return instances of another type of class instead, call
    #  AQuery.Rows() with the desired class.
    #
    #  This method allows the query to be treated as an iterable object within
    #  Python code (i.e., as part of a <tt>for..in</tt> statement).
    def __iter__(Self):
        return AQueryIterator(Self)
        
    ## Retrieve the "length" of the query -- the number of rows returned.
    def __len__(Self):
        return Self._recordCount
