'''
Copyright (c) 2011-2014, Agora Games, LLC All rights reserved.

https://github.com/agoragames/haigha/blob/master/LICENSE.txt
'''

from haigha.transports.transport import Transport

import errno
import socket


class SocketTransport(Transport):

    '''
    A simple blocking socket transport.
    '''

    def __init__(self, *args):
        super(SocketTransport, self).__init__(*args)
        self._synchronous = True
        self._buffer = bytearray()

    ###
    # Transport API
    ###
    def connect(self, (host, port), klass=socket.socket):
        '''
        Connect assuming a host and port tuple.
        '''
        self._host = "%s:%s" % (host, port)
        self._sock = klass()
        self._sock.setblocking(True)
        self._sock.settimeout(self.connection._connect_timeout)
        if self.connection._sock_opts:
            for k, v in self.connection._sock_opts.iteritems():
                family, type = k
                self._sock.setsockopt(family, type, v)
        self._sock.connect((host, port))

        # After connecting, switch to full-blocking mode.
        self._sock.settimeout(None)

    def read(self, timeout=None):
        '''
        Read from the transport. If timeout>0, will only block for `timeout`
        seconds.
        '''
        e = None
        if not hasattr(self, '_sock'):
            return None

        try:
            # Note that we ignore both None and 0, i.e. we either block with a
            # timeout or block completely and let gevent sort it out.
            if timeout:
                self._sock.settimeout(timeout)
            else:
                self._sock.settimeout(None)
            data = self._sock.recv(
                self._sock.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF))

            if len(data):
                if self.connection.debug > 1:
                    self.connection.logger.debug(
                        'read %d bytes from %s' % (len(data), self._host))
                if len(self._buffer):
                    self._buffer.extend(data)
                    data = self._buffer
                    self._buffer = bytearray()
                return data

            # Note that no data means the socket is closed and we'll mark that
            # below

        except socket.timeout as e:
            # Note that this is implemented differently and though it would be
            # caught as an EnvironmentError, it has no errno. Not sure whose
            # fault that is.
            return None

        except EnvironmentError as e:
            # thrown if we have a timeout and no data
            if e.errno in (errno.EAGAIN, errno.EWOULDBLOCK, errno.EINTR):
                return None

            self.connection.logger.exception(
                'error reading from %s' % (self._host))

        self.connection.transport_closed(
            msg='error reading from %s' % (self._host))
        if e:
            raise

    def buffer(self, data):
        '''
        Buffer unused bytes from the input stream.
        '''
        if not hasattr(self, '_sock'):
            return None

        # data will always be a byte array
        if len(self._buffer):
            self._buffer.extend(data)
        else:
            self._buffer = bytearray(data)

    def write(self, data):
        '''
        Write some bytes to the transport.
        '''
        if not hasattr(self, '_sock'):
            return None

        try:
            self._sock.sendall(data)

            if self.connection.debug > 1:
                self.connection.logger.debug(
                    'sent %d bytes to %s' % (len(data), self._host))

            return
        except EnvironmentError:
            # sockets raise this type of error, and since if sendall() fails
            # we're left in an indeterminate state, assume that any error we
            # catch means that the connection is dead. Note that this
            # assumption requires this to be a blocking socket; if we ever
            # support non-blocking in this class then this whole method has
            # to change a lot.
            self.connection.logger.exception(
                'error writing to %s' % (self._host))

        self.connection.transport_closed(
            msg='error writing to %s' % (self._host))

    def disconnect(self):
        '''
        Disconnect from the transport. Typically socket.close(). This call is
        welcome to raise exceptions, which the Connection will catch.
        '''
        if not hasattr(self, '_sock'):
            return None

        try:
            self._sock.close()
        finally:
            self._sock = None
