/**
 * 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.
 */

/* !
 * \file cgerc_test.cpp
 * \brief Test for cgerc operator
 */

#include <cstdint>
#include <iostream>
#include <vector>
#include <cmath>
#include "acl/acl.h"
#include "cann_ops_blas.h"
#include "complex.h"

#define CHECK_RET(cond, return_expr) \
    do {                             \
        if (!(cond)) {               \
            return_expr;             \
        }                            \
    } while (0)

#define LOG_PRINT(message, ...)         \
    do {                                \
        printf(message, ##__VA_ARGS__); \
    } while (0)

uint32_t VerifyResult(
    const std::vector<aclblasComplex>& output, const std::vector<aclblasComplex>& golden,
    const char* test_name)
{
    std::cout << "\n========== " << test_name << " ==========" << std::endl;

    // Print first few results for comparison (horizontal format)
    constexpr size_t maxPrintCount = 5;
    std::cout << "\n--- First " << maxPrintCount << " results ---" << std::endl;
    std::cout << "Index  | Output (real, imag)       | Golden (real, imag)        | Diff" << std::endl;
    std::cout << "-------|---------------------------|----------------------------|--------" << std::endl;
    for (size_t i = 0; i < std::min(output.size(), maxPrintCount); i++) {
        printf(
            "%-6zu | (%8.4f, %8.4f) | (%8.4f, %8.4f)  | %8.6f\n", i, output[i].real, output[i].imag,
            golden[i].real, golden[i].imag, blasComplexAbs(output[i] - golden[i]));
    }

    const float epsilon = 1e-3;
    uint32_t errors = 0;
    float maxError = 0.0f;
    size_t maxErrorIndex = 0;

    for (size_t i = 0; i < output.size(); i++) {
        float error = blasComplexAbs(output[i] - golden[i]);
        if (error > maxError) {
            maxError = error;
            maxErrorIndex = i;
        }
        if (error > epsilon) {
            if (errors < 5) { // Only print first 5 errors
                printf(
                    "Mismatch[%zu]: out=(%.4f,%.4f) gold=(%.4f,%.4f) diff=%.6f\n", i, output[i].real,
                    output[i].imag, golden[i].real, golden[i].imag, error);
            }
            errors++;
        }
    }

    std::cout << "\n--- Statistics ---" << std::endl;
    printf(
        "Total: %zu, MaxErr: %.6f @ idx %zu, Threshold: %.6f, Errors: %u\n", output.size(), maxError, maxErrorIndex,
        epsilon, errors);

    if (errors == 0) {
        std::cout << "[Success] " << test_name << " verification passed." << std::endl;
        return 0;
    } else {
        std::cout << "[Failed] " << test_name << " verification failed!" << std::endl;
        return 1;
    }
}

// Compute golden result for cgerc: A = alpha * x * conj(y^T) + A
void computeGolden(
    std::vector<aclblasComplex>& A, const std::vector<aclblasComplex>& x,
    const std::vector<aclblasComplex>& y, const aclblasComplex& alpha, int64_t m, int64_t n)
{
    // A = alpha * x * conj(y^T) + A
    // For each element A[i][j] = alpha * x[i] * conj(y[j]) + A[i][j]
    for (int64_t i = 0; i < m; i++) {
        for (int64_t j = 0; j < n; j++) {
            aclblasComplex conjY{y[j].real, -y[j].imag};
            A[i * n + j] = alpha * x[i] * conjY + A[i * n + j];
        }
    }
}

int32_t main(int32_t argc, char* argv[])
{
    int32_t deviceId = 0;
    aclrtStream stream = nullptr;
    aclblasHandle handle = nullptr;

    aclError ret = aclInit(nullptr);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclInit failed. ERROR: %d\n", ret); return ret);

    ret = aclrtSetDevice(deviceId);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSetDevice failed. ERROR: %d\n", ret); aclFinalize(); return ret);

    ret = aclrtCreateStream(&stream);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtCreateStream failed. ERROR: %d\n", ret); aclFinalize(); return ret);

    aclblasCreate(&handle);
    aclblasSetStream(handle, stream);

    constexpr int64_t m = 4;
    constexpr int64_t n = 4;
    constexpr int64_t incx = 1;
    constexpr int64_t incy = 1;
    constexpr int64_t lda = m;

    aclblasComplex alpha{1.0f, 0.0f};

    std::vector<aclblasComplex> xHost(m);
    for (int64_t i = 0; i < m; i++) {
        xHost[i] = aclblasComplex{i + 1.0f, i + 0.5f};
    }

    std::vector<aclblasComplex> yHost(n);
    for (int64_t i = 0; i < n; i++) {
        yHost[i] = aclblasComplex{i + 2.0f, i + 1.5f};
    }

    std::vector<aclblasComplex> AHost(m * n);
    for (int64_t i = 0; i < m * n; i++) {
        AHost[i] = aclblasComplex{i * 0.1f, i * 0.2f};
    }

    std::vector<aclblasComplex> goldenHost = AHost;
    computeGolden(goldenHost, xHost, yHost, alpha, m, n);

    size_t xSize = m * sizeof(aclblasComplex);
    size_t ySize = n * sizeof(aclblasComplex);
    size_t aSize = m * n * sizeof(aclblasComplex);

    aclblasComplex* xDevice = nullptr;
    aclblasComplex* yDevice = nullptr;
    aclblasComplex* ADevice = nullptr;

    aclError aclRet = aclrtMalloc((void**)&xDevice, xSize, ACL_MEM_MALLOC_HUGE_FIRST);
    CHECK_RET(aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMalloc xDevice failed. ERROR: %d\n", aclRet); return aclRet);
    aclRet = aclrtMalloc((void**)&yDevice, ySize, ACL_MEM_MALLOC_HUGE_FIRST);
    CHECK_RET(aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMalloc yDevice failed. ERROR: %d\n", aclRet); return aclRet);
    aclRet = aclrtMalloc((void**)&ADevice, aSize, ACL_MEM_MALLOC_HUGE_FIRST);
    CHECK_RET(aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMalloc ADevice failed. ERROR: %d\n", aclRet); return aclRet);

    aclRet = aclrtMemcpy(xDevice, xSize, xHost.data(), xSize, ACL_MEMCPY_HOST_TO_DEVICE);
    CHECK_RET(aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy xDevice failed. ERROR: %d\n", aclRet); return aclRet);
    aclRet = aclrtMemcpy(yDevice, ySize, yHost.data(), ySize, ACL_MEMCPY_HOST_TO_DEVICE);
    CHECK_RET(aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy yDevice failed. ERROR: %d\n", aclRet); return aclRet);
    aclRet = aclrtMemcpy(ADevice, aSize, AHost.data(), aSize, ACL_MEMCPY_HOST_TO_DEVICE);
    CHECK_RET(aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy ADevice failed. ERROR: %d\n", aclRet); return aclRet);

    ret = aclblasCgerc(handle, m, n, alpha, xDevice, incx, yDevice, incy, ADevice, lda);
    CHECK_RET(ret == ACLBLAS_STATUS_SUCCESS, LOG_PRINT("aclblasCgerc failed. ERROR: %d\n", ret); return ret);

    aclRet = aclrtSynchronizeStream(stream);
    CHECK_RET(aclRet == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", aclRet); return aclRet);

    aclRet = aclrtMemcpy(AHost.data(), aSize, ADevice, aSize, ACL_MEMCPY_DEVICE_TO_HOST);
    CHECK_RET(aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy AHost failed. ERROR: %d\n", aclRet); return aclRet);

    aclrtFree(xDevice);
    aclrtFree(yDevice);
    aclrtFree(ADevice);

    aclblasDestroy(handle);
    aclrtDestroyStream(stream);
    aclrtResetDevice(deviceId);
    aclFinalize();

    return VerifyResult(AHost, goldenHost, "cgerc_test");
}