from typing import Type, Any
from pymasep.communication import BaseThreadedCommunication, Frame
import socket
import struct

import traceback


class Client(BaseThreadedCommunication):
    """
    Base class for Client

    :param ident: string id of the client
    :param host: host to connect. If None, the client will search a server to connect to
    :param port: port to connect. If None, the client will search a server to connect to
    :param socket_handler_class: class used to handle the socket (send, receive)
    :param socket_handler_args: arguments to instantiate the SocketHandler class
    """

    def __init__(self,
                 ident: str,
                 socket_handler_class: Type[Any],
                 socket_handler_args: tuple,
                 host: str = None,
                 port: int = None,
                 ):
        super().__init__(ident=ident,
                         host=host,
                         port=port,
                         socket_handler_class=socket_handler_class,
                         socket_handler_args=socket_handler_args)
        self.server_id = None

    def __del__(self):
        """
        delete the instance. Close the socket
        """
        self.socket.close()

    def connect(self):
        """
        Connect the client to a server. Create the handler and register the connection.
        """
        self.socket.connect((self.host, self.port))
        clt_handler = self.socket_handler_class(self.socket, 'localhost', self.logger,
                                                *self.socket_handler_args)
        # use clt_handler because the handshake is not done and i don't have the server id
        clt_handler.send_frame(Frame.hello_frame(self.id))
        server_hello_frame = clt_handler.receive_frame()
        self.server_id = server_hello_frame.message.decode('utf-8')

        self.add_connection(self.server_id, clt_handler)

        self.logger.info(
            'Client (' + str(self.id) + ') connected to server(' + server_hello_frame.message.decode('utf-8') + ')')

        clt_handler.start()

    def search_server(self):
        """
        Search a server on the local net using multicast UPD
        """
        message = b'pymasep server?'
        multicast_group = ('224.3.29.71', 10000)

        # Create the datagram socket
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

        # Set a timeout so the socket does not block indefinitely when trying
        # to receive data.
        sock.settimeout(1)

        # Set the time-to-live for messages to 1 so they do not go past the
        # local network segment.
        ttl = struct.pack('b', 1)
        sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl)
        sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, 1)

        data = 0
        try:

            # Send data to the multicast group
            self.logger.info('C : sending ' + message.decode())
            _ = sock.sendto(message, multicast_group)

            # Look for responses from all recipients
            while True:
                self.logger.info('C : waiting to receive')
                try:
                    data, server = sock.recvfrom(1024)
                except socket.timeout:
                    self.logger.info('C: timed out, no more responses')
                    if self.host is None or self.port is None:
                        self.logger.info('C: No data received')
                        raise ConnectionError('No data received')
                    break
                else:
                    self.logger.info('C : received' + str((data.decode(), server)))
                    self.host = data.decode().split(":")[0]
                    self.port = int(data.decode().split(":")[1])
        except Exception as e:
            self.logger.error(traceback.format_exc())
            self.logger.error(str(e))
        finally:
            self.logger.info('C : closing socket')
            sock.close()
