# RUN: %PYTHON %s | FileCheck %s

import gc
import io
import itertools
from mlir.ir import *


def run(f):
    print("\nTEST:", f.__name__)
    f()
    gc.collect()
    assert Context._get_live_count() == 0
    return f


# CHECK-LABEL: TEST: testSymbolTableInsert
@run
def testSymbolTableInsert():
    with Context() as ctx:
        ctx.allow_unregistered_dialects = True
        m1 = Module.parse(
            """
      func.func private @foo()
      func.func private @bar()"""
        )
        m2 = Module.parse(
            """
      func.func private @qux()
      func.func private @foo()
      "foo.bar"() : () -> ()"""
        )

        symbol_table = SymbolTable(m1.operation)

        # CHECK: func private @foo
        # CHECK: func private @bar
        assert "foo" in symbol_table
        print(symbol_table["foo"])
        assert "bar" in symbol_table
        bar = symbol_table["bar"]
        print(symbol_table["bar"])

        assert "qux" not in symbol_table

        del symbol_table["bar"]
        try:
            symbol_table.erase(symbol_table["bar"])
        except KeyError:
            pass
        else:
            assert False, "expected KeyError"

        # CHECK: module
        # CHECK:   func private @foo()
        print(m1)
        assert "bar" not in symbol_table

        bar._set_invalid()
        try:
            print(bar)
        except RuntimeError as e:
            if "the operation has been invalidated" not in str(e):
                raise
        else:
            assert False, "expected RuntimeError due to invalidated operation"

        qux = m2.body.operations[0]
        m1.body.append(qux)
        symbol_table.insert(qux)
        assert "qux" in symbol_table

        # Check that insertion actually renames this symbol in the symbol table.
        foo2 = m2.body.operations[0]
        m1.body.append(foo2)
        updated_name = symbol_table.insert(foo2)
        assert foo2.name.value != "foo"
        assert foo2.name == updated_name
        assert isinstance(updated_name, StringAttr)

        # CHECK: module
        # CHECK:   func private @foo()
        # CHECK:   func private @qux()
        # CHECK:   func private @foo{{.*}}
        print(m1)

        try:
            symbol_table.insert(m2.body.operations[0])
        except ValueError as e:
            if "Expected operation to have a symbol name" not in str(e):
                raise
        else:
            assert False, "exepcted ValueError when adding a non-symbol"


# CHECK-LABEL: testSymbolTableRAUW
@run
def testSymbolTableRAUW():
    with Context() as ctx:
        m = Module.parse(
            """
      func.func private @foo() {
        call @bar() : () -> ()
        return
      }
      func.func private @bar()
      """
        )
        foo, bar = list(m.operation.regions[0].blocks[0].operations)[0:2]

        # Do renaming just within `foo`.
        SymbolTable.set_symbol_name(bar, "bam")
        SymbolTable.replace_all_symbol_uses("bar", "bam", foo)
        # CHECK: call @bam()
        # CHECK: func private @bam
        print(m)
        # CHECK: Foo symbol: StringAttr("foo")
        # CHECK: Bar symbol: StringAttr("bam")
        print(f"Foo symbol: {repr(SymbolTable.get_symbol_name(foo))}")
        print(f"Bar symbol: {repr(SymbolTable.get_symbol_name(bar))}")

        # Do renaming within the module.
        SymbolTable.set_symbol_name(bar, "baz")
        SymbolTable.replace_all_symbol_uses("bam", "baz", m.operation)
        # CHECK: call @baz()
        # CHECK: func private @baz
        print(m)
        # CHECK: Foo symbol: StringAttr("foo")
        # CHECK: Bar symbol: StringAttr("baz")
        print(f"Foo symbol: {repr(SymbolTable.get_symbol_name(foo))}")
        print(f"Bar symbol: {repr(SymbolTable.get_symbol_name(bar))}")


# CHECK-LABEL: testSymbolTableVisibility
@run
def testSymbolTableVisibility():
    with Context() as ctx:
        m = Module.parse(
            """
      func.func private @foo() {
        return
      }
      """
        )
        foo = m.operation.regions[0].blocks[0].operations[0]
        # CHECK: Existing visibility: StringAttr("private")
        print(f"Existing visibility: {repr(SymbolTable.get_visibility(foo))}")
        SymbolTable.set_visibility(foo, "public")
        # CHECK: func public @foo
        print(m)


# CHECK: testWalkSymbolTables
@run
def testWalkSymbolTables():
    with Context() as ctx:
        m = Module.parse(
            """
      module @outer {
        module @inner{
        }
      }
      """
        )

        def callback(symbol_table_op, uses_visible):
            print(f"SYMBOL TABLE: {uses_visible}: {symbol_table_op}")

        # CHECK: SYMBOL TABLE: True: module @inner
        # CHECK: SYMBOL TABLE: True: module @outer
        SymbolTable.walk_symbol_tables(m.operation, True, callback)

        # Make sure exceptions in the callback are handled.
        def error_callback(symbol_table_op, uses_visible):
            assert False, "Raised from python"

        try:
            SymbolTable.walk_symbol_tables(m.operation, True, error_callback)
        except RuntimeError as e:
            # CHECK: GOT EXCEPTION: Exception raised in callback:
            # CHECK: AssertionError: Raised from python
            print(f"GOT EXCEPTION: {e}")
