"""defines the protocol handler interface and default implementation"""
from _exceptions import *
from narval.lib import AL_NS, NO_NS
from narval.lib import select_one, select_many, SelectException

class ProtocolHandlerInterface:
    """raw interface"""
    def getPrototype(self):
        """returns an XML string
        The string is a valid document, according to the protocol
        handler prototype DTD"""
        
    def activate(self,activator):
        """Activates the handler with the given element.
        A handler can be activated more than once, and should behave
        accordingly. For instance an HTTP handler can be activated
        on different ports. The handler should use the eid of the
        element to be able to deactivate the proper service. This
        method can raise a HandlerAlreadyActivated exception if the
        activate method already has been called with a semantically
        identical activator."""
        
    def deactivate(self, eid):
        """Deactivates the handler.
        The eid of the activation element is passed as an argument. This
        method can raise a HandlerNotActivated exception if the handler
        had not been activated with the element."""

    def handleOutgoing(self,arguments):
        """passes arguments matching one of the inputs of the handler.
        This method can raise a HandlerNotActivated exception if the
        handler has not been activated, or has not been activated to
        handle the input, or is not expecting any input."""

    def stop(self):
        """stops the handler.
        Stops all threads launched by the handler, if any"""


from narval.dom import DomReader

class ProtocolHandler(ProtocolHandlerInterface):
    """Base class for protocol handlers
    Derived classes should define three attribute:
    self.namespace is a sequence of ('prefix','nsuri) used in prototype
    self.prototype is a 3-uple : (('activator desc',('activator xpaths',)),
                                  ('input desc',('input xpaths',)),
                                  ('output desc',('output xpaths',)))
    self.documentation is a string
    The docstring of the class is used as the documentation if self.documentation
    is not set."""
    
    ACTIVATOR,INPUT,OUTPUT = range(3)
    DESC,MATCH = range(2)
    def __init__(self):
        self.namespaces = (   ('al', AL_NS), )
        self.prototype = (('activator desc',('activator xpaths',)),
                          ('input desc',('input xpaths',)),
                          ('output desc',('output xpaths',))
                          )
        self.documentation = None
        self.__xml_prototype = None
        self.__reader = DomReader()
        
    def getPrototype(self):
        """returns the prototype of the handler
        This method uses self.namespaces, self.prototype and self.documentation
        to generate an XML prototype."""
        self.__build_prototype()
        return self.__xml_prototype
    
    def __build_prototype(self):
        if self.__xml_prototype is None:
            activator_matches = ''.join(["<al:match>%s</al:match>"%xpath
                                         for xpath in self.prototype[self.ACTIVATOR][self.MATCH]])
            input_matches = ''.join(["<al:match>%s</al:match>"%xpath
                                         for xpath in self.prototype[self.INPUT][self.MATCH]])
            output_matches = ''.join(["<al:match>%s</al:match>"%xpath
                                         for xpath in self.prototype[self.OUTPUT][self.MATCH]])
            xmlns = ' '.join(["xmlns:%s=%s"%(p,repr(u)) for (p,u) in self.namespaces])
            
            self.__xml_prototype = ''.join([ "<al:prototype ",xmlns,">",
                                            "<al:description lang='en'>",
                                            self.documentation or self.__doc__,
                                            "</al:description>",
                                            "<al:activator id='activator'>",
                                            "<al:description lang='en'>",
                                            self.prototype[self.ACTIVATOR][self.DESC],
                                            "</al:description>",
                                            activator_matches,
                                            "</al:activator>",
                                            "<al:input>"\
                                            "<al:description lang='en'>",
                                            self.prototype[self.INPUT][self.DESC],
                                            "</al:description>",
                                            input_matches,
                                            "</al:input>",
                                            "<al:output>"\
                                            "<al:description lang='en'>",
                                            self.prototype[self.OUTPUT][self.DESC],
                                            "</al:description>",
                                            output_matches,
                                            "</al:output>",
                                            "</al:prototype>"])

    def activate(self,activator):
        """Activates the handler with the given element.
        A handler can be activated more than once, and should behave
        accordingly. For instance an HTTP handler can be activated
        on different ports. The handler should use the eid of the
        element to be able to deactivate the proper service. This
        method can raise a HandlerAlreadyActivated exception if the
        activate method already has been called with a semantically
        identical activator."""
        
    def deactivate(self, eid):
        """Deactivates the handler.
        The eid of the activation element is passed as an argument. This
        method can raise a HandlerNotActivated exception if the handler
        had not been activated with the element."""

    def handleOutgoing(self,arguments):
        """passes arguments matching one of the inputs of the handler.
        This method can raise a HandlerNotActivated exception if the
        handler has not been activated, or has not been activated to
        handle the input, or is not expecting any input."""

    def stop(self):
        """stops the handler.
        Stops all threads launched by the handler, if any"""

    def build_dom(self,xml):
        """Builds a domlette document from an XML string"""
        return self.__reader.fromString(xml)

    def build_string(self,dom, encoding="UTF-8"):
        from StringIO import StringIO
        from xml.dom.ext import Print
        s = StringIO()
        Print(dom, s, encoding=encoding)
        str = s.getvalue()
        s.close()
        return str


    def check_output(self, arguments_node) :
        """Check that arguments_node matches all the outputs defined in the
        prototype.
        """
        dom = arguments_node.as_dom()
        for xpath in self.prototype[self.OUTPUT][self.MATCH] :
            if not select_many(dom, '//'+xpath,
                                nss=tuple2dict(self.namespaces)):
                return 0
        return 1

    def check_input(self, arguments_node):
        """Check that arguments_node matches one of the inputs 
        defined in the prototype.
        """
        for xpath in self.prototype[self.INPUT][self.MATCH] :
            if select_many(arguments_node, '//'+xpath, nss=tuple2dict(self.namespaces)):
                return 1
        return 0
       
    def check_activator(self, element):
        """ check if element matches the activator"""
        for xpath in self.prototype[self.ACTIVATOR][self.MATCH]:
            try:
                return select_one(element, '//'+xpath, nss=tuple2dict(self.namespaces))
            except SelectException :
                return 0

def tuple2dict(tuple):
    dict = {}
    for e in tuple: dict[e[0]]=e[1]
    return dict
