# Copyright (c) 2002 Sean R. Lynch <seanl@chaosring.org>
#
# This file is part of PythonVerse.
#
# PythonVerse 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 2 of the License, or
# (at your option) any later version.
# 
# PythonVerse 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 General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with PythonVerse; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
import re, string, asyncore, asynchat, socket


class HandlerError(Exception): pass


class InputHandler:
    def __init__(self, callbacks):
        """Callbacks is a sequence of (command, callback, regular expression,
        sequence of thunks)"""

        self.dict = {}

        for command, callback, r, thunks in callbacks:
            self.dict[command] = (callback, re.compile(r), thunks)

    def handle(self, s):
        try:
            # Extract the command
            cmd, args = string.split(s, ' ', 1)
        except ValueError:
            # Assume a command with no arguments
            cmd = s
            args = ''
            
        try:
            func, r, conv = self.dict[cmd]
        except KeyError:
            raise HandlerError, 'Unknown: %s' % cmd
        else:
            # Parse the arguments
            match = r.match(args)
            if match is None: raise HandlerError, 'Failed parse: %s' % args
            elif match.end() != len(args):
                raise HandlerError, 'Not all arguments parsed: %s' % \
                      (len(args), match.end(), args)
            else:
                # Convert the arguments to the correct types
                apply(func, map(lambda x: x[0](x[1]),
                                zip(conv, match.groups())))


class Connection(asynchat.async_chat):
    """Connection object that can handle a client or server"""
    def __init__(self, handler, sock=None):
        asynchat.async_chat.__init__(self, sock)
        self.buffer = ''
        self.outbuf = ''
        self.handler = handler
        self.set_terminator('\r\n')

    # asynchat/asyncore handlers

    def debug(self): pass
    
    def handle_connect(self):
        # Should override this
        pass
    
    def collect_incoming_data(self, data):
        self.buffer = self.buffer + data
    
    def found_terminator(self):
        self.debug('-> %s' % string.strip(self.buffer))
        try: self.handler.handle(self.buffer)
        except HandlerError, info: self.debug(str(info))
        self.buffer = ''

    def writable(self):
        return len(self.outbuf) > 0

    def handle_write(self):
        bytes = self.send(self.outbuf)
        self.outbuf = self.outbuf[bytes:]

    def write(self, data):
        self.debug("<- %s" % string.strip(data))
        self.outbuf = self.outbuf + data


