import array
import copy
from enum import Enum
from xml.etree import ElementTree as ET

from urh.util import util
from urh.util.GenericCRC import GenericCRC


class WSPChecksum(object):
    """
    This class implements the three checksums from Wireless Short Packet (WSP) standard
    http://hes-standards.org/doc/SC25_WG1_N1493.pdf
    """

    class ChecksumMode(Enum):
        auto = 0
        checksum4 = 1
        checksum8 = 2
        crc8 = 3

    CRC_8_POLYNOMIAL = array.array("B", [1,
                                         0, 0, 0, 0, 0, 1, 1, 1])  # x^8+x^2+x+1

    def __init__(self, mode=ChecksumMode.auto):
        self.mode = mode
        self.caption = str(mode)

    def __eq__(self, other):
        if not isinstance(other, WSPChecksum):
            return False

        return self.mode == other.mode

    def __hash__(self):
        return hash(self.mode)

    def calculate(self, msg: array.array) -> array.array:
        """
        Get the checksum for a WSP message. There are three hashes possible:
        1) 4 Bit Checksum - For Switch Telegram (RORG=5 or 6 and STATUS = 0x20 or 0x30)
        2) 8 Bit Checksum: STATUS bit 2^7 = 0
        3) 8 Bit CRC: STATUS bit 2^7 = 1

        :param msg: the message without Preamble/SOF and EOF. Message starts with RORG and ends with CRC
        """
        try:
            if self.mode == self.ChecksumMode.auto:
                if msg[0:4] == util.hex2bit("5") or msg[0:4] == util.hex2bit("6"):
                    # Switch telegram
                    return self.checksum4(msg)

                status = msg[-16:-8]
                if status[0]:
                    return self.crc8(msg[:-8])  # ignore trailing hash
                else:
                    return self.checksum8(msg[:])

            elif self.mode == self.ChecksumMode.checksum4:
                return self.checksum4(msg)
            elif self.mode == self.ChecksumMode.checksum8:
                return self.checksum8(msg[:])
            elif self.mode == self.ChecksumMode.crc8:
                return self.crc8(msg[:-8])

        except IndexError:
            return None

    @classmethod
    def search_for_wsp_checksum(cls, bits_behind_sync):
        data_start, data_stop, crc_start, crc_stop = 0, 0, 0, 0

        if bits_behind_sync[-4:].tobytes() != array.array("B", [1, 0, 1, 1]).tobytes():
            return 0, 0, 0, 0  # Check for EOF

        rorg = bits_behind_sync[0:4].tobytes()
        if rorg == array.array("B", [0, 1, 0, 1]).tobytes() or rorg == array.array("B", [0, 1, 1, 0]).tobytes():
            # Switch telegram
            if cls.checksum4(bits_behind_sync[-8:]).tobytes() == bits_behind_sync[-8:-4].tobytes():
                crc_start = len(bits_behind_sync) - 8
                crc_stop = len(bits_behind_sync) - 4
                data_stop = crc_start
                return data_start, data_stop, crc_start, crc_stop

        # todo: Find crc8 and checksum8

        return 0, 0, 0, 0

    @classmethod
    def checksum4(cls, bits: array.array) -> array.array:
        hash = 0
        val = copy.copy(bits)
        val[-4:] = array.array("B", [False, False, False, False])
        for i in range(0, len(val), 8):
            hash += int("".join(map(str, map(int, val[i:i + 8]))), 2)
        hash = (((hash & 0xf0) >> 4) + (hash & 0x0f)) & 0x0f
        return array.array("B", list(map(bool, map(int, "{0:04b}".format(hash)))))

    @classmethod
    def checksum8(cls, bits: array.array) -> array.array:
        hash = 0
        for i in range(0, len(bits) - 8, 8):
            hash += int("".join(map(str, map(int, bits[i:i + 8]))), 2)
        return array.array("B", list(map(bool, map(int, "{0:08b}".format(hash % 256)))))

    @classmethod
    def crc8(cls, bits: array.array):
        return array.array("B", GenericCRC(polynomial=cls.CRC_8_POLYNOMIAL).crc(bits))

    def to_xml(self) -> ET.Element:
        root = ET.Element("wsp_checksum")
        root.set("mode", str(self.mode.name))
        return root

    @classmethod
    def from_xml(cls, tag: ET.Element):
        return WSPChecksum(mode=WSPChecksum.ChecksumMode[tag.get("mode", "auto")])
