class CRCException(Exception):
    pass

class Protocol:
    def __init__(self):
        self.start_delimiter = b"!!L:"
        self.end_delimiter = b'E!!'
        self.start_delimiter_size = len(self.start_delimiter)
        self.end_delimiter_size = len(self.end_delimiter)
        self.length_size = 2
        self.crc_size = 2
        self.prefix_size = len(self.start_delimiter) + self.length_size
        self.suffix_size = len(self.end_delimiter) + self.crc_size

    def encode_message(self, message):
        prefix = self.generate_prefix(message)
        suffix = self.generate_suffix(message)
        encoded_message = prefix + message.encode() + suffix
        return encoded_message

    def generate_prefix(self, message):
        length = len(message)
        prefix = self.start_delimiter + length.to_bytes(2, byteorder='big')
        return prefix

    def generate_suffix(self, message):
        crc = self.crc16(message.encode())
        suffix = crc.to_bytes(2, byteorder='big') + self.end_delimiter
        return suffix

    def decode_buffer(self, buffer):
        messages = buffer.split(self.end_delimiter)
        decoded_messages = []
        for message in messages:
            if message.startswith(self.start_delimiter):
                prefix = message[0:self.prefix_size]
                length = int.from_bytes(
                    prefix[self.start_delimiter_size:self.start_delimiter_size + self.length_size], byteorder='big')
                received_message = message[self.prefix_size:self.prefix_size + length]
                suffix = message[self.prefix_size + length:]
                crc_received = int.from_bytes(
                    suffix[0:self.crc_size], byteorder='big')
                crc_calculated = self.crc16(received_message)
                if crc_received == crc_calculated:
                    decoded_messages.append(received_message.decode())
                else:
                    raise CRCException(
                        f'Calculated and received checksum do not match.\nExpected checksum: {crc_calculated}, received checksum: {crc_received}')
        return decoded_messages

    def crc16(self, message):
        poly = 0x1021
        crc = 0xffff

        for byte in message:
            v = 0x80
            for _ in range(8):
                xor_flag = crc & 0x8000
                crc = crc << 1
                if byte & v:
                    crc += 1
                if xor_flag:
                    crc = crc ^ poly
                v = v >> 1

        return crc & 0xffff

