# 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.
# pylint: disable=invalid-name,missing-docstring
import tvm
from tvm.relax.frontend import nn
from tvm.script import ir as I
from tvm.script import relax as R


def _iter_binding_names(mod):
    """Helper function to compare the names of relax variables"""
    for block in mod["forward"].body.blocks:
        for binding in block.bindings:
            yield binding.var.name_hint


def test_nn_export_to_relax():
    class TestModule(nn.Module):
        def __init__(self, in_features: int, out_features: int):
            super().__init__()
            self.in_features = in_features
            self.out_features = out_features
            self.linear_1 = nn.Linear(in_features, out_features, bias=False)
            self.linear_2 = nn.Linear(in_features, out_features, bias=False)

        def forward(self, x: nn.Tensor):
            x1 = self.linear_1(x)
            x2 = self.linear_2(x)
            return x1 + x2

    @I.ir_module
    class ExpectedModule:
        @R.function
        def forward(
            x: R.Tensor((1, 10), dtype="float32"),
            packed_params: R.Tuple(
                R.Tensor((20, 10), dtype="float32"), R.Tensor((20, 10), dtype="float32")
            ),
        ):
            R.func_attr({"num_input": 1})
            with R.dataflow():
                linear_1_weight = packed_params[0]
                linear_2_weight = packed_params[1]
                matmul_1_weight = R.permute_dims(linear_1_weight)
                matmul = R.matmul(x, matmul_1_weight)
                matmul_2_weight = R.permute_dims(linear_2_weight)
                matmul1 = R.matmul(x, matmul_2_weight)
                add = R.add(matmul, matmul1)
                gv = add
                R.output(gv)
            return gv

    model = TestModule(10, 20)
    mod, _ = model.export_tvm(
        spec={
            "forward": {
                "x": nn.spec.Tensor([1, model.in_features], "float32"),
                "$": {
                    "param_mode": "packed",
                    "effect_mode": "none",
                },
            }
        }
    )
    tvm.ir.assert_structural_equal(mod, ExpectedModule)

    for name, expected_name in zip(_iter_binding_names(mod), _iter_binding_names(ExpectedModule)):
        assert name == expected_name


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