# 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.
"""
Apply ScheduleRules onto an IRModule to generate default schedules without tuning,
or a space for MetaSchedule tuning
"""

from tvm import s_tir, tirx
from tvm.ir import IRModule
from tvm.ir.transform import PassContext, module_pass
from tvm.target import Target

from .schedule_rule import ScheduleRule


def _is_scheduled(func: tirx.PrimFunc) -> bool:
    if not isinstance(func, tirx.PrimFunc):
        return False
    if "tirx.is_scheduled" not in func.attrs:
        return False
    return func.attrs["tirx.is_scheduled"] == 1


def _get_target(func: tirx.PrimFunc) -> Target:
    target = func.attrs.get("target")
    if target is None:
        return Target.current(allow_none=False)
    else:
        return target


@module_pass(opt_level=0, name="ApplyDefaultSchedule")
class ApplyDefaultSchedule:  # pylint: disable=too-few-public-methods
    """A IRModule pass that applies a list of ScheduleRules to all PrimFuncs in the module."""

    def __init__(self, *rules: ScheduleRule):
        """Construct a new ApplyDefaultSchedule pass.

        Parameters
        ----------
        *rules : ScheduleRule
            The ScheduleRules to apply to all PrimFuncs in the module.
        """
        self.rules = list(rules)

    def transform_module(  # pylint: disable=missing-function-docstring
        self,
        mod: IRModule,
        _: PassContext,
    ) -> IRModule:
        updated_functions = {}
        for g_var, func in mod.functions_items():
            if isinstance(func, tirx.PrimFunc) and not _is_scheduled(func):
                target = _get_target(func)

                sch = _apply_rules(func, target, self.rules, tunable=False)
                if sch is not None:
                    assert len(sch) == 1
                    updated_functions[g_var] = (
                        sch[0].mod["main"].with_attr("tirx.is_scheduled", True)
                    )
        for g_var, func in updated_functions.items():
            mod[g_var] = func
        return mod


def _apply_rules(
    func: tirx.PrimFunc,
    target: Target,
    rules: list[ScheduleRule],
    tunable: bool,
) -> list[s_tir.Schedule] | None:
    for rule in rules:
        space = rule.apply(func, target, tunable)
        if space is None:
            continue
        if isinstance(space, s_tir.Schedule):
            space = [space]
        return space
    return None
