"""
Created on 30 May 2017

@author: Bruno Beloff (bruno.beloff@southcoastscience.com)

A network socket abstraction, implementing ProcessComms
"""

import socket
import time

from scs_core.sys.process_comms import ProcessComms


# --------------------------------------------------------------------------------------------------------------------

class NetworkSocket(ProcessComms):
    """
    classdocs
    """

    __TIMEOUT =         4.0         # seconds
    __BUFFER_SIZE =     1024        # bytes
    __BACKLOG =         5

    __ACK =             "ACK"


    # ----------------------------------------------------------------------------------------------------------------

    def __init__(self, host, port=2000):        # a receiving socket should have host ''
        """
        Constructor
        """
        self.__address = (host, port)

        self.__socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
        self.__conn = None


    # ----------------------------------------------------------------------------------------------------------------

    def connect(self, wait_for_availability=True):
        while True:
            try:
                self.__socket.connect(self.__address)
                break

            except ConnectionRefusedError as ex:
                if not wait_for_availability:
                    raise ConnectionRefusedError(ex)

                time.sleep(0.1)


    def close(self):
        try:
            if self.__conn:
                self.__conn.close()
        except RuntimeError:
            pass

        try:
            self.__socket.close()
        except RuntimeError:
            pass


    # ----------------------------------------------------------------------------------------------------------------

    def read(self):
        # socket...
        self.__socket.bind(self.__address)
        self.__socket.listen(NetworkSocket.__BACKLOG)

        self.__conn, _ = self.__socket.accept()

        # data...
        while True:
            message = self.__conn.recv(NetworkSocket.__BUFFER_SIZE).decode().strip()

            if len(message) == 0:
                break

            yield message


    def write(self, message, wait_for_availability=True):
        while True:
            try:
                # data...
                self.__socket.send(message.encode())

                # wait for ACK...
                timeout = time.time() + NetworkSocket.__TIMEOUT

                while self.__socket.recv(NetworkSocket.__BUFFER_SIZE).decode() != NetworkSocket.__ACK:
                    time.sleep(0.001)

                    if time.time() > timeout:
                        break

                break

            except ConnectionError:
                if not wait_for_availability:
                    raise

                self.close()

                time.sleep(0.1)

                self.__socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
                self.connect()


    # ----------------------------------------------------------------------------------------------------------------

    def ack(self):
        self.__conn.send(str(NetworkSocket.__ACK).encode())


    # ----------------------------------------------------------------------------------------------------------------

    def __str__(self, *args, **kwargs):
        return "NetworkSocket:{address:%s, socket:%s}" % (self.__address, self.__socket)

[((37, 24, 37, 85), 'socket.socket', 'socket.socket', (), '', False, 'import socket\n'), ((53, 16, 53, 31), 'time.sleep', 'time.sleep', ({(53, 27, 53, 30): '(0.1)'}, {}), '(0.1)', False, 'import time\n'), ((95, 26, 95, 37), 'time.time', 'time.time', ({}, {}), '()', False, 'import time\n'), ((98, 20, 98, 37), 'time.sleep', 'time.sleep', ({(98, 31, 98, 36): '(0.001)'}, {}), '(0.001)', False, 'import time\n'), ((111, 16, 111, 31), 'time.sleep', 'time.sleep', ({(111, 27, 111, 30): '(0.1)'}, {}), '(0.1)', False, 'import time\n'), ((113, 32, 113, 93), 'socket.socket', 'socket.socket', (), '', False, 'import socket\n'), ((100, 23, 100, 34), 'time.time', 'time.time', ({}, {}), '()', False, 'import time\n')]