#!/usr/bin/python3
#
# Copyright (c) 2019-2022 Valve Corporation
# Copyright (c) 2019-2022 LunarG, Inc.
# Copyright (c) 2019-2022 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.
#
# Author: Charles Giessen <charles@lunarg.com>

import re
import sys
import operator
from collections import OrderedDict
import generator as gen
from common_codegen import GetFeatureProtect
from generator import GeneratorOptions, OutputGenerator

LICENSE_HEADER = '''
/*
 * Copyright (c) 2019-2022 The Khronos Group Inc.
 * Copyright (c) 2019-2022 Valve Corporation
 * Copyright (c) 2019-2022 LunarG, 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.
 *
 * Author: Charles Giessen <charles@lunarg.com>
 *
 */

/*
 * This file is generated from the Khronos Vulkan XML API Registry.
 */
'''

CUSTOM_FORMATTERS = r'''
template <typename T>
std::string to_hex_str(const T i) {
    std::stringstream stream;
    stream << "0x" << std::setfill('0') << std::setw(sizeof(T)) << std::hex << i;
    return stream.str();
}

template <typename T>
std::string to_hex_str(Printer &p, const T i) {
    if (p.Type() == OutputType::json)
        return std::to_string(i);
    else if (p.Type() == OutputType::vkconfig_output)
        return std::string("\"") + to_hex_str(i) + std::string("\"");
    else
        return to_hex_str(i);
}

'''


# used in the .cpp code
STRUCTURES_TO_GEN = ['VkExtent3D', 'VkExtent2D', 'VkPhysicalDeviceLimits', 'VkPhysicalDeviceFeatures', 'VkPhysicalDeviceSparseProperties',
                     'VkSurfaceCapabilitiesKHR', 'VkSurfaceFormatKHR', 'VkLayerProperties', 'VkPhysicalDeviceToolProperties', 'VkFormatProperties',
                     'VkSurfacePresentScalingCapabilitiesEXT', 'VkSurfacePresentModeCompatibilityEXT', 'VkPhysicalDeviceHostImageCopyProperties']
ENUMS_TO_GEN = ['VkResult', 'VkFormat', 'VkPresentModeKHR',
                'VkPhysicalDeviceType', 'VkImageTiling']
FLAGS_TO_GEN = ['VkSurfaceTransformFlagsKHR', 'VkCompositeAlphaFlagsKHR', 'VkSurfaceCounterFlagsEXT', 'VkQueueFlags',
                'VkDeviceGroupPresentModeFlagsKHR', 'VkFormatFeatureFlags', 'VkFormatFeatureFlags2', 'VkMemoryPropertyFlags', 'VkMemoryHeapFlags']
FLAG_STRINGS_TO_GEN = ['VkQueueFlags']

STRUCT_SHORT_VERSIONS_TO_GEN = ['VkExtent3D']

STRUCT_COMPARISONS_TO_GEN = ['VkSurfaceFormatKHR', 'VkSurfaceFormat2KHR', 'VkSurfaceCapabilitiesKHR',
                             'VkSurfaceCapabilities2KHR', 'VkSurfaceCapabilities2EXT']
# don't generate these structures
STRUCT_BLACKLIST = ['VkVideoProfileListInfoKHR', 'VkVideoProfileInfoKHR', 'VkDrmFormatModifierPropertiesListEXT', 'VkDrmFormatModifierPropertiesEXT', 'VkDrmFormatModifierPropertiesList2EXT']
# These structures are only used in version 1.1, otherwise they are included in the promoted structs
STRUCT_1_1_LIST = ['VkPhysicalDeviceProtectedMemoryFeatures', 'VkPhysicalDeviceShaderDrawParametersFeatures', 'VkPhysicalDeviceSubgroupProperties', 'VkPhysicalDeviceProtectedMemoryProperties']

# generate these structures such that they only print when not in json mode (as json wants them separate)
PORTABILITY_STRUCTS = ['VkPhysicalDevicePortabilitySubsetFeaturesKHR', 'VkPhysicalDevicePortabilitySubsetPropertiesKHR']

# iostream or custom outputter handles these types
PREDEFINED_TYPES = ['char', 'VkBool32', 'uint32_t', 'uint8_t', 'int32_t',
                    'float', 'uint64_t', 'size_t', 'VkDeviceSize', 'int64_t']

NAMES_TO_IGNORE = ['sType', 'pNext', 'stdProfileIdc']

EXTENSION_TYPE_INSTANCE = 'instance'
EXTENSION_TYPE_DEVICE = 'device'
EXTENSION_TYPE_BOTH = 'both'

# Types that need pNext Chains built. 'extends' is the xml tag used in the structextends member. 'type' can be device, instance, or both
EXTENSION_CATEGORIES = OrderedDict((
    ('phys_device_props2',
        {'extends': 'VkPhysicalDeviceProperties2',
         'type': EXTENSION_TYPE_BOTH,
         'holder_type': 'VkPhysicalDeviceProperties2',
         'print_iterator': True,
         'can_show_promoted_structs': True}),
    ('phys_device_mem_props2',
        {'extends': 'VkPhysicalDeviceMemoryProperties2',
         'type': EXTENSION_TYPE_DEVICE,
         'holder_type':'VkPhysicalDeviceMemoryProperties2',
         'print_iterator': False,
         'can_show_promoted_structs': False}),
    ('phys_device_features2',
        {'extends': 'VkPhysicalDeviceFeatures2,VkDeviceCreateInfo',
         'type': EXTENSION_TYPE_DEVICE,
         'holder_type': 'VkPhysicalDeviceFeatures2',
         'print_iterator': True,
         'can_show_promoted_structs': True}),
    ('surface_capabilities2',
        {'extends': 'VkSurfaceCapabilities2KHR',
         'type': EXTENSION_TYPE_BOTH,
         'holder_type': 'VkSurfaceCapabilities2KHR',
         'print_iterator': True,
         'can_show_promoted_structs': False,
         'exclude': ['VkSurfacePresentScalingCapabilitiesEXT', 'VkSurfacePresentModeCompatibilityEXT']}),
    ('format_properties2',
        {'extends': 'VkFormatProperties2',
         'type': EXTENSION_TYPE_DEVICE,
         'holder_type':'VkFormatProperties2',
         'print_iterator': True,
         'can_show_promoted_structs': False}),
    ('queue_properties2',
        {'extends': 'VkQueueFamilyProperties2',
         'type': EXTENSION_TYPE_DEVICE,
         'holder_type': 'VkQueueFamilyProperties2',
         'print_iterator': True,
         'can_show_promoted_structs': False})
                                   ))
class VulkanInfoGeneratorOptions(GeneratorOptions):
    def __init__(self,
                 conventions=None,
                 input=None,
                 filename=None,
                 directory='.',
                 genpath = None,
                 apiname=None,
                 profile=None,
                 versions='.*',
                 emitversions='.*',
                 defaultExtensions=None,
                 addExtensions=None,
                 removeExtensions=None,
                 emitExtensions=None,
                 sortProcedure=None,
                 prefixText='',
                 genFuncPointers=True,
                 protectFile=True,
                 protectFeature=True,
                 protectProto=None,
                 protectProtoStr=None,
                 apicall='',
                 apientry='',
                 apientryp='',
                 indentFuncProto=True,
                 indentFuncPointer=False,
                 alignFuncParam=0,
                 expandEnumerants=True,
                 ):
        GeneratorOptions.__init__(self,
                 conventions = conventions,
                 filename = filename,
                 directory = directory,
                 genpath = genpath,
                 apiname = apiname,
                 profile = profile,
                 versions = versions,
                 emitversions = emitversions,
                 defaultExtensions = defaultExtensions,
                 addExtensions = addExtensions,
                 removeExtensions = removeExtensions,
                 emitExtensions = emitExtensions,
                 sortProcedure = sortProcedure)
        self.input = input
        self.prefixText = prefixText
        self.genFuncPointers = genFuncPointers
        self.protectFile = protectFile
        self.protectFeature = protectFeature
        self.protectProto = protectProto
        self.protectProtoStr = protectProtoStr
        self.apicall = apicall
        self.apientry = apientry
        self.apientryp = apientryp
        self.indentFuncProto = indentFuncProto
        self.indentFuncPointer = indentFuncPointer
        self.alignFuncParam = alignFuncParam

# VulkanInfoGenerator - subclass of OutputGenerator.
# Generates a vulkan info output helper function


class VulkanInfoGenerator(OutputGenerator):

    def __init__(self,
                 errFile=sys.stderr,
                 warnFile=sys.stderr,
                 diagFile=sys.stdout):
        OutputGenerator.__init__(self, errFile, warnFile, diagFile)

        self.constants = OrderedDict()

        self.types_to_gen = set()

        self.extension_sets = OrderedDict()
        for ext_cat in EXTENSION_CATEGORIES.keys():
            self.extension_sets[ext_cat] = set()

        self.enums = []
        self.flags = []
        self.bitmasks = []
        self.format_ranges = []
        self.all_structures = []
        self.aliases = OrderedDict()

        self.extFuncs = OrderedDict()
        self.extTypes = OrderedDict()

        self.vendor_abbreviations = []
        self.vulkan_versions = []

    def beginFile(self, genOpts):
        gen.OutputGenerator.beginFile(self, genOpts)

        for node in self.registry.reg.findall('enums'):
            if node.get('name') == 'API Constants':
                for item in node.findall('enum'):
                    self.constants[item.get('name')] = item.get('value')

        for node in self.registry.reg.find('extensions').findall('extension'):
            ext = VulkanExtension(node)
            for item in ext.vktypes:
                if item not in self.extTypes:
                    self.extTypes[item] = []
                self.extTypes[item].append(ext)
            for item in ext.vkfuncs:
                self.extFuncs[item] = ext

        # need list of venders to blacklist vendor extensions
        for tag in self.registry.reg.find('tags'):
            if tag.get('name') not in ['KHR', 'EXT']:
                self.vendor_abbreviations.append('_' + tag.get('name'))

        for ver in self.registry.reg.findall('feature'):
            self.vulkan_versions.append(VulkanVersion(ver))

    def endFile(self):
        self.findFormatRanges()

        # gather the types that are needed to generate
        types_to_gen = set()
        for s in ENUMS_TO_GEN:
            types_to_gen.add(s)

        for f in FLAGS_TO_GEN:
            types_to_gen.add(f)

        types_to_gen.update(
            GatherTypesToGen(self.all_structures, STRUCTURES_TO_GEN))
        for key, info in EXTENSION_CATEGORIES.items():
            types_to_gen.update(
                GatherTypesToGen(self.all_structures, self.extension_sets[key], info.get('exclude')))
        types_to_gen = sorted(types_to_gen)

        names_of_structures_to_gen = set()
        for s in self.all_structures:
            if s.name in types_to_gen:
                names_of_structures_to_gen.add(s.name)
        names_of_structures_to_gen = sorted(names_of_structures_to_gen)

        structs_to_comp = set()
        for s in STRUCT_COMPARISONS_TO_GEN:
            structs_to_comp.add(s)
        structs_to_comp.update(
            GatherTypesToGen(self.all_structures, STRUCT_COMPARISONS_TO_GEN))

        for key, value in self.extension_sets.items():
            self.extension_sets[key] = sorted(value)

        self.enums = sorted(self.enums, key=operator.attrgetter('name'))
        self.flags = sorted(self.flags, key=operator.attrgetter('name'))
        self.bitmasks = sorted(self.bitmasks, key=operator.attrgetter('name'))
        self.all_structures = sorted(self.all_structures, key=operator.attrgetter('name'))

        # print the types gathered
        out = ''
        out += LICENSE_HEADER + '\n'
        out += '#include "vulkaninfo.h"\n'
        out += '#include "outputprinter.h"\n'
        out += CUSTOM_FORMATTERS

        for enum in (e for e in self.enums if e.name in types_to_gen):
            out += PrintEnumToString(enum, self)
            out += PrintEnum(enum, self)

        for flag in self.flags:
            if flag.name in types_to_gen:
                for bitmask in (b for b in self.bitmasks if b.name == flag.enum):
                    out += PrintBitMask(bitmask, flag.name, self)

            if flag.name in FLAG_STRINGS_TO_GEN:
                for bitmask in (b for b in self.bitmasks if b.name == flag.enum):
                    out += PrintBitMaskToString(bitmask, flag.name, self)

        for s in (x for x in self.all_structures if x.name in types_to_gen and x.name not in STRUCT_BLACKLIST):
            out += PrintStructure(s)

        for key, value in EXTENSION_CATEGORIES.items():
            out += PrintChainStruct(key, self.extension_sets[key], self.all_structures, value, self.extTypes, self.aliases, self.vulkan_versions)

        for s in (x for x in self.all_structures if x.name in structs_to_comp):
            out += PrintStructComparisonForwardDecl(s)
        for s in (x for x in self.all_structures if x.name in structs_to_comp):
            out += PrintStructComparison(s)
        for s in (x for x in self.all_structures if x.name in STRUCT_SHORT_VERSIONS_TO_GEN):
            out += PrintStructShort(s)

        out += 'auto format_ranges = std::array{\n'
        for f in self.format_ranges:
            out += f'    FormatRange{{{f.minimum_instance_version}, {f.extension_name if f.extension_name is not None else "nullptr"}, '
            out += f'static_cast<VkFormat>({f.first_format}), static_cast<VkFormat>({f.last_format})}},\n'
        out += '};\n'


        gen.write(out, file=self.outFile)

        gen.OutputGenerator.endFile(self)

    def genCmd(self, cmd, name, alias):
        gen.OutputGenerator.genCmd(self, cmd, name, alias)

    # These are actually constants
    def genEnum(self, enuminfo, name, alias):
        gen.OutputGenerator.genEnum(self, enuminfo, name, alias)

    # These are actually enums
    def genGroup(self, groupinfo, groupName, alias):
        gen.OutputGenerator.genGroup(self, groupinfo, groupName, alias)

        if alias is not None:
            if alias in self.aliases.keys():
                self.aliases[alias].append(groupName)
            else:
                self.aliases[alias] = [groupName, ]
            return

        if groupinfo.elem.get('type') == 'bitmask':
            self.bitmasks.append(VulkanBitmask(groupinfo.elem))
        elif groupinfo.elem.get('type') == 'enum':
            self.enums.append(VulkanEnum(groupinfo.elem))

    def genType(self, typeinfo, name, alias):
        gen.OutputGenerator.genType(self, typeinfo, name, alias)

        if alias is not None:
            if alias in self.aliases.keys():
                self.aliases[alias].append(name)
            else:
                self.aliases[alias] = [name, ]
            return

        if typeinfo.elem.get('category') == 'bitmask':
            self.flags.append(VulkanFlags(typeinfo.elem))

        if typeinfo.elem.get('category') == 'struct':
            self.all_structures.append(VulkanStructure(
                name, typeinfo.elem, self.constants, self.extTypes))

        for vendor in self.vendor_abbreviations:
            for node in typeinfo.elem.findall('member'):
                if node.get('values') is not None:
                    if node.get('values').find(vendor) != -1:
                        return

        for key, value in EXTENSION_CATEGORIES.items():
            if str(typeinfo.elem.get('structextends')).find(value.get('extends')) != -1:
                if value.get('exclude') is None or name not in value.get('exclude'):
                    self.extension_sets[key].add(name)

    # finds all the ranges of formats from core (1.0), core versions (1.1+), and extensions
    def findFormatRanges(self):
        for enums in self.registry.reg.findall('enums'):
            if enums.get('name') == 'VkFormat':
                min_val = 2**32
                max_val = 0
                for enum in enums.findall('enum'):
                    if enum.get('value') is None:
                        continue
                    value = int(enum.get('value'))
                    min_val = min(min_val, value)
                    max_val = max(max_val, value)
                if min_val < 2**32 and max_val > 0:
                    self.format_ranges.append(VulkanFormatRange(0, None, min_val, max_val))

        for feature in self.registry.reg.findall('feature'):
            for require in feature.findall('require'):
                comment = require.get('comment')
                original_ext = None
                if comment is not None and comment.find('Promoted from') >= 0:
                    # may need tweaking in the future - some ext names aren't just the upper case version
                    original_ext = comment.split(' ')[2].upper() + '_EXTENSION_NAME'
                    # insert an underscore before numbers in the name define
                    original_ext = re.sub(r'([A-Z])(\d+)', r'\1_\2', original_ext)
                min_val = 2**32
                max_val = 0
                for enum in require.findall('enum'):
                    if enum.get('extends') == 'VkFormat':
                        value = CalcEnumValue(int(enum.get('extnumber')), int(enum.get('offset')))
                        min_val = min(min_val, value)
                        max_val = max(max_val, value)
                if min_val < 2**32 and max_val > 0:
                    self.format_ranges.append(VulkanFormatRange(feature.get('name').replace('_VERSION_', '_API_VERSION_'), None, min_val, max_val))
                    # If the formats came from an extension, add a format range for that extension so it'll be printed if the ext is supported but not the core version
                    if original_ext is not None:
                        self.format_ranges.append(VulkanFormatRange(0, original_ext, min_val, max_val))

        for extension in self.registry.reg.find('extensions').findall('extension'):
            if not self.genOpts.apiname in extension.get('supported').split(','):
                continue

            min_val = 2**32
            max_val = 0
            enum_name_string = ''
            for require in extension.findall('require'):
                for enum in require.findall('enum'):
                    if enum.get('value') is not None and enum.get('value').find(extension.get('name')):
                        enum_name_string = enum.get('name')
                    if enum.get('extends') == 'VkFormat':
                        if enum.get('offset') is None:
                            continue
                        value = CalcEnumValue(int(extension.get('number')), int(enum.get('offset')))
                        min_val = min(min_val, value)
                        max_val = max(max_val, value)
            if min_val < 2**32 and max_val > 0:
                self.format_ranges.append(VulkanFormatRange(0, enum_name_string, min_val, max_val))



def GatherTypesToGen(structure_list, structures, exclude = None):
    if exclude is None:
        exclude = []
    types = set()
    for s in structures:
        types.add(s)
    added_stuff = True  # repeat until no new types are added
    while added_stuff is True:
        added_stuff = False
        for s in structure_list:
            if s.name in types:
                for m in s.members:
                    if m.typeID not in PREDEFINED_TYPES and m.name not in NAMES_TO_IGNORE:
                        if m.typeID not in types:
                            if s.name not in exclude:
                                types.add(m.typeID)
                                added_stuff = True
    return types


def GetExtension(name, generator):
    if name in generator.extFuncs:
        return generator.extFuncs[name]
    elif name in generator.extTypes:
        return generator.extTypes[name][0]
    else:
        return None


def AddGuardHeader(obj):
    if obj is not None and obj.guard is not None:
        return f'#ifdef {obj.guard}\n'
    else:
        return ''


def AddGuardFooter(obj):
    if obj is not None and obj.guard is not None:
        return f'#endif  // {obj.guard}\n'
    else:
        return ''

def CalcEnumValue(num, offset):
    base = 1000000000
    block_size = 1000
    return base + (num - 1) * block_size + offset

def PrintEnumToString(enum, generator):
    out = ''
    out += AddGuardHeader(GetExtension(enum.name, generator))
    out += f'std::string {enum.name}String({enum.name} value) {{\n'
    out += '    switch (value) {\n'
    for v in enum.options:
        out += f'        case ({v.name}): return "{v.name[3:]}";\n'
    out += f'        default: return std::string("UNKNOWN_{enum.name}_value") + std::to_string(value);\n'
    out += '    }\n}\n'
    out += AddGuardFooter(GetExtension(enum.name, generator))
    return out


def PrintEnum(enum, generator):
    out = ''
    out += AddGuardHeader(GetExtension(enum.name, generator))
    out += f'''void Dump{enum.name}(Printer &p, std::string name, {enum.name} value) {{
    if (p.Type() == OutputType::json)
        p.PrintKeyString(name, std::string("VK_") + {enum.name}String(value));
    else
        p.PrintKeyString(name, {enum.name}String(value));
}}
'''
    out += AddGuardFooter(GetExtension(enum.name, generator))
    return out


def PrintGetFlagStrings(name, bitmask):
    out = ''
    out += f'std::vector<const char *> {name}GetStrings({name} value) {{\n'
    out += '    std::vector<const char *> strings;\n'
    # If a bitmask contains a field whose value is zero, we want to support printing the correct bitflag
    # Otherwise, use "None" for when there are not bits set in the bitmask
    if bitmask.options[0].value != 0:
        out += '    if (value == 0) { strings.push_back("None"); return strings; }\n'
    for v in bitmask.options:
        # only check single-bit flags
        if (v.value & (v.value - 1)) == 0:
            out += f'    if ({v.name} & value) strings.push_back("{v.name[3:]}");\n'
    out += '    return strings;\n}\n'
    return out


def PrintFlags(bitmask, name):
    out = f'void Dump{name}(Printer &p, std::string name, {name} value) {{\n'
    out += f'''    if (static_cast<{bitmask.name}>(value) == 0) {{
        ArrayWrapper arr(p, name, 0);
        if (p.Type() != OutputType::json && p.Type() != OutputType::vkconfig_output)
            p.SetAsType().PrintString("None");
        return;
    }}
    auto strings = {bitmask.name}GetStrings(static_cast<{bitmask.name}>(value));
    ArrayWrapper arr(p, name, strings.size());
    for(auto& str : strings){{
        if (p.Type() == OutputType::json)
            p.SetAsType().PrintString(std::string("VK_") + str);
        else
            p.SetAsType().PrintString(str);
    }}
}}
'''
    return out


def PrintFlagBits(bitmask):
    return f'''void Dump{bitmask.name}(Printer &p, std::string name, {bitmask.name} value) {{
    auto strings = {bitmask.name}GetStrings(value);
    if (strings.size() > 0) {{
        if (p.Type() == OutputType::json)
            p.PrintKeyString(name, std::string("VK_") + strings.at(0));
        else
            p.PrintKeyString(name, strings.at(0));
    }}
}}
'''



def PrintBitMask(bitmask, name, generator):
    out = PrintGetFlagStrings(bitmask.name, bitmask)
    out += AddGuardHeader(GetExtension(bitmask.name, generator))
    out += PrintFlags(bitmask, name)
    out += PrintFlagBits(bitmask)
    out += AddGuardFooter(GetExtension(bitmask.name, generator))
    out += '\n'
    return out


def PrintBitMaskToString(bitmask, name, generator):
    out = AddGuardHeader(GetExtension(bitmask.name, generator))
    out += f'std::string {name}String({name} value) {{\n'
    out += '    std::string out;\n'
    out += '    bool is_first = true;\n'
    for v in bitmask.options:
        out += f'    if ({v.name} & value) {{\n'
        out += '        if (is_first) { is_first = false; } else { out += " | "; }\n'
        out += f'        out += "{str(v.name)[3:]}";\n'
        out += '    }\n'
    out += '    return out;\n'
    out += '}\n'
    out += AddGuardFooter(GetExtension(bitmask.name, generator))
    return out


def PrintStructure(struct):
    if len(struct.members) == 0:
        return ''
    out = ''
    out += AddGuardHeader(struct)
    max_key_len = 0
    for v in struct.members:
        if v.arrayLength is not None:
            if len(v.name) + len(v.arrayLength) + 2 > max_key_len:
                max_key_len = len(v.name) + len(v.arrayLength) + 2
        elif v.typeID in PREDEFINED_TYPES or v.typeID in STRUCT_BLACKLIST:
            if len(v.name) > max_key_len:
                max_key_len = len(v.name)

    out += f'void Dump{struct.name}(Printer &p, std::string name, const {struct.name} &obj) {{\n'
    if struct.name == 'VkPhysicalDeviceLimits':
        out += '    if (p.Type() == OutputType::json)\n'
        out += '        p.ObjectStart("limits");\n'
        out += '    else\n'
        out += '        p.SetSubHeader().ObjectStart(name);\n'
    elif struct.name == 'VkPhysicalDeviceSparseProperties':
        out += '    if (p.Type() == OutputType::json)\n'
        out += '        p.ObjectStart("sparseProperties");\n'
        out += '    else\n'
        out += '        p.SetSubHeader().ObjectStart(name);\n'
    else:
        out += '    ObjectWrapper object{p, name};\n'
    if max_key_len > 0:
        out += f'    p.SetMinKeyWidth({max_key_len});\n'
    for v in struct.members:
        # arrays
        if v.arrayLength is not None:
            # strings
            if v.typeID == 'char':
                out += f'    p.PrintKeyString("{v.name}", obj.{v.name});\n'
            # uuid's
            elif v.typeID == 'uint8_t' and (v.arrayLength == '8' or v.arrayLength == '16'):  # VK_UUID_SIZE
                if v.arrayLength == '8':
                    out += '    if (obj.deviceLUIDValid) { // special case\n'
                out += f'    p.PrintKeyValue("{v.name}", obj.{v.name});\n'
                if v.arrayLength == '8':
                    out += '    }\n'
            elif struct.name == 'VkQueueFamilyGlobalPriorityProperties' and v.name == 'priorities':
                out += f'    ArrayWrapper arr(p,"{v.name}", obj.priorityCount);\n'
                out += '    for (uint32_t i = 0; i < obj.priorityCount; i++) {\n'
                out += '       if (p.Type() == OutputType::json)\n'
                out += '           p.PrintString(std::string("VK_") + VkQueueGlobalPriorityString(obj.priorities[i]));\n'
                out += '       else\n'
                out += '           p.PrintString(VkQueueGlobalPriorityString(obj.priorities[i]));\n'
                out += '    }\n'
            elif v.arrayLength.isdigit():
                out += f'    {{\n        ArrayWrapper arr(p,"{v.name}", ' + v.arrayLength + ');\n'
                out += f'        for (uint32_t i = 0; i < {v.arrayLength}; i++) {{ p.PrintElement(obj.{v.name}[i]); }}\n'
                out += '    }\n'
            else:  # dynamic array length based on other member
                out += f'    if (obj.{v.arrayLength} == 0 || obj.{v.name} == nullptr) {{\n'
                out += f'        p.PrintKeyString("{v.name}", "NULL");\n'
                out += '    } else {\n'
                out += f'        ArrayWrapper arr(p,"{v.name}", obj.{v.arrayLength});\n'
                out += f'        for (uint32_t i = 0; i < obj.{v.arrayLength}; i++) {{\n'
                out += f'            Dump{v.typeID}(p, std::to_string(i), obj.{v.name}[i]);\n'
                out += '        }\n'
                out += '    }\n'
        elif v.typeID == 'VkBool32':
            out += f'    p.PrintKeyBool("{v.name}", static_cast<bool>(obj.{v.name}));\n'
        elif v.typeID == 'uint8_t':
            out += f'    p.PrintKeyValue("{v.name}", static_cast<uint32_t>(obj.{v.name}));\n'
        elif v.typeID == 'VkDeviceSize':
            out += f'    p.PrintKeyValue("{v.name}", to_hex_str(p, obj.{v.name}));\n'
        elif v.typeID in PREDEFINED_TYPES:
            out += f'    p.PrintKeyValue("{v.name}", obj.{v.name});\n'
        elif v.name not in NAMES_TO_IGNORE:
            # if it is an enum/flag/bitmask
            if v.typeID in ['VkFormatFeatureFlags', 'VkFormatFeatureFlags2']:
                out += '    p.SetOpenDetails();\n' # special case so that feature flags are open in html output
            out += f'    Dump{v.typeID}(p, "{v.name}", obj.{v.name});\n'

    if struct.name in ['VkPhysicalDeviceLimits', 'VkPhysicalDeviceSparseProperties']:
        out += '    p.ObjectEnd();\n'
    out += '}\n'

    out += AddGuardFooter(struct)
    return out


def PrintStructShort(struct):
    out = ''
    out += AddGuardHeader(struct)
    out += f'std::ostream &operator<<(std::ostream &o, {struct.name} &obj) {{\n'
    out += '    return o << "(" << '

    first = True
    for v in struct.members:
        if first:
            first = False
            out += f'obj.{v.name} << '
        else:
            out += f'\',\' << obj.{v.name} << '
    out += '")";\n'
    out += '}\n'
    out += AddGuardFooter(struct)
    return out

def PrintChainStruct(listName, structures, all_structures, chain_details, extTypes, aliases, vulkan_versions):
    sorted_structures = sorted(
        all_structures, key=operator.attrgetter('name'))

    version_desc = ''
    if chain_details.get('type') in [EXTENSION_TYPE_DEVICE, EXTENSION_TYPE_BOTH]:
        version_desc = 'gpu.api_version'
    else:
        version_desc = 'inst.instance_version'

    out = ''
    structs_to_print = []
    for s in sorted_structures:
        if s.name in structures:
            structs_to_print.append(s)
    # use default constructor and delete copy & move operators
    out += f'''struct {listName}_chain {{
    {listName}_chain() = default;
    {listName}_chain(const {listName}_chain &) = delete;
    {listName}_chain& operator=(const {listName}_chain &) = delete;
    {listName}_chain({listName}_chain &&) = delete;
    {listName}_chain& operator=({listName}_chain &&) = delete;
'''

    out += '    void* start_of_chain = nullptr;\n'
    for s in structs_to_print:
        if s.name in STRUCT_BLACKLIST:
            continue
        out += AddGuardHeader(s)
        if s.sTypeName is not None:
            out += f'    {s.name} {s.name[2:]}{{}};\n'
            # Specific versions of drivers have an incorrect definition of the size of these structs.
            # We need to artificially pad the structure it just so the driver doesn't write out of bounds and
            # into other structures that are adjacent. This bug comes from the in-development version of
            # the extension having a larger size than the final version, so older drivers try to write to
            # members which don't exist.
            if s.name in ['VkPhysicalDeviceShaderIntegerDotProductFeatures', 'VkPhysicalDeviceHostImageCopyFeaturesEXT']:
                out += f'    char {s.name}_padding[64];\n'
            if s.hasLengthmember:
                for member in s.members:
                    if member.lengthMember:
                        out += f'    std::vector<{member.typeID}> {s.name}_{member.name};\n'
        out += AddGuardFooter(s)
    out += '    void initialize_chain('
    if chain_details.get('type') in [EXTENSION_TYPE_INSTANCE, EXTENSION_TYPE_BOTH]:
        out += 'AppInstance &inst, '
    if chain_details.get('type') in [EXTENSION_TYPE_DEVICE, EXTENSION_TYPE_BOTH]:
        out += 'AppGpu &gpu '
    if chain_details.get('can_show_promoted_structs'):
        out += ', bool show_promoted_structs'
    out += ') noexcept {\n'
    for s in structs_to_print:
        if s.name in STRUCT_BLACKLIST:
            continue
        out += AddGuardHeader(s)
        out += f'        {s.name[2:]}.sType = {s.sTypeName};\n'
        out += AddGuardFooter(s)


    out += '        std::vector<VkBaseOutStructure*> chain_members{};\n'
    for s in structs_to_print:
        if s.name in STRUCT_BLACKLIST:
            continue
        out += AddGuardHeader(s)
        extEnables = {}
        for k, elem in extTypes.items():
            if k == s.name or (s.name in aliases.keys() and k in aliases[s.name]):
                for e in elem:
                    extEnables[e.extNameStr] = e.type

        version = None
        oldVersionName = None
        for v in vulkan_versions:
            if s.name in v.names:
                version = v
        if s.name in aliases.keys():
            for alias in aliases[s.name]:
                oldVersionName = alias

        has_version = version is not None
        has_extNameStr = len(extEnables) > 0 or s.name in aliases.keys()
        if has_version or has_extNameStr:
            out += '        if ('
            has_printed_condition = False
            if has_extNameStr:
                for key, value in extEnables.items():
                    if has_printed_condition:
                        out += '\n         || '
                    else:
                        has_printed_condition = True
                        if has_version:
                            out += '('
                    if value == EXTENSION_TYPE_DEVICE:
                        out += f'gpu.CheckPhysicalDeviceExtensionIncluded({key})'
                    elif value == EXTENSION_TYPE_INSTANCE:
                        out += f'inst.CheckExtensionEnabled({key})'
                    else:
                        assert False, 'Should never get here'
            if has_version:
                str_show_promoted_structs = '|| show_promoted_structs' if chain_details.get('can_show_promoted_structs') else ''
                if s.name in STRUCT_1_1_LIST:
                    out += f'{version_desc} == {version.constant} {str_show_promoted_structs}'
                elif has_printed_condition:
                    out += f')\n            && ({version_desc} < {version.constant} {str_show_promoted_structs})'
                else:
                    out += f'({version_desc} >= {version.constant})'
            out += ')\n            '
        else:
            out += '        '
        out += f'chain_members.push_back(reinterpret_cast<VkBaseOutStructure*>(&{s.name[2:]}));\n'
        out += AddGuardFooter(s)
    chain_param_list = []
    chain_arg_list = []
    if chain_details.get('type') in [EXTENSION_TYPE_INSTANCE, EXTENSION_TYPE_BOTH]:
        chain_param_list.append('AppInstance &inst')
        chain_arg_list.append('inst')
    if chain_details.get('type') in [EXTENSION_TYPE_DEVICE, EXTENSION_TYPE_BOTH]:
        chain_param_list.append('AppGpu &gpu')
        chain_arg_list.append('gpu')
    if chain_details.get('can_show_promoted_structs'):
        chain_param_list.append('bool show_promoted_structs')
        chain_arg_list.append('show_promoted_structs')

    out += f'''
        if (!chain_members.empty()) {{
            for(size_t i = 0; i < chain_members.size() - 1; i++){{
                chain_members[i]->pNext = chain_members[i + 1];
            }}
            start_of_chain = chain_members[0];
        }}
    }}
}};
void setup_{listName}_chain({chain_details['holder_type']}& start, std::unique_ptr<{listName}_chain>& chain, {','.join(chain_param_list)}){{
    chain = std::unique_ptr<{listName}_chain>(new {listName}_chain());
    chain->initialize_chain({','.join(chain_arg_list)});
    start.pNext = chain->start_of_chain;
}};
'''
    if chain_details.get('print_iterator'):
        out += '\n'
        out += f'void chain_iterator_{listName}(Printer &p, '
        if chain_details.get('type') in [EXTENSION_TYPE_INSTANCE, EXTENSION_TYPE_BOTH]:
            out += 'AppInstance &inst, '
        if chain_details.get('type') in [EXTENSION_TYPE_DEVICE, EXTENSION_TYPE_BOTH]:
            out += 'AppGpu &gpu, '
        if chain_details.get('can_show_promoted_structs'):
            out += 'bool show_promoted_structs, '
        out += 'void * place) {\n'
        out += '    while (place) {\n'
        out += '        struct VkBaseOutStructure *structure = (struct VkBaseOutStructure *)place;\n'
        out += '        p.SetSubHeader();\n'

        for s in sorted_structures:
            if s.sTypeName is None or s.name in STRUCT_BLACKLIST:
                continue

            extEnables = {}
            for k, elem in extTypes.items():
                if k == s.name or (s.name in aliases.keys() and k in aliases[s.name]):
                    for e in elem:
                        extEnables[e.extNameStr] = e.type

            version = None
            oldVersionName = None
            for v in vulkan_versions:
                if s.name in v.names:
                    version = v
            if s.name in aliases.keys():
                for alias in aliases[s.name]:
                    oldVersionName = alias

            if s.name in structures:
                out += AddGuardHeader(s)
                out += f'        if (structure->sType == {s.sTypeName}'
                if s.name in PORTABILITY_STRUCTS:
                    out += ' && p.Type() != OutputType::json'
                has_version = version is not None
                has_extNameStr = len(extEnables) > 0 or s.name in aliases.keys()
                out += ') {\n'
                out += f'            {s.name}* props = ({s.name}*)structure;\n'
                out += f'            Dump{s.name}(p, '
                if s.name in aliases.keys() and version is not None:
                    out += f'{version_desc} >= {version.constant} ?"{s.name}":"{oldVersionName}"'
                else:
                    out += f'"{s.name}"'
                out += ', *props);\n'
                out += '            p.AddNewline();\n'
                out += '        }\n'
                out += AddGuardFooter(s)
        out += '        place = structure->pNext;\n'
        out += '    }\n'
        out += '}\n'

    out += '\n'
    out += f'bool prepare_{listName}_twocall_chain_vectors(std::unique_ptr<{listName}_chain>& chain) {{\n'
    out += '    (void)chain;\n'
    is_twocall = False
    for s in structs_to_print:
        if not s.hasLengthmember:
            continue
        if s.name in STRUCT_BLACKLIST:
            continue
        out += AddGuardHeader(s)
        for member in s.members:
            if member.lengthMember:
                out += f'    chain->{s.name}_{member.name}.resize(chain->{s.name[2:]}.{member.arrayLength});\n'
                out += f'    chain->{s.name[2:]}.{member.name} = chain->{s.name}_{member.name}.data();\n'
        out += AddGuardFooter(s)
        is_twocall = True
    out += f'    return {"true" if is_twocall else "false"};\n'
    out += '}\n'

    return out


def PrintStructComparisonForwardDecl(structure):
    out = ''
    out += f'bool operator==(const {structure.name} & a, const {structure.name} b);\n'
    return out


def PrintStructComparison(structure):
    out = ''
    out += f'bool operator==(const {structure.name} & a, const {structure.name} b) {{\n'
    out += '    return '
    is_first = True
    for m in structure.members:
        if m.name not in NAMES_TO_IGNORE:
            if not is_first:
                out += '\n        && '
            else:
                is_first = False
            out += f'a.{m.name} == b.{m.name}'
    out += ';\n'
    out += '}\n'
    return out


class VulkanEnum:
    class Option:

        def __init__(self, name, value, bitpos, comment):
            self.name = name
            self.comment = comment

            if bitpos is not None:
                value = 1 << int(bitpos)
            elif isinstance(value, str):
                if value.lower().startswith('0x'):
                    value = int(value, 16)
                else:
                    value = int(value)

            self.value = value

        def values(self):
            return {
                'optName': self.name,
                'optValue': self.value,
                'optComment': self.comment,
            }

    def __init__(self, rootNode):
        self.name = rootNode.get('name')
        self.type = rootNode.get('type')
        self.options = []

        for child in rootNode:
            childName = child.get('name')
            childValue = child.get('value')
            childBitpos = child.get('bitpos')
            childComment = child.get('comment')
            childExtends = child.get('extends')
            childOffset = child.get('offset')
            childExtNum = child.get('extnumber')
            support = child.get('supported')
            if support == 'disabled':
                continue

            if childName is None:
                continue
            if childValue is None and childBitpos is None and childOffset is None:
                continue

            if childExtends is not None and childExtNum is not None and childOffset is not None:
                childValue = CalcEnumValue(int(childExtNum), int(childOffset))
                if 'dir' in child.keys():
                    childValue = -childValue
            duplicate = False
            for o in self.options:
                if o.values()['optName'] == childName:
                    duplicate = True
            if duplicate:
                continue

            self.options.append(VulkanEnum.Option(
                childName, childValue, childBitpos, childComment))


class VulkanBitmask:

    def __init__(self, rootNode):
        self.name = rootNode.get('name')
        self.type = rootNode.get('type')

        # Read each value that the enum contains
        self.options = []
        for child in rootNode:
            childName = child.get('name')
            childValue = child.get('value')
            childBitpos = child.get('bitpos')
            childComment = child.get('comment')
            support = child.get('supported')
            if childName is None or (childValue is None and childBitpos is None):
                continue
            if support == 'disabled':
                continue

            duplicate = False
            for option in self.options:
                if option.name == childName:
                    duplicate = True
            if duplicate:
                continue

            self.options.append(VulkanEnum.Option(
                childName, childValue, childBitpos, childComment))


class VulkanFlags:

    def __init__(self, rootNode):
        self.name = rootNode.get('name')
        self.type = rootNode.get('type')
        self.enum = rootNode.get('requires')
        # 64 bit flags use bitvalues, not requires
        if self.enum is None:
            self.enum = rootNode.get('bitvalues')


class VulkanVariable:
    def __init__(self, rootNode, constants):
        self.name = rootNode.find('name').text
        # Typename, dereferenced and converted to a useable C++ token
        self.typeID = rootNode.find('type').text
        self.baseType = self.typeID
        self.childType = None
        self.arrayLength = None
        self.text = ''
        for node in rootNode.itertext():
            comment = rootNode.find('comment')
            if comment is not None and comment.text == node:
                continue
            self.text += node

        typeMatch = re.search('.+?(?=' + self.name + ')', self.text)
        self.type = typeMatch.string[typeMatch.start():typeMatch.end()]
        self.type = ' '.join(self.type.split())
        bracketMatch = re.search('(?<=\\[)[a-zA-Z0-9_]+(?=\\])', self.text)
        if bracketMatch is not None:
            matchText = bracketMatch.string[bracketMatch.start(
            ):bracketMatch.end()]
            self.childType = self.type
            self.type += '[' + matchText + ']'
            if matchText in constants:
                self.arrayLength = constants[matchText]
            else:
                self.arrayLength = matchText

        self.lengthMember = False
        lengthString = rootNode.get('len')
        lengths = []
        if lengthString is not None:
            lengths = re.split(',', lengthString)
            lengths = list(filter(('null-terminated').__ne__, lengths))
        if self.arrayLength is None and len(lengths) > 0:
            self.childType = '*'.join(self.type.split('*')[0:-1])
            self.arrayLength = lengths[0]
            self.lengthMember = True
        if self.arrayLength is not None and self.arrayLength.startswith('latexmath'):
            code = self.arrayLength[10:len(self.arrayLength)]
            code = re.sub('\\[', '', code)
            code = re.sub('\\]', '', code)
            code = re.sub('\\\\(lceil|rceil)', '', code)
            code = re.sub('{|}', '', code)
            code = re.sub('\\\\mathit', '', code)
            code = re.sub('\\\\over', '/', code)
            code = re.sub('\\\\textrm', '', code)
            self.arrayLength = code

        # Dereference if necessary and handle members of variables
        if self.arrayLength is not None:
            self.arrayLength = re.sub('::', '->', self.arrayLength)
            sections = self.arrayLength.split('->')
            if sections[-1][0] == 'p' and sections[0][1].isupper():
                self.arrayLength = '*' + self.arrayLength


class VulkanStructure:
    def __init__(self, name, rootNode, constants, extTypes):
        self.name = name
        self.members = []
        self.guard = None
        self.sTypeName = None
        self.extendsStruct = rootNode.get('structextends')
        self.hasLengthmember = False

        for node in rootNode.findall('member'):
            if node.get('values') is not None:
                self.sTypeName = node.get('values')
            self.members.append(VulkanVariable(node, constants))

        for member in self.members:
            if member.lengthMember:
                self.hasLengthmember = True
                break

        for k, elem in extTypes.items():
            if k == self.name:
                for e in elem:
                    if e.guard is not None:
                        self.guard = e.guard


class VulkanExtension:
    def __init__(self, rootNode):
        self.name = rootNode.get('name')
        self.number = int(rootNode.get('number'))
        self.type = rootNode.get('type')
        self.dependency = rootNode.get('requires')
        self.guard = GetFeatureProtect(rootNode)
        self.supported = rootNode.get('supported')
        self.extNameStr = None
        self.vktypes = []
        self.vkfuncs = []
        self.constants = OrderedDict()
        self.enumValues = OrderedDict()
        self.node = rootNode

        for req in rootNode.findall('require'):
            for ty in req.findall('type'):
                self.vktypes.append(ty.get('name'))

            for func in req.findall('command'):
                self.vkfuncs.append(func.get('name'))

            for enum in req.findall('enum'):
                base = enum.get('extends')
                name = enum.get('name')
                value = enum.get('value')
                bitpos = enum.get('bitpos')
                offset = enum.get('offset')
                # gets the VK_XXX_EXTENSION_NAME string
                if value == f'"{self.name}"':
                    self.extNameStr = name

                if value is None and bitpos is not None:
                    value = 1 << int(bitpos)

                if offset is not None:
                    offset = int(offset)
                if base is not None and offset is not None:
                    enumValue = 1000000000 + 1000*(self.number - 1) + offset
                    if enum.get('dir') == '-':
                        enumValue = -enumValue
                    self.enumValues[base] = (name, enumValue)
                else:
                    self.constants[name] = value


class VulkanVersion:
    def __init__(self, rootNode):
        self.name = rootNode.get('name')
        self.constant = self.name.replace('_VERSION_', '_API_VERSION_')
        self.names = set()

        for req in rootNode.findall('require'):
            for ty in req.findall('type'):
                self.names.add(ty.get('name'))
            for func in req.findall('command'):
                self.names.add(func.get('name'))
            for enum in req.findall('enum'):
                self.names.add(enum.get('name'))
        self.names = sorted(self.names)

class VulkanFormatRange:
    def __init__(self, min_inst_version, ext_name, first, last):
        self.minimum_instance_version = min_inst_version
        self.extension_name = ext_name
        self.first_format = first
        self.last_format = last
