/**
 * Copyright (c) 2026 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 "acl/acl.h"
#include "data_utils.h"
#include "kernel_operator.h"

#ifdef ASCENDC_CPU_DEBUG
#include "cpu_debug_launch.h"
#endif

constexpr uint32_t CUBE_BLOCK = 16;
constexpr uint32_t kLaunchTimes = 20;
constexpr uint32_t M = 256;
constexpr uint32_t K = 64;
constexpr uint32_t N = 256;
constexpr uint32_t singleCoreM = 128;
constexpr uint32_t baseM = 128;
constexpr uint32_t baseK = 64;
constexpr uint32_t baseN = 256;

void ReleaseBuffer(uint8_t*& host, uint8_t*& device)
{
    if (device != nullptr) {
        (void)aclrtFree(device);
        device = nullptr;
    }
    if (host != nullptr) {
        (void)aclrtFreeHost(host);
        host = nullptr;
    }
}

bool InitInputBuffer(const char* filePath, size_t bufferSize, uint8_t*& host, uint8_t*& device)
{
    host = nullptr;
    device = nullptr;

    aclError aclRet = aclrtMallocHost(reinterpret_cast<void**>(&host), bufferSize);
    if (aclRet != ACL_SUCCESS) {
        ERROR_LOG("aclrtMallocHost failed for %s, ret = %d", filePath, static_cast<int32_t>(aclRet));
        return false;
    }

    aclRet = aclrtMalloc(reinterpret_cast<void**>(&device), bufferSize, ACL_MEM_MALLOC_HUGE_FIRST);
    if (aclRet != ACL_SUCCESS) {
        ERROR_LOG("aclrtMalloc failed for %s, ret = %d", filePath, static_cast<int32_t>(aclRet));
        ReleaseBuffer(host, device);
        return false;
    }

    size_t fileSize = bufferSize;
    if (!ReadFile(filePath, fileSize, host, bufferSize)) {
        ERROR_LOG("ReadFile failed for %s", filePath);
        ReleaseBuffer(host, device);
        return false;
    }

    aclRet = aclrtMemcpy(device, bufferSize, host, bufferSize, ACL_MEMCPY_HOST_TO_DEVICE);
    if (aclRet != ACL_SUCCESS) {
        ERROR_LOG("aclrtMemcpy host to device failed for %s, ret = %d", filePath, static_cast<int32_t>(aclRet));
        ReleaseBuffer(host, device);
        return false;
    }

    return true;
}

bool InitOutputBuffer(size_t bufferSize, uint8_t*& host, uint8_t*& device)
{
    host = nullptr;
    device = nullptr;

    aclError aclRet = aclrtMallocHost(reinterpret_cast<void**>(&host), bufferSize);
    if (aclRet != ACL_SUCCESS) {
        ERROR_LOG("aclrtMallocHost failed for output buffer, ret = %d", static_cast<int32_t>(aclRet));
        return false;
    }

    aclRet = aclrtMalloc(reinterpret_cast<void**>(&device), bufferSize, ACL_MEM_MALLOC_HUGE_FIRST);
    if (aclRet != ACL_SUCCESS) {
        ERROR_LOG("aclrtMalloc failed for output buffer, ret = %d", static_cast<int32_t>(aclRet));
        ReleaseBuffer(host, device);
        return false;
    }

    return true;
}

template <uint32_t TM, uint32_t TK, uint32_t TN, uint32_t TSingleCoreM, uint32_t TBaseM, uint32_t TBaseK, uint32_t TBaseN>
__global__ __cube__ void mmad_custom(__gm__ uint8_t* a, __gm__ uint8_t* b, __gm__ uint8_t* c)
{
    AscendC::GlobalTensor<half> aGM;
    AscendC::GlobalTensor<half> bGM;
    AscendC::GlobalTensor<half> cGM;
    const uint32_t mIterIdx = AscendC::GetBlockIdx() % (TM / TSingleCoreM);

    aGM.SetGlobalBuffer((__gm__ half*)a + mIterIdx * TSingleCoreM * TK);
    bGM.SetGlobalBuffer((__gm__ half*)b);
    cGM.SetGlobalBuffer((__gm__ half*)c + mIterIdx * TSingleCoreM * TN);

    AscendC::LocalMemAllocator<AscendC::Hardware::L1> l1Allocator;
    AscendC::LocalMemAllocator<AscendC::Hardware::L0A> l0aAllocator;
    AscendC::LocalMemAllocator<AscendC::Hardware::L0B> l0bAllocator;
    AscendC::LocalMemAllocator<AscendC::Hardware::L0C> l0cAllocator;

    AscendC::LocalTensor<half> a1Local = l1Allocator.Alloc<AscendC::TPosition::A1, half>(TBaseM * TBaseK);
    AscendC::LocalTensor<half> b1Local = l1Allocator.Alloc<AscendC::TPosition::B1, half>(TBaseK * TBaseN);
    AscendC::LocalTensor<half> a2Local = l0aAllocator.Alloc<AscendC::TPosition::A2, half>(TBaseM * TBaseK);
    AscendC::LocalTensor<half> b2Local = l0bAllocator.Alloc<AscendC::TPosition::B2, half>(TBaseK * TBaseN);
    AscendC::LocalTensor<float> cLocal = l0cAllocator.Alloc<AscendC::TPosition::CO1, float>(TBaseM * TBaseN);

    AscendC::DataCopy(a1Local, aGM, AscendC::Nd2NzParams{1, TBaseM, TBaseK, 0, TK, TBaseM, 1, 0});
    AscendC::DataCopy(b1Local, bGM, AscendC::Nd2NzParams{1, TBaseK, TBaseN, 0, TN, TBaseK, 1, 0});

    AscendC::SetFlag<AscendC::HardEvent::MTE2_MTE1>(EVENT_ID0);
    AscendC::WaitFlag<AscendC::HardEvent::MTE2_MTE1>(EVENT_ID0);

#if defined(__NPU_ARCH__) && (__NPU_ARCH__ == 2201)
    for (uint32_t i = 0; i < TBaseM / CUBE_BLOCK; ++i) {
        AscendC::LoadData(
            a2Local[i * TBaseK * CUBE_BLOCK],
            a1Local[i * 512 / sizeof(half)],
            AscendC::LoadData2DParams{0, TBaseK / CUBE_BLOCK, TBaseM / CUBE_BLOCK, 0, 0, false, 0});
    }
    for (uint32_t i = 0; i < TBaseK / CUBE_BLOCK; ++i) {
        AscendC::LoadData(
            b2Local[i * TBaseN * CUBE_BLOCK],
            b1Local[i * 512 / sizeof(half)],
            AscendC::LoadData2DParams{0, TBaseN / CUBE_BLOCK, TBaseK / CUBE_BLOCK, 0, 0, true, 0});
    }
#elif defined(__NPU_ARCH__) && (__NPU_ARCH__ == 3510)
    AscendC::LoadData(
        a2Local,
        a1Local,
        AscendC::LoadData2DParamsV2{
            0, 0, TBaseM / CUBE_BLOCK, TBaseK / CUBE_BLOCK, TBaseM / CUBE_BLOCK, TBaseM / CUBE_BLOCK, false, 0});
    AscendC::LoadData(
        b2Local,
        b1Local,
        AscendC::LoadData2DParamsV2{
            0, 0, TBaseK / CUBE_BLOCK, TBaseN / CUBE_BLOCK, TBaseK / CUBE_BLOCK, TBaseN / CUBE_BLOCK, true, 0});
#endif

    AscendC::SetFlag<AscendC::HardEvent::MTE1_M>(EVENT_ID0);
    AscendC::WaitFlag<AscendC::HardEvent::MTE1_M>(EVENT_ID0);
    AscendC::Mmad(cLocal, a2Local, b2Local, AscendC::MmadParams{TBaseM, TBaseN, TBaseK, 0, false, true});

    AscendC::SetFlag<AscendC::HardEvent::M_FIX>(EVENT_ID0);
    AscendC::WaitFlag<AscendC::HardEvent::M_FIX>(EVENT_ID0);
    AscendC::Fixpipe(
        cGM,
        cLocal,
        AscendC::FixpipeParamsV220{TBaseN, TBaseM, TBaseM, TN, false, QuantMode_t::F322F16, 0, 1, 0, 0, 0});
    AscendC::PipeBarrier<PIPE_ALL>();
}

int32_t main(int32_t argc, char* argv[])
{
    (void)argc;
    (void)argv;

    const size_t aFileSize = M * K * sizeof(half);
    const size_t bFileSize = K * N * sizeof(half);
    const size_t cFileSize = M * N * sizeof(half);
    const uint32_t numBlocks = M / singleCoreM;
    const int32_t deviceId = 0;

    aclError aclRet = aclInit(nullptr);
    if (aclRet != ACL_SUCCESS) {
        ERROR_LOG("aclInit failed, ret = %d", static_cast<int32_t>(aclRet));
        return 1;
    }

    int32_t exitCode = 1;
    aclrtStream stream = nullptr;
    uint8_t* aHost = nullptr;
    uint8_t* aDevice = nullptr;
    uint8_t* bHost = nullptr;
    uint8_t* bDevice = nullptr;
    uint8_t* cHost = nullptr;
    uint8_t* cDevice = nullptr;

    aclRet = aclrtSetDevice(deviceId);
    if (aclRet != ACL_SUCCESS) {
        ERROR_LOG("aclrtSetDevice failed, ret = %d", static_cast<int32_t>(aclRet));
        goto finalize_acl;
    }

    aclRet = aclrtCreateStream(&stream);
    if (aclRet != ACL_SUCCESS) {
        ERROR_LOG("aclrtCreateStream failed, ret = %d", static_cast<int32_t>(aclRet));
        goto reset_device;
    }

    if (!InitInputBuffer("./input/x1_gm.bin", aFileSize, aHost, aDevice)) {
        goto destroy_stream;
    }
    if (!InitInputBuffer("./input/x2_gm.bin", bFileSize, bHost, bDevice)) {
        goto free_a_buffer;
    }
    if (!InitOutputBuffer(cFileSize, cHost, cDevice)) {
        goto free_b_buffer;
    }

    for (uint32_t launchIdx = 0; launchIdx < kLaunchTimes; ++launchIdx) {
        mmad_custom<M, K, N, singleCoreM, baseM, baseK, baseN><<<numBlocks, 0, stream>>>(aDevice, bDevice, cDevice);
    }

    aclRet = aclrtSynchronizeStream(stream);
    if (aclRet != ACL_SUCCESS) {
        ERROR_LOG("aclrtSynchronizeStream failed, ret = %d", static_cast<int32_t>(aclRet));
        goto free_c_buffer;
    }

    aclRet = aclrtMemcpy(cHost, cFileSize, cDevice, cFileSize, ACL_MEMCPY_DEVICE_TO_HOST);
    if (aclRet != ACL_SUCCESS) {
        ERROR_LOG("aclrtMemcpy device to host failed, ret = %d", static_cast<int32_t>(aclRet));
        goto free_c_buffer;
    }
    if (!WriteFile("./output/output.bin", cHost, cFileSize)) {
        ERROR_LOG("WriteFile failed for ./output/output.bin");
        goto free_c_buffer;
    }

    exitCode = 0;

free_c_buffer:
    ReleaseBuffer(cHost, cDevice);
free_b_buffer:
    ReleaseBuffer(bHost, bDevice);
free_a_buffer:
    ReleaseBuffer(aHost, aDevice);
destroy_stream:
    if (stream != nullptr) {
        (void)aclrtDestroyStream(stream);
    }
reset_device:
    (void)aclrtResetDevice(deviceId);
finalize_acl:
    (void)aclFinalize();
    return exitCode;
}