from queue import Empty

import zmq

from clarabot_nano.common.zmq.base import AbstractBiPairZmqSocketThread, AbstractAioZmqSocketThread
from clarabot_nano.common.zmq.constants import QUEUE_PUSH_NOTIFICATION
from clarabot_nano.common.zmq.router_base import AbstractZmqRouterBaseThreadMixin
from clarabot_nano.common.zmq_utils import polling_send_multipart


# noinspection PyAbstractClass
class ZmqRouterServerThreadMixin(AbstractZmqRouterBaseThreadMixin):
    """
    This ROUTER socket thread is designed to be used as a server: accept incoming messages and produce responses.
    """
    SEND_HWM_TIMEOUT = 24.0

    def _thread_socket_loop(self, server_socket, inproc_socket):
        """
        A simple socket processing thread that buffers messages every which way.
        """
        # listen for input on all sockets
        pollin = zmq.Poller()
        pollin.register(server_socket, zmq.POLLIN)
        pollin.register(inproc_socket, zmq.POLLIN)
        # take care of hwm on router socket
        pollout = zmq.Poller()
        pollout.register(server_socket, zmq.POLLOUT)
        # take care of hwm on inproc socket
        inproc_pollout = zmq.Poller()
        inproc_pollout.register(inproc_socket, zmq.POLLOUT)
        # main loop
        while self._keep_running():
            sockets = dict(pollin.poll(1000))
            if server_socket in sockets:
                data = server_socket.recv_multipart()
                if len(data) >= 2:
                    self._thread_socket_send_to_logic(data, inproc_socket, inproc_pollout)
            if inproc_socket in sockets:
                data = inproc_socket.recv_multipart()
                if len(data) >= 2:
                    if data[0] == data[1] == QUEUE_PUSH_NOTIFICATION:
                        while True:
                            try:
                                data = self._inproc_queue.get(block=False)
                            except Empty:
                                break
                            polling_send_multipart(server_socket, data, pollout, self.SEND_HWM_TIMEOUT)
                    else:
                        polling_send_multipart(server_socket, data, pollout, self.SEND_HWM_TIMEOUT)


class BiPairZmqRouterServerThread(ZmqRouterServerThreadMixin, AbstractBiPairZmqSocketThread):
    def __init__(self, inproc_bind_name, security: str):
        ZmqRouterServerThreadMixin.__init__(self, security)
        AbstractBiPairZmqSocketThread.__init__(self, inproc_bind_name)


class AioZmqRouterServerThread(ZmqRouterServerThreadMixin, AbstractAioZmqSocketThread):
    def __init__(self, loop, inproc_bind_name, security: str):
        ZmqRouterServerThreadMixin.__init__(self, security)
        AbstractAioZmqSocketThread.__init__(self, loop, inproc_bind_name)
