from telnetlib import Telnet
import serial

DEBUG = True


class MAXPacketFactory:
    def create_packet(rec: str):
        pkt_type = int(rec[7:9], 16)
        if pkt_type == 0:
            return MAXPairPingPacket.from_received(rec)
        elif pkt_type == 1:
            return MAXPairPongPacket.from_received(rec)
        elif pkt_type == 2:
            return MAXAckPacket.from_received(rec)
        elif pkt_type == 0xF1:
            return MAXWakeUpPacket.from_received(rec)
        elif pkt_type == 0xF0:
            return MAXResetPacket.from_received(rec)
        else:
            print("Unknown message type: {}".format(pkt_type))
            return MAXPacket.from_received(rec)


class MAXPacket:
    def __init__(self, length, message_counter: str, message_type: str, message_flag: str, sender_address: str, dest_address: str,
                 group_id:str):
        self.length = length
        self.counter = message_counter
        self.type = message_type
        self.flag = message_flag
        self.sender_address = sender_address
        self.dest_address = dest_address
        self.group_id = group_id

    def from_received(rec: str):
        return MAXPacket(rec[1:3], rec[3:5], rec[5:7], rec[7:9], rec[9:15], rec[15:21], rec[21:23])

    def gen_header(self):
        header_str = "{}{}{}{}{}{}".format(self.counter, self.flag, self.type, self.sender_address,
                                               self.dest_address, self.group_id)
        return header_str

    def to_string(self):
        return "MAXPacket: len={}, counter={}, flag={}, type={}, sender_addr={}, dest_addr={}, group_id={}".format(
            self.length, self.counter, self.flag, self.type, self.sender_address, self.dest_address, self.group_id
        )


class MAXPairPingPacket(MAXPacket):
    def from_received(rec: str):
        result = MAXPacket.from_received(rec)
        firmware_val = int(rec[23:25], 16)
        result.firmware_major = firmware_val // 16
        result.firmware_minor = firmware_val % 16
        result.device_type = rec[25:27]
        result.test_result = rec[27:29]
        result.serial = rec[29:-2]
        return result

    def __init__(self, length, message_counter: str, message_type: str, message_flag: str, sender_address: str, dest_address: str,
                 group_id:str, firmware_major, firmware_minor, device_type, test_result, serial):
        MAXPacket.__init__(length, message_counter, message_type, message_flag, sender_address, dest_address, group_id)
        self.firmware_major = firmware_major
        self.firmware_minor = firmware_minor
        self.device_type = device_type
        self.test_result = test_result
        self.serial = serial

    def to_string(self):
        result = "{}\nMAXPairPingPacket: firmware_major={} firmware_minor={} device_type={} " \
                 "test_result={} serial={}".format(super().to_string(), self.firmware_major, self.firmware_minor,
                                                   self.device_type, self.test_result, self.serial)
        return result


class MAXPairPongPacket(MAXPacket):
    # def __init__(self, rec):
    #    super().__init__(rec)

    def __init__(self, message_counter: str, message_flag: str, sender_address: str, dest_address: str,
                 group_id:str):
        MAXPacket.__init__(message_counter, "01", message_flag, sender_address, dest_address, group_id)
        self.payload = "00"

    def serialize(self):
        header = self.gen_header()
        length = (len(header) + len(self.payload))/2
        return "Zs{}{}{}".format(length, header, self.payload)


class MAXAckPacket(MAXPacket):
    def __init__(self, rec):
        super().__init__(rec)


class MAXWakeUpPacket(MAXPacket):
    def __init__(self, rec):
        super().__init__(rec)


class MAXResetPacket(MAXPacket):
    def __init__(self, rec):
        super().__init__(rec)


class CUL:
    def __init__(self):
        self.client: serial.Serial = None

    def connect(self, port):
        if self.client is None:
            self.client = serial.Serial(port, 38400, timeout=1)

    def disconnect(self):
        if self.client is not None:
            self.client.close()
            self.client = None

    def request(self, req):
        if self.client is not None:
            self.client.write(req.encode())
            self.client.write(b"\n")
            if DEBUG:
                print(">>> {}".format(req))
        else:
            print("Request while not connected!")

    def response(self):
        if self.client is not None:
            response = self.client.read(100).decode()
            if DEBUG:
                print("<<< {}".format(response))
            return response
        else:
            print("Waiting for response while not connected!")
            return None

    def version_string(self):
        self.request("V")
        return self.response()

    def is_connected(self):
        return self.client is not None

    def set_moritz_mode(self, moritz_mode_enable):
        if moritz_mode_enable:
            self.request("Zr")
            #return self.response()
        else:
            self.request("Zx")


class CUN:
    def __init__(self):
        self.client: Telnet = None

    def connect(self, host, ip):
        if self.client is None:
            self.client = Telnet(host, ip)

    def disconnect(self):
        if self.client is not None:
            self.client.close()
            self.client = None

    def version_string(self):
        self.request("V")
        return self.response()

    def request(self, req):
        if self.client is not None:
            self.client.write(req.encode())
            self.client.write(b"\n")
            if DEBUG:
                print(">>> {}".format(req))
        else:
            print("Request while not connected!")

    def response(self):
        if self.client is not None:
            response = self.client.read_some().decode()
            if DEBUG:
                print("<<< {}".format(response))
            return response
        else:
            print("Waiting for response while not connected!")
            return None

    def set_moritz_mode(self, moritz_mode_enable):
        if moritz_mode_enable:
            self.request("Zr")
            #return self.response()
        else:
            self.request("Zx")

    def configure(self, ip, netmask, gateway, ):
        # ToDo: NYI
        pass

    def h_request(self):
        self.request("H?")
        return self.response()

    def send_moritz(self, packet: MAXPacket):
        # llnnccttssssssddddddpp...
        # ll - length
        # nn - msg counter
        # cc - control byte
        # tt - msg type
        # ss - sender address(3 byte)
        # dd - destination address(3 byte - 000000 for broadcast)
        # pp - payload...
        len_str = packet.length
        msg_counter_str = packet.ms
        control_byte_str = "00"
        message_type_str = "02"
        sender_addr_str = "000000"
        dest_addr_str = "000000"
        payload = "l:"
        self.request("Zs{}{}{}{}{}{}{}".format(packet.length, packet.message_counter, control_byte_str,
                                             message_type_str, sender_addr_str,
                                             dest_addr_str, payload))
        #self.request("l:")
        return self.response()
