import sys, os
from struct import pack, unpack, calcsize
from strunicode import tryencode

MAGIC = -0x3b83728b

CMSG_INIT        = 'i'
CMSG_START_GRAPH = '['
CMSG_ADD_NODE    = 'n'
CMSG_ADD_EDGE    = 'e'
CMSG_ADD_LINK    = 'l'
CMSG_FIXED_FONT  = 'f'
CMSG_STOP_GRAPH  = ']'
CMSG_MISSING_LINK= 'm'
CMSG_SAY         = 's'

MSG_OK           = 'O'
MSG_ERROR        = 'E'
MSG_RELOAD       = 'R'
MSG_FOLLOW_LINK  = 'L'

# ____________________________________________________________

long_min = -2147483648
long_max = 2147483647


def message(tp, *values):
    #print >> sys.stderr, tp, values
    typecodes = ['']
    values = map(tryencode, values)
    for v in values:
        if type(v) is str:
            typecodes.append('%ds' % len(v))
        elif 0 <= v < 256:
            typecodes.append('B')
        elif long_min <= v <= long_max:
            typecodes.append('l')
        else:
            typecodes.append('q')
    typecodes = ''.join(typecodes)
    if len(typecodes) < 256:
        return pack(("!B%dsc" % len(typecodes)) + typecodes,
                    len(typecodes), typecodes, tp, *values)
    else:
        # too many values - encapsulate the message in another one
        return message('\x00', typecodes, pack("!c" + typecodes, tp, *values))

def decodemessage(data):
    if data:
        limit = ord(data[0]) + 1
        if len(data) >= limit:
            typecodes = "!c" + data[1:limit]
            end = limit + calcsize(typecodes)
            if len(data) >= end:
                msg = unpack(typecodes, data[limit:end])
                if msg[0] == '\x00':
                    msg = unpack("!c" + msg[1], msg[2])
                return msg, data[end:]
            #elif end > 1000000:
            #    raise OverflowError
    return None, data

# ____________________________________________________________

class RemoteError(Exception):
    pass


class IO(object):
    _buffer = ''

    def sendmsg(self, tp, *values):
        self.sendall(message(tp, *values))

    def recvmsg(self):
        while True:
            msg, self._buffer = decodemessage(self._buffer)
            if msg is not None:
                break
            self._buffer += self.recv()
        if msg[0] != MSG_ERROR:
            return msg
        raise RemoteError(*msg[1:])


class FileIO(IO):
    def __init__(self, f_in, f_out):
        self.f_in = f_in
        self.f_out = f_out

    def sendall(self, data):
        self.f_out.write(data)
        self.f_out.flush()

    def recv(self):
        fd = self.f_in.fileno()
        data = os.read(fd, 16384)
        if not data:
            raise EOFError
        return data

    def close_sending(self):
        self.f_out.close()

    def close(self):
        self.f_out.close()
        self.f_in.close()


class SocketIO(IO):
    def __init__(self, s):
        self.s = s

    def sendall(self, data):
        self.s.sendall(data)

    def recv(self):
        data = self.s.recv(16384)
        if not data:
            raise EOFError
        return data

    def close_sending(self):
        self.s.shutdown(1)    # SHUT_WR

    def close(self):
        self.s.close()
