from typing import Type, Any
from threading import Lock
from logging import Logger
import socket

from pymasep.utils import setup_logger


class BaseThreadedCommunication:
    """
    Base class for threaded communication for Client and Server

    :param ident: string id of the client or server
    :param host: host to listen or connect
    :param port: port to listen or connect
    :param socket_handler_class: class used to handle the socket (send, receive)
    :param socket_handler_args: arguments as tuple to instantiate the SocketHandler class
    """

    def __init__(self,
                 ident: str,
                 socket_handler_class: Type[Any],
                 socket_handler_args: tuple,
                 host: str,
                 port: int):
        self.id = ident
        self.host = host
        self.port = port
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.logger: Logger = setup_logger(name=self.__class__.__name__,
                                           path='logs',
                                           log_filename=self.__class__.__name__ + '-' + str(self.id) + '.log')
        self.socket_handler_class = socket_handler_class
        self.socket_handler_args = socket_handler_args
        # https://stackoverflow.com/questions/13610654/how-to-make-built-in-containers-sets-dicts-lists-thread-safe
        self._current_connections = dict()  # dict (id -> SocketHandler)
        self._current_connections_lock = Lock()

    def add_connection(self, ident: str, sh) -> None:
        """
        Add a connection to the client or server

        :param ident: id of the opposite BaseThreadCommunication connected to self
        :param sh: instance of the socket handler used to handle this connection
        """
        with self._current_connections_lock:
            self._current_connections[ident] = sh

    def get_connection(self, ident):
        """
        Get the socket handler of the connection

        :param ident: id of the connection
        :return: the socket handler
        """
        with self._current_connections_lock:
            return self._current_connections[ident]

    def is_connection_registered(self, ident):
        """
        Check if a connection is registered in this BaseThreadCommunication.

        :param ident: id of the connection
        :return: True if a socket handler is associated to this connection id
        """
        with self._current_connections_lock:
            return ident in self._current_connections

    def send_frame_to(self, ident: str, frame):
        """
        Send a frame to a connection identified by ident

        :param ident: Id of the connection to send a frame
        :param frame: frame to send
        """
        self.get_connection(ident).send_frame_async(frame)
