# 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: F401
import tvm
import tvm.testing
from tvm import relax, tirx, topi
from tvm.script.ir_builder import relax as R
from tvm.script.ir_builder.base import IRBuilder


def test_function_simple():
    """
    @R.function
    def foo(x: R.Tensor((128, 128), "float32")) -> R.Tensor(None, "float32", ndim=2):
        out = R.call_dps_packed("extern_func", x, R.Tensor((128, 128), dtype="float32"))
        return out
    """
    # create with Script IRBuilder
    with IRBuilder() as ir_builder:
        with R.function():
            R.func_name("foo")
            R.func_attr({"Primitive": True})
            x = R.arg("x", relax.TensorType((128, 128), "float32"))
            R.func_ret_ty(relax.TensorType(dtype="float32", ndim=2))
            y = R.emit(
                R.call_dps_packed("extern_func", x, relax.TensorType((128, 128), dtype="float32"))
            )
            out = R.emit(
                R.call_dps_packed(
                    "extern_dps_func", y, relax.TensorType((128, 128), dtype="float32")
                )
            )
            IRBuilder.name("out", out)
            R.func_ret_value(out)
    func = ir_builder.get()
    # create with BlockBuilder
    x = relax.Var("x", relax.TensorType((128, 128), "float32"))
    bb = relax.BlockBuilder()
    with bb.function("foo", (x,), attrs={"Primitive": True}):
        y = bb.emit(
            relax.call_dps_packed("extern_func", x, relax.TensorType((128, 128), dtype="float32"))
        )
        out = bb.emit(
            relax.call_dps_packed(
                "extern_dps_func", y, relax.TensorType((128, 128), dtype="float32")
            )
        )
        bb.emit_func_output(out)
    mod = bb.get()

    tvm.ir.assert_structural_equal(func, mod["foo"])
    # check names
    assert func.params[0].name_hint == "x"
    assert func.body.body.name_hint == "out"


def test_emits():
    """Tests for R.emit, R.emit_match_cast, R.emit_var_binding

    @R.function
    def foo(x: R.Tensor(dtype="float32"), y: R.Tensor(dtype="float32")) -> R.Shape(ndim=2):
        m = T.int64()
        n = T.int64()
        gv: R.Tensor((m,), dtype="float32") = R.match_cast(x, R.Tensor((m,), dtype="float32"))
        gv1: R.Tensor((n,), dtype="float32") = R.match_cast(y, R.Tensor((n,), dtype="float32"))
        v: R.Tensor((n,), dtype="float32") = gv1
        return R.shape([m, n * 2])
    """
    # create with Script IRBuilder
    with IRBuilder() as ir_builder:
        with R.function():
            R.func_name("foo")
            x = R.arg("x", relax.TensorType(ndim=-1, dtype="float32"))
            y = R.arg("y", relax.TensorType(ndim=-1, dtype="float32"))
            m = tirx.Var("m", dtype="int64")
            n = tirx.Var("n", dtype="int64")
            _ = R.emit_match_cast(x, relax.TensorType((m,), "float32"))
            y1 = R.emit_match_cast(y, relax.TensorType((n,), "float32"))
            v = relax.Var("v", relax.TensorType((n,), "float32"))
            vb = relax.VarBinding(v, y1)
            v = R.emit_var_binding(vb)
            R.emit(v)

            IRBuilder.name("v", v)
            R.func_ret_value(relax.ShapeExpr([m, n * 2]))
    func = ir_builder.get()

    # create with BlockBuilder
    m = tirx.Var("m", dtype="int64")
    n = tirx.Var("n", dtype="int64")
    x = relax.Var("x", relax.TensorType(dtype="float32", ndim=-1))
    y = relax.Var("y", relax.TensorType(dtype="float32", ndim=-1))
    v = relax.Var("v", relax.TensorType((n,), "float32"))
    bb = relax.BlockBuilder()
    with bb.function("foo", (x, y)):
        _ = bb.match_cast(x, relax.TensorType((m,), "float32"))
        y1 = bb.match_cast(y, relax.TensorType((n,), "float32"))
        bb.emit_normalized(relax.VarBinding(v, y1))
        bb.emit(v)
        bb.emit_func_output(relax.ShapeExpr([m, n * 2]))
    mod = bb.get()

    tvm.ir.assert_structural_equal(func, mod["foo"])


def test_dataflow_block():
    """
    @R.function
    def foo(x: Tensor((128, 128), "float32")) -> Tensor(None, "float32", ndim = 2):
        # block 0
        with R.dataflow():
            lv0 = R.call_dps_packed("extern_func", (x,), R.Tensor((128, 128), dtype="float32"))
            gv: Tensor((128, 128), "float32") = lv0
            R.output(gv)
        return gv
    """
    # create with Script IRBuilder
    with IRBuilder() as ir_builder:
        with R.function():
            R.func_name("foo")
            x = R.arg("x", relax.TensorType((128, 128), "float32"))
            with R.dataflow() as df:
                lv0 = R.emit(
                    R.call_dps_packed(
                        "extern_func", x, relax.TensorType((128, 128), dtype="float32")
                    )
                )
                IRBuilder.name("lv0", lv0)
                gv = R.emit(lv0)
                IRBuilder.name("gv", gv)
                R.output(gv)
            (gv,) = df.output_vars
            R.func_ret_value(gv)
    func = ir_builder.get()

    # create with BlockBuilder
    x = relax.Var("x", relax.TensorType((128, 128), "float32"))
    bb = relax.BlockBuilder()
    with bb.function("foo", (x,)):
        with bb.dataflow():
            lv0 = bb.emit(
                relax.call_dps_packed(
                    "extern_func", x, relax.TensorType((128, 128), dtype="float32")
                )
            )
            gv = bb.emit_output(lv0)
        bb.emit_func_output(gv)

    tvm.ir.assert_structural_equal(func, bb.get()["foo"])


def test_regression_py_print():
    # Test that the py_print directs to python builtin print
    from tvm.script.ir_builder.relax.ir import py_print  # pylint: disable=import-outside-toplevel

    assert py_print == print


def test_function_subroutine_before_main():
    """The block builder can generate subroutines, and calls into subroutines"""

    from tvm.script import ir as I
    from tvm.script import relax as R

    # create with TVMScript
    @I.ir_module
    class expected:
        @R.function
        def main(
            A: R.Tensor((128, 128), "float32"), B: R.Tensor((128, 128), "float32")
        ) -> R.Tensor((128, 128), "float32"):
            out = expected.subroutine(A, B)
            return out

        @R.function
        def subroutine(
            A: R.Tensor((128, 128), "float32"), B: R.Tensor((128, 128), "float32")
        ) -> R.Tensor((128, 128), "float32"):
            out = R.add(A, B)
            return out

    # create with BlockBuilder
    bb = relax.BlockBuilder()

    A_sub = relax.Var("A", relax.TensorType((128, 128), "float32"))
    B_sub = relax.Var("B", relax.TensorType((128, 128), "float32"))
    with bb.function("subroutine", (A_sub, B_sub)):
        out = bb.emit(R.add(A_sub, B_sub))
        subroutine = bb.emit_func_output(out)

    A = relax.Var("A", relax.TensorType((128, 128), "float32"))
    B = relax.Var("B", relax.TensorType((128, 128), "float32"))
    with bb.function("main", (A, B)):
        out = bb.emit(subroutine(A, B))
        bb.emit_func_output(out)
    actual = bb.get()

    tvm.ir.assert_structural_equal(expected, actual)


def test_function_subroutine_during_main():
    """Subroutines may be generated as needed, pausing the main function collection"""

    from tvm.script import ir as I
    from tvm.script import relax as R

    # create with TVMScript
    @I.ir_module
    class expected:
        @R.function
        def main(
            A: R.Tensor((128, 128), "float32"), B: R.Tensor((128, 128), "float32")
        ) -> R.Tensor((128, 128), "float32"):
            out = expected.subroutine(A, B)
            return out

        @R.function
        def subroutine(
            A: R.Tensor((128, 128), "float32"), B: R.Tensor((128, 128), "float32")
        ) -> R.Tensor((128, 128), "float32"):
            out = R.add(A, B)
            return out

    # create with BlockBuilder
    bb = relax.BlockBuilder()

    A = relax.Var("A", relax.TensorType((128, 128), "float32"))
    B = relax.Var("B", relax.TensorType((128, 128), "float32"))
    with bb.function("main", (A, B)):
        A_sub = relax.Var("A", relax.TensorType((128, 128), "float32"))
        B_sub = relax.Var("B", relax.TensorType((128, 128), "float32"))
        with bb.function("subroutine", (A_sub, B_sub)):
            out = bb.emit(R.add(A_sub, B_sub))
            subroutine = bb.emit_func_output(out)

        out = bb.emit(subroutine(A, B))
        bb.emit_func_output(out)
    actual = bb.get()

    tvm.ir.assert_structural_equal(expected, actual)


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