/**

 * Copyright (c) 2025 Huawei Technologies Co., Ltd.

 * This program is free software, you can redistribute it and/or modify it under the terms and conditions of

 * CANN Open Software License Agreement Version 2.0 (the "License").

 * Please refer to the License for details. You may not use this file except in compliance with the License.

 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,

 * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.

 * See LICENSE in the root of the software repository for the full text of the License.

 */



#include <vector>

#include <iostream>

#include "utils.h"

#include "acl/acl.h"

#include "model_utils.h"

#include "aclnnop/aclnn_add.h"

#include "aclnnop/aclnn_mul.h"

using namespace std;

// aclnnadd计算:out = self + other * alpha

// aclnnmul计算: out = self * other

int main()

{

    int deviceId = 0;

    int blockDim = 1;

    // 初始化数据,包括三个算子任务所需要的输入输出

    uint32_t *numDevice = nullptr;

    void *selfDevice1 = nullptr;

    void *otherDevice1 = nullptr;

    void *outDevice1 = nullptr;

    void *selfDevice2 = nullptr;

    void *otherDevice2 = nullptr;

    void *outDevice2 = nullptr;

    void *selfDevice3 = nullptr;

    void *otherDevice3 = nullptr;

    void *outDevice3 = nullptr;

    aclTensor *self1 = nullptr;

    aclTensor *other1 = nullptr;

    aclScalar *alpha = nullptr;

    aclTensor *out1 = nullptr;

    aclTensor *self2 = nullptr;

    aclTensor *other2 = nullptr;

    aclTensor *out2 = nullptr;

    aclTensor *self3 = nullptr;

    aclTensor *other3 = nullptr;

    aclTensor *out3 = nullptr;

    vector<float> selfHostData1 = {1, 1, 1, 1, 1, 1, 1, 1};

    vector<float> otherHostData1 = {2, 2, 2, 2, 2, 2, 2, 2};

    vector<float> outHostData1 = {0, 0, 0, 0, 0, 0, 0, 0};

    vector<float> selfHostData2 = {2, 2, 2, 2, 2, 2, 2, 2};

    vector<float> otherHostData2 = {2, 2, 2, 2, 2, 2, 2, 2};

    vector<float> outHostData2 = {0, 0, 0, 0, 0, 0, 0, 0};

    vector<float> selfHostData3 = {3, 3, 3, 3, 3, 3, 3, 3};

    vector<float> otherHostData3 = {2, 2, 2, 2, 2, 2, 2, 2};

    vector<float> outHostData3 = {0, 0, 0, 0, 0, 0, 0, 0};

    vector<int64_t> shape = {4, 2};

    float alphaValue = 1.2f;

    uint64_t addWorkspaceSize1 = 0;

    uint64_t addWorkspaceSize2 = 0;

    uint64_t addWorkspaceSize3 = 0;

    aclOpExecutor *addExecutor1;

    aclOpExecutor *addExecutor2;

    aclOpExecutor *addExecutor3;

    aclrtContext context;

    int64_t size = ModelUtils::GetShapeSize(shape) * sizeof(float);

    CHECK_ERROR(aclInit(NULL));

    CHECK_ERROR(aclrtSetDevice(deviceId));

    CHECK_ERROR(aclrtCreateContext(&context, deviceId));

    // 创建三个算子任务,后续分发给每个流

    // stream1: out1 = self1 + other1 * alpha

    // stream2: out2 = self2 + other2 * alpha

    // stream3: out3 = self3 + other3 * alpha

    ModelUtils::CreateAclTensor(shape, &selfDevice1, aclDataType::ACL_FLOAT, &self1);

    ModelUtils::CreateAclTensor(shape, &otherDevice1, aclDataType::ACL_FLOAT, &other1);

    ModelUtils::CreateAclTensor(shape, &selfDevice2, aclDataType::ACL_FLOAT, &self2);

    ModelUtils::CreateAclTensor(shape, &otherDevice2, aclDataType::ACL_FLOAT, &other2);

    ModelUtils::CreateAclTensor(shape, &selfDevice3, aclDataType::ACL_FLOAT, &self3);

    ModelUtils::CreateAclTensor(shape, &otherDevice3, aclDataType::ACL_FLOAT, &other3);

    alpha = aclCreateScalar(&alphaValue, aclDataType::ACL_FLOAT);

    ModelUtils::CreateAclTensor(shape, &outDevice1, aclDataType::ACL_FLOAT, &out1);

    ModelUtils::CreateAclTensor(shape, &outDevice2, aclDataType::ACL_FLOAT, &out2);

    ModelUtils::CreateAclTensor(shape, &outDevice3, aclDataType::ACL_FLOAT, &out3);

    aclnnAddGetWorkspaceSize(self1, other1, alpha, out1, &addWorkspaceSize1, &addExecutor1);

    void *addWorkspaceAddr1 = nullptr;

    if (addWorkspaceSize1 > 0) {

        CHECK_ERROR(aclrtMalloc(&addWorkspaceAddr1, addWorkspaceSize1, ACL_MEM_MALLOC_HUGE_FIRST));

    }

    aclnnAddGetWorkspaceSize(self2, other2, alpha, out2, &addWorkspaceSize2, &addExecutor2);

    void *addWorkspaceAddr2 = nullptr;

    if (addWorkspaceSize2 > 0) {

        CHECK_ERROR(aclrtMalloc(&addWorkspaceAddr2, addWorkspaceSize2, ACL_MEM_MALLOC_HUGE_FIRST));

    }

    aclnnAddGetWorkspaceSize(self3, other3, alpha, out3, &addWorkspaceSize3, &addExecutor3);

    void *addWorkspaceAddr3 = nullptr;

    if (addWorkspaceSize3 > 0) {

        CHECK_ERROR(aclrtMalloc(&addWorkspaceAddr3, addWorkspaceSize3, ACL_MEM_MALLOC_HUGE_FIRST));

    }

    // switchstream函数需要的参数为device侧数据的地址,这里创建两个device侧的地址用于比较,一个为1,一个为2

    int32_t rightValue1 = 1;

    int32_t rightValue2 = 2;

    void *rightDevice1 = nullptr;

    void *rightDevice2 = nullptr;

    aclrtCondition condition = ACL_RT_EQUAL;

    aclrtCompareDataType dataType = ACL_RT_SWITCH_INT32;

    CHECK_ERROR(aclrtMalloc(&rightDevice1, sizeof(int32_t), ACL_MEM_MALLOC_HUGE_FIRST));

    CHECK_ERROR(aclrtMemcpy(rightDevice1, sizeof(int32_t), &rightValue1, sizeof(int32_t), ACL_MEMCPY_HOST_TO_DEVICE));

    CHECK_ERROR(aclrtMalloc(&rightDevice2, sizeof(int32_t), ACL_MEM_MALLOC_HUGE_FIRST));

    CHECK_ERROR(aclrtMemcpy(rightDevice2, sizeof(int32_t), &rightValue2, sizeof(int32_t), ACL_MEMCPY_HOST_TO_DEVICE));

    CHECK_ERROR(aclrtMalloc((void **)&numDevice, sizeof(uint32_t), ACL_MEM_MALLOC_HUGE_FIRST));

    uint32_t numInit = 0;

    CHECK_ERROR(aclrtMemcpy(numDevice, sizeof(uint32_t), &numInit, sizeof(uint32_t), ACL_MEMCPY_HOST_TO_DEVICE));

    // 通过 DeviceToDevice 异步拷贝将其置为 1,供 SwitchStream 分支验证。

    uint32_t val1 = 1;

    void *targetValDev_1 = nullptr;

    CHECK_ERROR(aclrtMalloc(&targetValDev_1, sizeof(uint32_t), ACL_MEM_MALLOC_HUGE_FIRST));

    CHECK_ERROR(aclrtMemcpy(targetValDev_1, sizeof(uint32_t), &val1, sizeof(uint32_t), ACL_MEMCPY_HOST_TO_DEVICE));

    // 创建五个流:四个工作流 + 一个汇聚流endStream

    aclmdlRI modelRI;

    aclrtStream stream1;

    aclrtStream stream2;

    aclrtStream stream3;

    aclrtStream stream4;

    aclrtStream endStream;

    // 增加一个独立的 copyStream,专门用于独立内存操作,不和模型环境绑定,防止流状态异常。

    aclrtStream copyStream;

    CHECK_ERROR(aclrtCreateStreamWithConfig(&stream1, 0x00U, ACL_STREAM_PERSISTENT));

    CHECK_ERROR(aclrtCreateStreamWithConfig(&stream2, 0x00U, ACL_STREAM_PERSISTENT));

    CHECK_ERROR(aclrtCreateStreamWithConfig(&stream3, 0x00U, ACL_STREAM_PERSISTENT));

    CHECK_ERROR(aclrtCreateStreamWithConfig(&stream4, 0x00U, ACL_STREAM_PERSISTENT));

    CHECK_ERROR(aclrtCreateStreamWithConfig(&endStream, 0x00U, ACL_STREAM_PERSISTENT));

    CHECK_ERROR(aclrtCreateStream(&copyStream));

    // 绑定五个流,复制算子需要的数据

    CHECK_ERROR(aclmdlRIBuildBegin(&modelRI, 0x00U));

    CHECK_ERROR(aclmdlRIBindStream(modelRI, stream1, ACL_MODEL_STREAM_FLAG_HEAD));

    CHECK_ERROR(aclmdlRIBindStream(modelRI, stream2, ACL_MODEL_STREAM_FLAG_DEFAULT));

    CHECK_ERROR(aclmdlRIBindStream(modelRI, stream3, ACL_MODEL_STREAM_FLAG_DEFAULT));

    CHECK_ERROR(aclmdlRIBindStream(modelRI, stream4, ACL_MODEL_STREAM_FLAG_DEFAULT));

    CHECK_ERROR(aclmdlRIBindStream(modelRI, endStream, ACL_MODEL_STREAM_FLAG_DEFAULT));

    // 对于不绑定的流操作,使用独立的 copyStream 进行初始化数据的拷入

    CHECK_ERROR(aclrtMemcpyAsync(selfDevice1, size, selfHostData1.data(), size, ACL_MEMCPY_HOST_TO_DEVICE, stream1));

    CHECK_ERROR(aclrtMemcpyAsync(selfDevice2, size, selfHostData2.data(), size, ACL_MEMCPY_HOST_TO_DEVICE, stream2));

    CHECK_ERROR(aclrtMemcpyAsync(selfDevice3, size, selfHostData3.data(), size, ACL_MEMCPY_HOST_TO_DEVICE, stream3));

    CHECK_ERROR(aclrtMemcpyAsync(otherDevice1, size, otherHostData1.data(), size, ACL_MEMCPY_HOST_TO_DEVICE, stream1));

    CHECK_ERROR(aclrtMemcpyAsync(otherDevice2, size, otherHostData2.data(), size, ACL_MEMCPY_HOST_TO_DEVICE, stream2));

    CHECK_ERROR(aclrtMemcpyAsync(otherDevice3, size, otherHostData3.data(), size, ACL_MEMCPY_HOST_TO_DEVICE, stream3));

    aclnnAdd(addWorkspaceAddr1, addWorkspaceSize1, addExecutor1, stream1);

    CHECK_ERROR(aclrtMemcpyAsync(numDevice, sizeof(uint32_t), targetValDev_1, sizeof(uint32_t), ACL_MEMCPY_DEVICE_TO_DEVICE, stream1));

    // stream2的任务为下发一个aclnn算子,完成后激活endStream

    aclnnAdd(addWorkspaceAddr2, addWorkspaceSize2, addExecutor2, stream2);

    CHECK_ERROR(aclrtActiveStream(endStream, stream2));

    // stream3的任务为下发一个aclnn算子,完成后激活endStream

    aclnnAdd(addWorkspaceAddr3, addWorkspaceSize3, addExecutor3, stream3);

    CHECK_ERROR(aclrtActiveStream(endStream, stream3));

    // 根据安全且强制写入的 numdevice=1 状态,流能够安全跳转至stream3。仅保留一个Switch验证分支打通

    // 将两条 SwitchStream 全部加回(必须确保第一个分支哪怕未命中时,设备侧仍兼容执行第二个分支)

    CHECK_ERROR(aclrtSwitchStream(numDevice, condition, rightDevice1, dataType, stream3, nullptr, stream1));

    CHECK_ERROR(aclrtSwitchStream(numDevice, condition, rightDevice2, dataType, stream4, nullptr, stream1));

    // stream4的任务为激活stream2,他们会并行执行,要注意,使用该接口的两个流应该都已绑定了模型实例

    CHECK_ERROR(aclrtActiveStream(stream2, stream4));

    // 结束任务:仅在汇聚流endStream上调用EndTask(一个模型只能有一个EndGraph)

    // 路径1(numDevice==1): stream1 → stream3 → ActiveStream(endStream) → EndTask

    CHECK_ERROR(aclmdlRIEndTask(modelRI, endStream));

    CHECK_ERROR(aclmdlRIBuildEnd(modelRI, NULL));

    aclrtStream executeStream;

    aclrtCreateStream(&executeStream);

    // 执行图任务:

    CHECK_ERROR(aclmdlRIExecuteAsync(modelRI, executeStream));

    CHECK_ERROR(aclrtSynchronizeStream(executeStream));

    // 使用刚才新建的独立的、完全未被绑定过的 copyStream 来做结果的拷回

    CHECK_ERROR(aclrtMemcpyAsync(outHostData1.data(), size, outDevice1, size, ACL_MEMCPY_DEVICE_TO_HOST, copyStream));

    CHECK_ERROR(aclrtMemcpyAsync(outHostData2.data(), size, outDevice2, size, ACL_MEMCPY_DEVICE_TO_HOST, copyStream));

    CHECK_ERROR(aclrtMemcpyAsync(outHostData3.data(), size, outDevice3, size, ACL_MEMCPY_DEVICE_TO_HOST, copyStream));

    CHECK_ERROR(aclrtSynchronizeStream(copyStream));

    INFO_LOG("After executing, print data1.");

    ModelUtils::PrintArray(outHostData1);

    INFO_LOG("After executing, print data2.");

    ModelUtils::PrintArray(outHostData2);

    INFO_LOG("After executing, print data3.");

    ModelUtils::PrintArray(outHostData3);

    // ================= 测试分支值为 2 的场景 =====================

    // 第二次执行模型前,我们要将 targetValDev_1 中的值修改为 2,

    // 以便让图里面录入的那句 DeviceToDevice 复制时把 2 写进 numDevice。

    uint32_t val2 = 2;

    // 使用独立的 copyStream 拷贝 2 到 targetValDev_1 中

    CHECK_ERROR(aclrtMemcpyAsync(targetValDev_1, sizeof(uint32_t), &val2, sizeof(uint32_t), ACL_MEMCPY_HOST_TO_DEVICE, copyStream));

    CHECK_ERROR(aclrtSynchronizeStream(copyStream));

    // 将上一次的主机结果清零,以免混淆

    outHostData1.assign(outHostData1.size(), 0);

    outHostData2.assign(outHostData2.size(), 0);

    outHostData3.assign(outHostData3.size(), 0);

    // 执行第二次图任务:

    CHECK_ERROR(aclmdlRIExecuteAsync(modelRI, executeStream));

    CHECK_ERROR(aclrtSynchronizeStream(executeStream));

    // 使用 copyStream 拷回结果

    CHECK_ERROR(aclrtMemcpyAsync(outHostData1.data(), size, outDevice1, size, ACL_MEMCPY_DEVICE_TO_HOST, copyStream));

    CHECK_ERROR(aclrtMemcpyAsync(outHostData2.data(), size, outDevice2, size, ACL_MEMCPY_DEVICE_TO_HOST, copyStream));

    CHECK_ERROR(aclrtMemcpyAsync(outHostData3.data(), size, outDevice3, size, ACL_MEMCPY_DEVICE_TO_HOST, copyStream));

    CHECK_ERROR(aclrtSynchronizeStream(copyStream));

    INFO_LOG("After second execution, print data1.");

    ModelUtils::PrintArray(outHostData1);

    INFO_LOG("After second execution, print data2.");

    ModelUtils::PrintArray(outHostData2);

    INFO_LOG("After second execution, print data3.");

    ModelUtils::PrintArray(outHostData3);

    // 解除模型实例与流的绑定

    CHECK_ERROR(aclmdlRIUnbindStream(modelRI, stream1));

    CHECK_ERROR(aclmdlRIUnbindStream(modelRI, stream2));

    CHECK_ERROR(aclmdlRIUnbindStream(modelRI, stream3));

    CHECK_ERROR(aclmdlRIUnbindStream(modelRI, stream4));

    CHECK_ERROR(aclmdlRIUnbindStream(modelRI, endStream));

    CHECK_ERROR(aclmdlRIDestroy(modelRI));

    CHECK_ERROR(aclrtDestroyStream(copyStream));

    CHECK_ERROR(aclrtDestroyStream(executeStream));

    CHECK_ERROR(aclrtDestroyStream(stream1));

    CHECK_ERROR(aclrtDestroyStream(stream2));

    CHECK_ERROR(aclrtDestroyStream(stream3));

    CHECK_ERROR(aclrtDestroyStream(stream4));

    CHECK_ERROR(aclrtDestroyStream(endStream));

    CHECK_ERROR(aclDestroyTensor(self1));

    CHECK_ERROR(aclDestroyTensor(other1));

    CHECK_ERROR(aclDestroyTensor(out1));

    CHECK_ERROR(aclDestroyTensor(self2));

    CHECK_ERROR(aclDestroyTensor(other2));

    CHECK_ERROR(aclDestroyTensor(out2));

    CHECK_ERROR(aclDestroyTensor(self3));

    CHECK_ERROR(aclDestroyTensor(other3));

    CHECK_ERROR(aclDestroyTensor(out3));

    CHECK_ERROR(aclDestroyScalar(alpha));

    CHECK_ERROR(aclrtFree(numDevice));

    CHECK_ERROR(aclrtFree(targetValDev_1));

    CHECK_ERROR(aclrtFree(rightDevice1));

    CHECK_ERROR(aclrtFree(rightDevice2));

    CHECK_ERROR(aclrtFree(selfDevice1));

    CHECK_ERROR(aclrtFree(otherDevice1));

    CHECK_ERROR(aclrtFree(outDevice1));

    CHECK_ERROR(aclrtFree(selfDevice2));

    CHECK_ERROR(aclrtFree(otherDevice2));

    CHECK_ERROR(aclrtFree(outDevice2));

    CHECK_ERROR(aclrtFree(selfDevice3));

    CHECK_ERROR(aclrtFree(otherDevice3));

    CHECK_ERROR(aclrtFree(outDevice3));

    if (addWorkspaceAddr1 != nullptr) {

        CHECK_ERROR(aclrtFree(addWorkspaceAddr1));

    }

    if (addWorkspaceAddr2 != nullptr) {

        CHECK_ERROR(aclrtFree(addWorkspaceAddr2));

    }

    if (addWorkspaceAddr3 != nullptr) {

        CHECK_ERROR(aclrtFree(addWorkspaceAddr3));

    }

    // 释放计算设备的资源

    CHECK_ERROR(aclrtDestroyContext(context));

    CHECK_ERROR(aclrtResetDeviceForce(deviceId));

    // 去初始化

    CHECK_ERROR(aclFinalize());

}