# 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.
"""Analysis used in TensorIR scheduling"""

import tvm_ffi

from tvm.runtime import Object
from tvm.tirx.buffer import Buffer
from tvm.tirx.expr import Expr
from tvm.tirx.function import IndexMap, PrimFunc
from tvm.tirx.stmt import For

from . import _ffi_api
from .schedule import SBlockRV, Schedule


def suggest_index_map(
    buffer: Buffer,
    indices: list[Expr],
    loops: list[For],
    predicate: Expr,
) -> IndexMap | None:
    """Provided the access pattern to a buffer, suggest one of the possible layout
    transformation to maximize the locality of the access pattern.

    Parameters
    ----------
    buffer : Buffer
        The buffer to be transformed.
    indices : List[Expr]
        The access pattern to the buffer.
    loops : List[For]
        The loops above the buffer.
    predicate : Expr
        The predicate of the access.

    Returns
    -------
    index_map : Optional[IndexMap]
        The suggested index map. None if no transformation is suggested.
    """
    return _ffi_api.SuggestIndexMap(  # type: ignore # pylint: disable=no-member
        buffer,
        indices,
        loops,
        predicate,
    )


@tvm_ffi.register_object("s_tir.schedule.TensorizeInfo")
class TensorizeInfo(Object):
    """Necessary information used for tensorization."""


def get_tensorize_loop_mapping(
    sch: Schedule, block: SBlockRV, desc_func: PrimFunc, allow_padding: bool = False
) -> TensorizeInfo | None:
    """Establish a mapping between loops in a target block and an intrinsic description

    Parameters
    ----------
    sch : Schedule
        The schedule to be tensorized
    block : SBlockRV
        The target block to match against
    desc_func : PrimFunc
        The prim func describing the computation to be tensorized
    allow_padding : bool
        Whether to allow padding the block iters to match the intrinsic description
    Returns
    -------
    tensorize_info : Optional[TensorizeInfo]
        TensorizeInfo structure if a valid mapping is found, None otherwise
    """
    return _ffi_api.GetTensorizeLoopMapping(sch, block, desc_func, allow_padding)  # type: ignore


@tvm_ffi.register_object("s_tir.schedule.AutoTensorizeMappingInfo")
class AutoTensorizeMappingInfo(Object):
    """Necessary information used to perform transformations for tensorization."""


def get_auto_tensorize_mapping_info(
    sch: Schedule, block: SBlockRV, desc_func: PrimFunc
) -> AutoTensorizeMappingInfo | None:
    """Get mapping info between a target block and an intrinsic description including layout
    transformations to apply.

    Parameters
    ----------
    sch : Schedule
        The schedule to be tensorized
    block : SBlockRV
        The compute block for auto tensorization
    desc_func : PrimFunc
        The prim func describing the computation to be tensorized

    Returns
    -------
    auto_tensorize_mapping_info : Optional[AutoTensorizeMappingInfo]
        AutoTensorizeMappingInfo structure if potential mappings found, None otherwise.

    Note
    ----
    Returning a valid AutoTensorizeMappingInfo doesn't guarantee the block can be tensorized.
    We will need to apply the suggested layout transformations and then match against the tensor
    intrinsics.
    """
    return _ffi_api.GetAutoTensorizeMappingInfo(sch, block, desc_func)  # type: ignore


def has_block(sch: Schedule, block_name: str) -> bool:
    """Query if the given block name exists in the module associated with the provided schedule.

    Parameters
    ----------
    sch : Schedule
        The schedule
    block_name : str
        The name of the block to query

    Returns
    -------
    yes/no: bool
        True if the given block exists in the schedule.
    """
    return _ffi_api.HasBlock(sch, block_name)  # type: ignore


def is_output_block(sch: Schedule, block: SBlockRV) -> bool:
    """Check whether the given block is an output block

    Parameters
    ----------
    sch : Schedule
        The schedule object of the block
    block : SBlockRV
        The blockRV to be checked

    Returns
    -------
    yes/no : bool
        True if the given block is an output block

    """
    return _ffi_api.IsOutputBlock(sch, block)  # type: ignore
