from __future__ import annotations

from abc import abstractclassmethod

from docstring_inheritance import NumpyDocstringInheritanceMeta
from typeguard import typechecked

from typing import Any, Optional

import relational_calculus.domain_calculus as dc


class Formula(metaclass=NumpyDocstringInheritanceMeta):
    """
    An abstract class used to represent a first order logic formula.
    This class has many different implementations, together forming a recursive data-structure to hold the formula.
    """

    @typechecked
    def __init__(self, children: list[Formula]) -> None:
        """
        Parameters
        ----------
        children : list[Formula]
            Each implementation of formula can hold a list of children that it is composed of.
        """
        self.children = children

    @typechecked
    @abstractclassmethod
    def __repr__(self) -> str:
        """
        Returs a string representation of a given formula formatted in Latex Math Mode.

        Returns
        -------
        str
            A string representation of the formula formatted in Latex Math Mode.
        """
        pass

    @typechecked
    @abstractclassmethod
    def __eq__(self, other: Any) -> bool:
        """
        Check for equality of syntax, not semantics.

        Returns
        -------
        bool
            If the objects are equal.
        """
        pass

    @typechecked
    def verify(self) -> bool:
        """
        Check if the formula is valid.

        Returns
        -------
        bool
            If the formula is valid.
        """
        formula = self.to_normal_form()
        variables = formula.get_used_variables()
        check = True
        for variable in variables:
            check = check and formula.check_variable_legality(variable)
        return check

    @typechecked
    def to_sql(self) -> Optional[str]:
        """
        Used by DomainCalculus::to_sql to convert the domain calculus specification into an equivalent SQL formula.

        Returns
        -------
        str
            The sub_query in SQL generated by this formula.
        """
        formula = self.prune_tuple_atoms()
        if formula is not None:
            return formula._to_sql()
        return ""

    @typechecked
    @abstractclassmethod
    def _to_sql(self) -> Optional[str]:
        """
        Used by Formula::to_sql to convert the domain calculus specification into an equivalent SQL formula.

        Returns
        -------
        str
            The sub_query in SQL generated by this formula.
        """
        pass

    @typechecked
    def to_normal_form(self) -> Formula:
        """
        Converts a given formula into an semantically equivalent Formula close to Prenex-Normalform.

        Returns
        -------
        Formula
            The semantically equivalent Formula close to Prenex-Normalform.
        """
        old_formula = self
        new_formula = old_formula._to_normal_form()
        while new_formula != old_formula:
            old_formula = new_formula
            new_formula = old_formula._to_normal_form()
        return new_formula

    @typechecked
    @abstractclassmethod
    def _to_normal_form(self) -> Formula:
        """
        Converts a given formula into an semantically equivalent Formula close to Prenex-Normalform.

        Returns
        -------
        Formula
            The semantically equivalent Formula close to Prenex-Normalform.
        """
        pass

    @typechecked
    @abstractclassmethod
    def contains_variable(self, variable: dc.Variable) -> bool:
        """
        Checks whether a given formula contains a given variable.

        Parameters
        ----------
        variable : Variable
            The variable to check in the formula.
        """
        pass

    @typechecked
    @abstractclassmethod
    def contains_variable_typing(self, variable: dc.Variable) -> bool:
        """
        Checks whether a given variable is being used in a tuple in a given formula.

        Parameters
        ----------
        variable : Variable
            The variable to check in the formula.
        """
        pass

    @typechecked
    @abstractclassmethod
    def contains_variable_quantification(self, variable: dc.Variable) -> bool:
        """
        Checks whether a given variable is being quantified in a given formula..

        Parameters
        ----------
        variable : Variable
            The variable to check in the formula.
        """
        pass

    _rename_index = 0

    @typechecked
    def rename_variable(self, variable: dc.Variable) -> Formula:
        """
        Renames a variable to an unused name and replaces all occurences of the old variable with the new one.

        Parameters
        ----------
        variable : Variable
            The variable to rename.
        """
        Formula._rename_index += 1
        new_variable = dc.Variable(
            variable.name + str(Formula._rename_index),
        )
        return self._rename_variable(variable, new_variable)

    @typechecked
    @abstractclassmethod
    def _rename_variable(
        self, old_variable: dc.Variable, new_variable: dc.Variable
    ) -> Formula:
        """
        Replaces all old occurences of an old variable with the new variable.

        Parameters
        ----------
        old_variable : Variable
            The variable to replace.
        new_variable : Variable
            The variable to replace with.
        """
        pass

    @typechecked
    def prune_tuple_atoms(self) -> Formula | None:
        """
        Converts a given formula into a formula stripped of Tuple-atoms.

        Returns
        -------
        Formula
            The formula stripped of Tuple-atoms.
        """
        formula = self
        new_formula = formula._prune_tuple_atoms()
        while formula != new_formula and new_formula is not None:
            formula = new_formula
            new_formula = formula._prune_tuple_atoms()
        return new_formula

    @typechecked
    @abstractclassmethod
    def _prune_tuple_atoms(self) -> Formula | None:
        """
        Converts a given formula into a formula stripped of Tuple-atoms.

        Returns
        -------
        Formula
            The formula stripped of Tuple-atoms.
        """
        pass

    @typechecked
    @abstractclassmethod
    def get_used_variables(self) -> set[dc.Variable]:
        """
        Returns a set of variables used in a given formula.

        Returns
        -------
        set[Variable]
            A set of variables used in the formula.
        """
        pass

    @typechecked
    @abstractclassmethod
    def get_used_tuples(self) -> set[dc.Tuple]:
        """
        Returns a set of tuples used in a given formula.

        Returns
        -------
        set[Variable]
            A set of variables used in the formula.
        """
        pass

    @typechecked
    def check_variable_legality(self, variable: dc.Variable) -> bool:
        """
        Checks if a given variable is used correctly in the given formula.

        Parameters
        ----------
        variable : Variable
            The variable to check the legal use of.

        Returns
        -------
        true
            If the variable's usage was legal.
        false
            Otherwise.

        Raises
        ------
        Exception
            If the variable was not used legally. The exception message also holds further explanation.
        """
        return self.contains_variable_typing(
            variable
        ) and self.to_normal_form()._check_variable_legality(variable)

    @typechecked
    @abstractclassmethod
    def _check_variable_legality(self, variable: dc.Variable) -> bool:
        """
        Checks if a given variable is used correctly in the given formula.

        Parameters
        ----------
        variable : Variable
            The variable to check the legal use of.

        Returns
        -------
        true
            If the variable's usage was legal.
        false
            Otherwise.

        Raises
        ------
        Exception
            If the variable was not used legally. The exception message also holds further explanation.
        """
        pass
