# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
# ruff: noqa: F841

import tvm
import tvm.testing
from tvm import tirx
from tvm.tirx import (
    EQ,
    LT,
    Add,
    Cast,
    Evaluate,
    FloatImm,
    For,
    IfThenElse,
    IntImm,
    Max,
    Min,
    Mul,
    PyStmtExprMutator,
    PyStmtExprVisitor,
    StringImm,
    Sub,
    Var,
)


class ASTLog:
    """Helper class to log AST"""

    def __init__(self) -> None:
        self.log = []
        self.indent = "\t"
        self.level = 0

    def push_scope(self):
        self.level += 1

    def pop_scope(self):
        self.level -= 1

    def add(self, s: str):
        self.log.append(self.indent * self.level + s)

    def __str__(self) -> str:
        return "\n".join(self.log)


@tirx.functor.visitor
class ASTPrinter(PyStmtExprVisitor):
    """Print tirx AST in structured format. The shape of Node is ignored."""

    def __init__(self) -> None:
        super().__init__()
        self.log = ASTLog()

    def visit_var_(self, op: Var) -> None:
        self.log.add("Stmt: Var")
        super().visit_var_(op)

    def visit_add_(self, op: Add) -> None:
        self.log.add("Stmt: Add")
        super().visit_add_(op)


@tirx.functor.visitor
class SimpleExprCounter(PyStmtExprVisitor):
    """Count expressions without recursion"""

    def __init__(self):
        super().__init__()
        self.var_count = 0
        self.add_count = 0
        self.mul_count = 0

    def visit_var_(self, op: Var):
        self.var_count += 1
        # Don't recursively visit children to avoid infinite recursion

    def visit_add_(self, op: Add):
        self.add_count += 1
        # Visit children manually
        super().visit_add_(op)

    def visit_mul_(self, op: Mul):
        self.mul_count += 1
        # Visit children manually
        super().visit_mul_(op)


@tirx.functor.mutator
class VariableReplacer(PyStmtExprMutator):
    """Replace variables with constants"""

    def __init__(self, replacements):
        super().__init__()
        self.replacements = replacements

    def visit_var_(self, op: Var):
        if op.name in self.replacements:
            return IntImm("int32", self.replacements[op.name])
        return op


@tirx.functor.mutator
class AddToSubMutator(PyStmtExprMutator):
    """Convert Add operations to Sub operations"""

    def visit_add_(self, op: Add):
        # First mutate the operands
        a = self.visit_expr(op.a)
        b = self.visit_expr(op.b)
        # Convert Add to Sub
        return Sub(a, b)


@tirx.functor.visitor
class SimpleStmtCounter(PyStmtExprVisitor):
    """Count statements without recursion"""

    def __init__(self):
        super().__init__()
        self.for_count = 0
        self.if_count = 0
        self.evaluate_count = 0

    def visit_for_(self, op: For):
        self.for_count += 1
        super().visit_for_(op)

    def visit_if_then_else_(self, op: IfThenElse):
        self.if_count += 1
        super().visit_if_then_else_(op)

    def visit_evaluate_(self, op: Evaluate):
        self.evaluate_count += 1
        super().visit_evaluate_(op)


@tirx.functor.mutator
class ForLoopUnroller(PyStmtExprMutator):
    """Simple loop unroller for demonstration"""

    def __init__(self, unroll_factor=2):
        super().__init__()
        self.unroll_factor = unroll_factor

    def visit_for_(self, op: For):
        # For demonstration, just return the original for now
        # In a real implementation, we would unroll small loops
        return super().visit_for_(op)


@tirx.functor.visitor
class SimpleStmtExprVisitor(PyStmtExprVisitor):
    """Visitor that handles both statements and expressions"""

    def __init__(self):
        super().__init__()
        self.expr_count = 0
        self.stmt_count = 0
        self.var_names = set()

    def visit_var_(self, op: Var):
        self.var_names.add(op.name)
        self.expr_count += 1

    def visit_evaluate_(self, op: Evaluate):
        self.stmt_count += 1
        # Visit the expression
        self.visit_expr(op.value)


@tirx.functor.mutator
class ComplexMutator(PyStmtExprMutator):
    """Mutator that handles both statements and expressions"""

    def __init__(self):
        super().__init__()
        self.modifications = 0

    def visit_add_(self, op: Add):
        self.modifications += 1
        # Convert a + b to a * 2 + b for demonstration
        a = self.visit_expr(op.a)
        b = self.visit_expr(op.b)
        return Add(Mul(a, IntImm("int32", 2)), b)


def test_basic_visitor():
    """Test the basic AST printer visitor"""
    expr = Add(Var("x", ty="int32"), Var("y", ty="int32"))
    printer = ASTPrinter()
    printer.visit_expr(expr)
    assert str(printer.log) == "\n".join(["Stmt: Add", "Stmt: Var", "Stmt: Var"])


def test_simple_expr_counter():
    """Test simple expression counting visitor"""
    x = Var("x", ty="int32")
    y = Var("y", ty="int32")

    # Create simple expression: x + y
    expr = Add(x, y)

    counter = SimpleExprCounter()
    counter.visit_expr(expr)

    assert counter.var_count == 2  # x and y
    assert counter.add_count == 1  # one add


def test_variable_replacer():
    """Test expression mutator that replaces variables"""
    x = Var("x", ty="int32")
    y = Var("y", ty="int32")
    expr = Add(x, Mul(y, IntImm("int32", 3)))

    replacer = VariableReplacer({"x": 10, "y": 5})
    result = replacer.visit_expr(expr)

    # Should be Add(IntImm(10), Mul(IntImm(5), IntImm(3)))
    assert isinstance(result, Add)
    assert isinstance(result.a, IntImm)
    assert result.a.value == 10
    assert isinstance(result.b, Mul)
    assert isinstance(result.b.a, IntImm)
    assert result.b.a.value == 5


def test_add_to_sub_mutator():
    """Test mutator that converts Add to Sub"""
    x = Var("x", ty="int32")
    y = Var("y", ty="int32")
    expr = Add(x, y)

    mutator = AddToSubMutator()
    result = mutator.visit_expr(expr)

    assert isinstance(result, Sub)
    assert isinstance(result.a, Var)
    assert isinstance(result.b, Var)
    assert result.a.name == "x"
    assert result.b.name == "y"


def test_simple_stmt_counter():
    """Test statement visitor that counts statements"""
    i = Var("i", ty="int32")

    # Create a simple for loop
    loop_body = Evaluate(IntImm("int32", 0))
    for_stmt = For(i, IntImm("int32", 0), IntImm("int32", 10), tirx.ForKind.SERIAL, loop_body)

    counter = SimpleStmtCounter()
    counter.visit_stmt(for_stmt)

    assert counter.for_count == 1  # One for loop
    assert counter.evaluate_count == 1  # One evaluate in the body


def test_if_then_else_visitor():
    """Test visitor with if-then-else statements"""
    x = Var("x", ty="int32")
    condition = EQ(x, IntImm("int32", 0))
    then_stmt = Evaluate(IntImm("int32", 1))
    else_stmt = Evaluate(IntImm("int32", 2))

    if_stmt = IfThenElse(condition, then_stmt, else_stmt)

    counter = SimpleStmtCounter()
    counter.visit_stmt(if_stmt)

    assert counter.if_count == 1
    assert counter.for_count == 0


def test_simple_stmt_expr_visitor():
    """Test stmt_expr_visitor with mixed statements and expressions"""
    x = Var("x", ty="int32")
    y = Var("y", ty="int32")

    # Create an evaluate statement with an expression
    expr = Add(x, y)
    stmt = Evaluate(expr)

    visitor = SimpleStmtExprVisitor()
    visitor.visit_stmt(stmt)

    assert visitor.stmt_count == 1  # One Evaluate statement
    assert visitor.expr_count == 2  # Two variables
    assert "x" in visitor.var_names
    assert "y" in visitor.var_names


def test_complex_mutator():
    """Test stmt_expr_mutator"""
    x = Var("x", ty="int32")
    y = Var("y", ty="int32")

    # Expression with Add operations
    expr = Add(x, y)
    stmt = Evaluate(expr)

    mutator = ComplexMutator()
    result = mutator.visit_stmt(stmt)
    print(type(mutator))

    assert mutator.modifications == 1  # One Add operation modified
    assert isinstance(result, Evaluate)

    # Check that the expression was modified
    modified_expr = result.value
    assert isinstance(modified_expr, Add)
    assert isinstance(modified_expr.a, Mul)  # First operand should be multiplied by 2


def test_different_expr_types():
    """Test visitor with various expression types"""
    x = Var("x", ty="int32")

    # Test different expression types individually
    exprs = [
        IntImm("int32", 42),
        FloatImm("float32", 3.14),
        StringImm("hello"),
        Cast("float32", x),
        Min(x, IntImm("int32", 10)),
        Max(x, IntImm("int32", 0)),
        LT(x, IntImm("int32", 5)),
    ]

    # Just test that we can create and visit each type
    counter = SimpleExprCounter()
    for expr in exprs:
        try:
            counter.visit_expr(expr)
        except Exception as e:
            # Some expressions might not be supported, that's ok
            pass


def test_decorator_functionality():
    """Test that decorators work correctly"""

    # Test that decorated classes are properly wrapped
    visitor = SimpleExprCounter()
    assert hasattr(visitor, "_outer")  # Should have the wrapper functionality

    mutator = VariableReplacer({})
    assert hasattr(mutator, "_outer")


def test_empty_expressions():
    """Test handling of simple expressions"""
    counter = SimpleExprCounter()

    # Test with just a variable
    x = Var("x", ty="int32")
    counter.visit_expr(x)

    assert counter.var_count == 1

    # Test with just a constant
    counter = SimpleExprCounter()
    const = IntImm("int32", 5)
    counter.visit_expr(const)

    # Constants don't increase var_count
    assert counter.var_count == 0


def test_stmt_mutator():
    """Test basic statement mutator functionality"""
    x = Var("x", ty="int32")
    stmt = Evaluate(Add(x, IntImm("int32", 1)))

    unroller = ForLoopUnroller()
    result = unroller.visit_stmt(stmt)

    # Should return the same statement (no actual unrolling implemented)
    assert isinstance(result, Evaluate)


def test_nested_expressions():
    """Test with nested expressions"""
    x = Var("x", ty="int32")
    y = Var("y", ty="int32")
    z = Var("z", ty="int32")

    # Create nested expression: (x + y) * z
    inner_add = Add(x, y)
    expr = Mul(inner_add, z)

    counter = SimpleExprCounter()
    counter.visit_expr(expr)

    assert counter.var_count == 3  # x, y, z
    assert counter.add_count == 1  # one add
    assert counter.mul_count == 1  # one mul


def test_simple_mutations():
    """Test simple expression mutations"""
    x = Var("x", ty="int32")
    y = Var("y", ty="int32")

    # Test multiple replacements
    expr = Add(x, y)
    replacer = VariableReplacer({"x": 1, "y": 2})
    result = replacer.visit_expr(expr)

    assert isinstance(result, Add)
    assert isinstance(result.a, IntImm)
    assert isinstance(result.b, IntImm)
    assert result.a.value == 1
    assert result.b.value == 2


if __name__ == "__main__":
    test_basic_visitor()
    tvm.testing.main()
