import logging
from multiprocessing import Process, Pipe
from threading import Thread
from typing import List

import zmq

logger = logging.getLogger("ZeroMqEventManager.StreamerDevice")


class StreamerDevice(object):
    def __init__(self, sub_address, push_address, min_port, max_port):
        # type: (str, str, int, int) -> None
        self.sub_address = sub_address
        self.push_address = push_address
        self.min_port = min_port
        self.max_port = max_port
        self.port = None
        self.sub_socket = None
        self.running = False
        self.parent_conn = None
        self.child_conn = None
        self.process = None

    def start(self):
        self.parent_conn, self.child_conn = Pipe()
        self.process = Process(target=self.execute)
        self.process.start()
        self.port = self.parent_conn.recv()
        print("port received: {}".format(self.port))
        return self.port

    def stop(self):
        self.process.terminate()
        print("process terminated: {}".format(self.port))

    def add_subjects(self, subjects):
        data = ["__INTERNAL_ADD_SUBJECTS__"]
        data.extend(subjects)
        self.parent_conn.send(data)

    def remove_subjects(self, subjects):
        data = ["__INTERNAL_REMOVE_SUBJECTS__"]
        data.extend(subjects)
        self.parent_conn.send(data)

    def execute(self):
        context = zmq.Context()

        try:
            self.sub_socket = context.socket(zmq.SUB)
            self.sub_socket.set_hwm(100000)
            self.sub_socket.connect(self.sub_address)

            push_socket = context.socket(zmq.PUSH)
            push_socket.set_hwm(100000)
            port = push_socket.bind_to_random_port(self.push_address, self.min_port, self.max_port)
            self.child_conn.send(port)
        except:
            logger.exception("Error binding sockets.")
            raise

        self.running = True
        t = Thread(target=self.listen_messages)
        t.daemon = True
        t.start()

        logger.info("Starting forwarder device: %s %s", self.sub_address, self.push_address)
        try:
            zmq.device(zmq.FORWARDER, self.sub_socket, push_socket)
        except:
            logger.exception("Forwarder device died")

        context.destroy()
        self.running = False

    def listen_messages(self):
        while self.running:
            try:
                subjects = self.child_conn.recv()
                command = subjects[0]
                subjects = subjects[1:]
                if command == "__INTERNAL_ADD_SUBJECTS__":
                    self.child_add_subjects(subjects)
                else:
                    self.child_remove_subjects(subjects)
            except Exception as ex:
                logger.exception("Error processing message")

    def child_add_subjects(self, subjects):
        # type: (List[str]) -> None
        for subject in subjects:
            self.sub_socket.setsockopt_string(zmq.SUBSCRIBE, subject + " ")

    def child_remove_subjects(self, subjects):
        # type: (List[str]) -> None
        for subject in subjects:
            self.sub_socket.setsockopt_string(zmq.UNSUBSCRIBE, subject + " ")
