import zmq
import json
import logging
from common.struct import Struct

SEND_TIMEOUT = 2 * 1000  # in milliseconds
RECV_TIMEOUT = 3 * 1000  # in milliseconds

class GaragePiClient(object):
    """
    Client that connects with GaragePi backend to perform tasks
    """

    def __init__(self, logger: logging.Logger, connect_port='5550'):
        assert logger is not None
        self.__logger = logger

        self.__connect_addr = "tcp://localhost:%s" % connect_port
        self.__logger.debug("Connect address: " + self.__connect_addr)

        self.__context = zmq.Context()
        self.__context.setsockopt(zmq.RCVTIMEO, RECV_TIMEOUT)
        self.__poller = zmq.Poller()
        self.__socket = None    # type: zmq.sugar.Socket
        self.__create_socket()


    def __create_socket(self):
        if self.__socket is not None:
            self.close()

        self.__logger.debug("Creating new socket")
        self.__socket = self.__context.socket(zmq.DEALER)
        self.__socket.connect(self.__connect_addr)
        self.__poller.register(self.__socket, zmq.POLLIN)

    def close(self):
        self.__logger.debug("Closing out existing socket")
        self.__socket.setsockopt(zmq.LINGER, 0)
        self.__socket.close()
        self.__poller.unregister(self.__socket)
        self.__socket = None

    def __send_recv_msg(self, msg):
        # Make sure we're sending bytes instead of strings
        msg = list(map(lambda s: str.encode(s) if type(s) is str else s, msg))
        self.__socket.send_multipart(msg)

        events = dict(self.__poller.poll(SEND_TIMEOUT))
        if events.get(self.__socket) == zmq.POLLIN:
            try:
                # Receive response and make sure the return is converted to string if necessary
                ret_msg = self.__socket.recv_multipart()[0]
                return bytes.decode(ret_msg) if type(ret_msg) is bytes else ret_msg
            except zmq.error.Again:
                # If the receive timed out then return None
                self.__logger.warning("Receive operation timed out!")
                self.__create_socket()
                return None
        else:
            self.__logger.warning("Send operation timed out!")
            self.__create_socket()
            return None


    def echo(self, message):
        self.__logger.debug("Requesting 'echo' with message: {0}".format(message))
        msg_json = json.dumps({'message': message})
        msg = ['echo', msg_json]
        reply_json = self.__send_recv_msg(msg)
        if reply_json is None: return None
        return json.loads(reply_json)['message']

    def get_status(self):
        self.__logger.debug("Requesting 'get_status'")
        msg = ['get_status', '{}']
        reply_json = self.__send_recv_msg(msg)
        if reply_json is None: return None
        return json.loads(reply_json)

    def trigger_relay(self, user_agent: str, login: str):
        self.__logger.debug("Requesting 'trigger_relay'")
        data = Struct(user_agent=user_agent, login=login)
        msg_json = data.to_json_bytes()
        msg = ['trigger_relay', msg_json]
        reply_json = self.__send_recv_msg(msg)
        if reply_json is None: return None
        return json.loads(reply_json)