from eventlet.api import trampoline, get_hub
from eventlet import util

BUFFER_SIZE = 4096

class wrapped_fd(object):
    newlines = '\r\n'
    mode = 'wb+'
    
    def __init__(self, fd):
        self._closed = False
        self.fd = fd
        self._fileno = fd.fileno()
        self.recvbuffer = ''
        self.recvcount = 0
        self.sendcount = 0        

    def getpeername(self, *args, **kw):
        fn = self.getpeername = self.fd.getpeername
        return fn(*args, **kw)

    def getsockname(self, *args, **kw):
        fn = self.getsockname = self.fd.getsockname
        return fn(*args, **kw)

    def listen(self, *args, **kw):
        fn = self.listen = self.fd.listen
        return fn(*args, **kw)

    def bind(self, *args, **kw):
        fn = self.bind = self.fd.bind
        return fn(*args, **kw)

    def getsockopt(self, *args, **kw):
        fn = self.getsockopt = self.fd.getsockopt
        return fn(*args, **kw)

    def setsockopt(self, *args, **kw):
        fn = self.setsockopt = self.fd.setsockopt
        return fn(*args, **kw)

    def connect_ex(self, *args, **kw):
        fn = self.connect_ex = self.fd.connect_ex
        return fn(*args, **kw)

    def fileno(self, *args, **kw):
        fn = self.fileno = self.fd.fileno
        return fn(*args, **kw)

    def setblocking(self, *args, **kw):
        fn = self.setblocking = self.fd.setblocking
        return fn(*args, **kw)

    def close(self, *args, **kw):
        if self._closed:
            return
        self._closed = True
        fn = self.close = self.fd.close
        try:
            res = fn(*args, **kw)
        finally:
            # This will raise socket.error(32, 'Broken pipe') if there's
            # a caller waiting on trampoline (e.g. server on .accept())
            get_hub().exc_descriptor(self._fileno)
        return res

    def accept(self):
        fd = self.fd
        while True:
            res = util.socket_accept(fd)
            if res is not None:
                client, addr = res
                util.set_nonblocking(client)
                return type(self)(client), addr
            trampoline(fd, read=True, write=True)

    def connect(self, address):
        fd = self.fd
        connect = util.socket_connect
        while not connect(fd, address):
            trampoline(fd, read=True, write=True)
    
    def recv(self, buflen):
        buf = self.recvbuffer
        if buf:
            chunk, self.recvbuffer = buf[:buflen], buf[buflen:]
            return chunk
        fd = self.fd
        recv = util.socket_recv
        bytes = recv(fd, buflen)
        while bytes is None:
            trampoline(fd, read=True)
            bytes = recv(fd, buflen)
        self.recvcount += len(bytes)
        return bytes

    def send(self, data):
        count = util.socket_send(self.fd, data)
        if not count:
            return 0
        self.sendcount += count
        return count

    def sendall(self, data):
        fd = self.fd
        tail = self.send(data)
        while tail < len(data):
            trampoline(self.fd, write=True)
            tail += self.send(data[tail:])

    def write(self, data):
        return self.sendall(data)
    
    def readuntil(self, terminator, size=None):
        buf, self.recvbuffer = self.recvbuffer, ''
        checked = 0
        if size is None:
            while True:
                found = buf.find(terminator, checked)
                if found != -1:
                    found += len(terminator)
                    chunk, self.recvbuffer = buf[:found], buf[found:]
                    return chunk
                checked = len(buf)
                d = self.recv(BUFFER_SIZE)
                if not d:
                    break
                buf += d
            return buf
        while len(buf) < size:
            found = buf.find(terminator, checked)
            if found != -1:
                found += len(terminator)
                chunk, self.recvbuffer = buf[:found], buf[found:]
                return chunk
            checked = len(buf)
            d = self.recv(BUFFER_SIZE)
            if not d:
                break
            buf += d
        chunk, self.recvbuffer = buf[:size], buf[size:]
        
    def readline(self, size=None):
        return self.readuntil(self.newlines, size=size)

    def __iter__(self):
        return self.xreadlines()

    def readlines(self, size=None):
        return list(self.xreadlines(size=size))

    def xreadlines(self, size=None):
        if size is None:
            while True:
                line = self.readline()
                if not line:
                    break
                yield line
        else:
            while size > 0:
                line = self.readline(size)
                if not line:
                    break
                yield line
                size -= len(line)

    def writelines(self, lines):
        for line in lines:
            self.write(line)
        
    def read(self, size=None):
        buf, self.recvbuffer = self.recvbuffer, ''
        lst = [buf]
        if size is None:
            while True:
                d = self.recv(BUFFER_SIZE)
                if not d:
                    break
                lst.append(d)
        else:
            buflen = len(buf)
            while buflen < size:
                d = self.recv(BUFFER_SIZE)
                if not d:
                    break
                buflen += len(d)
                lst.append(d)
            else:
                d = lst[-1]
                overbite = buflen - size
                if overbite:
                    lst[-1], self.recvbuffer = d[:-overbite], d[-overbite:]
                else:
                    lst[-1], self.recvbuffer = d, ''
        return ''.join(lst)

    def makefile(self, *args, **kw):
        return type(self)(self.fd)
