# 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, RUF005
import tvm
import tvm.testing
from tvm import relax
from tvm.relax.testing import nn
from tvm.script import ir as I
from tvm.script import relax as R
from tvm.script import tirx as T


def test_emit():
    class ReLU(nn.Module):
        def forward(self, input: relax.Expr) -> relax.Var:
            return nn.emit(relax.op.nn.relu(input))

    @I.ir_module
    class Expected:
        @R.function
        def main(x: R.Tensor((32, 32), dtype="float32")) -> R.Tensor((32, 32), dtype="float32"):
            gv: R.Tensor((32, 32), dtype="float32") = R.nn.relu(x)
            return gv

    bb = relax.BlockBuilder()
    with bb.function("main"):
        model = ReLU()
        x = nn.Placeholder((32, 32), dtype="float32", name="x")
        output = model(x)
        params = [x] + model.parameters()
        bb.emit_func_output(output, params)

    tvm.ir.assert_structural_equal(bb.get(), Expected)


def test_get_param():
    class Plus1(nn.Module):
        def __init__(self):
            self.const_1 = relax.const(1, "float32")

        def forward(self, input: relax.Expr) -> relax.Var:
            return nn.emit(relax.op.add(input, self.const_1))

    model = Plus1()
    assert model.parameters() == []


def test_define_subroutine():
    """Define subroutines when nn.Module.define_subroutine is True"""

    class Activation(nn.Module):
        define_subroutine = True

        def forward(self, state: relax.Expr) -> relax.Var:
            return relax.op.nn.relu(state)

    class Layer(nn.Module):
        define_subroutine = True

        def __init__(self, in_features, out_features):
            self.weights = nn.Parameter(
                (in_features, out_features), dtype="float32", name="weights"
            )
            self.activation = Activation()

        def forward(self, input: relax.Expr) -> relax.Var:
            state = relax.op.matmul(input, self.weights)
            return self.activation(state)

    @I.ir_module
    class Expected:
        @R.function
        def main(
            state: R.Tensor(("batch_size", 64), dtype="float32"),
            weights: R.Tensor((64, 32), dtype="float32"),
        ) -> R.Tensor(("batch_size", 32), dtype="float32"):
            state = Expected.layer(state, weights)
            return state

        @R.function(private=True)
        def layer(
            state: R.Tensor(("batch_size", 64), dtype="float32"),
            weights: R.Tensor((64, 32), dtype="float32"),
        ) -> R.Tensor(("batch_size", 32), dtype="float32"):
            state = R.matmul(state, weights)
            state = Expected.activation(state)
            return state

        @R.function(private=True)
        def activation(state: R.Tensor(("batch_size", 32), dtype="float32")) -> R.Tensor(
            ("batch_size", 32), dtype="float32"
        ):
            state = R.nn.relu(state)
            return state

    model = Layer(64, 32)
    batch_size = tvm.tirx.Var("batch_size", "int64")
    input = nn.Placeholder((batch_size, 64), dtype="float32", name="input")

    bb = relax.BlockBuilder()
    with bb.function("main", params=[input, *model.parameters()]):
        output = model(input)
        bb.emit_func_output(output)

    tvm.ir.assert_structural_equal(Expected, bb.get())


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