"""The format specification classes."""


class Signature:
  """The format specification signature.

  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.
  """
  def __init__(self, pattern, offset=None):
    """Initializes a format specification signature.

    The signatures can be defined in 3 different "modes":
    * where offset >= 0, which represents that the signature is bound to the
      start of the data and only the relevant part is scanned;
    * where offset < 0, which represents that the signature is bound to the
      end of the data and only the relevant part is scanned;
    * offset == None, which represents that the signature is not offset bound
      and that all of the data is scanned.

    Args:
      pattern (bytes): pattern of the signature. Wildcards or regular
          expressions (regexp) are not supported.
      offset (int): offset of the signature. None is used to indicate
          the signature has no offset. A positive offset is relative from
          the start of the data a negative offset is relative from 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:
  """The format specification."""

  def __init__(self, identifier, text_format=False):
    """Initializes a format specification.

    Args:
      identifier (str): unique name for the format.
      text_format (Optional[bool]): True if the format is a text format,
          False otherwise.
    """
    super().__init__()
    self._text_format = text_format
    self.identifier = identifier
    self.signatures = []

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

    Args:
      pattern (bytes): pattern of the signature.
      offset (int): offset of the signature. None is used to indicate
          the signature has no offset. A positive offset is relative from
          the start of the data a negative offset is relative from the end
          of the data.
    """
    self.signatures.append(Signature(pattern, offset=offset))

  def IsTextFormat(self):
    """Determines if the format is a text format.

    Returns:
      bool: True if the format is a text format, False otherwise.
    """
    return self._text_format


class FormatSpecificationStore:
  """The store for format specifications."""

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

  @property
  def specifications(self):
    """iterator: specifications iterator."""
    return iter(self._format_specifications.values())

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

    Args:
      identifier (str): format identifier, which should be unique for the 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 format 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: format specification or None if the signature
          identifier does not exist within the specification store.
    """
    return self._signature_map.get(signature_identifier)
