from .expression import BinaryExpression, Expression, UnaryExpression, ValueExpression
from .operation import (
    BraceOperation,
    FunctionOperation,
    Operation,
    OperatorOperation,
    priorities,
)
from .tokenizer import Token, Tokenizer
from .types import ValueType
from .constants import CONSTANTS


class Parser:
    """
    Class that accepts math expression in its constructor,
    parses it and provides `evaluate` method to substitue
    variables values to it
    """

    def __init__(self, input_expr: str):
        self.input_expr = input_expr
        self.variables_names: set[str] = set()

        self._tokenize()
        self._parse()

    def _tokenize(self):
        """
        Uses `Tokenizer` class for math expression splitting
        """
        self.tokens = Tokenizer(self.input_expr)

    def _parse(self):
        """
        Generates an evaluation tree from tokens by utilizing two
        stacks - one for values and one for operations. Values and
        operations are placed into corresponding stacks, and when
        possible, assempled into tree node.
        """
        self.val_stack: list[Expression] = []
        self.op_stack: list[Operation] = []

        for t_val, t_type in self.tokens:
            if t_type in (Token.Number, Token.Variable):
                self.val_stack.append(ValueExpression(t_val))

                if t_type == Token.Variable:
                    self.variables_names.add(t_val)

            elif t_type == Token.Function:
                self.op_stack.append(FunctionOperation(t_val))

            elif t_type == Token.LBrace:
                self.op_stack.append(BraceOperation("("))

            elif t_type == Token.RBrace:
                while len(self.op_stack) > 0 and not (
                    self.op_stack[-1].size == 0 and self.op_stack[-1].priority == 0
                ):  # until next in stack is lbrace
                    self._do_one()
                self.op_stack.pop()  # pop lbrace

            elif t_type == Token.Operator:
                t_priority = priorities[t_val]

                while (
                    len(self.op_stack) > 0 and self.op_stack[-1].priority > t_priority
                ):
                    self._do_one()

                self.op_stack.append(OperatorOperation(t_val))

        while len(self.op_stack) > 0:
            self._do_one()

        self._evaluator = self.val_stack[0].evaluate

        self.__debug_expr = repr(self.val_stack)

    def _do_one(self):
        """
        Assembles one operation into `Expression` tree node that is stored
        on value stack.
        """
        op = self.op_stack.pop()

        if op.size == 1:
            a = self.val_stack.pop()
            self.val_stack.append(UnaryExpression(op.evaluator, a))
        elif op.size == 2:
            b, a = self.val_stack.pop(), self.val_stack.pop()  # inversed pop order
            self.val_stack.append(BinaryExpression(op.evaluator, a, b))

    def evaluate(self, variables: dict[str, ValueType]):
        """
        Evaluates supplied to constructor expression with provided 
        variables values
        """
        variables |= CONSTANTS
        return self._evaluator(variables)

    def __repr__(self):
        return self.__debug_expr