"""The parser and parser plugin presets."""

import yaml

from plaso.containers import artifacts
from plaso.lib import errors
from plaso.parsers import logger


class ParserPreset:
    """Parser and parser plugin preset.

    Attributes:
      deprecated (bool): True if the preset is deprecated.
      name (str): name of the preset.
      operating_systems (list[OperatingSystemArtifact]): operating system
          artifact attribute containers, that specify to which operating
          systems the preset applies.
      parsers (list[str]): names of parser and parser plugins.
    """

    def __init__(self, name, parsers):
        """Initializes a parser and parser plugin preset.

        Attributes:
          name (str): name of the preset.
          parsers (list[str]): names of parser and parser plugins.
        """
        super().__init__()
        self.deprecated = False
        self.name = name
        self.operating_systems = []
        self.parsers = parsers


class ParserPresetsManager:
    """The parsers and plugin presets manager."""

    def __init__(self):
        """Initializes a parser and parser plugin presets manager."""
        super().__init__()
        self._definitions = {}

    def _ReadOperatingSystemArtifactValues(self, operating_system_values):
        """Reads an operating system artifact from a dictionary.

        Args:
          operating_system_values (dict[str, object]): operating system values.

        Returns:
          OperatingSystemArtifact: an operating system artifact attribute container.

        Raises:
          MalformedPresetError: if the format of the operating system values are
              not set or incorrect.
        """
        if not operating_system_values:
            raise errors.MalformedPresetError("Missing operating system values.")

        family = operating_system_values.get("family")
        product = operating_system_values.get("product")
        version = operating_system_values.get("version")

        if not family and not product:
            raise errors.MalformedPresetError(
                "Invalid operating system missing family and product."
            )

        return artifacts.OperatingSystemArtifact(
            family=family, product=product, version=version
        )

    def _ReadParserPresetValues(self, preset_definition_values):
        """Reads a parser preset from a dictionary.

        Args:
          preset_definition_values (dict[str, object]): preset definition values.

        Returns:
          ParserPreset: a parser preset.

        Raises:
          MalformedPresetError: if the format of the preset definition is not set
              or incorrect, or the preset of a specific operating system has already
              been set.
        """
        if not preset_definition_values:
            raise errors.MalformedPresetError("Missing preset definition values.")

        name = preset_definition_values.get("name")
        if not name:
            raise errors.MalformedPresetError("Invalid preset definition missing name.")

        parsers = preset_definition_values.get("parsers")
        if not parsers:
            raise errors.MalformedPresetError(
                "Invalid preset definition missing parsers."
            )

        parser_preset = ParserPreset(name, parsers)

        parser_preset.deprecated = preset_definition_values.get("deprecated", False)

        for operating_system_values in preset_definition_values.get(
            "operating_systems", []
        ):
            operating_system = self._ReadOperatingSystemArtifactValues(
                operating_system_values
            )
            parser_preset.operating_systems.append(operating_system)

        return parser_preset

    def _ReadPresetsFromFileObject(self, file_object):
        """Reads parser and parser plugin presets from a file-like object.

        Args:
          file_object (file): file-like object containing the parser and parser
              plugin presets definitions.

        Yields:
          ParserPreset: a parser preset.

        Raises:
          MalformedPresetError: if one or more plugin preset definitions are
              malformed.
        """
        yaml_generator = yaml.safe_load_all(file_object)

        last_preset_definition = None
        for yaml_definition in yaml_generator:
            try:
                preset_definition = self._ReadParserPresetValues(yaml_definition)
            except errors.MalformedPresetError as exception:
                error_location = "At start"
                if last_preset_definition:
                    error_location = f"After: {last_preset_definition.name!s}"

                raise errors.MalformedPresetError(f"{error_location!s} {exception!s}")

            yield preset_definition
            last_preset_definition = preset_definition

    def GetNames(self):
        """Retrieves the preset names.

        Returns:
          list[str]: preset names in alphabetical order.
        """
        return sorted(self._definitions.keys())

    def GetParsersByPreset(self, preset_name):
        """Retrieves the parser and plugin names of a specific preset.

        Args:
          preset_name (str): name of the preset.

        Returns:
          list[str]: parser and plugin names in alphabetical order.

        Raises:
          KeyError: if the preset does not exist.
        """
        lookup_name = preset_name.lower()
        preset_definition = self._definitions.get(lookup_name)
        if preset_definition is None:
            raise KeyError(f"Preset: {preset_name!s} is not defined")

        if preset_definition.deprecated:
            logger.warning(
                f"Preset: {preset_name!s} is deprecated and will be removed "
                f"in a future version"
            )

        return sorted(preset_definition.parsers)

    def GetPresetByName(self, name):
        """Retrieves a specific preset definition by name.

        Args:
          name (str): name of the preset.

        Returns:
          ParserPreset: a parser preset or None if not available.
        """
        lookup_name = name.lower()
        return self._definitions.get(lookup_name)

    def GetPresetsByOperatingSystem(self, operating_system):
        """Retrieves preset definitions for a specific operating system.

        Args:
          operating_system (OperatingSystemArtifact): an operating system artifact
              attribute container.

        Returns:
          list[PresetDefinition]: preset definition that correspond with the
              operating system.
        """
        preset_definitions = []
        for preset_definition in self._definitions.values():
            for preset_operating_system in preset_definition.operating_systems:
                if preset_operating_system.IsEquivalent(operating_system):
                    preset_definitions.append(preset_definition)

        return preset_definitions

    def GetPresetsInformation(self):
        """Retrieves the presets information.

        Returns:
          list[tuple]: containing:

            str: preset name.
            str: comma separated parser and plugin names that are defined by
                the preset.
        """
        presets_information = []
        for name, parser_preset in sorted(self._definitions.items()):
            preset_information_tuple = (name, ", ".join(parser_preset.parsers))
            # TODO: refactor to pass PresetDefinition.
            presets_information.append(preset_information_tuple)

        return presets_information

    def ReadFromFile(self, path):
        """Reads parser and parser plugin presets from a file.

        Args:
          path (str): path of file that contains the the parser and parser plugin
              presets configuration.

        Raises:
          MalformedPresetError: if one or more plugin preset definitions are
              malformed.
        """
        self._definitions = {}

        with open(path, "r", encoding="utf-8") as file_object:
            for preset_definition in self._ReadPresetsFromFileObject(file_object):
                self._definitions[preset_definition.name] = preset_definition
