#!/usr/bin/python3 -i
#
# Copyright (c) 2015-2023 The Khronos Group Inc.
# Copyright (c) 2015-2023 Valve Corporation
# Copyright (c) 2015-2023 LunarG, Inc.
# Copyright (c) 2015-2023 Google Inc.
# Copyright (c) 2023-2023 RasterGrid Kft.
#
# 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.

import os
from generators.generator_utils import buildListVUID, getVUID, PlatformGuardHelper
from generators.vulkan_object import Handle, Command, Struct, Member, Param
from generators.base_generator import BaseGenerator

# This class is a container for any source code, data, or other behavior that is necessary to
# customize the generator script for a specific target API variant (e.g. Vulkan SC). As such,
# all of these API-specific interfaces and their use in the generator script are part of the
# contract between this repository and its downstream users. Changing or removing any of these
# interfaces or their use in the generator script will have downstream effects and thus
# should be avoided unless absolutely necessary.
class APISpecific:
    # Tells whether an object handle type is implicitly destroyed because it does not have
    # destroy APIs or its parent object type does not have destroy APIs
    @staticmethod
    def IsImplicitlyDestroyed(targetApiName: str, handleType: str) -> bool:
        match targetApiName:

            # Vulkan specific implicitly destroyed handle types
            case 'vulkan':
                implicitly_destroyed_set = {
                'VkDisplayKHR',
                'VkDisplayModeKHR'
                }

        return handleType in implicitly_destroyed_set


    # Returns whether allocation callback related VUIDs are enabled
    @staticmethod
    def AreAllocVUIDsEnabled(targetApiName: str) -> bool:
        match targetApiName:

            # Vulkan has allocation callback related VUIDs
            case 'vulkan':
                return True


class ObjectTrackerOutputGenerator(BaseGenerator):
    def __init__(self,
                 valid_usage_file):
        BaseGenerator.__init__(self)
        self.valid_vuids = buildListVUID(valid_usage_file)

        # Commands which are not autogenerated but still intercepted
        self.no_autogen_list = [
            'vkDestroyInstance',
            'vkCreateInstance',
            'vkCreateDevice',
            'vkEnumeratePhysicalDevices',
            'vkGetPhysicalDeviceQueueFamilyProperties',
            'vkGetPhysicalDeviceQueueFamilyProperties2',
            'vkGetPhysicalDeviceQueueFamilyProperties2KHR',
            'vkGetDeviceQueue',
            'vkGetDeviceQueue2',
            'vkCreateDescriptorSetLayout',
            'vkDestroyDescriptorPool',
            'vkDestroyCommandPool',
            'vkAllocateCommandBuffers',
            'vkAllocateDescriptorSets',
            'vkFreeDescriptorSets',
            'vkFreeCommandBuffers',
            'vkUpdateDescriptorSets',
            'vkBeginCommandBuffer',
            'vkGetDescriptorSetLayoutSupport',
            'vkGetDescriptorSetLayoutSupportKHR',
            'vkDestroySwapchainKHR',
            'vkGetSwapchainImagesKHR',
            'vkCmdPushDescriptorSetKHR',
            'vkCmdPushDescriptorSet2KHR',
            'vkDestroyDevice',
            'vkResetDescriptorPool',
            'vkGetPhysicalDeviceDisplayPropertiesKHR',
            'vkGetPhysicalDeviceDisplayProperties2KHR',
            'vkGetDisplayModePropertiesKHR',
            'vkGetDisplayModeProperties2KHR',
            'vkCreateFramebuffer',
            'vkSetDebugUtilsObjectNameEXT',
            'vkSetDebugUtilsObjectTagEXT',
            'vkCreateDescriptorUpdateTemplate',
            'vkCreateDescriptorUpdateTemplateKHR',
            'vkCmdBuildAccelerationStructuresKHR',
            'vkCmdBuildAccelerationStructuresIndirectKHR',
            'vkBuildAccelerationStructuresKHR',
            'vkCreateRayTracingPipelinesKHR',
            'vkExportMetalObjectsEXT',
            'vkGetDescriptorEXT',
            'vkGetPrivateData',
            'vkSetPrivateData',
            ]
        # These VUIDS are not implicit, but are best handled in this layer. Codegen for vkDestroy calls will generate a key
        # which is translated here into a good VU.  Saves ~40 checks.
        self.manual_vuids = dict()
        self.manual_vuids = {
            "fence-compatalloc": "\"VUID-vkDestroyFence-fence-01121\"",
            "fence-nullalloc": "\"VUID-vkDestroyFence-fence-01122\"",
            "event-compatalloc": "\"VUID-vkDestroyEvent-event-01146\"",
            "event-nullalloc": "\"VUID-vkDestroyEvent-event-01147\"",
            "buffer-compatalloc": "\"VUID-vkDestroyBuffer-buffer-00923\"",
            "buffer-nullalloc": "\"VUID-vkDestroyBuffer-buffer-00924\"",
            "image-compatalloc": "\"VUID-vkDestroyImage-image-01001\"",
            "image-nullalloc": "\"VUID-vkDestroyImage-image-01002\"",
            "shaderModule-compatalloc": "\"VUID-vkDestroyShaderModule-shaderModule-01092\"",
            "shaderModule-nullalloc": "\"VUID-vkDestroyShaderModule-shaderModule-01093\"",
            "pipeline-compatalloc": "\"VUID-vkDestroyPipeline-pipeline-00766\"",
            "pipeline-nullalloc": "\"VUID-vkDestroyPipeline-pipeline-00767\"",
            "sampler-compatalloc": "\"VUID-vkDestroySampler-sampler-01083\"",
            "sampler-nullalloc": "\"VUID-vkDestroySampler-sampler-01084\"",
            "renderPass-compatalloc": "\"VUID-vkDestroyRenderPass-renderPass-00874\"",
            "renderPass-nullalloc": "\"VUID-vkDestroyRenderPass-renderPass-00875\"",
            "descriptorUpdateTemplate-compatalloc": "\"VUID-vkDestroyDescriptorUpdateTemplate-descriptorSetLayout-00356\"",
            "descriptorUpdateTemplate-nullalloc": "\"VUID-vkDestroyDescriptorUpdateTemplate-descriptorSetLayout-00357\"",
            "imageView-compatalloc": "\"VUID-vkDestroyImageView-imageView-01027\"",
            "imageView-nullalloc": "\"VUID-vkDestroyImageView-imageView-01028\"",
            "pipelineCache-compatalloc": "\"VUID-vkDestroyPipelineCache-pipelineCache-00771\"",
            "pipelineCache-nullalloc": "\"VUID-vkDestroyPipelineCache-pipelineCache-00772\"",
            "pipelineLayout-compatalloc": "\"VUID-vkDestroyPipelineLayout-pipelineLayout-00299\"",
            "pipelineLayout-nullalloc": "\"VUID-vkDestroyPipelineLayout-pipelineLayout-00300\"",
            "descriptorSetLayout-compatalloc": "\"VUID-vkDestroyDescriptorSetLayout-descriptorSetLayout-00284\"",
            "descriptorSetLayout-nullalloc": "\"VUID-vkDestroyDescriptorSetLayout-descriptorSetLayout-00285\"",
            "semaphore-compatalloc": "\"VUID-vkDestroySemaphore-semaphore-01138\"",
            "semaphore-nullalloc": "\"VUID-vkDestroySemaphore-semaphore-01139\"",
            "queryPool-compatalloc": "\"VUID-vkDestroyQueryPool-queryPool-00794\"",
            "queryPool-nullalloc": "\"VUID-vkDestroyQueryPool-queryPool-00795\"",
            "bufferView-compatalloc": "\"VUID-vkDestroyBufferView-bufferView-00937\"",
            "bufferView-nullalloc": "\"VUID-vkDestroyBufferView-bufferView-00938\"",
            "surface-compatalloc": "\"VUID-vkDestroySurfaceKHR-surface-01267\"",
            "surface-nullalloc": "\"VUID-vkDestroySurfaceKHR-surface-01268\"",
            "framebuffer-compatalloc": "\"VUID-vkDestroyFramebuffer-framebuffer-00893\"",
            "framebuffer-nullalloc": "\"VUID-vkDestroyFramebuffer-framebuffer-00894\"",
            "VkGraphicsPipelineCreateInfo-basePipelineHandle": "\"VUID-VkGraphicsPipelineCreateInfo-flags-07984\"",
            "VkComputePipelineCreateInfo-basePipelineHandle": "\"VUID-VkComputePipelineCreateInfo-flags-07984\"",
            "VkRayTracingPipelineCreateInfoNV-basePipelineHandle": "\"VUID-VkRayTracingPipelineCreateInfoNV-flags-07984\"",
			"VkRayTracingPipelineCreateInfoKHR-basePipelineHandle": "\"VUID-VkRayTracingPipelineCreateInfoKHR-flags-07984\"",
            "VkVideoSessionKHR-videoSession-compatalloc": "\"VUID-vkDestroyVideoSessionKHR-videoSession-07193\"",
            "VkVideoSessionKHR-videoSession-nullalloc": "\"VUID-vkDestroyVideoSessionKHR-videoSession-07194\"",
            "VkVideoSessionParametersKHR-videoSessionParameters-compatalloc": "\"VUID-vkDestroyVideoSessionParametersKHR-videoSessionParameters-07213\"",
            "VkVideoSessionParametersKHR-videoSessionParameters-nullalloc": "\"VUID-vkDestroyVideoSessionParametersKHR-videoSessionParameters-07214\"",
            "VkAccelerationStructureKHR-accelerationStructure-compatalloc": "\"VUID-vkDestroyAccelerationStructureKHR-accelerationStructure-02443\"",
            "VkAccelerationStructureKHR-accelerationStructure-nullalloc": "\"VUID-vkDestroyAccelerationStructureKHR-accelerationStructure-02444\"",
            "VkAccelerationStructureNV-accelerationStructure-compatalloc": "\"VUID-vkDestroyAccelerationStructureNV-accelerationStructure-03753\"",
            "VkAccelerationStructureNV-accelerationStructure-nullalloc": "\"VUID-vkDestroyAccelerationStructureNV-accelerationStructure-03754\"",
            "shader-compatalloc": "\"VUID-vkDestroyShaderEXT-pAllocator-08483\"",
            "shader-nullalloc": "\"VUID-vkDestroyShaderEXT-pAllocator-08484\"",
            "callback-compatalloc": "\"VUID-vkDestroyDebugReportCallbackEXT-instance-01242\"",
            "callback-nullalloc": "\"VUID-vkDestroyDebugReportCallbackEXT-instance-01243\"",
            "messenger-compatalloc": "\"VUID-vkDestroyDebugUtilsMessengerEXT-messenger-01915\"",
            "messenger-nullalloc": "\"VUID-vkDestroyDebugUtilsMessengerEXT-messenger-01916\"",
            "operation-compatalloc": "\"VUID-vkDestroyDeferredOperationKHR-operation-03434\"",
            "operation-nullalloc": "\"VUID-vkDestroyDeferredOperationKHR-operation-03435\"",
            "micromap-compatalloc": "\"VUID-vkDestroyMicromapEXT-micromap-07442\"",
            "micromap-nullalloc": "\"VUID-vkDestroyMicromapEXT-micromap-07443\"",
            "privateDataSlot-compatalloc": "\"VUID-vkDestroyPrivateDataSlot-privateDataSlot-04062\"",
            "privateDataSlot-nullalloc": "\"VUID-vkDestroyPrivateDataSlot-privateDataSlot-04063\"",
            "validationCache-compatalloc": "\"VUID-vkDestroyValidationCacheEXT-validationCache-01537\"",
            "validationCache-nullalloc": "\"VUID-vkDestroyValidationCacheEXT-validationCache-01538\"",
           }

        # Structures that do not define parent/commonparent VUIDs for vulkan handles.
        # This overlaps with https://gitlab.khronos.org/vulkan/vulkan/-/issues/3553#note_424431
        self.structs_that_forgot_about_parent_vuids = [
            'VkMappedMemoryRange',
            'VkSparseMemoryBind',
            'VkSparseImageMemoryBind',
            'VkBufferViewCreateInfo',
            'VkPipelineShaderStageCreateInfo',
            'VkPipelineLayoutCreateInfo',
            'VkBufferMemoryBarrier',
            'VkImageMemoryBarrier',
            'VkImageMemoryRequirementsInfo2',
            'VkBufferMemoryRequirementsInfo2',
            'VkImageSparseMemoryRequirementsInfo2',
            'VkSemaphoreWaitInfo',
            'VkSemaphoreSignalInfo',
            'VkBufferMemoryBarrier2',
            'VkImageMemoryBarrier2',
            'VkSemaphoreSubmitInfo',
            'VkCommandBufferSubmitInfo',
            'VkBindVideoSessionMemoryInfoKHR',
            'VkVideoPictureResourceInfoKHR',
            'VkVideoDecodeInfoKHR',
            'VkMemoryGetWin32HandleInfoKHR',
            'VkMemoryGetFdInfoKHR',
            'VkImportSemaphoreWin32HandleInfoKHR',
            'VkSemaphoreGetWin32HandleInfoKHR',
            'VkImportSemaphoreFdInfoKHR',
            'VkSemaphoreGetFdInfoKHR',
            'VkImportFenceWin32HandleInfoKHR',
            'VkFenceGetWin32HandleInfoKHR',
            'VkImportFenceFdInfoKHR',
            'VkFenceGetFdInfoKHR',
            'VkPhysicalDeviceSurfaceInfo2KHR',
            'VkMemoryMapInfoKHR',
            'VkMemoryUnmapInfoKHR',
            'VkVideoEncodeSessionParametersGetInfoKHR',
            'VkVideoEncodeInfoKHR',
            'VkCuFunctionCreateInfoNVX',
            'VkCuLaunchInfoNVX',
            'VkConditionalRenderingBeginInfoEXT',
            'VkMemoryGetAndroidHardwareBufferInfoANDROID',
            'VkPipelineLibraryCreateInfoKHR',
            'VkGeometryAABBNV',
            'VkAccelerationStructureMemoryRequirementsInfoNV',
            'VkCopyMemoryToImageInfoEXT',
            'VkCopyImageToMemoryInfoEXT',
            'VkHostImageLayoutTransitionInfoEXT',
            'VkReleaseSwapchainImagesInfoEXT',
            'VkIndirectCommandsLayoutTokenNV',
            'VkBufferCaptureDescriptorDataInfoEXT',
            'VkImageCaptureDescriptorDataInfoEXT',
            'VkImageViewCaptureDescriptorDataInfoEXT',
            'VkSamplerCaptureDescriptorDataInfoEXT',
            'VkMemoryGetZirconHandleInfoFUCHSIA',
            'VkImportSemaphoreZirconHandleInfoFUCHSIA',
            'VkSemaphoreGetZirconHandleInfoFUCHSIA',
            'VkMemoryGetRemoteAddressInfoNV',
            'VkMicromapCreateInfoEXT',
            'VkMicromapBuildInfoEXT',
            'VkCopyMicromapToMemoryInfoEXT',
            'VkCopyMemoryToMicromapInfoEXT',
            'VkDescriptorSetBindingReferenceVALVE',
            'VkShaderCreateInfoEXT',
            'VkAccelerationStructureCreateInfoKHR',
            'VkCopyAccelerationStructureToMemoryInfoKHR',
            'VkCopyMemoryToAccelerationStructureInfoKHR',
            'VkDisplaySurfaceCreateInfoKHR',
            'VkDisplayPlaneInfo2KHR',
            'VkSparseBufferMemoryBindInfo',
            'VkSparseImageOpaqueMemoryBindInfo',
            'VkSparseImageMemoryBindInfo',
            'VkImportMemoryBufferCollectionFUCHSIA',
            'VkBufferCollectionBufferCreateInfoFUCHSIA',
            'VkBufferCollectionImageCreateInfoFUCHSIA',
            'VkImageSwapchainCreateInfoKHR',
            'VkSamplerYcbcrConversionInfo',
            'VkShaderModuleValidationCacheCreateInfoEXT',
            'VkGraphicsPipelineShaderGroupsCreateInfoNV',
            'VkSubpassShadingPipelineCreateInfoHUAWEI',
            'VkBindImageMemorySwapchainInfoKHR',
            'VkRenderingFragmentDensityMapAttachmentInfoEXT',
            'VkRenderingFragmentShadingRateAttachmentInfoKHR',
            'VkSwapchainPresentFenceInfoEXT',
            'VkIndirectCommandsStreamNV',
            'VkDescriptorBufferBindingPushDescriptorBufferHandleEXT',
            'VkBufferDeviceAddressInfo',
            'VkDeviceMemoryOpaqueCaptureAddressInfo',
            'VkPipelineIndirectDeviceAddressInfoNV',
            'VkAccelerationStructureDeviceAddressInfoKHR',
            ]

        # Commands that define parent requirements using "-parent" instead of "-commonparent" VUID
        # for the cases when multiple objects share the same parent.
        self.use_parent_instead_of_commonparent_commands = [
            'vkBindBufferMemory',
            'vkBindImageMemory',
            'vkMergePipelineCaches',
            'vkCreateGraphicsPipelines',
            'vkCreateComputePipelines',
            'vkUpdateDescriptorSetWithTemplate',
            'vkUpdateDescriptorSetWithTemplateKHR',
            'vkAcquireNextImageKHR',
            'vkMergeValidationCachesEXT',
            'vkBindOpticalFlowSessionImageNV',
            ]

        # Commands that include the dispatchable parameter in the "-commonparent" list of handles.
        # For example, if the dispatchable parameter is VkDevice, but commonparent VUID defines
        # VkInstance as a parent then such VUID will also ask to validate VkDevice againt VkInstance.
        # In other cases, the dispatchable parameter is considered to be a parent and it is not
        # included in the VUID's list of handles.
        self.dispatchable_has_parent_vuid_commands = [
            'vkGetDeviceGroupSurfacePresentModesKHR',
            'vkDisplayPowerControlEXT',
            'vkRegisterDisplayEventEXT',
            'vkGetPhysicalDeviceSurfaceSupportKHR',
            'vkGetPhysicalDeviceSurfaceCapabilitiesKHR',
            'vkGetPhysicalDeviceSurfaceFormatsKHR',
            'vkGetPhysicalDeviceSurfacePresentModesKHR',
            'vkGetPhysicalDevicePresentRectanglesKHR',
            'vkGetPhysicalDeviceSurfaceCapabilities2EXT'
        ]

    # Work up Handle's parents to see if it VkDevice
    def isParentDevice(self, handle: Handle) -> bool:
        while handle.parent is not None:
            if handle.parent.name == 'VkDevice':
                return True
            handle = handle.parent
        return False

    def allComments(self, lines: list[str]) -> bool:
        not_empty_lines = [line for line in lines.splitlines() if line.strip()]
        return all(line.startswith('//') for line in not_empty_lines)

    def generate(self):
        self.write(f'''// *** THIS FILE IS GENERATED - DO NOT EDIT ***
            // See {os.path.basename(__file__)} for modifications

            /***************************************************************************
            *
            * Copyright (c) 2015-2023 The Khronos Group Inc.
            * Copyright (c) 2015-2023 Valve Corporation
            * Copyright (c) 2015-2023 LunarG, Inc.
            * Copyright (c) 2015-2023 Google Inc.
            * Copyright (c) 2015-2023 RasterGrid Kft.
            *
            * 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.
            ****************************************************************************/\n''')
        self.write('// NOLINTBEGIN') # Wrap for clang-tidy to ignore

        if self.filename == 'object_tracker.h':
            self.generateHeader()
        elif self.filename == 'object_tracker.cpp':
            self.generateSource()
        else:
            self.write(f'\nFile name {self.filename} has no code to generate\n')

        self.write('// NOLINTEND') # Wrap for clang-tidy to ignore


    def generateHeader(self):
        out = []
        guard_helper = PlatformGuardHelper()
        for command in self.vk.commands.values():
            out.extend(guard_helper.add_guard(command.protect))
            (pre_call_validate, pre_call_record, post_call_record) = self.generateFunctionBody(command)

            prototype = (command.cPrototype.split('VKAPI_CALL ')[1])[2:-1]
            terminator = ';\n' if 'ValidationCache' in command.name else ' override;\n'

            # If a function has manual implementation we still need to generate its signature in the header file
            function_signature_for_no_autogen = command.name in self.no_autogen_list and not command.name == 'vkCreateInstance'

            generate_pre_call_validate = (pre_call_validate and not self.allComments(pre_call_validate)) or function_signature_for_no_autogen

            if generate_pre_call_validate:
                prePrototype = prototype.replace(')', ', const ErrorObject& error_obj)')
                out.append(f'bool PreCallValidate{prePrototype} const{terminator}')

            prototype = prototype.replace(')', ', const RecordObject& record_obj)')
            if pre_call_record:
                out.append(f'void PreCallRecord{prototype}{terminator}')

            if post_call_record:
                out.append(f'void PostCallRecord{prototype}{terminator}')

        out.extend(guard_helper.add_guard(None))

        out.append('''

            void PostCallRecordDestroyInstance(VkInstance instance, const VkAllocationCallbacks *pAllocator, const RecordObject& record_obj) override;
            void PreCallRecordResetDescriptorPool(VkDevice device, VkDescriptorPool descriptorPool, VkDescriptorPoolResetFlags flags, const RecordObject& record_obj) override;
            void PostCallRecordGetPhysicalDeviceQueueFamilyProperties(VkPhysicalDevice physicalDevice, uint32_t *pQueueFamilyPropertyCount, VkQueueFamilyProperties *pQueueFamilyProperties, const RecordObject& record_obj) override;
            void PreCallRecordFreeCommandBuffers(VkDevice device, VkCommandPool commandPool, uint32_t commandBufferCount, const VkCommandBuffer *pCommandBuffers, const RecordObject& record_obj) override;
            void PreCallRecordFreeDescriptorSets(VkDevice device, VkDescriptorPool descriptorPool, uint32_t descriptorSetCount, const VkDescriptorSet *pDescriptorSets, const RecordObject& record_obj) override;
            void PostCallRecordGetPhysicalDeviceQueueFamilyProperties2(VkPhysicalDevice physicalDevice, uint32_t *pQueueFamilyPropertyCount, VkQueueFamilyProperties2 *pQueueFamilyProperties, const RecordObject& record_obj) override;
            void PostCallRecordGetPhysicalDeviceQueueFamilyProperties2KHR(VkPhysicalDevice physicalDevice, uint32_t *pQueueFamilyPropertyCount, VkQueueFamilyProperties2 *pQueueFamilyProperties, const RecordObject& record_obj) override;
            void PostCallRecordGetPhysicalDeviceDisplayPropertiesKHR(VkPhysicalDevice physicalDevice, uint32_t *pPropertyCount, VkDisplayPropertiesKHR *pProperties, const RecordObject& record_obj) override;
            void PostCallRecordGetDisplayModePropertiesKHR(VkPhysicalDevice physicalDevice, VkDisplayKHR display, uint32_t *pPropertyCount, VkDisplayModePropertiesKHR *pProperties, const RecordObject& record_obj) override;
            void PostCallRecordGetPhysicalDeviceDisplayProperties2KHR(VkPhysicalDevice physicalDevice, uint32_t *pPropertyCount, VkDisplayProperties2KHR *pProperties, const RecordObject& record_obj) override;
            void PostCallRecordGetDisplayModeProperties2KHR(VkPhysicalDevice physicalDevice, VkDisplayKHR display, uint32_t *pPropertyCount, VkDisplayModeProperties2KHR *pProperties, const RecordObject& record_obj) override;
            void PostCallRecordGetPhysicalDeviceDisplayPlanePropertiesKHR(VkPhysicalDevice physicalDevice, uint32_t* pPropertyCount, VkDisplayPlanePropertiesKHR* pProperties, const RecordObject& record_obj) override;
            void PostCallRecordGetPhysicalDeviceDisplayPlaneProperties2KHR(VkPhysicalDevice physicalDevice, uint32_t* pPropertyCount, VkDisplayPlaneProperties2KHR* pProperties, const RecordObject& record_obj) override;
            ''')
        self.write("".join(out))

    def generateSource(self):
        out = []
        out.append('// clang-format off')
        out.append('''
#include "chassis.h"
#include "object_tracker/object_lifetime_validation.h"
ReadLockGuard ObjectLifetimes::ReadLock() const { return ReadLockGuard(validation_object_mutex, std::defer_lock); }
WriteLockGuard ObjectLifetimes::WriteLock() { return WriteLockGuard(validation_object_mutex, std::defer_lock); }

// ObjectTracker undestroyed objects validation function
bool ObjectLifetimes::ReportUndestroyedInstanceObjects(VkInstance instance, const Location& loc) const {
    bool skip = false;
    const std::string error_code = "VUID-vkDestroyInstance-instance-00629";
''')
        for handle in [x for x in self.vk.handles.values() if not x.dispatchable and not self.isParentDevice(x)]:
            comment_prefix = ''
            if APISpecific.IsImplicitlyDestroyed(self.targetApiName, handle.name):
                comment_prefix = '// No destroy API or implicitly freed/destroyed -- do not report: '
            out.append(f'    {comment_prefix}skip |= ReportLeakedInstanceObjects(instance, kVulkanObjectType{handle.name[2:]}, error_code, loc);\n')
        out.append('    return skip;\n')
        out.append('}\n')

        out.append('''
bool ObjectLifetimes::ReportUndestroyedDeviceObjects(VkDevice device, const Location& loc) const {
    bool skip = false;
    const std::string error_code = "VUID-vkDestroyDevice-device-05137";
''')

        comment_prefix = ''
        if APISpecific.IsImplicitlyDestroyed(self.targetApiName, 'VkCommandBuffer'):
            comment_prefix = '// No destroy API or implicitly freed/destroyed -- do not report: '
        out.append(f'    {comment_prefix}skip |= ReportLeakedDeviceObjects(device, kVulkanObjectTypeCommandBuffer, error_code, loc);\n')

        for handle in [x for x in self.vk.handles.values() if not x.dispatchable and self.isParentDevice(x)]:
            comment_prefix = ''
            if APISpecific.IsImplicitlyDestroyed(self.targetApiName, handle.name):
                comment_prefix = '// No destroy API or implicitly freed/destroyed -- do not report: '
            out.append(f'    {comment_prefix}skip |= ReportLeakedDeviceObjects(device, kVulkanObjectType{handle.name[2:]}, error_code, loc);\n')
        out.append('    return skip;\n')
        out.append('}\n')

        out.append('\nvoid ObjectLifetimes::DestroyLeakedInstanceObjects() {\n')
        for handle in [x for x in self.vk.handles.values() if not x.dispatchable and not self.isParentDevice(x)]:
            out.append(f'    DestroyUndestroyedObjects(kVulkanObjectType{handle.name[2:]});\n')
        out.append('}\n')

        out.append('\nvoid ObjectLifetimes::DestroyLeakedDeviceObjects() {\n')
        out.append('    DestroyUndestroyedObjects(kVulkanObjectTypeCommandBuffer);\n')
        for handle in [x for x in self.vk.handles.values() if not x.dispatchable and self.isParentDevice(x)]:
            out.append(f'    DestroyUndestroyedObjects(kVulkanObjectType{handle.name[2:]});\n')
        out.append('}\n')

        out.append('// clang-format on')
        guard_helper = PlatformGuardHelper()
        for command in [x for x in self.vk.commands.values() if x.name not in self.no_autogen_list]:
            out.extend(guard_helper.add_guard(command.protect))

            # Generate object handling code
            (pre_call_validate, pre_call_record, post_call_record) = self.generateFunctionBody(command)

            prototype = (command.cPrototype.split('VKAPI_CALL ')[1])[2:-1]

            # Output PreCallValidateAPI function if necessary
            if pre_call_validate:
                prePrototype = prototype.replace(')', ', const ErrorObject& error_obj)')
                if self.allComments(pre_call_validate):
                     out.append(f'''
                        // {command.name}:
                        {pre_call_validate}
                        ''')
                elif command.alias:
                    # For alias that are promoted, just point to new function, ErrorObject will allow us to distinguish the caller
                    paramList = [param.name for param in command.params]
                    paramList.append('error_obj')
                    params = ', '.join(paramList)
                    out.append(f'''
                        bool ObjectLifetimes::PreCallValidate{prePrototype} const {{
                            return PreCallValidate{command.alias[2:]}({params});
                        }}
                        ''')
                else:
                    out.append(f'''
                        bool ObjectLifetimes::PreCallValidate{prePrototype} const {{
                            bool skip = false;
                            {pre_call_validate}
                            return skip;
                        }}
                        ''')

            # Output PreCallRecordAPI function if necessary
            if pre_call_record:
                postPrototype = prototype.replace(')', ', const RecordObject& record_obj)')
                out.append(f'''
                    void ObjectLifetimes::PreCallRecord{postPrototype} {{
                        {pre_call_record}
                    }}
                    ''')

            # Output PosCallRecordAPI function if necessary
            if post_call_record:
                out.append('\n')
                postPrototype = f'void ObjectLifetimes::PostCallRecord{prototype} {{\n'
                postPrototype = postPrototype.replace(')', ', const RecordObject& record_obj)')
                if command.returnType == 'VkResult':
                    failureCondition = 'record_obj.result != VK_SUCCESS'
                    # VK_INCOMPLETE is considered a success
                    if 'EnumeratePhysicalDeviceGroups' in command.name:
                        failureCondition += ' && record_obj.result != VK_INCOMPLETE'
                    if 'CreateShadersEXT' in command.name:
                        failureCondition += ' && record_obj.result != VK_ERROR_INCOMPATIBLE_SHADER_BINARY_EXT'
                    # The two createpipelines APIs may create on failure -- skip the success result check
                    if 'CreateGraphicsPipelines' not in command.name and 'CreateComputePipelines' not in command.name and 'CreateRayTracingPipelines' not in command.name:
                        postPrototype = postPrototype.replace('{', f'{{\n    if ({failureCondition}) return;')
                out.append(postPrototype)

                out.append(f'{post_call_record}\n')
                out.append('}\n')

        out.extend(guard_helper.add_guard(None))

        self.write("".join(out))

    def structContainsObject(self, struct: Struct) -> bool:
        for member in struct.members:
            if member.type in self.vk.handles:
                return True
            # recurse for member structs, guard against infinite recursion
            elif member.type in self.vk.structs and member.type != struct.name:
                if self.structContainsObject(self.vk.structs[member.type]):
                    return True
        return False

    def getAllocVUID(self, param: Param, allocType: str) -> str:
        # Do not report allocation callback VUIDs if the target API does not support them
        if not APISpecific.AreAllocVUIDsEnabled(self.targetApiName):
            return "kVUIDUndefined"

        lookup_string = '%s-%s' %(param.name, allocType)
        vuid = self.manual_vuids.get(lookup_string, None)
        if vuid is not None:
            return vuid
        lookup_string = '%s-%s-%s' %(param.type, param.name, allocType)
        vuid = self.manual_vuids.get(lookup_string, None)
        if vuid is not None:
            return vuid
        return "kVUIDUndefined"


    def getParamVUID(self, member: Member, parentName: str) -> str:
        # Replace with alias if one
        alias = self.vk.commands[parentName].alias if parentName in self.vk.commands else None
        parent = alias if alias else parentName
        vuid_string = f'VUID-{parent}-{member.name}-parameter'

        # TODO: Currently just brute force check all VUs, but should be smarter what makes these `-parameter` VUs
        param_vuid = f'"{vuid_string}"' if vuid_string in self.valid_vuids else "kVUIDUndefined"
        return param_vuid

    def hasFieldParentVUID(self, member: Member, structName: str) -> bool:
        # Not a vulkan handle. Parent VUIDs are only for vulkan handles
        if member.type not in self.vk.handles:
            return False

        # All struct members that are vulkan handles should have parent VUID.
        # There is a bunch of structs though, for which the specification does not do this.
        return structName not in self.structs_that_forgot_about_parent_vuids


    def hasParameterParentVUID(self, parameter: Member, commandName: str) -> bool:
        # Check for commands that, except the first dispatchable parameter,
        # do not have other parameters that are Vulkan handles.
        # Such commands can't have parent VUIDs (e.g. vkQueueWaitIdle)
        params = self.vk.commands[commandName].params
        only_dispatchable_parameter = len([x for x in params if x.type in self.vk.handles and (not x.pointer or x.const)]) == 1
        if only_dispatchable_parameter:
            return False

        # Special case: vkReleaseFullScreenExclusiveModeEXT.
        # The specification does not define a parent VUID for the swapchain parameter.
        # It mentions in a free form that device should be associated with a swapchain.
        if commandName == 'vkReleaseFullScreenExclusiveModeEXT':
            return False

        # Not a vulkan handle. Parent VUIDs are only for vulkan handles
        if parameter.type not in self.vk.handles:
            return False

        # Skip output parameter
        if parameter.pointer and not parameter.const:
            return False

        # Non-dispatchable handles need parent
        if not self.vk.handles[parameter.type].dispatchable:
            return True

        # Queue/command buffer handles have parent vuids
        if parameter.type == 'VkQueue' or parameter.type == 'VkCommandBuffer':
            return True

        # For other dispatchable handles it depends on the API function
        return commandName in self.dispatchable_has_parent_vuid_commands


    def getFieldParentVUID(self, member: Member, structName: str, commandName: str, singleParentVuid: bool) -> str:
        if not self.hasFieldParentVUID(member, structName):
            return 'kVUIDUndefined'

        # Special cases
        # Make sure function is not in 'structs_that_forgot_about_parent_vuids'
        if commandName == 'vkCreateImageView' and member.name == 'image':
            return "\"VUID-vkCreateImageView-image-09179\""
        if 'vkCmdBeginRenderPass' in commandName and member.name == 'pAttachments':
            return "\"VUID-VkRenderPassBeginInfo-framebuffer-02780\""
        if 'vkGetPipelineExecutablePropertiesKHR' in commandName and member.name == 'pipeline':
                return "\"VUID-vkGetPipelineExecutablePropertiesKHR-pipeline-03271\""
        if 'VkPipelineExecutableInfoKHR' in structName and member.name == 'pipeline':
            if commandName == 'vkGetPipelineExecutableStatisticsKHR':
                return "\"VUID-vkGetPipelineExecutableStatisticsKHR-pipeline-03273\""
            elif commandName == 'vkGetPipelineExecutableInternalRepresentationsKHR':
                return "\"VUID-vkGetPipelineExecutableInternalRepresentationsKHR-pipeline-03277\""

        if singleParentVuid:
            return getVUID(self.valid_vuids, f'VUID-{structName}-{member.name}-parent')
        else:
            return getVUID(self.valid_vuids, f'VUID-{structName}-commonparent')


    def getParameterParentVUID(self, parameter: Member, commandName: str, singleParentVuid: bool) -> str:
        if not self.hasParameterParentVUID(parameter, commandName):
            return 'kVUIDUndefined'

        # Replace with alias if one
        alias = self.vk.commands[commandName].alias
        parent = alias if alias else commandName

        if singleParentVuid or (commandName in self.use_parent_instead_of_commonparent_commands):
            return getVUID(self.valid_vuids, f'VUID-{parent}-{parameter.name}-parent')
        else:
            return getVUID(self.valid_vuids, f'VUID-{parent}-commonparent')


    # recursively walks struct members (and command params)
    # parentName == Struct or Command calling into this
    # topCommand == The command called from (when in a struct)
    def validateObjects(self, members: list[Member], prefix: str, arrayIndex: int, parentName: str, topCommand: str, errorLoc: str) -> str:
        pre_call_validate = ''
        index = f'index{str(arrayIndex)}'
        arrayIndex += 1
        is_struct = (parentName != topCommand)

        if is_struct:
            handle_types = [x.type for x in members if self.hasFieldParentVUID(x, parentName)]
        else:
            handle_types = [x.type for x in members if self.hasParameterParentVUID(x, parentName)]

        single_parent_vuid = (len(handle_types) == 1)

        # Parent type in parent/commonparent VUIDs: Device, PhysicalDevice or Instance
        parent_type = 'Device'
        for type in handle_types:
            current_type = self.vk.handles[type].parent.type
            if current_type in ['VK_OBJECT_TYPE_PHYSICAL_DEVICE', 'VK_OBJECT_TYPE_DISPLAY_KHR', 'VK_OBJECT_TYPE_DISPLAY_MODE_KHR']:
                parent_type = 'PhysicalDevice'
                # continue search in case instance parent exists
            elif current_type == 'VK_OBJECT_TYPE_INSTANCE':
                parent_type = 'Instance'
                # end search
                break

        # Process any objects in this structure and recurse for any sub-structs in this struct
        for member in members:
            if member.pointer and not member.const:
                continue # ignore output parameters

            if member.type in self.vk.handles:
                if member.noAutoValidity:
                    nullAllowed = 'true'
                elif member.length:
                    nullAllowed = str(member.optionalPointer).lower()
                else:
                    nullAllowed = str(member.optional).lower()

                param_vuid = self.getParamVUID(member, parentName)

                if is_struct:
                    parent_vuid = self.getFieldParentVUID(member, parentName, topCommand, single_parent_vuid)
                else:
                    parent_vuid = self.getParameterParentVUID(member, parentName, single_parent_vuid)

                # Do not generate validation code for the function's dispatchable parameter (the first one).
                # Validation of such parameters is always successful based on the model of how the chassis
                # dispactches the calls to ValidationObject. If invalid handle is used it will cause
                # crash/corruption on the chassis level (in get_dispatch_key or later). And if correct handle
                # is passed, then due to the mapping done by get_dispatch_key() the handle will belong to the
                # retrieved validation object, which guarantees positive result of the parenting test.
                function_dispatchable_parameter = not is_struct and member == members[0]
                if function_dispatchable_parameter:
                    chassis_parent_vuid = parent_vuid # keep parent vuid for "Checked by chassis" comment
                    parent_vuid = 'kVUIDUndefined'

                parent_object_type = '' # empty value corresponds to C++ default parameter value (kVulkanObjectTypeDevice)
                if parent_type != 'Device':
                    parent_object_type = f', kVulkanObjectType{parent_type}'

                # Special case: VUID-VkSwapchainCreateInfoKHR-commonparent
                # This is a problematic VUID in the form it's defined in the spec 1.3.265. Details (internal link):
                # https://gitlab.khronos.org/vulkan/vulkan/-/issues/2983
                # Ideally such VUID should be split into two since there are two different parents:
                # VkInstance for surface and VkDevice for oldSwapchain
                if parentName == 'VkSwapchainCreateInfoKHR' and member.name == 'oldSwapchain':
                    parent_object_type = ", kVulkanObjectTypeDevice"

                if member.length:
                    location = f'{errorLoc}.dot(Field::{member.name}, {index})'
                    countName = f'{prefix}{member.length}'
                    pre_call_validate += f'''
                        if (({countName} > 0) && ({prefix}{member.name})) {{
                            for (uint32_t {index} = 0; {index} < {countName}; ++{index}) {{
                                skip |= ValidateObject({prefix}{member.name}[{index}], kVulkanObjectType{member.type[2:]}, {nullAllowed}, {param_vuid}, {parent_vuid}, {location}{parent_object_type});
                            }}
                        }}\n'''
                elif 'basePipelineHandle' in member.name:
                    pre_call_validate += f'if (({prefix}flags & VK_PIPELINE_CREATE_DERIVATIVE_BIT) && ({prefix}basePipelineIndex == -1))\n'
                    manual_vuid_index = parentName + '-' + member.name
                    param_vuid = self.manual_vuids.get(manual_vuid_index, "kVUIDUndefined")
                    pre_call_validate += f'skip |= ValidateObject({prefix}{member.name}, kVulkanObjectType{member.type[2:]}, false, {param_vuid}, {parent_vuid}, error_obj.location{parent_object_type});\n'
                elif function_dispatchable_parameter:
                    pre_call_validate += f'// Checked by chassis: {member.name}: {param_vuid}\n'
                    if chassis_parent_vuid != 'kVUIDUndefined':
                        pre_call_validate += f'// Checked by chassis: {member.name}: {chassis_parent_vuid}\n'
                else:
                    location = f'{errorLoc}.dot(Field::{member.name})'
                    if self.vk.commands[topCommand].device and self.vk.handles[member.type].instance:
                        # Use case when for device-level API call we should use instance-level validation object
                        pre_call_validate += 'auto instance_data = GetLayerDataPtr(get_dispatch_key(instance), layer_data_map);\n'
                        pre_call_validate += 'auto instance_object_lifetimes = instance_data->GetValidationObject<ObjectLifetimes>();\n'
                        pre_call_validate += f'skip |= instance_object_lifetimes->ValidateObject({prefix}{member.name}, kVulkanObjectType{member.type[2:]}, {nullAllowed}, {param_vuid}, {parent_vuid}, {location}{parent_object_type});\n'
                    else:
                        pre_call_validate += f'skip |= ValidateObject({prefix}{member.name}, kVulkanObjectType{member.type[2:]}, {nullAllowed}, {param_vuid}, {parent_vuid}, {location}{parent_object_type});\n'

            # Handle Structs that contain objects at some level
            elif member.type in self.vk.structs:
                nested_struct = []
                struct = self.vk.structs[member.type]

                # Structs at first level will have an object
                contains_object = self.structContainsObject(struct)

                # Struct Array
                if member.length is not None:
                    # Update struct prefix
                    nested_struct.append(f'if ({prefix}{member.name}) {{\n')
                    nested_struct.append(f'    for (uint32_t {index} = 0; {index} < {prefix}{member.length}; ++{index}) {{\n')
                    new_error_loc = f'{index}_loc'
                    nested_struct.append(f'[[maybe_unused]] const Location {new_error_loc} = {errorLoc}.dot(Field::{member.name}, {index});\n')
                    new_prefix = f'{prefix}{member.name}[{index}].'
                # Single Struct Pointer
                elif member.pointer:
                    # Update struct prefix
                    new_prefix = f'{prefix}{member.name}->'
                    # Declare safe_VarType for struct
                    nested_struct.append(f'if ({prefix}{member.name}) {{\n')
                    new_error_loc = f'{member.name}_loc'
                    nested_struct.append(f'    [[maybe_unused]] const Location {new_error_loc} = {errorLoc}.dot(Field::{member.name});\n')
                # Single Nested Struct
                else:
                    # Update struct prefix
                    new_prefix = f'{prefix}{member.name}.'
                    new_error_loc = f'{member.name}_loc'
                    nested_struct.append(f'[[maybe_unused]] const Location {new_error_loc} = {errorLoc}.dot(Field::{member.name});\n')
                    # Process sub-structs

                if contains_object:
                    nested_struct.append(self.validateObjects(struct.members, new_prefix, arrayIndex, member.type, topCommand, new_error_loc))

                contains_pNext = False
                if struct.extendedBy:
                    guard_helper = PlatformGuardHelper()
                    for extendedBy in struct.extendedBy:
                        extended_struct = self.vk.structs[extendedBy]
                        extended_members = [x for x in extended_struct.members if x.type in self.vk.handles]
                        if not extended_members:
                            continue
                        contains_pNext = True
                        nested_struct.extend(guard_helper.add_guard(extended_struct.protect))
                        nested_struct.append(f'if (auto pNext = vku::FindStructInPNextChain<{extendedBy}>({new_prefix}pNext)) {{\n')
                        nested_struct.append(f'    const Location pNext_loc = {new_error_loc}.pNext(Struct::{extendedBy});\n')
                        nested_struct.append(self.validateObjects(extended_members, 'pNext->', arrayIndex + 1, extendedBy, topCommand, 'pNext_loc'))
                        nested_struct.append('}\n')
                    nested_struct.extend(guard_helper.add_guard(None))
                # Close indentation
                if member.length is not None:
                    nested_struct.append('}\n')
                    nested_struct.append('}\n')
                elif member.pointer:
                    nested_struct.append('}\n')

                # Only print if called into validateObjects
                if contains_object or contains_pNext:
                    pre_call_validate += "".join(nested_struct)

        return pre_call_validate
    #
    # For a particular API, generate the object handling code
    def generateFunctionBody(self, command: Command):
        pre_call_validate = ''
        pre_call_record = ''
        post_call_record = ''
        isGetCreate = 'vkGet' in command.name and command.params[-1].pointer and not command.params[-1].const
        isCreate = any(x in command.name for x in ['Create', 'Allocate', 'Enumerate', 'RegisterDeviceEvent', 'RegisterDisplayEvent', 'AcquirePerformanceConfigurationINTEL']) or isGetCreate
        isDestroy = any(x in command.name for x in ['Destroy', 'Free', 'ReleasePerformanceConfigurationINTEL'])

        pre_call_validate += self.validateObjects(command.params, '', 0, command.name, command.name, 'error_obj.location')

        # Handle object create operations if last parameter is created by this call
        if isCreate:
            handle_type = command.params[-1].type
            isCreatePipelines = 'CreateGraphicsPipelines' in command.name or 'CreateComputePipelines' in command.name or 'CreateRayTracingPipelines' in command.name
            isCreateShaders = 'CreateShaders' in command.name

            if handle_type in self.vk.handles:
                # Check for special case where multiple handles are returned
                objectArray = command.params[-1].length is not None

                if objectArray:
                    if isCreatePipelines:
                        post_call_record += 'if (VK_ERROR_VALIDATION_FAILED_EXT == record_obj.result) return;\n'

                    post_call_record += f'if ({command.params[-1].name}) {{\n'
                    countIsPointer = '*' if command.params[-2].type == 'uint32_t' and command.params[-2].pointer else ''
                    post_call_record += f'for (uint32_t index = 0; index < {countIsPointer}{command.params[-1].length}; index++) {{\n'

                if isCreatePipelines:
                    post_call_record += 'if (!pPipelines[index]) continue;\n'
                elif isCreateShaders:
                    post_call_record += 'if (!pShaders[index]) break;\n'

                allocator = command.params[-2].name if command.params[-2].type == 'VkAllocationCallbacks' else 'nullptr'
                objectDest = f'{command.params[-1].name}[index]' if objectArray else f'*{command.params[-1].name}'
                location = f'record_obj.location.dot(Field::{command.params[-1].name}, index)' if objectArray else 'record_obj.location'

                post_call_record += f'CreateObject({objectDest}, kVulkanObjectType{handle_type[2:]}, {allocator}, {location});\n'
                if objectArray:
                    post_call_record += '}\n'
                    post_call_record += '}\n'
            # Physical device groups are not handles, but a set of handles, they need to be tracked as well
            elif handle_type == 'VkPhysicalDeviceGroupProperties':
                post_call_record += f'''
                    if ({command.params[-1].name}) {{
                        const RecordObject record_obj(vvl::Func::vkEnumeratePhysicalDevices, VK_SUCCESS);
                        for (uint32_t device_group_index = 0; device_group_index < *{command.params[-2].name}; device_group_index++) {{
                            PostCallRecordEnumeratePhysicalDevices({command.params[0].name}, &{command.params[-1].name}[device_group_index].physicalDeviceCount, {command.params[-1].name}[device_group_index].physicalDevices, record_obj);
                        }}
                    }}\n'''
        # Handle object destroy operations
        if isDestroy:
            # Check for special case where multiple handles are returned
            handle_param = command.params[-1] if 'ReleasePerformanceConfigurationINTEL' in command.name else command.params[-2]
            allocator = 'nullptr' if 'ReleasePerformanceConfigurationINTEL' in command.name else 'pAllocator'

            compatallocVUID = self.getAllocVUID(handle_param, "compatalloc")
            nullallocVUID = self.getAllocVUID(handle_param, "nullalloc")
            if handle_param.type in self.vk.handles:
                # Call Destroy a single time
                pre_call_validate += f'skip |= ValidateDestroyObject({handle_param.name}, kVulkanObjectType{handle_param.type[2:]}, {allocator}, {compatallocVUID}, {nullallocVUID}, error_obj.location);\n'
                pre_call_record += f'RecordDestroyObject({handle_param.name}, kVulkanObjectType{handle_param.type[2:]});\n'

        return pre_call_validate, pre_call_record, post_call_record

