# 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=missing-docstring, invalid-name, unnecessary-comprehension, unused-argument

import tvm
import tvm.testing
from tvm import relax
from tvm.contrib.hexagon import hexagon_unary_ops


def op_replace(call_node, func) -> bool:
    if not isinstance(call_node, relax.Call):
        return False
    call_tir_op = tvm.ir.Op.get("relax.call_tir")
    if call_node.op != call_tir_op:
        return False
    ops = [
        "qnn.tanh",
        "qnn.sqrt",
        "qnn.rsqrt",
        "qnn.exp",
        "qnn.erf",
        "qnn.sigmoid",
        "qnn.hardswish",
        "qnn.log",
        "qnn.abs",
    ]
    if func.attrs["op_attrs"]["op_name"] in ops:
        return True
    return False


@relax.expr_functor.mutator
class Tanh2TakeReplace(tvm.relax.PyExprMutator):
    def __init__(self, mod: tvm.IRModule) -> None:
        super().__init__(mod)
        self.mod_ = mod

    def transform(self) -> tvm.IRModule:
        # Iterate over all the nodes to check for the node replaceable
        for global_var, func in self.mod_.functions.items():
            # Skip non-relax functions
            if not isinstance(func, relax.Function):
                continue
            updated_func = self.visit_expr(func)
            self.builder_.normalize(updated_func)
            self.builder_.update_func(global_var, updated_func)
        # At the end of the transformation we return the updated IRModule from the BlockBuilder.
        return self.builder_.get()

    def visit_call_(self, call_node: relax.Call) -> relax.Call:
        call_tir_op = tvm.ir.Op.get("relax.call_tir")
        if call_node.op != call_tir_op:
            return call_node

        var = call_node.args[0]
        func = self.mod_[var]

        if call_node.args[1][0].ty.dtype == "uint8":
            if op_replace(call_node, func):
                inp, inp_scale, inp_zp, out_scale, out_zp = [x for x in call_node.args[1]]
                # LUT node creation
                LUT = hexagon_unary_ops.LUT_generation(
                    inp_scale, inp_zp, out_scale, out_zp, call_node.args[0].name_hint
                )
                # Take operation node creation
                take_func = hexagon_unary_ops.generate_take_primfunc(inp, call_node.ty)
                take_func = take_func.without_attr("global_symbol")
                take_func_gv = self.builder_.add_func(take_func, "take")
                take_node = relax.call_tir(
                    take_func_gv,
                    relax.expr.Tuple(
                        [call_node.args[1][0], relax.expr.Constant(tvm.runtime.tensor(LUT))]
                    ),
                    call_node.ty,
                )
                return take_node
        return call_node


@tvm.ir.transform.module_pass(opt_level=2, name="replace_tanh_take")
class PassReplaceWithTakeOpPrimFuncs:
    def transform_module(self, mod, ctx):
        return Tanh2TakeReplace(mod).transform()
