/*******************************************************************************
 *
 * MIT License
 *
 * Copyright (C) 2023-2025 Advanced Micro Devices, Inc. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 *******************************************************************************/
#pragma once

#include "hiptensor_types.h"
#include "internal/hiptensor-version.h"
#include "internal/hiptensor_utility.hpp"

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

//! @brief Allocates and initializes a hipTensor library handle.
//!
//! @details This function creates a hipTensor handle associated with the current device. To use a different device, call `hipInit(0)` to set the new device, then create another hipTensor handle with `hiptensorCreate()`.
//! @param[out] handle A pointer to the `hiptensorHandle_t` pointer that will store the newly created handle.
//! @returns `HIPTENSOR_STATUS_SUCCESS` if the handle is created successfully, otherwise an error code.
hiptensorStatus_t hiptensorCreate(hiptensorHandle_t* handle);

//! @brief Deallocates a hipTensor library handle.
//! @param[out] handle The `hiptensorHandle_t` to be deallocated.
//! @returns `HIPTENSOR_STATUS_SUCCESS` on successful deallocation, otherwise an error code.
hiptensorStatus_t hiptensorDestroy(hiptensorHandle_t handle);

//! @brief Resizes the plan cache associated with a hipTensor handle.
//! @param[in] handle The hipTensor handle.
//! @param[in] numEntries Number of entries the cache will support.
//! @returns `HIPTENSOR_STATUS_SUCCESS` on success, or an error code otherwise.
hiptensorStatus_t hiptensorHandleResizePlanCache(hiptensorHandle_t handle,
                                                 const uint32_t    numEntries);

//! @brief Writes the plan cache of a hipTensor handle to a file.
//! @param[in] handle The hipTensor handle whose plan cache will be written.
//! @param[in] fileName The name of the file to write the cache to.
//! @returns `HIPTENSOR_STATUS_SUCCESS` on success, or an error code otherwise.
hiptensorStatus_t hiptensorHandleWritePlanCacheToFile(const hiptensorHandle_t handle,
                                                      const char              fileName[]);

//! @brief Reads a plan cache from a file into a hipTensor handle.
//! @param[in] handle The hipTensor handle to populate with the plan cache.
//! @param[in] fileName The name of the file to read the cache from.
//! @param[out] numCachelinesRead On exit, this variable will hold the number of successfully-read cachelines.
//! @returns `HIPTENSOR_STATUS_SUCCESS` on success, or an error code otherwise.
hiptensorStatus_t hiptensorHandleReadPlanCacheFromFile(hiptensorHandle_t handle,
                                                       const char        fileName[],
                                                       uint32_t*         numCachelinesRead);

//! @brief Writes the kernel cache of a hipTensor handle to a file.
//! @param[in] handle The hipTensor handle whose kernel cache will be written.
//! @param[in] fileName The name of the file to write the cache to.
//! @returns `HIPTENSOR_STATUS_SUCCESS` on success, or an error code otherwise.
hiptensorStatus_t hiptensorWriteKernelCacheToFile(const hiptensorHandle_t handle,
                                                  const char              fileName[]);

//! @brief Reads a kernel cache from a file into a hipTensor handle.
//! @param[in] handle The hipTensor handle to populate with the kernel cache.
//! @param[in] fileName The name of the file to read the cache from.
//! @returns `HIPTENSOR_STATUS_SUCCESS` on success, or an error code otherwise.
hiptensorStatus_t hiptensorReadKernelCacheFromFile(hiptensorHandle_t handle, const char fileName[]);

//! @brief Creates and initializes a tensor descriptor.
//! @details This function allocates an instance of `hiptensorTensorDescriptor_t`. Call `hiptensorDestroyTensorDescriptor()` to free this instance.
//! @param[in] handle An opaque handle representing the hipTensor library context.
//! @param[out] desc A pointer to the `hiptensorTensorDescriptor_t` object to be allocated.
//! @param[in] numModes The number of modes (dimensions) for the tensor.
//! @param[in] lens An array specifying the extent (length) of each mode; all values must be greater than zero.
//! @param[in] strides An array where `strides[i]` is the displacement between consecutive elements in the i-th mode. If `NULL`, a generalized packed column-major memory layout is assumed (strides increase monotonically from left to right).
//! @param[in] dataType The data type of the tensor elements.
//! @param[in] alignmentRequirement The memory alignment requirement for the tensor.
//! @retval `HIPTENSOR_STATUS_SUCCESS` if the operation completes successfully.
//! @retval `HIPTENSOR_STATUS_NOT_INITIALIZED` if the handle is not initialized.
//! @retval `HIPTENSOR_STATUS_ARCH_MISMATCH` if the data type is not supported.
//! @retval `HIPTENSOR_STATUS_INVALID_VALUE` if any parameters are invalid.
hiptensorStatus_t hiptensorCreateTensorDescriptor(const hiptensorHandle_t      handle,
                                                  hiptensorTensorDescriptor_t* desc,
                                                  const uint32_t               numModes,
                                                  const int64_t                lens[],
                                                  const int64_t                strides[],
                                                  hiptensorDataType_t          dataType,
                                                  uint32_t alignmentRequirement);

//! @brief Destroys a tensor descriptor.
//!
//! @param[in] desc A pointer to the tensor descriptor object to be deallocated.
//! @retval `HIPTENSOR_STATUS_SUCCESS` if the operation completes successfully.
hiptensorStatus_t hiptensorDestroyTensorDescriptor(hiptensorTensorDescriptor_t desc);

//! @brief Allocates and initializes a `hiptensorOperationDescriptor` object for a tensor contraction of the form \f$D = \alpha \mathcal{A} \mathcal{B} + \beta \mathcal{C}\f$.
//!
//! @details Free this object by calling `hiptensorDestroyOperationDescriptor()`.
//! @param[in] handle Opaque handle representing the hipTensor library context.
//! @param[out] desc Pointer to the `hiptensorOperationDescriptor_t` object that will be allocated and populated with contraction operation details.
//! @param[in] descA Tensor descriptor for A, specifying data type, modes, and strides.
//! @param[in] modeA Array with `nmodeA` entries representing tensor A's modes. Each `modeA[i]` corresponds to the `extent[i]` and `stride[i]` from `hiptensorInitTensorDescriptor`.
//! @param[in] opA Unary operator applied to each element of A before processing. A's original data remains unchanged.
//! @param[in] descB Tensor descriptor for B.
//! @param[in] modeB Array with `nmodeB` entries representing tensor B's modes.
//! @param[in] opB Unary operator applied to each element of B.
//! @param[in] descC Tensor descriptor for C.
//! @param[in] modeC Array with `nmodeC` entries representing tensor C's modes.
//! @param[in] opC Unary operator applied to each element of C.
//! @param[in] descD Tensor descriptor for D (must match `descC`).
//! @param[in] modeD Array with `nmodeD` entries representing tensor D's modes (must match `modeC`).
//! @param[in] descCompute Data type used for intermediate computation of \f$T = A * B\f$.
//! @retval `HIPTENSOR_STATUS_NOT_SUPPORTED` When data type combinations or operations aren't supported.
//! @retval `HIPTENSOR_STATUS_INVALID_VALUE` When tensor dimensions or modes contain illegal values.
//! @retval `HIPTENSOR_STATUS_SUCCESS` When the operation completes successfully.
//! @retval `HIPTENSOR_STATUS_NOT_INITIALIZED` When the handle isn't initialized.
hiptensorStatus_t hiptensorCreateContraction(const hiptensorHandle_t            handle,
                                             hiptensorOperationDescriptor_t*    desc,
                                             const hiptensorTensorDescriptor_t  descA,
                                             const int32_t                      modeA[],
                                             hiptensorOperator_t                opA,
                                             const hiptensorTensorDescriptor_t  descB,
                                             const int32_t                      modeB[],
                                             hiptensorOperator_t                opB,
                                             const hiptensorTensorDescriptor_t  descC,
                                             const int32_t                      modeC[],
                                             hiptensorOperator_t                opC,
                                             const hiptensorTensorDescriptor_t  descD,
                                             const int32_t                      modeD[],
                                             const hiptensorComputeDescriptor_t descCompute);

//! @brief Releases all resources linked to a `hiptensorOperationDescriptor` object.
//! @param[in,out] desc The `hiptensorOperationDescriptor_t` object to deallocate.
//! @retval `HIPTENSOR_STATUS_SUCCESS` when successful, otherwise returns an error code.
hiptensorStatus_t hiptensorDestroyOperationDescriptor(hiptensorOperationDescriptor_t desc);

//! @brief Configures an attribute in a `hiptensorOperationDescriptor_t` object.
//! @param[in] handle Opaque handle for the hipTensor library context.
//! @param[in] desc The `hiptensorOperationDescriptor_t` object being modified.
//! @param[in] attr The attribute to configure.
//! @param[in] buf Pointer to the buffer containing the attribute's new value.
//! @param[in] sizeInBytes Size of the `buf` in bytes.
//! @returns `HIPTENSOR_STATUS_SUCCESS` when successful, otherwise returns an error code.
hiptensorStatus_t
    hiptensorOperationDescriptorSetAttribute(const hiptensorHandle_t                 handle,
                                             hiptensorOperationDescriptor_t          desc,
                                             hiptensorOperationDescriptorAttribute_t attr,
                                             const void*                             buf,
                                             size_t                                  sizeInBytes);

//! @brief Extracts an attribute from a `hiptensorOperationDescriptor_t` object.
//! @param[in] handle Opaque handle representing the hipTensor library context.
//! @param[in] desc The `hiptensorOperationDescriptor_t` object to examine.
//! @param[in] attr The attribute to extract.
//! @param[out] buf Pointer to the buffer where the attribute value will be written.
//! @param[in] sizeInBytes The buffer size in bytes.
//! @returns `HIPTENSOR_STATUS_SUCCESS` when successful, otherwise returns an error code.
hiptensorStatus_t
    hiptensorOperationDescriptorGetAttribute(const hiptensorHandle_t                 handle,
                                             hiptensorOperationDescriptor_t          desc,
                                             hiptensorOperationDescriptorAttribute_t attr,
                                             void*                                   buf,
                                             size_t                                  sizeInBytes);

//! @brief Creates a `hiptensorPlanPreference_t` object that lets users limit kernel options for a plan/operation.
//! @param[in] handle Opaque handle representing the hipTensor library context.
//! @param[out] pref Pointer to the new `hiptensorPlanPreference_t` structure.
//! @param[in] algo Controls algorithm selection. Use `HIPTENSOR_ALGO_DEFAULT` to let the heuristic choose. Returns `HIPTENSOR_STATUS_NOT_SUPPORTED` if the specified algorithm isn't available.
//! @param[in] jitMode Controls whether hipTensor can use JIT-compiled kernels.
//! @retval HIPTENSOR_STATUS_SUCCESS When the operation completes successfully.
hiptensorStatus_t hiptensorCreatePlanPreference(const hiptensorHandle_t    handle,
                                                hiptensorPlanPreference_t* pref,
                                                hiptensorAlgo_t            algo,
                                                hiptensorJitMode_t         jitMode);

//! @brief Releases all resources associated with a `hiptensorPlanPreference_t` object.
//! @param[in,out] pref The `hiptensorPlanPreference_t` object to deallocate.
//! @retval `HIPTENSOR_STATUS_SUCCESS` when successful, otherwise returns an error code.
hiptensorStatus_t hiptensorDestroyPlanPreference(hiptensorPlanPreference_t pref);

//! @brief Configures an attribute in a `hiptensorPlanPreference_t` object.
//! @param[in] handle Opaque handle representing the hipTensor library context.
//! @param[in,out] pref Opaque structure that narrows the search space for viable kernel candidates.
//! @param[in] attr The attribute to configure.
//! @param[in] buf Buffer (of size `sizeInBytes`) containing the new value for `attr`.
//! @param[in] sizeInBytes Size of `buf` in bytes.
//! @retval `HIPTENSOR_STATUS_SUCCESS` When the operation completes successfully.
//! @retval `HIPTENSOR_STATUS_NOT_INITIALIZED` When the handle isn't initialized.
//! @retval `HIPTENSOR_STATUS_INVALID_VALUE` When input data is invalid (typically user error).
hiptensorStatus_t hiptensorPlanPreferenceSetAttribute(const hiptensorHandle_t            handle,
                                                      hiptensorPlanPreference_t          pref,
                                                      hiptensorPlanPreferenceAttribute_t attr,
                                                      const void*                        buf,
                                                      size_t sizeInBytes);

//! @brief Fetches information from an existing plan.
//! @param[in] handle Opaque handle representing the hipTensor library context.
//! @param[in] plan The existing plan (created via `hiptensorCreatePlan` or `hiptensorCreatePlanAutotuned`).
//! @param[in] attr The attribute to retrieve.
//! @param[out] buf Buffer that will contain the requested attribute information upon successful return.
//! @param[in] sizeInBytes Size of `buf` in bytes.
//! @retval `HIPTENSOR_STATUS_SUCCESS` When the operation completes successfully.
//! @retval `HIPTENSOR_STATUS_INVALID_VALUE` When input data is invalid (typically user error).
hiptensorStatus_t hiptensorPlanGetAttribute(const hiptensorHandle_t  handle,
                                            const hiptensorPlan_t    plan,
                                            hiptensorPlanAttribute_t attr,
                                            void*                    buf,
                                            size_t                   sizeInBytes);

//! @brief Calculates the workspace size needed for a specific operation.
//! @param[in] handle Opaque handle representing the hipTensor library context.
//! @param[in] desc Opaque structure encoding the operation details.
//! @param[in] planPref Opaque structure limiting the viable candidate space.
//! @param[in] workspacePref Parameter that affects workspace size calculation.
//! @param[out] workspaceSizeEstimate The estimated workspace size in bytes needed for the operation.
//! @retval `HIPTENSOR_STATUS_SUCCESS` When the operation completes successfully.
//! @retval `HIPTENSOR_STATUS_NOT_INITIALIZED` When the handle isn't initialized.
//! @retval `HIPTENSOR_STATUS_INVALID_VALUE` When input data is invalid (typically user error).
hiptensorStatus_t hiptensorEstimateWorkspaceSize(const hiptensorHandle_t              handle,
                                                 const hiptensorOperationDescriptor_t desc,
                                                 const hiptensorPlanPreference_t      planPref,
                                                 const hiptensorWorksizePreference_t  workspacePref,
                                                 uint64_t* workspaceSizeEstimate);

//! @brief Creates an operation descriptor for tensor permutation.
//!
//! @param[in] handle Opaque handle containing the hipTENSOR library context.
//! @param[out] desc Opaque structure that will be allocated and filled with the encoded permutation information.
//! @param[in] descA Descriptor containing information about A's data type, modes, and strides.
//! @param[in] modeA Array of size descA->numModes containing the mode names of A.
//! @param[in] opA Unary operator applied to each element of A before further processing. The original tensor data remains unchanged.
//! @param[in] descB Descriptor containing information about B's data type, modes, and strides.
//! @param[in] modeB Array of size descB->numModes containing the mode names of B.
//! @param[in] descCompute Determines the precision used for this operation.
//! @retval HIPTENSOR_STATUS_NOT_SUPPORTED When data type combinations or operations aren't supported
//! @retval HIPTENSOR_STATUS_INVALID_VALUE When tensor dimensions or modes contain illegal values
//! @retval HIPTENSOR_STATUS_SUCCESS When the operation completes successfully
//! @retval HIPTENSOR_STATUS_NOT_INITIALIZED When the handle isn't initialized.
hiptensorStatus_t hiptensorCreatePermutation(const hiptensorHandle_t            handle,
                                             hiptensorOperationDescriptor_t*    desc,
                                             const hiptensorTensorDescriptor_t  descA,
                                             const int32_t                      modeA[],
                                             hiptensorOperator_t                opA,
                                             const hiptensorTensorDescriptor_t  descB,
                                             const int32_t                      modeB[],
                                             const hiptensorComputeDescriptor_t descCompute);

//! @brief Creates a `hiptensorPlan_t` object that selects an appropriate kernel for an operation and prepares execution.
//!
//! @details Uses hipTensor's heuristic to select a kernel for operations created by functions like `hiptensorCreateContraction`,
//! `hiptensorCreateReduction`, `hiptensorCreatePermutation`, `hiptensorCreateElementwiseBinary`, or `hiptensorCreateElementwiseTrinary`.
//! The resulting plan can be passed to the corresponding `hiptensor*Execute` function to perform the operation.
//! The plan is created for the currently active HIP device.
//! @param[in] handle Opaque handle representing the hipTensor library context.
//! @param[out] plan Pointer to the `hiptensorPlan_t` structure that will contain all execution information (including selected kernel).
//! @param[in] desc Opaque structure encoding the operation details.
//! @param[in] pref Opaque structure limiting the applicable kernels. May be `nullptr` to use defaults.
//! @param[in] workspaceSizeLimit Maximum workspace size in bytes that the operation may use.
//! @retval `HIPTENSOR_STATUS_SUCCESS` When a viable kernel is found.
//! @retval `HIPTENSOR_STATUS_NOT_SUPPORTED` When no viable kernel can be found.
//! @retval `HIPTENSOR_STATUS_NOT_INITIALIZED` When the handle isn't initialized.
//! @retval `HIPTENSOR_STATUS_INSUFFICIENT_WORKSPACE` When the provided workspace is too small.
//! @retval `HIPTENSOR_STATUS_INVALID_VALUE` When input data is invalid (typically user error).
hiptensorStatus_t hiptensorCreatePlan(const hiptensorHandle_t              handle,
                                      hiptensorPlan_t*                     plan,
                                      const hiptensorOperationDescriptor_t desc,
                                      const hiptensorPlanPreference_t      pref,
                                      uint64_t                             workspaceSizeLimit);

//! @brief Releases all resources associated with a plan.
//! @param[in,out] plan The `hiptensorPlan_t` object to deallocate.
//! @retval `HIPTENSOR_STATUS_SUCCESS` when successful, otherwise returns an error code.
hiptensorStatus_t hiptensorDestroyPlan(hiptensorPlan_t plan);

//! @brief Performs tensor contraction \f$D = \alpha \mathcal{A} \mathcal{B} + \beta \mathcal{C}\f$.
//!
//! @details Computes: \f$\mathcal{D}_{{modes}_\mathcal{D}} \gets \alpha * \mathcal{A}_{{modes}_\mathcal{A}} B_{{modes}_\mathcal{B}} + \beta \mathcal{C}_{{modes}_\mathcal{C}}\f$.
//! The active HIP device must match the device that was active during plan creation.
//! @param[in] handle Opaque handle representing the hipTensor library context.
//! @param[in] plan Opaque handle containing the contraction execution plan.
//! @param[in] alpha Scaling factor for \f$A*B\f$. Data type determined by `descCompute`. Pointer to host memory.
//! @param[in] A Pointer to tensor A data in GPU-accessible memory. Must not overlap with elements written to D.
//! @param[in] B Pointer to tensor B data in GPU-accessible memory. Must not overlap with elements written to D.
//! @param[in] beta Scaling factor for C. Data type determined by `descCompute`. Pointer to host memory.
//! @param[in] C Pointer to tensor C data in GPU-accessible memory.
//! @param[out] D Pointer to tensor D data in GPU-accessible memory.
//! @param[out] workspace Optional parameter (can be `NULL`). Additional device memory workspace for optimizations.
//! @param[in] workspaceSize Size of the `workspace` array in bytes.
//! @param[in] stream HIP stream for all computations.
//! @retval `HIPTENSOR_STATUS_NOT_SUPPORTED` When the operation isn't supported.
//! @retval `HIPTENSOR_STATUS_INVALID_VALUE` When input data is invalid (typically user error).
//! @retval `HIPTENSOR_STATUS_SUCCESS` When the operation completes successfully.
//! @retval `HIPTENSOR_STATUS_NOT_INITIALIZED` When the handle isn't initialized.
//! @retval `HIPTENSOR_STATUS_ARCH_MISMATCH` When the plan was created for a different device than the currently active one.
//! @retval `HIPTENSOR_STATUS_INSUFFICIENT_DRIVER` When the driver is insufficient.
hiptensorStatus_t hiptensorContract(const hiptensorHandle_t handle,
                                    const hiptensorPlan_t   plan,
                                    const void*             alpha,
                                    const void*             A,
                                    const void*             B,
                                    const void*             beta,
                                    const void*             C,
                                    void*                   D,
                                    void*                   workspace,
                                    uint64_t                workspaceSize,
                                    hipStream_t             stream);

//! @brief Returns a descriptive string for a given error code.
//! @param[in] error The error code to convert to a string.
//! @retval ErrorString A string describing the error.
const char* hiptensorGetErrorString(const hiptensorStatus_t error);

//! @brief Executes tensor permutation
//! @details Computes the permutation operation:
//! \f[
//! B_{\Pi^B(i_0,i_1,...,i_n)} = \alpha \Psi(A_{\Pi^A(i_0,i_1,...,i_n)})
//! \f]
//! @param[in] handle Opaque handle containing hipTensor's library context.
//! @param[in] plan Opaque handle with permutation information.
//! @param[in] alpha Scaling factor for A (typeScalar type). Pointer to host memory.
//! @param[in] A Multi-mode tensor (typeA type) with nmodeA modes. Pointer to GPU-accessible memory.
//! @param[in,out] B Multi-mode tensor (typeB type) with nmodeB modes. Pointer to GPU-accessible memory.
//! @param[in] stream HIP stream for all operations.
//! @retval HIPTENSOR_STATUS_NOT_SUPPORTED When data type combinations or operations aren't supported
//! @retval HIPTENSOR_STATUS_INVALID_VALUE When tensor dimensions or modes contain illegal values
//! @retval HIPTENSOR_STATUS_SUCCESS When the operation completes successfully
//! @retval HIPTENSOR_STATUS_NOT_INITIALIZED When the handle isn't initialized.
hiptensorStatus_t hiptensorPermute(const hiptensorHandle_t handle,
                                   const hiptensorPlan_t   plan,
                                   const void*             alpha,
                                   const void*             A,
                                   void*                   B,
                                   const hipStream_t       stream);

//! @brief Creates an operation descriptor for elementwise binary operations.
//!
//! @param[in] handle Opaque handle containing hipTensor's library context.
//! @param[out] desc Opaque structure allocated and filled with the elementwise operation information.
//! @param[in] descA Descriptor containing A's data type, modes, and strides.
//! @param[in] modeA Host memory array of size descA->numModes with A's mode names.
//! @param[in] opA Unary operator applied to each element of A before processing. A's original data remains unchanged.
//! @param[in] descC Descriptor containing C's data type, modes, and strides.
//! @param[in] modeC Host memory array of size descC->numModes with C's mode names.
//! @param[in] opC Unary operator applied to each element of C before processing. C's original data remains unchanged.
//! @param[in] descD Descriptor containing D's data type, modes, and strides. Currently must be identical to descC.
//! @param[in] modeD Host memory array of size descD->numModes with D's mode names.
//! @param[in] opAC Element-wise binary operator.
//! @param[in] descCompute Determines the precision for this operation.
//! @retval HIPTENSOR_STATUS_NOT_SUPPORTED When data type combinations or operations aren't supported
//! @retval HIPTENSOR_STATUS_INVALID_VALUE When tensor dimensions or modes contain illegal values
//! @retval HIPTENSOR_STATUS_SUCCESS When the operation completes successfully
//! @retval HIPTENSOR_STATUS_NOT_INITIALIZED When the handle isn't initialized.
hiptensorStatus_t hiptensorCreateElementwiseBinary(const hiptensorHandle_t            handle,
                                                   hiptensorOperationDescriptor_t*    desc,
                                                   const hiptensorTensorDescriptor_t  descA,
                                                   const int32_t                      modeA[],
                                                   hiptensorOperator_t                opA,
                                                   const hiptensorTensorDescriptor_t  descC,
                                                   const int32_t                      modeC[],
                                                   hiptensorOperator_t                opC,
                                                   const hiptensorTensorDescriptor_t  descD,
                                                   const int32_t                      modeD[],
                                                   hiptensorOperator_t                opAC,
                                                   const hiptensorComputeDescriptor_t descCompute);

//! @brief Executes element-wise tensor operation on two input tensors.
//!
//! @details This function computes the element-wise operation:
//! \f[
//! D_{\Pi^C(i_0,i_1,...,i_n)} = \Phi_{AC}(\alpha \Psi_A(A_{\Pi^A(i_0,i_1,...,i_n)}), \gamma \Psi_C(C_{\Pi^C(i_0,i_1,...,i_n)}))
//! \f]
//! where:
//!   - \f$D\f$ is the output tensor.
//!   - \f$A\f$ and \f$C\f$ are the input tensors.
//!   - \f$\alpha\f$ and \f$\gamma\f$ are scalar scaling factors.
//!   - \f$\Psi_A\f$ and \f$\Psi_C\f$ are unary operators (applied only if \f$\alpha\f$ and \f$\gamma\f$ are non-zero).
//!   - \f$\Phi_{AC}\f$ is a binary element-wise operator.
//!   - \f$\Pi^A\f$ and \f$\Pi^C\f$ represent mode permutations.
//!
//! @param[in] handle Opaque handle containing hipTensor's library context.
//! @param[in] plan Opaque handle with elementwise operation information.
//! @param[in] alpha Scaling factor for A. Host memory pointer.
//! @param[in] A Multi-mode tensor in GPU-accessible memory. Must not overlap with elements written to D.
//! @param[in] gamma Scaling factor for C. Host memory pointer.
//! @param[in] C Multi-mode tensor in GPU-accessible memory. Must not overlap with elements written to D.
//! @param[out] D Multi-mode tensor in GPU-accessible memory. C and D may be identical only if descC == descD.
//! @param[in] stream Stream for performing the operation.
//! @retval HIPTENSOR_STATUS_NOT_SUPPORTED When data type combinations or operations aren't supported
//! @retval HIPTENSOR_STATUS_INVALID_VALUE When tensor dimensions or modes contain illegal values
//! @retval HIPTENSOR_STATUS_SUCCESS When the operation completes successfully
//! @retval HIPTENSOR_STATUS_NOT_INITIALIZED When the handle isn't initialized.
hiptensorStatus_t hiptensorElementwiseBinaryExecute(const hiptensorHandle_t handle,
                                                    const hiptensorPlan_t   plan,
                                                    const void*             alpha,
                                                    const void*             A,
                                                    const void*             gamma,
                                                    const void*             C,
                                                    void*                   D,
                                                    hipStream_t             stream);

//! @brief Creates an operation descriptor for elementwise trinary operations.
//!
//! @param[in] handle Opaque handle containing hipTensor's library context.
//! @param[out] desc Opaque structure allocated and filled with the elementwise operation information.
//! @param[in] descA Descriptor containing A's data type, modes, and strides.
//! @param[in] modeA Host memory array of size descA->numModes with A's mode names.
//! @param[in] opA Unary operator applied to each element of A before processing. A's original data remains unchanged.
//! @param[in] descB Descriptor containing B's data type, modes, and strides.
//! @param[in] modeB Host memory array of size descB->numModes with B's mode names.
//! @param[in] opB Unary operator applied to each element of B before processing. B's original data remains unchanged.
//! @param[in] descC Descriptor containing C's data type, modes, and strides.
//! @param[in] modeC Host memory array of size descC->numModes with C's mode names.
//! @param[in] opC Unary operator applied to each element of C before processing. C's original data remains unchanged.
//! @param[in] descD Descriptor containing D's data type, modes, and strides. Currently must be identical to descC.
//! @param[in] modeD Host memory array of size descD->numModes with D's mode names.
//! @param[in] opAB Element-wise binary operator.
//! @param[in] opABC Element-wise binary operator.
//! @param[in] descCompute Determines the precision for this operation.
//! @retval HIPTENSOR_STATUS_SUCCESS When the operation completes successfully.
//! @retval HIPTENSOR_STATUS_NOT_INITIALIZED When the handle isn't initialized.
//! @retval HIPTENSOR_STATUS_INVALID_VALUE When input data is invalid (typically user error).
//! @retval HIPTENSOR_STATUS_ARCH_MISMATCH When the device isn't ready or the target architecture isn't supported.
hiptensorStatus_t hiptensorCreateElementwiseTrinary(const hiptensorHandle_t            handle,
                                                    hiptensorOperationDescriptor_t*    desc,
                                                    const hiptensorTensorDescriptor_t  descA,
                                                    const int32_t                      modeA[],
                                                    hiptensorOperator_t                opA,
                                                    const hiptensorTensorDescriptor_t  descB,
                                                    const int32_t                      modeB[],
                                                    hiptensorOperator_t                opB,
                                                    const hiptensorTensorDescriptor_t  descC,
                                                    const int32_t                      modeC[],
                                                    hiptensorOperator_t                opC,
                                                    const hiptensorTensorDescriptor_t  descD,
                                                    const int32_t                      modeD[],
                                                    hiptensorOperator_t                opAB,
                                                    hiptensorOperator_t                opABC,
                                                    const hiptensorComputeDescriptor_t descCompute);

//! @brief Executes element-wise tensor operation on three input tensors.
//!
//! @details This function computes the element-wise operation:
//! \f[ D_{\Pi^C(i_0,i_1,...,i_n)} = \Phi_{ABC}(\Phi_{AB}(\alpha \Psi_A(A_{\Pi^A(i_0,i_1,...,i_n)}), \beta \Psi_B(B_{\Pi^B(i_0,i_1,...,i_n)})), \gamma \Psi_C(C_{\Pi^C(i_0,i_1,...,i_n)})) \f]
//!
//! Tensor modes can appear in any order, providing flexibility. However, the following restrictions apply:
//!   - Modes present in \f$A\f$ or \f$B\f$ must also be present in the output tensor \f$D\f$. Modes only in inputs would imply contraction, which is handled by hiptensorContraction or hiptensorReduction.
//!   - Each mode can appear at most once in each tensor.
//!
//! @param[in] handle Opaque handle containing hipTensor's library context.
//! @param[in] plan Opaque handle with elementwise operation information.
//! @param[in] alpha Scaling factor for A. Host memory pointer.
//! @param[in] A Multi-mode tensor in GPU-accessible memory. Must not overlap with elements written to D.
//! @param[in] beta Scaling factor for B. Host memory pointer.
//! @param[in] B Multi-mode tensor in GPU-accessible memory. Must not overlap with elements written to D.
//! @param[in] gamma Scaling factor for C. Host memory pointer.
//! @param[in] C Multi-mode tensor in GPU-accessible memory. Must not overlap with elements written to D.
//! @param[out] D Multi-mode tensor in GPU-accessible memory. C and D may be identical only if descC == descD.
//! @param[in] stream Stream for performing the operation.
//! @retval HIPTENSOR_STATUS_NOT_SUPPORTED When data type combinations or operations aren't supported
//! @retval HIPTENSOR_STATUS_INVALID_VALUE When tensor dimensions or modes contain illegal values
//! @retval HIPTENSOR_STATUS_SUCCESS When the operation completes successfully
//! @retval HIPTENSOR_STATUS_NOT_INITIALIZED When the handle isn't initialized.
hiptensorStatus_t hiptensorElementwiseTrinaryExecute(const hiptensorHandle_t handle,
                                                     const hiptensorPlan_t   plan,
                                                     const void*             alpha,
                                                     const void*             A,
                                                     const void*             beta,
                                                     const void*             B,
                                                     const void*             gamma,
                                                     const void*             C,
                                                     void*                   D,
                                                     hipStream_t             stream);

//! @brief Creates a hiptensorOperatorDescriptor_t object that encodes a tensor reduction of the form \f$ D = alpha * opReduce(opA(A)) + beta * opC(C) \f$.
//!
//! @param[in] handle Opaque handle holding hipTensor's library context.
//! @param[out] desc This opaque struct gets allocated and filled with the information that encodes the requested tensor reduction operation.
//! @param[in] descA The descriptor that holds the information about the data type, modes and strides of A.
//! @param[in] modeA Array with 'nmodeA' entries that represent the modes of A.
//! @param[in] opA Unary operator that will be applied to each element of A before it is further processed. The original data of this tensor remains unchanged.
//! @param[in] descC The descriptor that holds the information about the data type, modes and strides of C.
//! @param[in] modeC Array with 'nmodeC' entries that represent the modes of C.
//! @param[in] opC Unary operator that will be applied to each element of C before it is further processed. The original data of this tensor remains unchanged.
//! @param[in] descD Must be identical to descC for now.
//! @param[in] modeD Must be identical to modeC for now.
//! @param[in] opReduce binary operator used to reduce elements of A.
//! @param[in] descCompute All arithmetic is performed using this data type.
//!
//! @retval HIPTENSOR_STATUS_NOT_SUPPORTED if operation is not supported.
//! @retval HIPTENSOR_STATUS_INVALID_VALUE if some input data is invalid (this typically indicates an user error).
//! @retval HIPTENSOR_STATUS_SUCCESS The operation completed successfully.
//! @retval HIPTENSOR_STATUS_NOT_INITIALIZED if the handle is not initialized.
hiptensorStatus_t hiptensorCreateReduction(const hiptensorHandle_t            handle,
                                           hiptensorOperationDescriptor_t*    desc,
                                           const hiptensorTensorDescriptor_t  descA,
                                           const int32_t                      modeA[],
                                           hiptensorOperator_t                opA,
                                           const hiptensorTensorDescriptor_t  descC,
                                           const int32_t                      modeC[],
                                           hiptensorOperator_t                opC,
                                           const hiptensorTensorDescriptor_t  descD,
                                           const int32_t                      modeD[],
                                           hiptensorOperator_t                opReduce,
                                           const hiptensorComputeDescriptor_t descCompute);

//! @brief Performs the tensor reduction that is encoded by `plan`.
//!
//! @param[in] handle An opaque handle representing the hipTensor library context.
//! @param[in] plan Opaque handle with elementwise operation information.
//! @param[in] alpha Scaling for A. Its data type is determined by 'descCompute'. Pointer to the host memory.
//! @param[in] A Pointer to the data corresponding to A in device memory. Pointer to the GPU-accessible memory. The data accessed via this pointer must not overlap with the elements written to D.
//! @param[in] beta Scaling for C. Its data type is determined by 'descCompute'. Pointer to the host memory.
//! @param[in] C Pointer to the data corresponding to C in device memory. Pointer to the GPU-accessible memory.
//! @param[out] D Pointer to the data corresponding to C in device memory. Pointer to the GPU-accessible memory.
//! @param[out] workspace Scratchpad (device) memory of size --at least-- `workspaceSize` bytes.
//! @param[in] workspaceSize Please use \ref hiptensorEstimateWorkspaceSize() to query the required workspace.
//! @param[in] stream The stream in which all the computation is performed.
//! @retval HIPTENSOR_STATUS_SUCCESS The operation completed successfully.
hiptensorStatus_t hiptensorReduce(const hiptensorHandle_t handle,
                                  const hiptensorPlan_t   plan,
                                  const void*             alpha,
                                  const void*             A,
                                  const void*             beta,
                                  const void*             C,
                                  void*                   D,
                                  void*                   workspace,
                                  uint64_t                workspaceSize,
                                  hipStream_t             stream);

//! @brief Registers a callback function to be invoked by logger calls.
//! @param[in] callback The callback function pointer to provide to the logger.
//! @retval `HIPTENSOR_STATUS_SUCCESS` if the operation completed successfully.
//! @retval `HIPTENSOR_STATUS_INVALID_VALUE` if the given callback is invalid.
hiptensorStatus_t hiptensorLoggerSetCallback(hiptensorLoggerCallback_t callback);

//! @brief Registers a file output stream to redirect logging output.
//! @note The file stream must be open and writable in text mode.
//! @param[in] file A file stream pointer to provide to the logger.
//! @retval `HIPTENSOR_STATUS_SUCCESS` if the operation completed successfully.
//! @retval `HIPTENSOR_STATUS_IO_ERROR` if the output file is not valid (defaults back to stdout).
hiptensorStatus_t hiptensorLoggerSetFile(FILE* file);

//! @brief Redirects log output to a user-specified file.
//! @param[in] logFile The file name (relative to binary) or full path to redirect logger output.
//! @retval `HIPTENSOR_STATUS_SUCCESS` if the operation completed successfully.
//! @retval `HIPTENSOR_STATUS_IO_ERROR` if the output file is not valid (defaults back to stdout).
hiptensorStatus_t hiptensorLoggerOpenFile(const char* logFile);

//! @brief Sets the user-specified logging level. Logs in other contexts will not be recorded.
//! @param[in] level The logging level to enforce.
//! @retval `HIPTENSOR_STATUS_SUCCESS` if the operation completed successfully.
//! @retval `HIPTENSOR_STATUS_INVALID_VALUE` if the given log level is invalid.
hiptensorStatus_t hiptensorLoggerSetLevel(hiptensorLogLevel_t level);

//! @brief Sets the user-specified logging mask. A mask can be a binary OR combination of several log levels. Logs in other contexts will not be recorded.
//! @param[in] mask The logging mask to enforce.
//! @retval `HIPTENSOR_STATUS_SUCCESS` if the operation completed successfully.
//! @retval `HIPTENSOR_STATUS_INVALID_VALUE` if the given log mask is invalid.
hiptensorStatus_t hiptensorLoggerSetMask(int32_t mask);

//! @brief Disables logging.
//! @retval `HIPTENSOR_STATUS_SUCCESS` if the operation completed successfully.
hiptensorStatus_t hiptensorLoggerForceDisable();

//! @brief Queries the HIP runtime version.
//! @retval -1 If the operation failed.
//! @retval Integer An integer representing the HIP runtime version if the operation succeeded.
int hiptensorGetHiprtVersion();

//! @brief Returns the version number of hipTensor
//! @details Return the version with three least significant digits for patch version,
//! the next three digits for minor version, and the most significant digits for major version.
//! @returns The version number calculated as major * 10000 + minor * 100 + patch.
size_t hiptensorGetVersion();

#if defined(__cplusplus)
}
#endif /* __cplusplus */
