"""
This Calculator holds the logic for the calculator.
"""
import pytest
from calculator.operators import Operator, STANDARD_OPERATORS
from calculator.expression import Token, Term, Expression, TermExpression, OperatorExpression


class Calculator:
    """
    Calculator class is a simple calculator that can parse and evaluate expressions of the form:
    expression ::= term | expression operator expression
    operator ::= + | - | * | /
    with the usual precedence rules.
    """

    def __init__(self, operators=None):
        if operators is None:
            operators = STANDARD_OPERATORS
        self.operators = operators

    def tokenize(self, line: str) -> list[Token]:
        """
        Tokenize an expression into a list of tokens.
        """
        tokens = []
        for token in line.split():
            if token in self.operators:
                tokens.append(self.operators[token])
            else:
                try:
                    term = float(token)
                    tokens.append(term)
                except ValueError as exc:
                    raise ValueError(f"Invalid token {token}") from exc
        return tokens

    def parse(self, tokens: list[Token]) -> Expression:
        """
        Parse a list of tokens into an ordered expression.
        """
        if not tokens:
            raise ValueError("Empty expression")
        if len(tokens) == 1:
            if isinstance(tokens[0], Term):
                return TermExpression(tokens[0])
            raise ValueError(f"Expected a term, got {tokens[0]}")
        if len(tokens) == 2:
            raise ValueError("Invalid expression")

        # Find the rightest operator with the lowest precedence
        operator = None
        for i, token in enumerate(tokens):
            if isinstance(token, Operator):
                if operator is None or token.precedence <= operator.precedence:
                    operator = token
                    operator_index = i

        # Split the expression into two parts
        left = tokens[:operator_index]
        right = tokens[operator_index + 1:]

        # Parse the left and right parts recursively
        return OperatorExpression(operator, self.parse(left), self.parse(right))

    def __call__(self, expression: str) -> Term:
        return self.parse(self.tokenize(expression))()


@pytest.fixture(scope="module", name="setup")
def fixture_setup():
    """
    Setup the test suite, by instantiating the calculator and the operators.
    """
    plus = Operator('+', 1, lambda a, b: a + b)
    minus = Operator('-', 1, lambda a, b: a - b)
    times = Operator('*', 2, lambda a, b: a * b)
    divide = Operator('/', 2, lambda a, b: a / b)
    calculator = Calculator(
        operators={'+': plus, '-': minus, '*': times, '/': divide})
    yield plus, minus, times, divide, calculator


def test_tokenizer(setup):
    """
    Test the tokenizer.
    """
    plus, minus, times, divide, calc = setup
    assert calc.tokenize("1 + 2") == [1.0, plus, 2.0]
    assert calc.tokenize("1 + 2 * 3") == [1.0, plus, 2.0, times, 3.0]
    assert calc.tokenize(
        "1 + 2 * 3 / 4") == [1.0, plus, 2.0, times, 3.0, divide, 4.0]
    assert calc.tokenize(
        "1 + 2 * 3 / 4 - 5") == [1.0, plus, 2.0, times, 3.0, divide, 4.0, minus, 5.0]


def test_parser(setup):
    """
    Test the parser.
    """
    _, _, _, _, calc = setup
    assert repr(calc.parse(calc.tokenize("1 + 2"))) == '(1.0 + 2.0)'
    assert repr(calc.parse(calc.tokenize("1 + 2 * 3"))
                ) == '(1.0 + (2.0 * 3.0))'
    assert repr(calc.parse(calc.tokenize(
        "1 + 2 * 3 / 4"))) == '(1.0 + ((2.0 * 3.0) / 4.0))'
    assert repr(calc.parse(calc.tokenize(
        "1 + 2 * 3 / 4 - 5"))) == '((1.0 + ((2.0 * 3.0) / 4.0)) - 5.0)'


def test_evaluation(setup):
    """
    Test the evaluation.
    """
    _, _, _, _, calc = setup
    assert calc("1 + 2") == 3
    assert calc("1 + 2 * 3") == 7
    assert calc("1 + 2 * 3 / 4") == 2.5
    assert calc("1 + 2 * 3 / 4 - 5") == -2.5