import selectors
import socket
import traceback

from ..core.device import Device
from ..core.observer import dispatcher
from ..core.events import ApplicationEvents, DeviceEvents
from ..core.logger import log
from ..core.message.base import Message
from .base import DeviceConnector


DEFAULT_HOST = "localhost"
DEFAULT_PORT = 9588
DEFAULT_HEADER_LEN = 4
DEFAULT_MAX_MSG_LEN = 4096


class PacketsHandler(object):
    """
    This class is here to handle the packets (before handling the message)
    It allows to check packet header and to collatione the packet if message is not
    complete
    """
    def __init__(self):
        self._buffer = bytearray([])
        self._pid = -1
         
    def push(self, packet):        
        if not packet:
            raise RuntimeError("Connection error, empty packet")

        packet = bytearray(packet)
        header = packet[:2]
        payload = packet[2:]

        #check version
        version = ((header[0]>>4)&0xF)
        issecure = (header[0]&8)
        msgtyp = ((header[0]&6)>>1)
        islast = (header[0]&1)
        pid = header[1]

        if (version != 1):
            raise RuntimeError("Bad protocol version %d, only supporting version 1" % version)

        if (msgtyp != 1):
            raise RuntimeError("Bad msg type %d, only supporting application message (1)" % msgtyp)

        if (self._pid != -1 and pid != self._pid):
            raise RuntimeError("This version of proxy doesn't support interleaved messages")

        self._pid = pid
        self._buffer += payload

        if not islast:
            return None

        self._pid = -1
        message = self._buffer
        self._buffer = bytearray([])

        return Message.from_msgpack(message)


class RecvStatus:
    def __init__(self, device):
        self.device = device
        self.expected = 0
        self.binary_data = []


class SocketServerConnector(DeviceConnector):

    def __init__(self, config: dict = {}) -> None:
        self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

        self._selector = selectors.DefaultSelector()

        self._host = config.get("host", DEFAULT_HOST)
        self._port = config.get("port", DEFAULT_PORT)
        self._pck_max_len = config.get("pck_max_len", DEFAULT_MAX_MSG_LEN)

        self._run = False

        self._handler = PacketsHandler()

        dispatcher.add_observer(ApplicationEvents.ON_QUIT, self.on_exit)

    def on_exit(self):
        self._run = False

    def open(self) -> None:
        self._socket.bind((self._host, self._port))
        self._socket.listen()
        self._socket.setblocking(False)

        log(f"Socket Server Listenning on {self._host}:{self._port}")

        self._selector.register(self._socket, selectors.EVENT_READ, data=None)

    def close(self) -> None:
        log(f"Socket Server Closing")
        self._socket.close()
        self._selector.close()

    def new_device(self, socket: socket.socket) -> None:
        conn, addr = socket.accept()  # Should be ready to read

        device = Device(addr)

        conn.setblocking(False)
        events = selectors.EVENT_READ | selectors.EVENT_WRITE
        self._selector.register(conn, events, data=RecvStatus(device))

        dispatcher.notify(DeviceEvents.CONNECTED, device)

    def on_device_msg(self, device: Device, socket: socket.socket, msg: Message) -> None:
        log(f"Socket Server Got message from {device.name}: {msg.to_json()}")
        dispatcher.notify(DeviceEvents.ON_MESSAGE, device, msg)

    def read_device(self, socket: socket.socket, status: RecvStatus) -> Message:
        log(f"Socket Server Got Msg")

        device = status.device
        if status.expected == 0:
            length_data = []
            while len(length_data) < DEFAULT_HEADER_LEN:
                try:
                    length_data += socket.recv(DEFAULT_HEADER_LEN - len(length_data))
                except BlockingIOError as err:
                    log(f"Socket Connector Error Reading Device header {err}", logger_level="ERROR")
                    return None
                except ConnectionResetError as err:
                    log(f"Socket Connector: Connection Reset Error Reading Device {err}", logger_level="ERROR")
                    raise err
                
                if len(length_data) == 0:
                    raise RuntimeError("Socket was closed")

            status.expected = int.from_bytes(length_data, "little")
            status.binary_data = []

        log(f"Socket waiting for {status.expected-len(status.binary_data)} bytes.", logger_level="INFO")
        while len(status.binary_data) < status.expected:
            try:
                status.binary_data += socket.recv(status.expected - len(status.binary_data))
            except BlockingIOError as err:
                log(f"Socket Connector Error Reading Device payload {err}", logger_level="ERROR")
                log(f"Already read {len(status.binary_data)} bytes", logger_level="INFO")
                return None
            except ConnectionResetError as err:
                log(f"Socket Connector: Connection Reset Error Reading Device {err}", logger_level="ERROR")
                raise err
           
            if len(status.binary_data) == 0:
                raise RuntimeError("Socket was closed")
        
        status.expected = 0

        return self._handler.push(status.binary_data.copy())

    def handle_connection(self, key, mask) -> None:
        sock = key.fileobj
        status: RecvStatus = key.data

        try:
            if mask & selectors.EVENT_READ:
                try:
                    msg = self.read_device(sock, status)
                    if msg:
                        self.on_device_msg(status.device, sock, msg)
                except RuntimeError as e:
                    self._selector.unregister(sock)
                    log("Closing case 1.1 %s" % e)
                    sock.close()
                    dispatcher.notify(DeviceEvents.DISCONNECTED, status.device)
                except ConnectionResetError as e:
                    log("Closing case 1.2 %s" % e)
                    dispatcher.notify(DeviceEvents.DISCONNECTED, status.device)

            if mask & selectors.EVENT_WRITE:
                if len(status.device.out_data):
                    data = status.device.out_data[0]
                    l = len(data)
                    log(f"Socket sending {l} bytes to device.")
                    remaining = 4
                    while (remaining):
                        remaining -= sock.send(l.to_bytes(4, "little"))
                    remaining = l
                    while (remaining):
                        remaining -= sock.send(data)
                    status.device.out_data = status.device.out_data[1:]

        except ConnectionResetError:
            self._selector.unregister(sock)
            log("Closing case 2")
            sock.close()
            dispatcher.notify(DeviceEvents.DISCONNECTED, status.device)

    def run(self) -> None:
        try:
            self._run = True
            while self._run:
                ev = self._selector.select(timeout=1)
                for key, mask in ev:
                    if key.data is None:
                        self.new_device(key.fileobj)
                    else:
                        self.handle_connection(key, mask)
        except KeyboardInterrupt:
            self._run = False
        finally:
            log("Closing case 3")
            traceback.print_exc()
            self.close()
