/* Copyright (c) 2024-2026 LunarG, Inc.
 *
 * 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
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <spirv/unified1/GLSL.std.450.h>
#include "generated/spirv_grammar_helper.h"
#include "gpuav/core/gpuav.h"
#include "gpuav/resources/gpuav_state_trackers.h"
#include "gpuav/shaders/gpuav_error_codes.h"
#include "gpuav/shaders/gpuav_error_header.h"

namespace gpuav {

static std::string GetSpirvSpecLink(const uint32_t opcode) {
    // Currently the Working Group decided to not provide "real" VUIDs as it would become duplicating the SPIR-V spec
    // So these are not "UNASSIGNED", but instead are "SPIRV" VUs because we can point to the instruction in the SPIR-V spec
    // (https://gitlab.khronos.org/vulkan/vulkan/-/merge_requests/7853)
    return "\nSee more at https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#" + std::string(string_SpvOpcode(opcode));
}
void RegisterSanitizer(Validator& gpuav, CommandBufferSubState& cb) {
    if (!gpuav.gpuav_settings.shader_instrumentation.sanitizer) {
        return;
    }

    cb.on_instrumentation_error_logger_register_functions.emplace_back([](Validator& gpuav, CommandBufferSubState& cb,
                                                                          const LastBound& last_bound) {
        CommandBufferSubState::InstrumentationErrorLogger inst_error_logger =
            [](Validator& gpuav, const Location& loc, const uint32_t* error_record, const InstrumentedShader*,
               std::string& out_error_msg, std::string& out_vuid_msg) {
                using namespace glsl;
                bool error_found = false;
                if (GetErrorGroup(error_record) != kErrorGroup_InstSanitizer) {
                    return error_found;
                }
                error_found = true;

                std::ostringstream strm;

                const uint32_t error_sub_code = GetSubError(error_record);
                switch (error_sub_code) {
                    case kErrorSubCode_Sanitizer_DivideZero: {
                        const uint32_t opcode = error_record[kInst_LogError_ParameterOffset_0];
                        const uint32_t vector_size = error_record[kInst_LogError_ParameterOffset_1];
                        const bool is_float = opcode == spv::OpFMod || opcode == spv::OpFRem;
                        strm << (is_float ? "Float" : "Integer") << " divide by zero. Operand 2 of " << string_SpvOpcode(opcode)
                             << " is ";
                        if (vector_size == 0) {
                            strm << "zero.";
                        } else {
                            strm << "a " << vector_size << "-wide vector which contains a zero value.";
                        }
                        if (is_float) {
                            strm << " The result value is undefined.";
                        }
                        strm << GetSpirvSpecLink(opcode);
                        out_vuid_msg = "SPIRV-Sanitizer-Divide-By-Zero";
                    } break;
                    case kErrorSubCode_Sanitizer_ImageGather: {
                        const uint32_t component_value = error_record[kInst_LogError_ParameterOffset_0];
                        const int32_t signed_value = (int32_t)component_value;
                        strm << "OpImageGather has a component value of ";
                        if (signed_value > 0) {
                            strm << component_value;
                        } else {
                            strm << signed_value;
                        }
                        strm << ", but it must be 0, 1, 2, or 3" << GetSpirvSpecLink(spv::OpImageGather);
                        out_vuid_msg = "SPIRV-Sanitizer-Image-Gather";
                    } break;
                    case kErrorSubCode_Sanitizer_Pow: {
                        // Pow is only valid with a scalar/vector of 16/32-bit float
                        const uint32_t vector_size = error_record[kInst_LogError_ParameterOffset_0];
                        // Should use std::bit_cast but requires c++20
                        const float x_value = *(float*)(error_record + kInst_LogError_ParameterOffset_1);
                        const float y_value = *(float*)(error_record + kInst_LogError_ParameterOffset_2);
                        strm << "Pow (from GLSL.std.450) has an undefined result because x < 0, or x == 0 and y <= 0. ";
                        if (vector_size > 0) {
                            // Would need a new way to print more than 2 bytes out to get this to work
                            strm << "Using a vector of size " << vector_size
                                 << "; currently only scalar values can be printed in error messages.";
                        } else {
                            strm << "X == " << x_value << ", Y == " << y_value;
                        }
                        out_vuid_msg = "SPIRV-Sanitizer-Pow";
                    } break;
                    case kErrorSubCode_Sanitizer_Atan2: {
                        // Atan is only valid with a scalar/vector of 16/32-bit float
                        strm << "Atan2 (from GLSL.std.450) has an undefined result because both the y and x operands are zero.";
                        out_vuid_msg = "SPIRV-Sanitizer-Atan2";
                    } break;
                    case kErrorSubCode_Sanitizer_Fminmax: {
                        // simple encoding done in inst_sanitizer_fminman (sanitizer.comp)
                        const uint32_t invalid_encode = error_record[kInst_LogError_ParameterOffset_0];
                        const bool x_is_invalid = (invalid_encode & 0x1) != 0;
                        const bool y_is_invalid = (invalid_encode & 0x2) != 0;
                        const uint32_t vector_size = error_record[kInst_LogError_ParameterOffset_1];
                        const uint32_t glsl_opcode = error_record[kInst_LogError_ParameterOffset_2];
                        strm << (glsl_opcode == GLSLstd450FMin ? "FMin" : "FMax")
                             << " (from GLSL.std.450) has an undefined result because ";
                        if (x_is_invalid && y_is_invalid) {
                            strm << "both the x and y operands are NaN.";
                        } else if (x_is_invalid) {
                            strm << "the x operand is NaN.";
                        } else {
                            strm << "the y operand is NaN.";
                        }
                        if (vector_size > 0) {
                            strm << " Using a vector of size " << vector_size
                                 << "; currently only scalar values can be printed in error messages.";
                        }
                        out_vuid_msg = "SPIRV-Sanitizer-Fminmax";
                    } break;
                    case kErrorSubCode_Sanitizer_CoopMatAlignment: {
                        const uint32_t opcode = error_record[kInst_LogError_ParameterOffset_0];
                        const uint32_t required_alignment = error_record[kInst_LogError_ParameterOffset_1];
                        const uint32_t misaligned_flags = error_record[kInst_LogError_ParameterOffset_2];
                        const bool stride_misaligned = (misaligned_flags & 0x1) != 0;
                        const bool pointer_misaligned = (misaligned_flags & 0x2) != 0;
                        strm << string_SpvOpcode(opcode) << " has a ";
                        if (stride_misaligned && pointer_misaligned) {
                            strm << "Pointer and Stride operand that are";
                        } else if (pointer_misaligned) {
                            strm << "Pointer operand that is";
                        } else {
                            strm << "Stride operand that is";
                        }
                        strm << " not aligned to the required " << required_alignment
                             << " byte alignment (the lesser of 16 bytes and the natural alignment of a row or column of the "
                                "matrix).";
                        out_vuid_msg = "VUID-RuntimeSpirv-OpCooperativeMatrixLoadKHR-08986";
                    } break;
                    default:
                        error_found = false;
                        break;
                }
                out_error_msg += strm.str();
                return error_found;
            };

        return inst_error_logger;
    });
}

}  // namespace gpuav
