"""YAML-based filter file."""

import io
import yaml

from plaso.engine import path_filters
from plaso.lib import errors


class YAMLFilterFile:
  """YAML-based filter file.

  A YAML-based filter file contains one or more path filters.
  description: Include filter with Linux paths.
  type: include
  path_separator: '/'
  paths:
  - '/usr/bin'

  Where:
  * description, is an optional description of the purpose of the path filter;
  * type, defines the filter type, which can be "include" or "exclude";
  * path_separator, defines the path segment separator, which is "/" by default;
  * paths, defines regular expression of paths to filter on.

  Note that the regular expression need to be defined per path segment, for
  example to filter "/usr/bin/echo" and "/usr/sbin/echo" the following
  expression could be defined "/usr/(bin|sbin)/echo".

  Note that when the path segment separator is defined as "\\" it needs to be
  escaped as "\\\\", since "\\" is used by the regular expression as escape
  character.

  A path may contain path expansion attributes, for example:
  %{SystemRoot}\\\\System32
  """

  _SUPPORTED_KEYS = frozenset([
      'description',
      'path_separator',
      'paths',
      'type'])

  def _ReadFilterDefinition(self, filter_definition_values):
    """Reads a filter definition from a dictionary.

    Args:
      filter_definition_values (dict[str, object]): filter definition values.

    Returns:
      PathFilter: a path filter.

    Raises:
      ParseError: if the format of the filter definition is not set
          or incorrect.
    """
    if not filter_definition_values:
      raise errors.ParseError('Missing filter definition values.')

    different_keys = set(filter_definition_values) - self._SUPPORTED_KEYS
    if different_keys:
      different_keys = ', '.join(different_keys)
      raise errors.ParseError(f'Undefined keys: {different_keys:s}')

    filter_type = filter_definition_values.get('type')
    if not filter_type:
      raise errors.ParseError('Invalid path filter definition missing type.')

    if filter_type not in (
        path_filters.PathFilter.FILTER_TYPE_EXCLUDE,
        path_filters.PathFilter.FILTER_TYPE_INCLUDE):
      raise errors.ParseError(
          f'Invalid path filter definition unsupported type: {filter_type!s}.')

    paths = filter_definition_values.get('paths')

    if not paths:
      raise errors.ParseError('Invalid path filter definition missing paths.')

    description = filter_definition_values.get('description')
    path_separator = filter_definition_values.get('path_separator', '/')

    return path_filters.PathFilter(
        filter_type, description=description, path_separator=path_separator,
        paths=paths)

  def _ReadFromFileObject(self, file_object):
    """Reads the path filters from a file-like object.

    Args:
      file_object (file): filter file-like object.

    Yields:
      PathFilter: path filter.
    """
    yaml_generator = yaml.safe_load_all(file_object)

    for yaml_definition in yaml_generator:
      yield self._ReadFilterDefinition(yaml_definition)

  def ReadFromFile(self, path):
    """Reads the path filters from the YAML-based filter file.

    Args:
      path (str): path to a filter file.

    Returns:
      list[PathFilter]: path filters.
    """
    with io.open(path, 'r', encoding='utf-8') as file_object:
      return list(self._ReadFromFileObject(file_object))
