/*
 * Copyright (c) 2023-2026 The Khronos Group Inc.
 * Copyright (c) 2023-2026 Valve Corporation
 * Copyright (c) 2023-2026 LunarG, Inc.
 * Copyright (C) 2025 Arm Limited.
 *
 * Licensed 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
 */

#include "cooperative_matrix_helper.h"
#include <spirv/unified1/spirv.hpp>
#include <vulkan/vulkan_core.h>
#include <vulkan/utility/vk_struct_helper.hpp>
#include "containers/container_utils.h"
#include "layer_validation_tests.h"

CooperativeMatrixHelper::CooperativeMatrixHelper(VkLayerTest& layer_test) : layer_test(layer_test) {
    const VkPhysicalDevice gpu = layer_test.Gpu();
    uint32_t props_count = 0;
    vk::GetPhysicalDeviceCooperativeMatrixPropertiesKHR(gpu, &props_count, nullptr);
    for (uint32_t i = 0; i < props_count; i++) {
        coop_matrix_props.emplace_back(vku::InitStruct<VkCooperativeMatrixPropertiesKHR>());
    }
    vk::GetPhysicalDeviceCooperativeMatrixPropertiesKHR(gpu, &props_count, coop_matrix_props.data());

    if (layer_test.IsExtensionsEnabled(VK_NV_COOPERATIVE_MATRIX_2_EXTENSION_NAME)) {
        props_count = 0;
        vk::GetPhysicalDeviceCooperativeMatrixFlexibleDimensionsPropertiesNV(gpu, &props_count, nullptr);
        for (uint32_t i = 0; i < props_count; i++) {
            coop_matrix_flex_props.emplace_back(vku::InitStruct<VkCooperativeMatrixFlexibleDimensionsPropertiesNV>());
        }
        vk::GetPhysicalDeviceCooperativeMatrixFlexibleDimensionsPropertiesNV(gpu, &props_count, coop_matrix_flex_props.data());
    }
}

bool CooperativeMatrixHelper::SupportsStage(VkShaderStageFlags required_stage) {
    if (required_stage == 0) {
        return true;
    }
    VkPhysicalDeviceCooperativeMatrixPropertiesKHR props = vku::InitStructHelper();
    layer_test.GetPhysicalDeviceProperties2(props);
    return ((props.cooperativeMatrixSupportedStages & required_stage) != 0);
}

bool CooperativeMatrixHelper::Has8BitComponentType(const VkCooperativeMatrixPropertiesKHR& prop) const {
    const VkComponentTypeKHR type_8bit[6] = {
        VK_COMPONENT_TYPE_SINT8_KHR,       VK_COMPONENT_TYPE_UINT8_KHR,       VK_COMPONENT_TYPE_SINT8_PACKED_NV,
        VK_COMPONENT_TYPE_UINT8_PACKED_NV, VK_COMPONENT_TYPE_FLOAT8_E4M3_EXT, VK_COMPONENT_TYPE_FLOAT8_E5M2_EXT,
    };
    return IsValueIn(prop.AType, type_8bit) || IsValueIn(prop.BType, type_8bit) || IsValueIn(prop.CType, type_8bit) ||
           IsValueIn(prop.ResultType, type_8bit);
}

bool CooperativeMatrixHelper::Has64BitComponentType(const VkCooperativeMatrixPropertiesKHR& prop) const {
    const VkComponentTypeKHR type_64bit[3] = {VK_COMPONENT_TYPE_FLOAT64_KHR, VK_COMPONENT_TYPE_SINT64_KHR,
                                              VK_COMPONENT_TYPE_UINT64_KHR};
    return IsValueIn(prop.AType, type_64bit) || IsValueIn(prop.BType, type_64bit) || IsValueIn(prop.CType, type_64bit) ||
           IsValueIn(prop.ResultType, type_64bit);
}

bool CooperativeMatrixHelper::Has16x16UintProperty() const {
    for (const auto& prop : coop_matrix_props) {
        if (prop.scope == VK_SCOPE_SUBGROUP_KHR && prop.KSize == 16 && prop.MSize == 16 && prop.NSize == 16 &&
            prop.AType == VK_COMPONENT_TYPE_UINT8_KHR && prop.BType == VK_COMPONENT_TYPE_UINT8_KHR &&
            prop.CType == VK_COMPONENT_TYPE_UINT32_KHR && prop.ResultType == VK_COMPONENT_TYPE_UINT32_KHR) {
            return true;
        }
    }
    return false;
}

bool CooperativeMatrixHelper::HasValidProperty(VkScopeKHR scope, uint32_t m, uint32_t n, uint32_t k,
                                               VkComponentTypeKHR type) const {
    bool found_a = false;
    bool found_b = false;
    bool found_c = false;
    bool found_r = false;
    for (const auto& prop : coop_matrix_props) {
        if (prop.scope == scope && prop.AType == type && prop.MSize == m && prop.KSize == k) {
            found_a = true;
        }
        if (prop.scope == scope && prop.BType == type && prop.KSize == k && prop.NSize == n) {
            found_b = true;
        }
        if (prop.scope == scope && prop.CType == type && prop.MSize == m && prop.NSize == n) {
            found_c = true;
        }
        if (prop.scope == scope && prop.ResultType == type && prop.MSize == m && prop.NSize == n) {
            found_r = true;
        }
    }
    if (found_a && found_b && found_c && found_r) {
        return true;
    }

    found_a = false;
    found_b = false;
    found_c = false;
    found_r = false;
    for (const auto& prop : coop_matrix_flex_props) {
        if (prop.scope == scope && prop.AType == type && (m % prop.MGranularity) == 0 && (k % prop.KGranularity) == 0) {
            found_a = true;
        }
        if (prop.scope == scope && prop.BType == type && (k % prop.KGranularity) == 0 && (n % prop.NGranularity) == 0) {
            found_b = true;
        }
        if (prop.scope == scope && prop.CType == type && (m % prop.MGranularity) == 0 && (n % prop.NGranularity) == 0) {
            found_c = true;
        }
        if (prop.scope == scope && prop.ResultType == type && (m % prop.MGranularity) == 0 && (n % prop.NGranularity) == 0) {
            found_r = true;
        }
    }
    if (found_a && found_b && found_c && found_r) {
        return true;
    }

    return false;
}

bool CooperativeMatrixHelper::HasSupportedMatrixUse(VkScopeKHR scope, uint32_t rows, uint32_t cols, VkComponentTypeKHR type,
                                                    uint32_t use) const {
    for (const auto& prop : coop_matrix_props) {
        if (prop.scope != scope) continue;
        if (use == spv::CooperativeMatrixUseMatrixAKHR && prop.AType == type && prop.MSize == rows && prop.KSize == cols)
            return true;
        if (use == spv::CooperativeMatrixUseMatrixBKHR && prop.BType == type && prop.KSize == rows && prop.NSize == cols)
            return true;
        if (use == spv::CooperativeMatrixUseMatrixAccumulatorKHR && (prop.CType == type || prop.ResultType == type) &&
            prop.MSize == rows && prop.NSize == cols)
            return true;
    }
    for (const auto& prop : coop_matrix_flex_props) {
        if (prop.scope != scope) continue;
        if (use == spv::CooperativeMatrixUseMatrixAKHR && prop.AType == type && (rows % prop.MGranularity) == 0 &&
            (cols % prop.KGranularity) == 0)
            return true;
        if (use == spv::CooperativeMatrixUseMatrixBKHR && prop.BType == type && (rows % prop.KGranularity) == 0 &&
            (cols % prop.NGranularity) == 0)
            return true;
        if (use == spv::CooperativeMatrixUseMatrixAccumulatorKHR && (prop.CType == type || prop.ResultType == type) &&
            (rows % prop.MGranularity) == 0 && (cols % prop.NGranularity) == 0)
            return true;
    }
    return false;
}

const char* CooperativeMatrixHelper::VkComponentTypeToGLSL(VkComponentTypeKHR type) const {
    switch (type) {
        case VK_COMPONENT_TYPE_FLOAT16_KHR:
            return "float16_t";
        case VK_COMPONENT_TYPE_FLOAT32_KHR:
            return "float32_t";
        case VK_COMPONENT_TYPE_FLOAT64_KHR:
            return "float64_t";
        case VK_COMPONENT_TYPE_SINT8_KHR:
            return "int8_t";
        case VK_COMPONENT_TYPE_SINT16_KHR:
            return "int16_t";
        case VK_COMPONENT_TYPE_SINT32_KHR:
            return "int32_t";
        case VK_COMPONENT_TYPE_SINT64_KHR:
            return "int64_t";
        case VK_COMPONENT_TYPE_UINT8_KHR:
            return "uint8_t";
        case VK_COMPONENT_TYPE_UINT16_KHR:
            return "uint16_t";
        case VK_COMPONENT_TYPE_UINT32_KHR:
            return "uint32_t";
        case VK_COMPONENT_TYPE_UINT64_KHR:
            return "uint64_t";
        default:
            return "unknown";
    }
}