# -*- python -*-

"""TCP-ghost

Handles pass-through Presentations, Requests for symbols and
resolutions (associating names for things with connexions), Listens,
and Closes.

Could also answer requests for (host . port) pairs by establishing outbound
connexions and returning write-handles to requesters, or something like that.
"""

from dp2.util import clone
from dp2.ghostutil import puke
from dp2.corpora import Request, Presentation, Path, Listen, Close
from dp2.ghosts import Ghost, PredicateGhost
from dp2.predicates import *
from dp2 import dispatch
import asyncore
import threading
from socket import AF_INET, SOCK_STREAM
name = 'tcp'

connexions = {}
listeners = {}

selector = threading.Thread(target=asyncore.loop, kwargs={'timeout': 2})
selector.setDaemon(True)
selector_mutex = threading.Lock()

def pass_(aff): # vicar
    dest = aff.target.tail
    if isinstance(aff.actor, ReadWriter):
        return clone(aff, actor=Path(ghost, aff.actor.target),
                          target=dest)
    actor = Path(ghost, aff.actor)
    def continue_():
        dispatch.schedule(Listen(actor=actor, target=dest))
        dispatch.schedule(clone(aff, actor=actor, target=dest))
    if connexions.has_key(dest): # hmmm....
        dest = connexions[dest]
        continue_()
    else:
        dest = ReadWriter(dest, callback=continue_) # listeners don't callback
        if is_listen(aff):
            try:
                dest.listen()
            except asyncore.socket.error, e:
                print 'error making %s listen to %s: %s' \
                      % (dest, dest.target, e)
            else:
                return clone(aff, actor=actor, target=dest)
        else:
            try:
                dest.connect()
            except asyncore.socket.error, e:
                print 'error connecting %s to %s: %s' % (dest, dest.target, e)

ghost = pass_ # let's just assume we get only affection

class Connexion(asyncore.dispatcher_with_send):
    def __init__(self, ghost, *args, **kwargs):
        self.trying = False
        if kwargs.has_key('callback'):
            self.handle_connect = kwargs['callback']
            del kwargs['callback']
        self.ghost = ghost
        asyncore.dispatcher_with_send.__init__(self, *args, **kwargs)
        if not self.socket:
            self.create_socket(AF_INET, SOCK_STREAM)
        selector_mutex.acquire()
        if not selector.isAlive():
            selector._Thread__started = False
            selector.start()
        selector_mutex.release()
    def readable(self):
        return (self.trying or self.accepting or self.connected) \
               and self.ghost.parsers
    def writable(self):
        return self.trying and asyncore.dispatcher_with_send.writable(self)
    def handle_read(self):
        s = self.recv(0x100)
        if s:
            self.ghost.broadcast(Presentation, object=s)
    def handle_write(self):
        res = asyncore.dispatcher_with_send.handle_write(self)
        return res
    def send(self, *args):
        res = asyncore.dispatcher_with_send.send(self, *args)
        return res
    def handle_accept(self):
        sock, info = self.accept()
        connexions[info] = self.ghost.spawn(info, socket=sock)
    def handle_close(self):
        self.close()
    def connect(self, *args):
        res = asyncore.dispatcher_with_send.connect(self, *args)
        self.trying = True
        return res
    def close(self):
        if self.accepting:
            del listeners[self.ghost.target]
        else:
            del connexions[self.ghost.target]
        asyncore.dispatcher_with_send.close(self)
#        self.ghost.broadcast(Alert, notice='closed')

connexion_reactor = PredicateGhost(puke)

class ReadWriter(Ghost):
    backlog = 0x10
#    weltgeist = connexion_reactor
    def weltgeist(self, aff): return connexion_reactor(self, aff)
    def __init__(self, target, *parsers, **kwargs):
        weltgeist = kwargs.get('weltgeist')
        if weltgeist:
            self.weltgeist = weltgeist
        Ghost.__init__(self, self.weltgeist)
        self.vocabulary = {}
        self.target = target
        self.parsers = list(parsers)
        self.connexion = Connexion(self, sock=kwargs.get('socket'),
                                         callback=kwargs.get('callback'))
    def connect(self):
        res = self.connexion.connect(self.target)
        connexions[self.target] = self
        return res
    def listen(self):
        self.connexion.bind(self.target)
        self.connexion.listen(self.backlog)
        listeners[self.target] = self
    def spawn(self, *args, **kwargs):
        return self.__class__(*(args+tuple(self.parsers)),
                              **kwargs)
    def symbol_for(self, corpus):
        symbol = 'corpus-' + hex(id(corpus))[2:]
        self.vocabulary[symbol] = corpus
        return symbol
    def resolve(self, req):
        item = req.item
        if isinstance(item, str):
            item = self.vocabulary[item]
        else:
            item = self.symbol_for(item)
        return Presentation(actor=self, object=item, target=req.actor)
    def disconnect(self, close):
        self.connexion.close()
    def subscribe(self, listen):
        listener = listen.actor
        if listener not in self.parsers:
            self.parsers.append(listener)
    def write(self, present):
        s = present.object
        try:
            self.connexion.send(s)
        except asyncore.socket.error, e:
            # XXX should be able to requeue with a delay....
            print self, 'failed to send:', e
            # We should be able to Signal an error....
    def delegate(self, d):
        self.parsers.append(d)
    def broadcast(self, class_, **args):
        for parser in self.parsers:
            aff = class_(actor=self, target=parser, **args)
            dispatch.schedule(aff)

connexion_reactor.delegate(lambda s, *c: is_request(*c), ReadWriter.resolve)
connexion_reactor.delegate(lambda s, *c: is_presentation(*c), ReadWriter.write)
connexion_reactor.delegate(lambda s, *c: is_listen(*c), ReadWriter.subscribe)
connexion_reactor.delegate(lambda s, *c: is_close(*c), ReadWriter.disconnect)
