import enum
from typing import Any, Optional
from fandango.constraints.base import GeneticBase
from fandango.language.tree import DerivationTree


class Comparison(enum.Enum):
    """
    Enum class for comparison operations.
    """

    EQUAL = "=="
    NOT_EQUAL = "!="
    GREATER = ">"
    GREATER_EQUAL = ">="
    LESS = "<"
    LESS_EQUAL = "<="


class ComparisonSide(enum.Enum):
    """
    Enum class for comparison side.
    """

    LEFT = "left"
    RIGHT = "right"


class FailingTree:
    """
    Class to represent a failing tree, i.e., a tree that does not satisfy a given constraint.
    """

    def __init__(
        self,
        tree: DerivationTree,
        cause: "GeneticBase",
        suggestions: Optional[list[tuple[Comparison, Any, ComparisonSide]]] = None,
    ):
        """
        Initialize the FailingTree with the given tree, cause, and suggestions.

        :param DerivationTree tree: The tree that failed to satisfy the constraint.
        :param GeneticBase cause: The cause of the failure.
        :param Optional[list[tuple[Comparison, Any, ComparisonSide]]] suggestions: The list of suggestions to
        which causes the cause to fail.
        """
        self.tree = tree
        self.cause = cause
        self.suggestions = suggestions or []

    def __hash__(self):
        return hash((self.tree, self.cause))

    def __eq__(self, other):
        return self.tree == other.tree and self.cause == other.cause

    def __repr__(self):
        return f"FailingTree({self.tree}, {self.cause}, {self.suggestions})"

    def __str__(self):
        return self.__repr__()


class BoundsFailingTree(FailingTree):
    """
    Class to represent a failing tree that has was generated by a bounds constraint.
    """

    def __init__(
        self,
        tree: DerivationTree,
        starting_rep_tree: DerivationTree,
        ending_rep_tree: DerivationTree,
        starting_rep_value: DerivationTree,
        ending_rep_value: DerivationTree,
        cause: "GeneticBase",
        suggestions: Optional[list[tuple[Comparison, Any, ComparisonSide]]] = None,
    ):
        """
        Initialize the BoundsFailingTree with the given tree, cause, lower bound, upper bound, and suggestions.

        :param DerivationTree tree: The tree that failed to satisfy the constraint.
        :param DerivationTree starting_rep_tree: The first tree generated by the repetition.
        :param DerivationTree ending_rep_tree: The last tree generated by the repetition.
        :param DerivationTree starting_rep_value: The reference to the subtree giving min repetition value
        :param DerivationTree ending_rep_value: A reference to the subtree giving max repetition value
        :param GeneticBase cause: The cause of the failure.
        :param Optional[list[tuple[Comparison, Any, ComparisonSide]]] suggestions: The list of suggestions to
        which causes the cause to fail.
        """
        super().__init__(tree, cause, suggestions)
        self.starting_rep_tree = starting_rep_tree
        self.ending_rep_tree = ending_rep_tree
        self.starting_rep_value = starting_rep_value
        self.ending_rep_value = ending_rep_value

    def __repr__(self):
        return f"BoundsFailingTree({self.tree}, {self.cause}, {str(self.starting_rep_value)}, {self.ending_rep_value}, {self.suggestions})"
