import sys
import zmq


def open_sub_socket(zmq_host, zmq_port):
    """
    Opens a ZeroMQ subscriber socket, connects to it and returns it

    Creates a ZeroMQ context and a socket associated with the context.
    Socket type is zmq.SUB. After the creation, the socket connects
    to remote ZeroMQ socket with tcp protocol and URI passed by the
    user. It returns a tuple with the context and the socket.

    :param zmq_host: ZeroMQ host IP
    :type zmq_host: string
    :param zmq_port: ZeroMQ host port
    :type zmq_port: str | int
    :return context_tuple: Tuple with ZeroMQ context and associated socket
    :type context_tuple: tuple(zmq.Context, zmq.Socket)
    """

    def try_catch_wrapper(func, **kwargs):
        """
        Wraps a zmq function in a try catch block and returns the output

        Takes as input a function and a variable number of keyword arguments to
        use as the function parameters. If the function fails with ZMQERROR the
        error code and a message is printed before the program exits.

        :param func: The function to be run
        :type func: function
        :param **kwargs: Keyword arguments
        :type **kwargs: Any
        :return : The func return value
        :type : Any
        """
        try:
            return func(**kwargs)
        except zmq.error.ZMQError as e:
            str_args = str(list(kwargs.values()))
            str_args = str_args.replace('[', '').replace(']', '')
            print(
                "[ZMQError {}]: {} error in {}.{} with argument {}".format(
                    e.errno,
                    e.strerror,
                    func.__module__,
                    func.__name__,
                    str_args)
            )
            sys.exit(1)

    context = zmq.Context()
    socket = try_catch_wrapper(context.socket, socket_type=zmq.SUB)
    uri = "tcp://{}:{}".format(zmq_host, zmq_port)
    try_catch_wrapper(socket.connect, addr=uri)
    try_catch_wrapper(socket.setsockopt, option=zmq.SUBSCRIBE, optval=b'')

    context_tuple = (context, socket)
    return context_tuple
