"""The event filter expression parser expression classes."""

import abc

from dfdatetime import interface as dfdatetime_interface

from plaso.filters import filters
from plaso.filters import logger
from plaso.filters import value_types
from plaso.lib import errors


class Expression:
    """An event filter parser expression.

    Attributes:
      attribute (str): attribute or None if not set.
      args (list[object]): arguments.
      number_of_args (int): expected number of arguments.
      operator (str): operator or None if not set.
    """

    # TODO: this currently needs to be a class attribute for objectfilter.
    # See if this can be changed to an instance attribute.
    attribute = None

    def __init__(self):
        """Initializes an event filter parser expression."""
        super().__init__()
        self.attribute = None
        self.args = []
        self.number_of_args = 1
        self.operator = None

    def AddArgument(self, argument):
        """Adds a new argument to this expression.

        Args:
          argument (object): argument to add.

        Returns:
          bool: True if the argument is the last argument, False otherwise.

        Raises:
          ParseError: If there are too many arguments.
        """
        self.args.append(argument)
        if len(self.args) > self.number_of_args:
            raise errors.ParseError("Too many arguments for this expression.")

        if len(self.args) == self.number_of_args:
            return True

        return False

    @abc.abstractmethod
    def Compile(self):
        """Compiles the expression into a filter.

        Returns:
          Filter: filter object corresponding the expression.
        """

    def SetAttribute(self, attribute):
        """Sets the attribute.

        Args:
          attribute (str): attribute, or None if not set.
        """
        self.attribute = attribute

    def SetOperator(self, operator):
        """Set the operator.

        Args:
          operator (str): operator, such as "and" or "&&", or None if not set.
        """
        self.operator = operator


class BinaryExpression(Expression):
    """An event filter parser expression which takes two other expressions."""

    def __init__(self, operator=""):
        """Initializes an event filter parser binary expression.

        Args:
          operator (str): operator, such as "and" or "&&".
        """
        super().__init__()
        self.args = []
        self.operator = operator

    def __repr__(self):
        """Retrieves a string representation of the object for debugging."""
        if len(self.args) == 2:
            return f"({self.args[0]!s}) {self.operator:s} {self.args[1]!s}"
        return self.operator

    def AddOperands(self, lhs, rhs):
        """Adds an operand.

        Args:
          lhs (Expression): left hand side expression.
          rhs (Expression): right hand side expression.

        Raises:
          ParseError: if either left hand side or right hand side expression
              is not an instance of Expression.
        """
        # TODO: add information about what lhs and rhs are to these error messages.
        # https://github.com/log2timeline/plaso/pull/2498
        if not isinstance(lhs, Expression):
            raise errors.ParseError("Left hand side is not an expression")

        if not isinstance(rhs, Expression):
            raise errors.ParseError("Right hand side is not an expression")

        self.args = [lhs, rhs]

    def Compile(self):
        """Compiles the expression into a filter.

        Returns:
          Filter: filter object corresponding the expression.

        Raises:
          ParseError: if the operator is not supported.
        """
        operator = self.operator.lower()
        if operator in ("and", "&&"):
            filter_class = filters.AndFilter
        elif operator in ("or", "||"):
            filter_class = filters.OrFilter
        else:
            raise errors.ParseError(f"Unusupported operator: {operator:s}.")

        args = [argument.Compile() for argument in self.args]
        return filter_class(arguments=args)


class IdentityExpression(Expression):
    """An event filter parser expression which always evaluates to True."""

    def Compile(self):
        """Compiles the expression into a filter.

        Returns:
          IdentityFilter: filter object which always evaluates to True.
        """
        return filters.IdentityFilter()


class EventExpression(Expression):
    """Event expression."""

    # TODO: add an IsOperator
    _OPERATORS = {
        "==": filters.EqualsOperator,
        ">": filters.GreaterThanOperator,
        ">=": filters.GreaterEqualOperator,
        "<": filters.LessThanOperator,
        "<=": filters.LessEqualOperator,
        "!=": filters.NotEqualsOperator,
        "contains": filters.Contains,
        "equals": filters.EqualsOperator,
        "inset": filters.InSet,
        "iregexp": filters.RegexpInsensitive,
        "is": filters.EqualsOperator,
        "regexp": filters.Regexp,
    }

    _EVENT_FILTER_ALIAS = {
        "date": "timestamp",
        "datetime": "timestamp",
        "time": "timestamp",
    }

    _DEPRECATED_EVENT_FILTER_ALIAS = frozenset(
        ["description_long", "description", "description_short"]
    )

    def __init__(self):
        """Initializes an event expression."""
        super().__init__()
        self._bool_value = True

    def __repr__(self):
        """Retrieves a string representation of the object for debugging."""
        return f"{self.attribute:s} {self.operator:s} {self.args!s}"

    def Compile(self):
        """Compiles the expression into a filter.

        Returns:
          Filter: filter object corresponding the expression.

        Raises:
          ParseError: if the operator is missing or unknown.
        """
        if self.attribute in self._DEPRECATED_EVENT_FILTER_ALIAS:
            logger.warning(
                f'Event filter alias: "{self.attribute:s}" no longer supported'
            )

        if not self.operator:
            raise errors.ParseError("Missing operator.")

        lookup_key = self.operator.lower()
        operator = self._OPERATORS.get(lookup_key)
        if not operator:
            raise errors.ParseError(f"Unknown operator: {self.operator:s}.")

        self.attribute = self._EVENT_FILTER_ALIAS.get(self.attribute, self.attribute)

        # Convert date and time values to (dfdatetime) date time objects.
        if self.attribute == "timestamp":
            date_time_arguments = []
            for argument in self.args:
                if isinstance(argument, dfdatetime_interface.DateTimeValues):
                    date_time = argument

                else:
                    logger.warning(
                        "Implicit event filter date and time conversion is deprecated "
                        "use the DATETIME() value type indicator instead"
                    )
                    try:
                        date_time = value_types.DateTimeValueType(argument)
                    except ValueError as exception:
                        raise errors.ParseError(exception)

                date_time_arguments.append(date_time)

            self.args = date_time_arguments

        arguments = [self.attribute]
        arguments.extend(self.args)
        ops = operator(arguments=arguments)
        if not self._bool_value:
            if hasattr(ops, "FlipBool"):
                ops.FlipBool()

        return ops

    def Negate(self):
        """Reverses the logic of (negates) the expression."""
        self._bool_value = not self._bool_value
