from abc import ABCMeta, abstractmethod
from asyncio import Queue
from typing import Callable, List, Iterator

from rx import Observable
from rx.subjects import Subject

from interstate_py.log_factory import LogFactory
from interstate_py.reactive.rx_stream_connection import RxStreamConnection
from interstate_py.reactive.sending_observer import SendingObserver
from interstate_py.stream_connection import StreamConnection


class ConnectionAgentBase(metaclass=ABCMeta):
    @abstractmethod
    def get(self, identity):
        pass

    @abstractmethod
    def connections(self):
        """
        :return: Current connections, open or not.
        :note: this will be terribly slow over time if there are many connections
        """
        pass

    @abstractmethod
    def dump(self, identity):
        pass

    @abstractmethod
    def dump_all(self, identities: Iterator[str]):
        pass


class RxConnectionAgent(ConnectionAgentBase):
    """
    Holds references to the current active connections and provides consumers a message based interface
    to access the said connections.
    """
    _log = LogFactory.get_logger(__name__)

    def __init__(self, supplier: Callable[[Subject], Observable], outbound_queue: Queue):
        self._connections = {}
        self._supplier = supplier
        self._outbound_queue = outbound_queue

    def get(self, identity: str) -> StreamConnection:
        if identity in self._connections:
            return self._connections.get(identity)
        else:
            subject = Subject()
            outbound = self._supplier(subject)
            outbound \
                .subscribe(SendingObserver(identity, self._outbound_queue))
            self._log.info("[CONTROL] Added new connection (identity=%s)", identity)
            connection = RxStreamConnection(identity, subject, outbound)
            self._connections[identity] = connection
            return connection

    def connections(self) -> List[StreamConnection]:
        """
        :return: Current connections, open or not.
        :note: this will be terribly slow over time if there are many connections
        """
        return list(self._connections.values())

    def dump_all(self, identities: Iterator[str]):
        for i in identities:
            self.dump(i)

    def dump(self, identity: str):
        if identity in self._connections:
            self._connections.pop(identity)
