# Example: expression tree + evaluation

from enum import Enum


class TokenType(Enum):
    NUMBER = 1
    OPERATOR = 2


# either store an operator (+, -, *, /) or a number
class Token:
    def __init__(self, type: TokenType, value: str):
        self.type = type
        self.value = value


# each individual element in the tree
class Node:
    left = None
    right = None

    def __init__(self, token: Token, left=None, right=None):
        self.token = token
        self.left = left
        self.right = right


# holder for the tree
class ExpressionTree:
    def __init__(self, root: Node | None):
        self.root = root


def evaluate(tree: ExpressionTree) -> int:
    return _evaluate(tree.root)


# evaluate a tree recursively starting from the node
def _evaluate(node: Node | None) -> int:
    if node is None:
        return 0

    if node.token.type == TokenType.NUMBER:
        return int(node.token.value)

    elif node.token.type == TokenType.OPERATOR:
        if node.left is None or node.right is None:
            raise ValueError(f"Operator {node.token.value} requires two operands")

        left = _evaluate(node.left)
        right = _evaluate(node.right)

        if node.token.value == "+":
            return left + right
        elif node.token.value == "-":
            return left - right
        elif node.token.value == "*":
            return left * right
        elif node.token.value == "/":
            return left // right

    raise ValueError(f"Unknown operator: {node.token.value}")


# test cases
test_cases = [
    (ExpressionTree(Node(Token(TokenType.NUMBER, "5"))), 5),
    (
        ExpressionTree(
            Node(
                Token(TokenType.OPERATOR, "+"),
                Node(Token(TokenType.NUMBER, "3")),
                Node(Token(TokenType.NUMBER, "2")),
            )
        ),
        5,
    ),
    (
        ExpressionTree(
            Node(
                Token(TokenType.OPERATOR, "+"),
                Node(Token(TokenType.NUMBER, "3")),
                Node(
                    Token(TokenType.OPERATOR, "*"),
                    Node(Token(TokenType.NUMBER, "2")),
                    Node(Token(TokenType.NUMBER, "3")),
                ),
            )
        ),
        9,
    ),
    (
        ExpressionTree(
            Node(
                Token(TokenType.OPERATOR, "+"),
                Node(
                    Token(TokenType.OPERATOR, "-"),
                    Node(Token(TokenType.NUMBER, "3")),
                    Node(Token(TokenType.NUMBER, "2")),
                ),
                Node(
                    Token(TokenType.OPERATOR, "*"),
                    Node(Token(TokenType.NUMBER, "2")),
                    Node(Token(TokenType.NUMBER, "3")),
                ),
            )
        ),
        7,
    ),
]


def test_evaluate() -> None:
    for tree, expected in test_cases:
        evaluation = evaluate(tree) == expected
        print(f"Test case: evaluate({tree}) == {expected} -> {evaluation}")


if __name__ == "__main__":
    test_evaluate()
