import select
import socket
import struct
import sys
import types

VERBOSE = None

class SocketProtocol:
    """A simple protocol for sending strings across a socket"""
    BUF_SIZE = 8192

    def __init__(self, sock):
        self.sock = sock
        self._buffer = ''
        self._closed = 0

    def close(self):
        self._closed = 1
        self.sock.close()

    def send(self, buf):
        """Encode buf and write it on the socket"""
        if VERBOSE:
            VERBOSE.write('send %d:%s\n' % (len(buf), `buf`))
        self.sock.send('%d:%s' % (len(buf), buf))

    def receive(self, timeout=0):
        """Get next complete string from socket or return None

        Raise EOFError on EOF
        """
        buf = self._read_from_buffer()
        if buf is not None:
            return buf
        recvbuf = self._read_from_socket(timeout)
        if recvbuf is None:
            return None
        if recvbuf == '' and self._buffer == '':
            raise EOFError
        if VERBOSE:
            VERBOSE.write('recv %s\n' % `recvbuf`)
        self._buffer = self._buffer + recvbuf
        r = self._read_from_buffer()
        return r

    def _read_from_socket(self, timeout):
        """Does not block"""
        if self._closed:
            return ''
        if timeout is not None:
            r, w, x = select.select([self.sock], [], [], timeout)
        if timeout is None or r:
            return self.sock.recv(self.BUF_SIZE)
        else:
            return None

    def _read_from_buffer(self):
        buf = self._buffer
        i = buf.find(':')
        if i == -1:
            return None
        buflen = int(buf[:i])
        enclen = i + 1 + buflen
        if len(buf) >= enclen:
            s = buf[i+1:enclen]
            self._buffer = buf[enclen:]
            return s
        else:
            self._buffer = buf
        return None

# helpers for registerHandler method below

def get_methods(obj):
    methods = []
    for name in dir(obj):
        attr = getattr(obj, name)
        if callable(attr):
            methods.append(name)
    if type(obj) == types.InstanceType:
        methods = methods + get_methods(obj.__class__)
    if type(obj) == types.ClassType:
        for super in obj.__bases__:
            methods = methods + get_methods(super)
    return methods

class CommandProtocol:
    def __init__(self, sockp):
        self.sockp = sockp
        self.seqno = 0
        self.handlers = {}

    def close(self):
        self.sockp.close()
        self.handlers.clear()

    def registerHandler(self, handler):
        """A Handler is an object with handle_XXX methods"""
        for methname in get_methods(handler):
            if methname[:7] == "handle_":
                name = methname[7:]
                self.handlers[name] = getattr(handler, methname)

    def send(self, cmd, arg='', seqno=None):
        if arg:
            msg = "%s %s" % (cmd, arg)
        else:
            msg = cmd
        if seqno is None:
            seqno = self.get_seqno()
        msgbuf = self.encode_seqno(seqno) + msg
        self.sockp.send(msgbuf)
        if cmd == "reply":
            return
        reply = self.sockp.receive(timeout=None)
        r_cmd, r_arg, r_seqno = self._decode_msg(reply)
        assert r_seqno == seqno and r_cmd == "reply", "bad reply"
        return r_arg

    def _decode_msg(self, msg):
        seqno = self.decode_seqno(msg[:self.SEQNO_ENC_LEN])
        msg = msg[self.SEQNO_ENC_LEN:]
        parts = msg.split(" ", 2)
        if len(parts) == 1:
            cmd = msg
            arg = ''
        else:
            cmd = parts[0]
            arg = parts[1]
        return cmd, arg, seqno

    def dispatch(self):
        msg = self.sockp.receive()
        if msg is None:
            return
        cmd, arg, seqno = self._decode_msg(msg)
        self._current_reply = seqno
        h = self.handlers.get(cmd, self.default_handler)
        try:
            r = h(arg)
        except TypeError, msg:
            raise TypeError, "handle_%s: %s" % (cmd, msg)
        if self._current_reply is None:
            if r is not None:
                sys.stderr.write("ignoring %s return value type %s\n" % \
                                 (cmd, type(r).__name__))
            return
        if r is None:
            r = ''
        if type(r) != types.StringType:
            raise ValueError, "invalid return type for %s" % cmd
        self.send("reply", r, seqno=seqno)

    def reply(self, arg=''):
        """Send a reply immediately

        otherwise reply will be sent when handler returns
        """
        self.send("reply", arg, self._current_reply)
        self._current_reply = None

    def default_handler(self, arg):
        sys.stderr.write("WARNING: unhandled message %s\n" % arg)
        return ''

    SEQNO_ENC_LEN = 4

    def get_seqno(self):
        seqno = self.seqno
        self.seqno = seqno + 1
        return seqno

    def encode_seqno(self, seqno):
        return struct.pack("I", seqno)

    def decode_seqno(self, buf):
        return struct.unpack("I", buf)[0]


class StdioRedirector:
    """Redirect sys.std{in,out,err} to a set of file-like objects"""

    def __init__(self, stdin, stdout, stderr):
        self.stdin = stdin
        self.stdout = stdout
        self.stderr = stderr

    def redirect(self):
        self.save()
        sys.stdin = self.stdin
        sys.stdout = self.stdout
        sys.stderr = self.stderr

    def save(self):
        self._stdin = sys.stdin
        self._stdout = sys.stdout
        self._stderr = sys.stderr

    def restore(self):
        sys.stdin = self._stdin
        sys.stdout = self._stdout
        sys.stderr = self._stderr

class IOWrapper:
    """Send output from a file-like object across a SocketProtocol

    XXX Should this be more tightly integrated with the CommandProtocol?
    """

    def __init__(self, name, cmdp):
        self.name = name
        self.cmdp = cmdp
        self.buffer = []

class InputWrapper(IOWrapper):
    def write(self, buf):
        # XXX what should this do on Windows?
        raise IOError, (9, '[Errno 9] Bad file descriptor')

    def read(self, arg=None):
        if arg is not None:
            if arg <= 0:
                return ''
        else:
            arg = 0
        return self.cmdp.send(self.name, "read,%s" % arg)

    def readline(self):
        return self.cmdp.send(self.name, "readline")

class OutputWrapper(IOWrapper):
    def write(self, buf):
        self.cmdp.send(self.name, buf)

    def read(self, arg=None):
        return ''

class RemoteInterp:
    def __init__(self, sock):
        self._sock = SocketProtocol(sock)
        self._cmd = CommandProtocol(self._sock)
        self._cmd.registerHandler(self)

    def run(self):
        try:
            while 1:
                self._cmd.dispatch()
        except EOFError:
            pass

    def handle_execfile(self, arg):
        self._cmd.reply()
        io = StdioRedirector(InputWrapper("stdin", self._cmd),
                             OutputWrapper("stdout", self._cmd),
                             OutputWrapper("stderr", self._cmd))
        io.redirect()
        execfile(arg, {'__name__':'__main__'})
        io.restore()
        self._cmd.send("terminated")

    def handle_quit(self, arg):
        self._cmd.reply()
        self._cmd.close()

def startRemoteInterp(id):
    import os
    # UNIX domain sockets are simpler for starters
    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    sock.bind("/var/tmp/ri.%s" % id)
    try:
        sock.listen(1)
        cli, addr = sock.accept()
        rinterp = RemoteInterp(cli)
        rinterp.run()
    finally:
        os.unlink("/var/tmp/ri.%s" % id)

class RIClient:
    """Client of the remote interpreter"""
    def __init__(self, sock):
        self._sock = SocketProtocol(sock)
        self._cmd = CommandProtocol(self._sock)
        self._cmd.registerHandler(self)

    def execfile(self, file):
        self._cmd.send("execfile", file)

    def run(self):
        try:
            while 1:
                self._cmd.dispatch()
        except EOFError:
            pass

    def handle_stdout(self, buf):
        sys.stdout.write(buf)
##        sys.stdout.flush()

    def handle_stderr(self, buf):
        sys.stderr.write(buf)

    def handle_stdin(self, arg):
        if arg == "readline":
            return sys.stdin.readline()
        i = arg.find(",") + 1
        bytes = int(arg[i:])
        if bytes == 0:
            return sys.stdin.read()
        else:
            return sys.stdin.read(bytes)

    def handle_terminated(self, arg):
        self._cmd.reply()
        self._cmd.send("quit")
        self._cmd.close()

def riExec(id, file):
    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    sock.connect("/var/tmp/ri.%s" % id)
    cli = RIClient(sock)
    cli.execfile(file)
    cli.run()

if __name__ == "__main__":
    import sys
    import getopt

    SERVER = 1
    opts, args = getopt.getopt(sys.argv[1:], 'cv')
    for o, v in opts:
        if o == '-c':
            SERVER = 0
        elif o == '-v':
            VERBOSE = sys.stderr
    id = args[0]

    if SERVER:
        startRemoteInterp(id)
    else:
        file = args[1]
        riExec(id, file)
