#!/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 a class which can be used to manage sessions which are
#  stored in a data source. 

from Plugins.Session.Sessions import ASession
from Library.IO.Logging.Logs import AMessageWithTimeStamp

## The delimiter used when serializing data session meta information
dsItemsDelimiter = "|"

## The error message used when failing to update session information in the data
#  source
dsSessionUpdateError = ("failed to update session \"{}\" because: {} \n"
  "The query string used was: {}")

## The error message used when failing to retrieve session information from the
#  data source
dsSessionLoadError = "failed to load session \"{}\" because: {}"

## The error message used when failing to create session information in the data
#  source
dsSessionCreateError = "failed to create session \"{}\" because: {}"
dsQueryCreateError = "the data operation could not be constructed"

## Represents an error that can occur during a session.  This class is defined
#  solely for aesthetics, since it does not alter the behavior of its parent
#  class.
class ASessionError(AMessageWithTimeStamp):
    pass

## A session (such as an HTTP session), for which the information is stored in 
#  a data source, such as a database.
class ADataSession(ASession):
    ## Construct a new session instance that makes use of the specified data
    #  source and message log.
    #
    #  The constructor assumes that \p params will contain the following items:
    #  - \p DataSource -- The data source from which session information will
    #                     be retrieved, and to which session information will
    #                     be stored.
    #  - \p category -- The name of the table or category within the data source
    #                   where session data is stored.
    def __init__(Self, **params):
        ASession.__init__(Self, **params)
        
        if "DataSource" in Self._params:
            Self._Source = Self._params["DataSource"]
            del Self._params["DataSource"]
            
        if "category" in params:
            Self._category = params["category"]
            del Self._params["category"]
        
    ## Initializer
    def init(Self):
        result = ASession.init(Self)
        
        if result == True:
            # Establish private attributes
            # The data source used by the session
            Self._Source = None
            # The category or table in which sessions are stored
            Self._category = ""

        return result
        
    ## Destructor
    def __del__(Self):
        ASession.__del__(Self)
                
        if Self._Source is not None:
            del Self._Source
    
    ## Flush session information to the data source.   
    def flush(Self):
        result = 0
        
        Event = Self.generate("flush")
        if Event.handled():
            result = Event.result()
            del Event
            return result
            
        del Event
        
        if Self._changed == True:
            result = Self._Source.update(
                Self._category, 
                fieldsValues = {
                    "expiration": Self._expiration,
                    "meta": Self._Variables.implode(dsItemsDelimiter)
                },
                criteria = [
                    ("{field:id} = {}", Self._id)
                ]
            )
                
            if result < 0:
                Self.error(ASessionError(dsSessionUpdateError,
                    Self._id, Self._Source.lastMessage()))
                  
            else:
                Self._changed = False
                
            return result
            
        else:
            return 0

    ## Create the specified session with the specified length, in seconds.
    def create(Self, maxLength=0):
        result = -1
        
        Event = Self.generate("create", maxLength=maxLength)
        if Event.handled():
            result = Event.result()
            del Event
            
            return result
            
        del Event
        
        Self._id = Self.identifier()
        Self._maxLength = maxLength
        Self._expiration = Self._opened + Self._maxLength
        
        # Create the specified session
        result = Self._Source.insert(Self._category,
            fieldsValues = {
                "id": Self._id,
                "expiration": Self._expiration
            }
        )
            
        if result < 0:
            Self.error(ASessionError(dsSessionCreateError,
                Self._id, Self._Source.lastMessage()))
                
        return result
    
    ## Load the session with the specified session ID.           
    def load(Self, sid=None):
        result = -1
        
        Event = Self.generate("load", sid=sid)
        if Event.handled():
            result = Event.result()
            del Event
            
            return result
            
        del Event
        
        if sid is not None:
            Self._id = sid
            
            # Load the specified session
            try:
                Rows = Self._Source.fetch(Self._category,
                    criteria = [
                        ("{field:id} = {}", Self._id)
                    ]
                )
                
                for Row in Rows:
                    Self._expiration = float(Row["expiration"])
                    Self._Variables.explode(Row["meta"], dsItemsDelimiter)
                    result += 1
                          
                del Rows
                
            except Exception, Err:
                Self.error(ASessionError(dsSessionLoadError, 
                    Self._id, str(Err)))
                    
            return result
    # Properties ---------------------------------------------------------------
    ## Retrieve the data source used by the session handler
    def DataSource(Self):
        return Self._Source
        
    ## Retrieve the category in the data source where session data is stored
    def category(Self):
        return Self._category
        
