/* Copyright (c) 2015-2026 The Khronos Group Inc.
 * Copyright (c) 2015-2026 Valve Corporation
 * Copyright (c) 2015-2026 LunarG, Inc.
 * Copyright (C) 2015-2024 Google 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 "generated/enum_flag_bits.h"
#include "stateless/stateless_validation.h"
#include <vulkan/utility/vk_format_utils.h>
#include "utils/image_utils.h"
#include "utils/math_utils.h"
#include "containers/range.h"

namespace stateless {

bool Device::manual_PreCallValidateAllocateMemory(VkDevice device, const VkMemoryAllocateInfo* pAllocateInfo,
                                                  const VkAllocationCallbacks* pAllocator, VkDeviceMemory* pMemory,
                                                  const Context& context) const {
    bool skip = false;
    const auto& error_obj = context.error_obj;

    if (!pAllocateInfo) {
        return skip;
    }
    const Location allocate_info_loc = error_obj.location.dot(Field::pAllocateInfo);
    auto chained_prio_struct = vku::FindStructInPNextChain<VkMemoryPriorityAllocateInfoEXT>(pAllocateInfo->pNext);
    if (chained_prio_struct && (chained_prio_struct->priority < 0.0f || chained_prio_struct->priority > 1.0f)) {
        skip |= LogError("VUID-VkMemoryPriorityAllocateInfoEXT-priority-02602", device,
                         allocate_info_loc.pNext(Struct::VkMemoryPriorityAllocateInfoEXT, Field::priority), "is %f",
                         chained_prio_struct->priority);
    }

    auto flags_info = vku::FindStructInPNextChain<VkMemoryAllocateFlagsInfo>(pAllocateInfo->pNext);
    const VkMemoryAllocateFlags flags = flags_info ? flags_info->flags : 0;

    skip |= ValidateAllocateMemoryExternal(device, *pAllocateInfo, flags, allocate_info_loc);

    if (flags) {
        const Location flags_loc = allocate_info_loc.pNext(Struct::VkMemoryAllocateFlagsInfo, Field::flags);
        if ((flags & VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_CAPTURE_REPLAY_BIT) && !enabled_features.bufferDeviceAddressCaptureReplay) {
            skip |= LogError("VUID-VkMemoryAllocateInfo-flags-03330", device, flags_loc,
                             "has VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_CAPTURE_REPLAY_BIT set, but "
                             "bufferDeviceAddressCaptureReplay feature is not enabled.");
        }
        if ((flags & VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT) && !enabled_features.bufferDeviceAddress) {
            skip |= LogError("VUID-VkMemoryAllocateInfo-flags-03331", device, flags_loc,
                             "has VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT set, but bufferDeviceAddress feature is not enabled.");
        }
    }
    return skip;
}

bool Device::ValidateDeviceImageMemoryRequirements(VkDevice device, const VkDeviceImageMemoryRequirements& memory_requirements,
                                                   const Context& context) const {
    bool skip = false;
    const Location loc = context.error_obj.location.dot(Field::pInfo);

    const VkImageCreateInfo& create_info = *(memory_requirements.pCreateInfo);
    if (vku::FindStructInPNextChain<VkImageSwapchainCreateInfoKHR>(create_info.pNext)) {
        skip |= LogError("VUID-VkDeviceImageMemoryRequirements-pCreateInfo-06416", device,
                         loc.dot(Field::pCreateInfo).dot(Field::pNext), "chain contains VkImageSwapchainCreateInfoKHR.\n%s",
                         PrintPNextChain(Struct::VkImageCreateInfo, create_info.pNext).c_str());
    }
    if (vku::FindStructInPNextChain<VkImageDrmFormatModifierExplicitCreateInfoEXT>(create_info.pNext)) {
        skip |= LogError("VUID-VkDeviceImageMemoryRequirements-pCreateInfo-06776", device,
                         loc.dot(Field::pCreateInfo).dot(Field::pNext),
                         "chain contains VkImageDrmFormatModifierExplicitCreateInfoEXT.\n%s",
                         PrintPNextChain(Struct::VkImageCreateInfo, create_info.pNext).c_str());
    }

    const bool has_disjoint = (create_info.flags & VK_IMAGE_CREATE_DISJOINT_BIT) != 0;
    if (vkuFormatIsMultiplane(create_info.format) && has_disjoint) {
        if (memory_requirements.planeAspect == VK_IMAGE_ASPECT_NONE) {
            skip |= LogError("VUID-VkDeviceImageMemoryRequirements-pCreateInfo-06417", device, loc.dot(Field::planeAspect),
                             "is VK_IMAGE_ASPECT_NONE with a multi-planar format and disjoint flag.");
        } else if ((create_info.tiling == VK_IMAGE_TILING_LINEAR || create_info.tiling == VK_IMAGE_TILING_OPTIMAL) &&
                   !IsOnlyOneValidPlaneAspect(create_info.format, memory_requirements.planeAspect)) {
            skip |= LogError("VUID-VkDeviceImageMemoryRequirements-pCreateInfo-06419", device, loc.dot(Field::planeAspect),
                             "is %s but is invalid for %s.", string_VkImageAspectFlags(memory_requirements.planeAspect).c_str(),
                             string_VkFormat(create_info.format));
        }
    }
    const uint64_t external_format = GetExternalFormat(memory_requirements.pCreateInfo->pNext);
    if (external_format != 0) {
        skip |= LogError("VUID-VkDeviceImageMemoryRequirements-pNext-06996", device, loc.dot(Field::pCreateInfo),
                         "pNext chain contains VkExternalFormatANDROID with externalFormat %" PRIu64 ".", external_format);
    }

    if (create_info.tiling == VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT || has_disjoint) {
        skip |= context.ValidateFlags(loc.dot(Field::planeAspect), vvl::FlagBitmask::VkImageAspectFlagBits,
                                      AllVkImageAspectFlagBits, memory_requirements.planeAspect, kOptionalSingleBit,
                                      "VUID-VkDeviceImageMemoryRequirements-planeAspect-12399", nullptr, false);
    }

    return skip;
}

bool Device::manual_PreCallValidateGetDeviceImageMemoryRequirements(VkDevice device, const VkDeviceImageMemoryRequirements* pInfo,
                                                                    VkMemoryRequirements2* pMemoryRequirements,
                                                                    const Context& context) const {
    bool skip = false;
    skip |= ValidateDeviceImageMemoryRequirements(device, *pInfo, context);
    return skip;
}

bool Device::manual_PreCallValidateGetDeviceImageSparseMemoryRequirements(
    VkDevice device, const VkDeviceImageMemoryRequirements* pInfo, uint32_t* pSparseMemoryRequirementCount,
    VkSparseImageMemoryRequirements2* pSparseMemoryRequirements, const Context& context) const {
    bool skip = false;
    skip |= ValidateDeviceImageMemoryRequirements(device, *pInfo, context);
    return skip;
}
bool Device::manual_PreCallValidateCmdDecompressMemoryEXT(VkCommandBuffer commandBuffer,
                                                          const VkDecompressMemoryInfoEXT* pDecompressMemoryInfoEXT,
                                                          const Context& context) const {
    bool skip = false;
    const auto& error_obj = context.error_obj;

    if (!enabled_features.memoryDecompression) {
        skip |= LogError("VUID-vkCmdDecompressMemoryEXT-memoryDecompression-11761", commandBuffer, error_obj.location,
                         "The memoryDecompression feature must be enabled.");
    }

    const auto& props = phys_dev_ext_props.memory_decompression_props;
    if ((pDecompressMemoryInfoEXT->decompressionMethod & props.decompressionMethods) == 0) {
        skip |= LogError("VUID-VkDecompressMemoryInfoEXT-decompressionMethod-11763", commandBuffer,
                         error_obj.location.dot(Field::decompressionMethod),
                         "(0x%" PRIx64 ") is not a supported decompression method bit (supported mask: 0x%" PRIx64 ").",
                         pDecompressMemoryInfoEXT->decompressionMethod, props.decompressionMethods);
    }

    if (pDecompressMemoryInfoEXT->decompressionMethod & VK_MEMORY_DECOMPRESSION_METHOD_GDEFLATE_1_0_BIT_EXT) {
        const Location memory_info_loc = error_obj.location.dot(Field::pDecompressMemoryInfoEXT);
        for (uint32_t i = 0; i < pDecompressMemoryInfoEXT->regionCount; ++i) {
            const Location region_loc = memory_info_loc.dot(Field::pRegions, i);
            const VkDecompressMemoryRegionEXT mem_region = pDecompressMemoryInfoEXT->pRegions[i];
            if (mem_region.decompressedSize > 65536) {
                skip |= LogError("VUID-VkDecompressMemoryInfoEXT-decompressionMethod-11762", commandBuffer,
                                 region_loc.dot(Field::decompressedSize),
                                 "(%" PRIu64 ") must be less than or equal to 65536 bytes.", mem_region.decompressedSize);
            }

            if (mem_region.compressedSize == 0) {
                skip |= LogError("VUID-VkDecompressMemoryRegionEXT-compressedSize-11795", commandBuffer,
                                 region_loc.dot(Field::compressedSize), "must not be zero.");
            }

            if (mem_region.decompressedSize == 0) {
                skip |= LogError("VUID-VkDecompressMemoryRegionEXT-decompressedSize-11796", commandBuffer,
                                 region_loc.dot(Field::decompressedSize), "must not be zero.");
            }

            if (!IsPointerAligned(mem_region.srcAddress, 4)) {
                skip |=
                    LogError("VUID-VkDecompressMemoryRegionEXT-srcAddress-07685", commandBuffer, region_loc.dot(Field::srcAddress),
                             "(0x%" PRIx64 ") is not 4-byte aligned.", mem_region.srcAddress);
            }

            if (!IsPointerAligned(mem_region.dstAddress, 4)) {
                skip |=
                    LogError("VUID-VkDecompressMemoryRegionEXT-dstAddress-07687", commandBuffer, region_loc.dot(Field::dstAddress),
                             "(0x%" PRIx64 ") is not 4-byte aligned.", mem_region.dstAddress);
            }

            const vvl::range<VkDeviceAddress> src_range{mem_region.srcAddress, mem_region.srcAddress + mem_region.compressedSize};
            const vvl::range<VkDeviceAddress> dst_range{mem_region.dstAddress, mem_region.dstAddress + mem_region.decompressedSize};
            if (src_range.intersects(dst_range)) {
                skip |= LogError("VUID-VkDecompressMemoryRegionEXT-srcAddress-07691", commandBuffer,
                                 region_loc.dot(Field::srcAddress), "range %s overlaps with dstAddress range %s.",
                                 string_range_hex(src_range).c_str(), string_range_hex(dst_range).c_str());
            }
        }
    }

    if (!IsPowerOfTwo(pDecompressMemoryInfoEXT->decompressionMethod)) {
        skip |= LogError("VUID-VkDecompressMemoryInfoEXT-decompressionMethod-07690", commandBuffer,
                         error_obj.location.dot(Field::pDecompressMemoryInfoEXT).dot(Field::decompressionMethod),
                         "(0x%" PRIx64 ") must have a single bit set.", pDecompressMemoryInfoEXT->decompressionMethod);
    }

    return skip;
}

bool Device::manual_PreCallValidateCmdDecompressMemoryIndirectCountEXT(
    VkCommandBuffer commandBuffer, VkMemoryDecompressionMethodFlagsEXT decompressionMethod, VkDeviceAddress indirectCommandsAddress,
    VkDeviceAddress indirectCommandsCountAddress, uint32_t maxDecompressionCount, uint32_t stride, const Context& context) const {
    bool skip = false;
    const auto& error_obj = context.error_obj;

    if (!enabled_features.memoryDecompression) {
        skip |= LogError("VUID-vkCmdDecompressMemoryIndirectCountEXT-None-07692", commandBuffer, error_obj.location,
                         "The memoryDecompression feature must be enabled.");
    }

    const auto& props = phys_dev_ext_props.memory_decompression_props;
    if ((decompressionMethod & props.decompressionMethods) == 0) {
        skip |= LogError("VUID-vkCmdDecompressMemoryIndirectCountEXT-decompressionMethod-11810", commandBuffer,
                         error_obj.location.dot(Field::decompressionMethod),
                         "(0x%" PRIx64 ") is not a supported decompression method bit (supported mask: 0x%" PRIx64 ").",
                         decompressionMethod, props.decompressionMethods);
    }

    if (!IsPowerOfTwo(decompressionMethod)) {
        skip |= LogError("VUID-vkCmdDecompressMemoryIndirectCountEXT-decompressionMethod-07690", commandBuffer,
                         error_obj.location.dot(Field::decompressionMethod), "(0x%" PRIx64 ") must have a single bit set.",
                         decompressionMethod);
    }

    if (!IsPointerAligned(indirectCommandsAddress, 4)) {
        skip |= LogError("VUID-vkCmdDecompressMemoryIndirectCountEXT-indirectCommandsAddress-07695", commandBuffer,
                         error_obj.location.dot(Field::indirectCommandsAddress), "(0x%" PRIx64 ") is not aligned to 4 bytes.",
                         indirectCommandsAddress);
    }

    if (!IsPointerAligned(indirectCommandsCountAddress, 4)) {
        skip |= LogError("VUID-vkCmdDecompressMemoryIndirectCountEXT-indirectCommandsCountAddress-07698", commandBuffer,
                         error_obj.location.dot(Field::indirectCommandsCountAddress), "(0x%" PRIx64 ") is not aligned to 4 bytes.",
                         indirectCommandsCountAddress);
    }

    if (!IsIntegerMultipleOf(stride, 4) || stride < sizeof(VkDecompressMemoryRegionEXT)) {
        skip |= LogError(
            "VUID-vkCmdDecompressMemoryIndirectCountEXT-stride-11767", commandBuffer, error_obj.location.dot(Field::stride),
            "(%" PRIu32
            ") must be a multiple of 4 and must be greater than or equal to size of VkDecompressMemoryRegionEXT (%" PRIu64 ").",
            stride, static_cast<uint64_t>(sizeof(VkDecompressMemoryRegionEXT)));
    }

    if (maxDecompressionCount > props.maxDecompressionIndirectCount) {
        skip |= LogError("VUID-vkCmdDecompressMemoryIndirectCountEXT-maxDecompressionCount-11768", commandBuffer,
                         error_obj.location.dot(Field::maxDecompressionCount),
                         "(%" PRIu32
                         ") must be less than or equal to "
                         "VkPhysicalDeviceMemoryDecompressionPropertiesEXT::maxDecompressionIndirectCount (%" PRIu64 ").",
                         maxDecompressionCount, props.maxDecompressionIndirectCount);
    }

    return skip;
}

bool Device::manual_PreCallValidateQueueBindSparse(VkQueue queue, uint32_t bindInfoCount, const VkBindSparseInfo* pBindInfo,
                                                   VkFence fence, const Context& context) const {
    bool skip = false;
    const auto& error_obj = context.error_obj;

    for (uint32_t bind_info_i = 0; bind_info_i < bindInfoCount; ++bind_info_i) {
        const VkBindSparseInfo& bind_info = pBindInfo[bind_info_i];
        for (uint32_t image_bind_i = 0; image_bind_i < bind_info.imageBindCount; ++image_bind_i) {
            const VkSparseImageMemoryBindInfo& image_bind = bind_info.pImageBinds[image_bind_i];
            for (uint32_t bind_i = 0; bind_i < image_bind.bindCount; ++bind_i) {
                const VkSparseImageMemoryBind& bind = image_bind.pBinds[bind_i];
                if (bind.extent.width == 0) {
                    const LogObjectList objlist(queue, image_bind.image);
                    skip |= LogError("VUID-VkSparseImageMemoryBind-extent-09388", objlist,
                                     error_obj.location.dot(Field::pBindInfo, bind_info_i)
                                         .dot(Field::pImageBinds, image_bind_i)
                                         .dot(Field::pBinds, bind_i)
                                         .dot(Field::extent)
                                         .dot(Field::width),
                                     "is zero.");
                }

                if (bind.extent.height == 0) {
                    const LogObjectList objlist(queue, image_bind.image);
                    skip |= LogError("VUID-VkSparseImageMemoryBind-extent-09389", objlist,
                                     error_obj.location.dot(Field::pBindInfo, bind_info_i)
                                         .dot(Field::pImageBinds, image_bind_i)
                                         .dot(Field::pBinds, bind_i)
                                         .dot(Field::extent)
                                         .dot(Field::height),
                                     "is zero.");
                }

                if (bind.extent.depth == 0) {
                    const LogObjectList objlist(queue, image_bind.image);
                    skip |= LogError("VUID-VkSparseImageMemoryBind-extent-09390", objlist,
                                     error_obj.location.dot(Field::pBindInfo, bind_info_i)
                                         .dot(Field::pImageBinds, image_bind_i)
                                         .dot(Field::pBinds, bind_i)
                                         .dot(Field::extent)
                                         .dot(Field::depth),
                                     "is zero.");
                }
            }
        }
    }

    return skip;
}

bool Device::manual_PreCallValidateSetDeviceMemoryPriorityEXT(VkDevice device, VkDeviceMemory memory, float priority,
                                                              const Context& context) const {
    bool skip = false;
    const auto& error_obj = context.error_obj;
    if (!IsBetweenInclusive(priority, 0.0F, 1.0F)) {
        skip |= LogError("VUID-vkSetDeviceMemoryPriorityEXT-priority-06258", device, error_obj.location.dot(Field::priority),
                         "is %f.", priority);
    }
    return skip;
}

bool Device::manual_PreCallValidateGetDynamicRenderingTilePropertiesQCOM(VkDevice device, const VkRenderingInfo* pRenderingInfo,
                                                                         VkTilePropertiesQCOM* pProperties,
                                                                         const Context& context) const {
    bool skip = false;
    const auto& error_obj = context.error_obj;
    const Location rendering_loc = error_obj.location.dot(Field::pRenderingInfo);
    skip |= ValidateRenderingInfo(device, *pRenderingInfo, rendering_loc);
    if (const auto tile_memory_size = vku::FindStructInPNextChain<VkTileMemorySizeInfoQCOM>(pRenderingInfo->pNext)) {
        skip |= ValidateTileMemorySizeInfo(*tile_memory_size, rendering_loc.pNext(Struct::VkTileMemorySizeInfoQCOM));
    }
    return skip;
}

bool Device::ValidateTileMemorySizeInfo(const VkTileMemorySizeInfoQCOM& tile_memory_size_info, const Location& loc) const {
    bool skip = false;
    uint64_t largest_heap_size = 0;
    uint32_t heap_index = 0;
    for (uint32_t i = 0; i < phys_dev_mem_props.memoryHeapCount; i++) {
        if (phys_dev_mem_props.memoryHeaps[i].flags & VK_MEMORY_HEAP_TILE_MEMORY_BIT_QCOM) {
            if (phys_dev_mem_props.memoryHeaps[i].size > largest_heap_size) {
                largest_heap_size = phys_dev_mem_props.memoryHeaps[i].size;
                heap_index = i;
            }
        }
    }
    if (tile_memory_size_info.size > largest_heap_size) {
        skip |= LogError("VUID-VkTileMemorySizeInfoQCOM-size-10729", device, loc.dot(Field::size),
                         "(%" PRIu64 ") must be less than or equal to %" PRIu64 ", found at memoryHeaps[%" PRIu32
                         "],"
                         " the largest VK_MEMORY_HEAP_TILE_MEMORY_BIT_QCOM heap.",
                         tile_memory_size_info.size, largest_heap_size, heap_index);
    }

    return skip;
}
}  // namespace stateless
