# Copyright (c) 2004-2005 DoCoMo Euro-Labs GmbH (Munich, Germany).
# Copyright (c) 2001-2005 LOGILAB S.A. (Paris, FRANCE).
#
# http://www.docomolab-euro.com/ -- mailto:tarlano@docomolab-euro.com
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
"""Core elements for narval memory's :

- error
- start-plan
- listen-on
- quit

The engine rely on those elements so this module should not be disabled !

:version: $Revision:$  
:author: Logilab

:copyright:
  2001-2005 LOGILAB S.A. (Paris, FRANCE)
  
  2004-2005 DoCoMo Euro-Labs GmbH (Munich, Germany)
  
:contact:
  http://www.logilab.fr/ -- mailto:contact@logilab.fr
  
  http://www.docomolab-euro.com/ -- mailto:tarlano@docomolab-euro.com
"""

__revision__ = "$Id: lib.py,v 1.78 2002/10/14 14:24:11 syt Exp $"
__docformat__ = "restructuredtext en"

from narval.public import AL_NS, NO_NS
from narval.element import NSAttribute, ALElement
from narval.xml_handlers import BaseXMLHandler, data_handler
from narval.serialutils import yn_value, yn_rev_value
from narval.interfaces.core import IError, IStartPlan, IListenOn


class ErrorElement(ALElement):
    """element representing an error during a plan execution

    :ivar type: optional error family
    :ivar msg: error message
    """
    __implements__ = (IError,)
    __xml_element__ = (AL_NS, 'error')
    __child_handler__ = data_handler('msg')
    type = NSAttribute(NO_NS, None, str, str)    
    msg = ''
    from_msg = None
    
    def children_as_xml(self, encoding='UTF-8'):
        """return the XML representation of the children node of this element

        :type encoding: str
        :param encoding: the encoding to use in the returned string

        :rtype: str
        :return: XML string representing the element
        """
        if self.msg:
            msg = self.msg
            if isinstance(msg, unicode):
                msg = msg.encode(encoding)
            return '<![CDATA[%s]]>' % msg
        return ''


        
class StartPlanElement(ALElement):
    """start plan element: ask for plan instanciation from a recipe

    :ivar recipe: identifier (<group>.<name>) of the recipe to start
    :ivar context:
      optional context, i.e. a match expressions used to select original
      plan inputs from memory
    """
    __implements__ = (IStartPlan,)    
    __xml_element__ = (AL_NS, 'start-plan')

    class StartPlanXMLHandler(BaseXMLHandler):
        """XML handler for start-plan elements"""

        def start_element(self, name, attrs):
            """SAX callback: start a new xml node

            :type name: tuple
            :param name: the tag name as a tuple (uri, name)

            :type attrs: dict
            :param attrs:
              the node's attribute values, indexed by attribute's name as a tuple
              (uri, name)
            """
            prefix, local = name
            if local == 'time':
                assert not self.elmt.delay
                time_def = (attrs.get((NO_NS, 'seconds'), '*'),
                            attrs.get((NO_NS, 'minutes'), '*'),
                            attrs.get((NO_NS, 'hours'), '*'),
                            attrs.get((NO_NS, 'monthdays'), '*'),
                            attrs.get((NO_NS, 'months'), '*'),
                            attrs.get((NO_NS, 'weekdays'), '*'))
                self.elmt.time = time_def
            elif local == 'delay':
                assert not self.elmt.time
                delay = 0
                for attr, fact in ( ('seconds', 1), ('minutes', 60), ('hours', 60*60),
                                    ('days', 60*60*24)):
                    delay += int(attrs.get((NO_NS, attr), 0)) * fact
                self.elmt.delay = delay
                self.elmt.delay_period = yn_value(attrs.get((NO_NS, 'period'), 'no'))
            else:
                raise AssertionError('unknown start plan child %s' % local)

    __child_handler__  = StartPlanXMLHandler
    
    recipe = NSAttribute(NO_NS, None, str, str)
    cancelled = NSAttribute(NO_NS, False, yn_value, yn_rev_value)
    cancelled_by = NSAttribute(NO_NS, False, int, str)
    cancelling = NSAttribute(NO_NS, False, int, str)
    context_expression = NSAttribute(NO_NS, None, str, str)
    time = None
    delay = None
    delay_period = False
    # FIXME: serialize/deserialize attributes below ?
    context = None
    parent_plan, parent_step = None, None
    
    def children_as_xml(self, encoding):
        if self.time:
            return ('<al:time seconds="%s" minutes="%s" hours="%s" '
                    'monthdays="%s" months="%s" weekdays="%s"/>' % self.time)
        if self.delay:
            return ('<al:delay seconds="%s" period="%s"/>' % (
                self.delay, yn_rev_value(self.delay_period)))
        return ''

    def context_elements(self, memory):
        """return elements that should be used as context for the started plan
        """
        if self.context_expression:
            context =  memory.match_elements(self.context_expression)
        else:
            context = self.context
        return context or []

    def cancel(self, by=None):
        """cancel this start plan of the one which have replaced it

        :type by: `StartPlanElement` or None
        :param by: the start plan element replacing this one if any

        :rtype: `StartPlanElement`
        :return: the actually canccelled start plan element
        """
        if self.cancelled_by:
            return self.cancelled_by.cancel(by)
        assert not self.cancelled
        log(LOG_NOTICE, 'cancelling start plan %s (%s)' % (self.recipe, self.eid))
        self.cancelled = True
        self.cancelled_by = by
        by.cancelling = self.eid
        return self
            
class ListenOnElement(ALElement):
    """listen-on element: ask to open a control communication channel

    :ivar type: the protocol identifier string
    :ivar port: the server port
    :ivar host: the server host
    """
    __implements__ = (IListenOn,)    
    __xml_element__ = (AL_NS, 'listen-on')
    
    type = NSAttribute(NO_NS, None, str, str)
    host = NSAttribute(NO_NS, 'localhost', str, str)
    port = NSAttribute(NO_NS, None, int, str)


class QuitElement(ALElement):
    """quit element: ask for narval shutdown"""
    
    __xml_element__ = (AL_NS, 'quit')


class GroupAliasElement(ALElement):
    """group-alias element: map an actual  cookbook or module to an alias name

    :ivar name: the alias name
    :ivar actual: the actual cookbook/module name
    """
    __xml_element__ = (AL_NS, 'group-alias')
    
    name = NSAttribute(NO_NS, None, str, str)
    actual = NSAttribute(NO_NS, None, str, str)
