from threading import Thread, Lock
import socket
import queue
import select
from logging import Logger
from pymasep.communication import Frame


class SocketHandler(Thread):
    """
    Thread used to handle received messages from conn, addr

    :param conn: connected socket
    :param addr: remote address
    :param logger: logger used to log send and receive message
    """

    def __init__(self,
                 conn,
                 addr,
                 logger: Logger):

        super().__init__(name='SocketHandler')
        self.lock = Lock()
        self.logger = logger
        self.socket = conn
        self.addr = addr
        # https://stackoverflow.com/questions/51104534/python-socket-receive-send-multi-threading
        self.r_sock, self.s_sock = socket.socketpair()
        self.send_queue = queue.Queue()

    def __del__(self):
        """
        delete the instance. close all sockets
        """
        self.r_sock.shutdown(1)
        self.s_sock.shutdown(1)
        self.r_sock.close()
        self.s_sock.close()

    def send_frame(self, frame):
        """
        send frame through socket

        :param frame: frame to send
        """
        with self.lock:
            self.socket.sendall(frame.size.to_bytes(2, 'little'))
            self.socket.sendall(frame.message)
        self.logger.info('Send : ' + str(frame.message))

    def receive_frame(self):
        """
        wait for receiving frame from socket
        """
        with self.lock:
            length_byte = self.socket.recv(2)
            length = int.from_bytes(length_byte, 'little')
            msg = self.socket.recv(length)
        result = Frame(msg)
        self.logger.info('Received : ' + str(result.message))
        return result

    def send_frame_async(self, frame):
        """
        Send a fram asynchronously. Use the internal queue and socket as describe here:
        https://stackoverflow.com/questions/51104534/python-socket-receive-send-multi-threading

        :param frame: frame to send
        """
        self.send_queue.put(frame)
        self.s_sock.send(b'\x00')

    def on_receive(self, frame):
        """
        Callback when a message is received (except for BYE frame).
        :param frame: frame received
        """
        pass

    def run(self):
        """
        Main thread run that receive and send frames
        """
        end = False
        while not end:
            rlist, _, _ = select.select([self.socket, self.r_sock], [], [])
            for ready_socket in rlist:
                if ready_socket is self.socket:
                    f = self.receive_frame()
                    if f.message == Frame.bye_frame().message:
                        end = True
                        self.logger.info('Received frame from ' + str(self.addr) + ' ' + str(f.message))
                    else:
                        self.on_receive(frame=f)
                        self.logger.info('Received frame from ' + str(self.addr) + ' (on_receive()) ' + str(f.message))
                else:
                    self.r_sock.recv(1)
                    m = self.send_queue.get()
                    if m.message == Frame.bye_frame().message:
                        end = True
                        self.logger.info('Received bye frame from self')
                    self.send_frame(m)
