# 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
"""Relax linear algebra operators"""

from tvm import DataType

from ..expr import Expr
from ..expr import Tuple as RxTuple
from . import _ffi_api
from .manipulate import permute_dims


def matmul(x1: Expr, x2: Expr, out_dtype: str | DataType | None = None) -> Expr:
    """General matrix multiplication of two tensors, with broadcasting on batched dimensions.

    The semantics and output shape deduction rule is specified as
    https://data-apis.org/array-api/latest/API_specification/generated/array_api.matmul.html.

    Parameters
    ----------
    x1 : relax.Expr
        The first input tensor.

    x2 : relax.Expr
        The second input tensor.

    out_dtype: Optional[Union[str, DataType]]
        The data type of the matmul result.
        When it is not specified, the output dtype will be the same as input dtype.

    Returns
    -------
    result : relax.Expr
        The computed result.
    """
    return _ffi_api.matmul(x1, x2, out_dtype)  # type: ignore


def linear(
    data: Expr,
    weight: Expr,
    bias: Expr | None = None,
    out_dtype: str | DataType | None = None,
) -> Expr:
    """Applies a linear transformation to the incoming data: y = xA^T + b

    Parameters
    ----------
    data : relax.Expr
        The input data.

    weight : relax.Expr
        The weight tensor.

    bias : Optional[Expr]
        The bias tensor.

    out_dtype: Optional[Union[str, DataType]]
        The data type of the matmul result.
        When it is not specified, the output dtype will be the same as input dtype.

    Notes
    -----
    Relax does not regard the Linear Op as a primitive Op,
    while combine the transpose, matmul and add op to implement it.

    Returns
    -------
    result : relax.Expr
        The computed result.
    """

    # Since weight can be 1D or 2D, we use `axes=None` to support both cases.
    x = matmul(data, permute_dims(weight, axes=None), out_dtype=out_dtype)
    return x + bias if bias is not None else x


def einsum(operands, subscripts):
    """Evaluates the Einstein summation convention on data

    Parameters
    ----------
    operands : Union(List[relax.Expr], Tuple[relax.Expr])
        A list of expression.

    subscripts : str
        The einsum expression string.

    Returns
    -------
    result : relax.Expr
        The output from the einsum op.
    """
    if isinstance(operands, list | tuple):
        operands = RxTuple(operands)

    return _ffi_api.einsum(operands, subscripts)  # type: ignore


def outer(x1: Expr, x2: Expr) -> Expr:
    """
    Computes the outer product of two input expressions.

    Parameters
    ----------
    x1 : relax.Expr
        The first input expression.

    x2 : relax.Expr
        The second input expression.

    Notes
    -----
    This operation computes the outer product between two expressions,
    resulting in a tensor where each element is the product of elements
    from `x1` and `x2`. It is commonly used in tensor and matrix operations
    to expand lower-dimensional inputs into higher-dimensional representations.

    Returns
    -------
    result : relax.Expr
        The resulting expression representing the outer product.
    """
    return _ffi_api.outer(x1, x2)
