"""The format specification classes."""


class Signature:
    """Signature of a format specification.

    The signature consists of a byte string pattern, an optional
    offset relative to the start of the data, and a value to indicate
    if the pattern is bound to the offset.

    Attributes:
      identifier (str): unique signature identifier for a specification store.
      offset (int): offset of the signature, where None indicates the signature
            has no offset. A positive offset or 0 is relative to the start of
            the data a negative offset is relative to the end of the data.
      pattern (bytes): pattern of the signature.
    """

    def __init__(self, pattern, offset=None):
        """Initializes a signature.

        Args:
          pattern (bytes): pattern of the signature. Wildcards or regular pattern
              (regexp) are not supported.
          offset (Optional[int]): offset of the signature, where None indicates
              the signature has no offset. A positive offset or 0 is relative
              from the start of the data a negative offset is relative to
              the end of the data.
        """
        super().__init__()
        self.identifier = None
        self.offset = offset
        self.pattern = pattern

    def SetIdentifier(self, identifier):
        """Sets the identifier of the signature in the specification store.

        Args:
          identifier (str): unique signature identifier for a specification store.
        """
        self.identifier = identifier


class FormatSpecification:
    """Format specification."""

    def __init__(self, identifier):
        """Initializes a specification.

        Args:
          identifier (str): unique name for the format.
        """
        super().__init__()
        self.identifier = identifier
        self.signatures = []

    def AddNewSignature(self, pattern, offset=None):
        """Adds a signature.

        Args:
          pattern (bytes): pattern of the signature. Wildcards or regular pattern
              (regexp) are not supported.
          offset (Optional[int]): offset of the signature, where None indicates
              the signature has no offset. A positive offset or 0 is relative
              from the start of the data a negative offset is relative to
              the end of the data.
        """
        self.signatures.append(Signature(pattern, offset=offset))


class FormatSpecificationStore:
    """Store for format specifications."""

    def __init__(self):
        """Initializes a format specification store."""
        super().__init__()
        self._format_specifications = {}
        # Maps signature identifiers to format specifications.
        self._signature_map = {}

    @property
    def specifications(self):
        """Retrieves the format specifications.

        Returns:
          generator[FormatSpecification]: format specifications.
        """
        return iter(self._format_specifications.values())

    def AddNewSpecification(self, identifier):
        """Adds a new format specification.

        Args:
          identifier (str): unique signature identifier for a specification store.

        Returns:
          FormatSpecification: format specification.

        Raises:
          KeyError: if the store already contains a specification with
              the same identifier.
        """
        if identifier in self._format_specifications:
            raise KeyError(
                f"Format specification {identifier:s} is already defined in store."
            )

        self._format_specifications[identifier] = FormatSpecification(identifier)

        return self._format_specifications[identifier]

    def AddSpecification(self, specification):
        """Adds a specification.

        Args:
          specification (FormatSpecification): format specification.

        Raises:
          KeyError: if the store already contains a specification with
              the same identifier.
        """
        if specification.identifier in self._format_specifications:
            raise KeyError(
                (
                    f"Format specification {specification.identifier:s} is already "
                    f"defined in store."
                )
            )

        self._format_specifications[specification.identifier] = specification

        for signature in specification.signatures:
            signature_index = len(self._signature_map)

            signature_identifier = f"{specification.identifier:s}:{signature_index:d}"
            if signature_identifier in self._signature_map:
                raise KeyError(
                    f"Signature {signature_identifier:s} is already defined in map."
                )

            signature.SetIdentifier(signature_identifier)
            self._signature_map[signature_identifier] = specification

    def GetSpecificationBySignature(self, signature_identifier):
        """Retrieves a specification mapped to a signature identifier.

        Args:
          signature_identifier (str): unique signature identifier for
              a specification store.

        Returns:
          FormatSpecification: A format specification or None if the signature
              identifier does not exist within the specification store.
        """
        return self._signature_map.get(signature_identifier)
