#from PyJabber import Jabber, JabberPackets, JabberParser
#from jabber import Message, Iq, Presence, JID
import jabber
import xmlstream
from ProtocolHandler import ProtocolHandler, tuple2dict
from threading import Thread, Lock, Semaphore
import sys
from narval.lib import select_one, AL_NS, NO_NS, domtree_to_string, read_domtree_from_string
Identifier_Lock = Lock()
# reassign jabberpy xmlstream encoding
xmlstream.ENCODING="utf-8"

""" JabberProtocolHandler acts like a gateway between narval and the jabber
    server (jabberd). It implements :
    - class JabberProtocolHandler, that contains the methods called by Narval
      to de/activate the handler and send the data narval generated in the
      format expected by jabberd
    - class JabberService heritates Client (class in jabber.py)
      which instances are Jabber clients able to connect to jabberd)
    - class RequestHandler contains methods that manipulate the request
      from the server to send to Narval
"""
JABBER_NS = 'jabber:client'

class JabberProtocolHandler(ProtocolHandler):

    def __init__(self,narval):
        ProtocolHandler.__init__(self)
        
        self.documentation = 'A Jabber server delegating operation to Narval'
        # this prototype is not complete.
        # will see the spec. in Jabber doc, to inform the non-facultative
        # elements

        self.namespaces = ( ('al', AL_NS), ('jabber', JABBER_NS))

        self.prototype = \
        (('port on which the server listens, recipe to instanciate',
           ('jabber-server[@port][@recipe]',)),
          ('response to an Jabber request',
           ('jabber:jabber-request[@type="outgoing"]',)
           ),
          ('Jabber request',
           ('jabber:jabber-request[@type="incoming"]',
            'jabber:jabber-request[@id]',
            'jabber:jabber-request/jabber:message' )
           )
          )

        self.eid_activator = {}
        self.activations = {}
        self.pending_requests = {}
        self.narval = narval
        self._data_id = {}


    def activate(self,activator):
        """
        server will be by default orion
        activator is the XML element <jabber-server>
        """
        from pprint import pprint
        print 'NOW : activation'
        dom = self.build_dom(activator)
        node = dom.documentElement
        eid = node.getAttributeNS(AL_NS,'eid')
        port = int(node.getAttributeNS(NO_NS,'port'))
        recipe = node.getAttributeNS(NO_NS,'recipe')
        host = node.getAttributeNS(NO_NS,'host')
        self.username  = node.getAttributeNS(NO_NS,'username')
        self.password  = node.getAttributeNS(NO_NS,'password')
        self.resource  = node.getAttributeNS(NO_NS,'resource')
        self.jabber_id = "%s@%s/%s"%(self.username,host,self.resource)

        ## create a context-id for nlp processing
        ## could be temporary. 1 context_id per jabber user
        ## it is not kept as soon as actions don't need it
        self.context_id = new_context_id(self.jabber_id)
        self.eid_activator[eid] = port

        if self.activations.has_key(port):
            raise HandlerAlreadyActivated(port)

        # create a ChabberClient-like
        service = JabberService((host,port),self,recipe)
        self.server = service

        # Launch server
        # new process, so that when narval will call activate, it will not be
        # blocked
        service.connect()
        service.sock = service.getSocket()
        print "Jabber Stream opened"
        login_client(service, password=self.password, username=self.username, 
                              resource=self.resource) 

        thread = Thread(target = service.serve_forever, name='JabberService_%d'%port)
        self.activations[port] = (service,thread)
        thread.start()

    def deactivate(self, eid):
        try:
            port = self.eid_activator[eid]
        except KeyError:
            raise HandlerNotActivated(eid)
        service,thread = self.activations[port]
        service.running = None

    def stop(self):
        for service,thread in self.activations.values():
            service.running = None
            thread.join()
        
    def handleOutgoing(self,arguments):
        """
        this method process the response generated by narval.
        *arguments* is an xml-string
        """
        try:
            from xml.dom.ext import PrettyPrint
            #dom is part of prototype: try to get the input
            dom = self.build_dom(arguments)
            for kid in dom.documentElement.childNodes:
                if kid.nodeName in ['iq','message','presence']:
                    text = self.build_string(kid)
                    break
            print "TEXT", text
            self.server.send(text) #send response    
        except:
            import traceback
            traceback.print_exc()


class JabberService(jabber.Client):
    """
    Must import some PyJabber methods
    Launch the JabberService, by initiating a client's session 
    """

    def __init__(self, server_addr, protocol_handler, recipe):
        """
        Initialize the connection to a JabberServer 
            server_addr is a tuple
        """
        # What's done, by jabber.Client:
        #   Starts a new thread for the client's session
        # Connection protocol is xmlstream.TCP

        jabber.Client.__init__(self, server_addr[0],
                                port=int(server_addr[1]), debug=None,
                                log=None)
        self.server_addr = server_addr
        self.sock = self.getSocket()
        self.protocol_handler = protocol_handler
        self.recipe = recipe
        self.running = 1
        self.logged = 0

# instead of set_callback
        self.setMessageHandler(JabberService._handle_packet)
# not useful yet
#        self.setPresenceHandler(self._handle_packet)
#        self.setIqHandler(self._handle_packet)

    def serve_forever(self):
        """ [handle one request at a time until doomsday(R)]
        set_callback is "constantly" sollicited as this JabberService works
        with a thread that runs periodically due to SimpleClient
        configuration!! 
        """
        import select
        while self.running:
            read, write, exception = select.select([self.sock.fileno()],[],[],0.1)
            if read:
                self.process(1)

    def iqHandler(self, iq_obj):
        """Called when an iq protocol element is recieved - can be
       overidden"""
        for dicts in self.iq_hdlrs: ## do stackables to check ##
            print "dict", dicts
            if dicts.has_key(iq_obj.getType()):
                if dicts[iq_obj.getType()].has_key(iq_obj.getQuery()):
                    dicts[iq_obj.getType()][iq_obj.getQuery()](self, iq_obj)
                else:
                    dicts[iq_obj.getType()]['default'](self, iq_obj)
            elif dicts.has_key('default'): 
                dicts['default']['default'](self, iq_obj)
            else: pass

    def _handle_packet(self, request):
        """ *get* and handle a request/packet, passed in self.packet """
        RequestHandler( request, self)

        
class  RequestHandler:
    """ Handle requests received by Narval and to be sent by narval"""

    def __init__(self, request, server):
        """ 
            server is a reference to the JabberService launched server.
            - request: a JabberPacket instance
        
        """
        self.request = request
        self.server = server

        try :
#            if not isinstance(request,JabberPackets.Stream) \
#                and not isinstance(request, Jabber.UnrecognisedPacket):
            self._process_request()
        # stolen from SocketServer method...
        finally :
            sys.exc_traceback = None    # Help garbage collection
            
    def _process_request(self):
        """ - After have gotten the request generated by jabberServer, 
            process_request means to give it to narval, so it will process it
            - This method handle the *self.request*, defined in the
              RequestHandler __init__
            - self.request is a JabberPacketsInstance
            - *self.request* is used by _build_xml_request,
              that generates a request then given to narval, through
              instantiateHandlerRecipe.
        """
        
        # The server reference (SimpleClient!), defined in RequestHandler
        server = self.server
        try:
            # generate a xml_request with gotten request/packet
            xml_request = self._build_xml_request()
            if xml_request:
                # start the plan/ Ask narval to process the request
                server.protocol_handler.narval.instantiateHandlerRecipe(
                    server.recipe, 
                    xml_request,
                    server.protocol_handler
                    )
        except:
            import traceback
            traceback.print_exc()
            # put a more significant xml error, of jabberPacket type!!!
            server.send("<error/>")
            self.disconnect() # close the jabber stream:stream
            raise
            

    def _build_xml_request(self):
        """ generates a JabberPacket which is then embedded in a jabber-request
            node.
        """
        if self.request.getFrom() != self.server.protocol_handler.jabber_id:
            if not self.request.getID():
                self.request.setID(id(self.request))
            body = self.request.getBody()
            print "BODY3", body.encode("utf-8")
            xml_request = self.request.asNode()._xmlnode2str()
##          xml_request = _unescape(xml_request)
            
            print type(self.request), xml_request.encode('utf-8')
            # make jabber-request
            xml_request="<jabber-request id='%s' type='incoming' xmlns='%s'>%s</jabber-request>"%\
                     (  self.server.protocol_handler.context_id, 
                        JABBER_NS, xml_request )
            return xml_request
        else:
            return None
### Methods to register and login client #####################################

def register_client(**d):
    """ Register narval as Jabber Client if doesn't exist"""
    print "register narvabber, the jabber user"
    # Send the first packet for communication with jabberd
    s = jabber.IQ()
    s.setType("set")
    qu = s.setQuery("jabber:iq:register")
    qu.insertTag("username").insertData(d['username'])
    qu.insertTag("password").insertData(d['password'])
    qu.insertTag("name").insertData(d['name'])
    qu.insertTag("email").insertData(d['email'])
    return s
 
def login_client(service, **d):
    """ login user, providing the fact it already exists """
    import sha
    print "Login "+d['username']+" at resource "+d['resource']
    s = jabber.Iq()
    s.setType("set")

    qu = s.setQuery("jabber:iq:auth")
    qu.insertTag("username").insertData(d['username'])
    qu.insertTag("resource").insertData(d['resource'])

    ## digesting the passwd/glurp
    digest = qu.insertTag('digest')
    digest.insertData(sha.new(
        service.getIncomingID() + d['password']).hexdigest() )
    service.send(s)

def new_context_id(ref):
    import md5
    from base64 import encodestring
    m = md5.new(ref)
    # pourquoi mettre repr?
    new_digest = repr(m.digest())
    return encodestring(new_digest)

def _unescape(phr):
    """&amp; --> &"""
    str = phr.replace('&quot;','"')
    str = str.replace('&apos;',"'")
    return str
