# Vendored from glasgow.protocol.jesd3

# Ref: JEDEC JESD3-C
# Accession: G00029

import re
from bitarray import bitarray
from atfu.output import global_output

__all__ = ["JESD3Parser", "JESD3ParsingError"]


class JESD3ParsingError(Exception):
    pass


class JESD3Lexer:
    """
    A JESD3 (JED) lexer.

    :type buffer: str
    :attr buffer:
        Input buffer.

    :type position: int
    :attr position:
        Offset into buffer from which the next field will be read.
    """

    # This follows the JESD3-C grammar, with the exception that spaces are more permissive.
    # As described, only 0x0D is allowed in between fields, which is absurd.
    _fields = (
        (r"N", r"[ \r\n]*(.*?)"),
        (r"D", r".*?"),
        (r"QF", r"([0-9]+)"),
        (r"QP", r"([0-9]+)"),
        (r"QV", r"([0-9]+)"),
        (r"F", r"([01])"),
        (r"L", r"([0-9]+)[ \r\n]+([01 \r\n]+)"),
        (r"C", r"([0-9A-F]{4})"),
        (r"EH", r"([0-9A-F]+)"),
        (r"E", r"([01]+)"),
        (r"UA", r"([\r\n\x20-\x29\x2B-\x7E]+)"),
        (r"UH", r"([0-9A-F]+)"),
        (r"U", r"([01]+)"),
        (r"J", r"([0-9]+)[ \r\n]+([0-9]+)"),
        (r"G", r"([01])"),
        (r"X", r"([01])"),
        (r"P", r"([ \r\n]*[0-9]+)+"),
        # This next one _can_ handle the QV (and has all vector types) but still won't checksum
        #
        # TODO fix this properly (currently we just error out with a "helpful" message)
        #        (r"V", r"([0-9]+)[ \r\n]+(?:QV\d+\* )?([0-9BCDFHLNPTUXZ]+)"),
        (r"V", r"([0-9]+)[ \r\n]+([0-9BCDFHLNPTUXZ]+)"),
        (r"S", r"([01]+)"),
        (r"R", r"([0-9A-F]{8})"),
        (r"T", r"([0-9]+)"),
        (r"A", r"([\r\n\x20-\x29\x2B-\x7E]*)([0-9]+)"),
    )
    _stx_spec_re = re.compile(r"\x02(.*?)\*[ \r\n]*", re.A | re.S)
    _stx_quirk_re = re.compile(r"\x02()[ \r\n]*", re.A | re.S)
    _etx_re = re.compile(r"\x03([0-9A-F]{4})", re.A | re.S)
    _ident_re = re.compile(r"|".join(ident for ident, args in _fields), re.A | re.S)
    _field_res = {
        ident: re.compile(ident + args + r"[ \r\n]*\*[ \r\n]*", re.A | re.S)
        for ident, args in _fields
    }

    def __init__(self, buffer, quirk_no_design_spec=False):
        self.buffer = buffer
        self.position = 0
        self.checksum = 0
        self._state = "start"
        if quirk_no_design_spec:
            self._stx_re = self._stx_quirk_re
        else:
            self._stx_re = self._stx_spec_re

    def line_column(self, position=None):
        """
        Return a ``(line, column)`` tuple for the given or, if not specified, current position.

        Both the line and the column start at 1.
        """
        line = len(re.compile(r"\n").findall(self.buffer, endpos=self.position))
        if line > 1:
            column = self.position - self.buffer.rindex("\n", 0, self.position)
        else:
            column = self.position
        return line + 1, column + 1

    def __iter__(self):
        return self

    def __next__(self):
        """Return the next token and advance the position."""
        if self._state == "start":
            match = self._stx_re.search(self.buffer, self.position)
            if not match:
                raise JESD3ParsingError("could not find STX marker")
            else:
                token = "start"
                self._state = "fields"
                self.checksum += sum(map(ord, match.group(0)))

        elif self._state == "fields":
            match = self._ident_re.match(self.buffer, self.position)

            # Another horrible hack - we can't handle empty fields, so just try to skip them...
            # Will be replaced by new parser, so there's that to look forward to at least...
            #
            if not match and self.buffer[self.position] == "*":
                while self.buffer[self.position] in "*\n ":
                    self.position += 1
                match = self._ident_re.match(self.buffer, self.position)
            #
            #

            if match:
                token = match.group(0)
                match = self._field_res[token].match(self.buffer, self.position)

                if not match:
                    if token == "V":
                        global_output().error(
                            "Failure",
                            "JEDEC file contains test vectors, which cannot be processed by this tool. Please rebuild without them.",
                        )
                        exit(1000)
                    else:
                        raise JESD3ParsingError(
                            "field %s has invalid format at line %d, column %d"
                            % (token, *self.line_column())
                        )
                else:
                    self.checksum += sum(map(ord, match.group(0)))

            else:
                match = self._etx_re.match(self.buffer, self.position)
                if not match:
                    raise JESD3ParsingError(
                        "unrecognized field at line %d, column %d (%r...)"
                        % (
                            *self.line_column(),
                            self.buffer[self.position : self.position + 16],
                        )
                    )
                else:
                    token = "end"
                    self._state = "end"
                    self.checksum += 0x03

        elif self._state == "end":
            raise StopIteration

        self.position = match.end()
        return token, match.start(), match.groups()


class JESD3Parser:
    def __init__(self, buffer, **kwargs):
        self._lexer = JESD3Lexer(buffer, **kwargs)
        self._position = 0

        self.design_spec = ""
        self.notes = []
        self.fuse = None
        self._fuse_default = None
        self._fuse_bit_count = 0
        self.electrical_fuse = None
        self.user_fuse = None
        self.security_fuse = None
        self.device_id = None

    def _parse_error(self, error):
        raise JESD3ParsingError(
            "%s at line %d, column %d"
            % (error, *self._lexer.line_column(self._position))
        )

    def parse(self):
        for token, position, args in self._lexer:
            self._position = position
            # print("lexem: %r %r" % (token, args))
            getattr(self, "_on_" + token)(*args)

    def _on_start(self, design_spec):
        """Start marker and design specification"""
        self.design_spec = design_spec

    def _on_N(self, note):
        """Note"""
        self.notes.append(note)

    def _on_D(self):
        """Device (obsolete)"""

    def _on_QF(self, count):
        """Fuse count"""
        if self.fuse is not None:
            self._parse_error("fuse count specified more than once")
        self.fuse = bitarray(int(count, 10), endian="little")

    def _on_QP(self, count):
        """Pin count (unsupported and ignored)"""

    def _on_QV(self, count):
        """Test vector count (unsupported)"""
        if int(count, 10) > 0:
            self._parse_error("test vectors are unsupported")

    def _on_F(self, state):
        """Fuse default state"""
        if self.fuse is None:
            self._parse_error("fuse default state specified before fuse count")
        if self._fuse_default is not None:
            self._parse_error("fuse default state specified more than once")
        if self._fuse_bit_count > 0:
            self._parse_error("fuse default state specified after fuse list")
        self._fuse_default = int(state, 2)
        self.fuse.setall(self._fuse_default)

    def _on_L(self, index, values):
        """Fuse list"""
        if self.fuse is None:
            self._parse_error("fuse list specified before fuse count")
        index = int(index, 10)
        values = bitarray(re.sub(r"[ \r\n]", "", values), endian="little")
        if index + len(values) > len(self.fuse):
            self._parse_error(
                "fuse list specifies range [%d:%d] beyond last fuse %d"
                % (index, index + len(values), len(self.fuse))
            )
        self.fuse[index : index + len(values)] = values
        self._fuse_bit_count += len(values)

    def _on_C(self, checksum):
        """Fuse checksum"""
        expected_checksum = int(checksum, 16)
        actual_checksum = sum(self.fuse.tobytes()) & 0xFFFF
        if expected_checksum != actual_checksum:
            self._parse_error(
                "fuse checksum mismatch: expected %04X, actual %04X"
                % (expected_checksum, actual_checksum)
            )

    def _set_electrical_fuse(self, value):
        if self.electrical_fuse is not None:
            self._parse_error("electrical fuse specified more than once")
        self.electrical_fuse = value

    def _on_EH(self, value):
        """Electrical fuse, hex"""
        self._set_electrical_fuse(int(value, 16))

    def _on_E(self, value):
        """Electrical fuse, binary"""
        self._set_electrical_fuse(int(value, 2))

    def _set_user_fuse(self, value):
        if self.user_fuse is not None:
            self._parse_error("user fuse specified more than once")
        self.user_fuse = value

    def _on_UA(self, value):
        """User fuse, 7-bit ASCII"""
        int_value = 0
        for char in reversed(value):
            int_value <<= 7
            int_value |= ord(char)
        self._set_user_fuse(int_value)

    def _on_UH(self, value):
        """User fuse, hex"""
        self._set_user_fuse(int(value, 16))

    def _on_U(self, value):
        """User fuse, binary"""
        self._set_user_fuse(int(value, 2))

    def _on_J(self, arch_code, pinout_code):
        """Device identification"""
        if self.device_id is not None:
            self._parse_error("device identification specified more than once")
        self.device_id = (int(arch_code, 10), int(pinout_code, 10))

    def _on_G(self, value):
        """Security fuse"""
        if self.security_fuse is not None:
            self._parse_error("security fuse specified more than once")
        self.security_fuse = int(value, 2)

    def _on_X(self, value):
        """Default test condition (unsupported and ignored)"""

    def _on_P(self, pin_numbers):
        """Pin list (unsupported and ignored)"""

    def _on_V(self, vector_number, test_conditions):
        """Test vector (unsupported and ignored)"""

    def _on_S(self, test_condition):
        """Signature analysis starting vector (unsupported)"""
        self._parse_error("signature analysis is not supported")

    def _on_R(self, test_sum):
        """Signature analysis resulting vector (unsupported and ignored)"""

    def _on_T(self, test_cycles):
        """Signature analysis test cycle count (unsupported and ignored)"""

    def _on_A(self, subfield, delay):
        """Propagation delay for test vectors (unsupported and ignored)"""

    def _on_end(self, checksum):
        """End marker and checksum"""
        expected_checksum = int(checksum, 16)
        if expected_checksum == 0x0000:
            return
        actual_checksum = self._lexer.checksum & 0xFFFF
        if expected_checksum != actual_checksum:
            self._parse_error(
                "transmission checksum mismatch: expected %04X, actual %04X"
                % (expected_checksum, actual_checksum)
            )

        if self._fuse_default is None and self._fuse_bit_count < len(self.fuse):
            self._parse_error(
                "fuse default state is not specified, and only %d out of %d fuse "
                "bits are explicitly defined" % (self._fuse_bit_count, len(self.fuse))
            )


if __name__ == "__main__":
    import sys

    with open(sys.argv[1], "r") as f:
        parser = JESD3Parser(f.read(), quirk_no_design_spec=False)
        parser.parse()
        for i in range(0, len(parser.fuse) + 63, 64):
            print("%08x: %s" % (i, parser.fuse[i : i + 64].to01()))
