import re
import urllib.request as req
from tokenize import tokenize
from io import BytesIO
import string
import os.path
import math

file_and_urls = [
    ("vk_platform.h",    'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vulkan/vk_platform.h',    True),
    ("vulkan_core.h",    'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vulkan/vulkan_core.h',    False),
    ("vk_layer.h",       'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vulkan/vk_layer.h',       True),
    ("vk_icd.h",         'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vulkan/vk_icd.h',         True),
    ("vulkan_win32.h",   'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vulkan/vulkan_win32.h',   False),
    ("vulkan_metal.h",   'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vulkan/vulkan_metal.h',   False),
    ("vulkan_macos.h",   'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vulkan/vulkan_macos.h',   False),
    ("vulkan_ios.h",     'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vulkan/vulkan_ios.h',     False),
    ("vulkan_wayland.h", 'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vulkan/vulkan_wayland.h', False),
    # Vulkan Video
    ("vulkan_video_codec_av1std.h", 'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vk_video/vulkan_video_codec_av1std.h', False),
    ("vulkan_video_codec_h264std.h", 'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vk_video/vulkan_video_codec_h264std.h', False),
    ("vulkan_video_codec_h265std.h", 'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vk_video/vulkan_video_codec_h265std.h', False),
    ("vulkan_video_codec_av1std_decode.h", 'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vk_video/vulkan_video_codec_av1std_decode.h', False),
    ("vulkan_video_codec_h264std_decode.h", 'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vk_video/vulkan_video_codec_h264std_decode.h', False),
    ("vulkan_video_codec_h265std_decode.h", 'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vk_video/vulkan_video_codec_h265std_decode.h', False),
    ("vulkan_video_codec_h264std_encode.h", 'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vk_video/vulkan_video_codec_h264std_encode.h', False),
    ("vulkan_video_codec_h265std_encode.h", 'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vk_video/vulkan_video_codec_h265std_encode.h', False),
]

for file, url, _ in file_and_urls:
    if not os.path.isfile(file):
        with open(file, 'w', encoding='utf-8') as f:
            f.write(req.urlopen(url).read().decode('utf-8'))

src = ""
for file, _, skip in file_and_urls:
    if skip: continue
    with open(file, 'r', encoding='utf-8') as f:
        src += f.read()


def no_vk(t):
    t = t.replace('PFN_vk_icd', 'Procicd')
    t = t.replace('PFN_vk', 'Proc')
    t = t.replace('PFN_', 'Proc')
    t = t.replace('PFN_', 'Proc')

    t = re.sub('(?:Vk|VK_)?(\w+)', '\\1', t)
    # Vulkan Video

    t = re.sub('(?:Std|STD_)?(\w+)', '\\1', t)
    return t

OPAQUE_STRUCTS = """
wl_surface   :: struct {} // Opaque struct defined by Wayland
wl_display   :: struct {} // Opaque struct defined by Wayland
IOSurfaceRef :: struct {} // Opaque struct defined by Apple’s CoreGraphics framework
""" 

def convert_type(t, prev_name, curr_name):
    table = {
        "Bool32":      'b32',
        "float":       'f32',
        "double":      'f64',
        "uint32_t":    'u32',
        "uint64_t":    'u64',
        "size_t":      'int',
        'int16_t':     'i16',
        'int32_t':     'i32',
        'int64_t':     'i64',
        'int':         'c.int',
        'uint8_t':     'u8',
        'int8_t':     'i8',
        "uint16_t":    'u16',
        "char":        "byte",
        "void":        "void",
        "void*":       "rawptr",
        "void *":      "rawptr",
        "char*":       'cstring',
        "const uint32_t* const*": "^[^]u32",
        "const void*": 'rawptr',
        "const char*": 'cstring',
        "const char* const*": '[^]cstring',
        "const ObjectTableEntryNVX* const*": "^^ObjectTableEntryNVX",
        "const void* const *": "[^]rawptr",
        "const AccelerationStructureGeometryKHR* const*": "^[^]AccelerationStructureGeometryKHR",
        "const AccelerationStructureBuildRangeInfoKHR* const*": "^[^]AccelerationStructureBuildRangeInfoKHR",
        "const MicromapUsageEXT* const*": "^[^]MicromapUsageEXT",
        "struct BaseOutStructure": "BaseOutStructure",
        "struct BaseInStructure":  "BaseInStructure",
        "struct wl_display": "wl_display",
        "struct wl_surface": "wl_surface",
        'v': '',
    }

    if t in table.keys():
        return table[t]

    if t == "":
        return t

    if t.startswith("const"):
        t = convert_type(t[6:], prev_name, curr_name)

    elif t.endswith("*"):
        pointer = "^"
        ttype = t[:len(t)-1]
        elem = convert_type(ttype, prev_name, curr_name)    

        if curr_name.endswith("s") or curr_name.endswith("Table"):
            if prev_name.endswith("Count") or prev_name.endswith("Counts"):
                pointer = "[^]"
            elif curr_name.startswith("pp"):
                if elem.startswith("[^]"):
                    pass
                else:
                    pointer = "[^]"
            elif curr_name.startswith("p"):
                pointer = "[^]"

        if curr_name and elem.endswith("Flags"):
            pointer = "[^]"

        return "{}{}".format(pointer, elem)
    elif t[0].isupper():
        return t

    return t

def parse_array(n, t):
    name, length = n.split('[', 1)
    length = no_vk(length[:-1])
    type_ = "[{}]{}".format(length, do_type(t))
    return name, type_

def remove_prefix(text, prefix):
    if text.startswith(prefix):
        return text[len(prefix):]
    return text
def remove_suffix(text, suffix):
    if text.endswith(suffix):
        return text[:-len(suffix)]
    return text


def to_snake_case(name):
    s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
    return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()

ext_suffixes = ["KHR", "EXT", "AMD", "NV", "NVX", "GOOGLE", "KHX"]
ext_suffixes_title = [ext.title() for ext in ext_suffixes]


def fix_arg(arg):
    name = arg

    # Remove useless pointer identifier in field name
    for p in ('s_', 'p_', 'pp_', 'pfn_'):
        if name.startswith(p):
            name = name[len(p)::]
    name = name.replace("__", "_")

    return name


def fix_ext_suffix(name):
    for ext in ext_suffixes_title:
        if name.endswith(ext):
            start = name[:-len(ext)]
            end = name[-len(ext):].upper()
            return start+end
    return name

def to_int(x):
    if x.startswith('0x'):
        return int(x, 16)
    return int(x)

def is_int(x):
    try:
        int(x)
        return True
    except ValueError:
        return False

def fix_enum_arg(name, is_flag_bit=False):
    # name = name.title()
    name = fix_ext_suffix(name)
    if len(name) > 0 and name[0].isdigit() and not name.startswith("0x") and not is_int(name):
        if name[1] == "D":
            name = name[1] + name[0] + (name[2:] if len(name) > 2 else "")
        else:
            name = "_"+name
    if is_flag_bit:
        name = name.replace("_BIT", "")
    return name

def do_type(t, prev_name="", name=""):
    return convert_type(no_vk(t), prev_name, name).replace("FlagBits", "Flags")

def parse_handles_def(f):
    f.write("// Handles types\n")
    handles = [h for h in re.findall(r"VK_DEFINE_HANDLE\(Vk(\w+)\)", src, re.S)]

    max_len = max(len(h) for h in handles)
    for h in handles:
        f.write("{} :: distinct Handle\n".format(h.ljust(max_len)))

    handles_non_dispatchable = [h for h in re.findall(r"VK_DEFINE_NON_DISPATCHABLE_HANDLE\(Vk(\w+)\)", src, re.S)]
    max_len = max(len(h) for h in handles_non_dispatchable)
    for h in handles_non_dispatchable:
        f.write("{} :: distinct NonDispatchableHandle\n".format(h.ljust(max_len)))


flags_defs = set()

def parse_flags_def(f):
    names = [n for n in re.findall(r"typedef VkFlags Vk(\w+?);", src)]

    global flags_defs
    flags_defs = set(names)


class FlagError(ValueError):
    pass
class IgnoreFlagError(ValueError):
    pass

def fix_enum_name(name, prefix, suffix, is_flag_bit):
    name = remove_prefix(name, prefix)
    if suffix:
        name = remove_suffix(name, suffix)
    if name.startswith("0x"):
        if is_flag_bit:
            i = int(name, 16)
            if i == 0:
                raise IgnoreFlagError(i)
            v = int(math.log2(i))
            if 2**v != i:
                raise FlagError(i)
            return str(v)
        return name
    elif is_flag_bit:
        ignore = False
        try:
            if int(name) == 0:
                ignore = True
        except:
            pass
        if ignore:
            raise IgnoreFlagError()

    return fix_enum_arg(name, is_flag_bit)


def fix_enum_value(value, prefix, suffix, is_flag_bit):
    v = no_vk(value)
    g = tokenize(BytesIO(v.encode('utf-8')).readline)
    tokens = [val for _, val, _, _, _ in g]
    assert len(tokens) > 2
    token = ''.join([t for t in tokens[1:-1] if t])
    token = fix_enum_name(token, prefix, suffix, is_flag_bit)
    return token

def parse_constants(f):
    f.write("// General Constants\n")
    all_data = re.findall(r"#define VK_(\w+)\s*(.*?)U?\n", src, re.S)
    allowed_names = (
        "HEADER_VERSION",
        "MAX_DRIVER_NAME_SIZE",
        "MAX_DRIVER_INFO_SIZE",
    )
    allowed_data = [nv for nv in all_data if nv[0] in allowed_names]
    max_len = max(len(name) for name, value in allowed_data)
    for name, value in allowed_data:
        f.write("{}{} :: {}\n".format(name, "".rjust(max_len-len(name)), value))

    f.write("\n// Vulkan Video Constants\n")
    vulkan_video_data = re.findall(r"#define STD_(\w+)\s*(.*?)U?\n", src, re.S)
    max_len = max(len(name) for name, value in vulkan_video_data)
    for name, value in vulkan_video_data:
        f.write("{}{} :: {}\n".format(name, "".rjust(max_len-len(name)), value))

    f.write("\n// Vendor Constants\n")
    fixes = '|'.join(ext_suffixes)
    inner = r"((?:(?:" + fixes + r")\w+)|(?:\w+" + fixes + r"))"
    pattern = r"#define\s+VK_" + inner + r"\s*(.*?)\n"
    data = re.findall(pattern, src, re.S)

    number_suffix_re = re.compile(r"(\d+)[UuLlFf]")

    max_len = max(len(name) for name, value in data)
    for name, value in data:
        value = remove_prefix(value, 'VK_')
        v = number_suffix_re.findall(value)
        if v:
            value = v[0]
        f.write("{}{} :: {}\n".format(name, "".rjust(max_len-len(name)), value))
    f.write("\n")


def parse_enums(f):
    f.write("import \"core:c\"\n\n")
    f.write("// Enums\n")

    data = re.findall(r"typedef enum (\w+) {(.+?)} \w+;", src, re.S)
    data = [(no_vk(n), f) for n, f in data]

    data.sort(key=lambda x: x[0])

    generated_flags = set()

    for name, fields in data:
        enum_name = name

        is_flag_bit = False
        if "FlagBits" in enum_name:
            is_flag_bit = True
            flags_name = enum_name.replace("FlagBits", "Flags")
            enum_name = enum_name.replace("FlagBits", "Flag")
            generated_flags.add(flags_name)
            f.write("{} :: distinct bit_set[{}; Flags]\n".format(flags_name, enum_name))


        if is_flag_bit:
            f.write("{} :: enum Flags {{\n".format(name.replace("FlagBits", "Flag")))
        else:
            f.write("{} :: enum c.int {{\n".format(name))

        prefix = to_snake_case(name).upper()
        suffix = None
        for ext in ext_suffixes:
            prefix_new = remove_suffix(prefix, "_"+ext)
            assert suffix is None
            if prefix_new != prefix:
                suffix = "_"+ext
                prefix = prefix_new
                break


        prefix = prefix.replace("_FLAG_BITS", "")
        prefix += "_"

        ff = []

        names_and_values = re.findall(r"VK_(\w+?) = (.*?)(?:,|})", fields, re.S)

        groups = []
        flags = {}

        for name, value in names_and_values:
            n = fix_enum_name(name, prefix, suffix, is_flag_bit)
            try:
                v = fix_enum_value(value, prefix, suffix, is_flag_bit)
            except FlagError as e:
                v = int(str(e))
                groups.append((n, v))
                continue
            except IgnoreFlagError as e:
                groups.append((n, 0))
                continue

            if n == v:
                continue
            try:
                flags[int(v)] = n
            except ValueError as e:
                pass

            if v == "NONE":
                continue

            ff.append((n, v))

        max_flag_value = max([int(v) for n, v in ff if is_int(v)] + [0])
        max_group_value = max([int(v) for n, v in groups if is_int(v)] + [0])
        if max_flag_value < max_group_value:
            if (1<<max_flag_value)+1 < max_group_value:
                ff.append(('_MAX', 31))
                flags[31] = '_MAX'
                pass

        max_len = max([len(n) for n, v in ff] + [0])

        flag_names = set([n for n, v in ff])

        for n, v in ff:
            if is_flag_bit and not is_int(v) and v not in flag_names:
                print("Ignoring", n, "=", v)
                continue
            f.write("\t{} = {},".format(n.ljust(max_len), v))
            if n == "_MAX":
                f.write(" // Needed for the *_ALL bit set")
            f.write("\n")



        f.write("}\n\n")

        for n, v in groups:
            used_flags = []
            for i in range(0, 32):
                if 1<<i & v != 0:
                    if i in flags:
                        used_flags.append('.'+flags[i])
                    else:
                        used_flags.append('{}({})'.format(enum_name, i))
            # Make sure the 's' is after Flags and not the extension name.
            ext_suffix = ''
            for suffix in ext_suffixes:
                if not enum_name.endswith(suffix):
                    continue

                ext_suffix = suffix
                enum_name = remove_suffix(enum_name, ext_suffix)
                break
            s = "{enum_name}s{ext_suffix}_{n} :: {enum_name}s{ext_suffix}{{".format(enum_name=enum_name, ext_suffix=ext_suffix, n=n)
            s += ', '.join(used_flags)
            s += "}\n"
            f.write(s)

        if len(groups) > 0:
            f.write("\n\n")


    unused_flags = [flag for flag in flags_defs if flag not in generated_flags]
    unused_flags.sort()

    max_len = max(len(flag) for flag in unused_flags)
    for flag in unused_flags:
        flag_name = flag.replace("Flags", "Flag")
        f.write("{} :: distinct bit_set[{}; Flags]\n".format(flag.ljust(max_len), flag_name))
        f.write("{} :: enum u32 {{}}\n".format(flag_name.ljust(max_len)))

def parse_fake_enums(f):
    data = re.findall(r"static const Vk(\w+FlagBits2) VK_(\w+?) = (\w+);", src, re.S)
    
    data.sort(key=lambda x: x[0])

    fake_enums = {}

    for type_name, name, value in data:
        if type_name in fake_enums:
            fake_enums[type_name].append((name,value))
        else:
            fake_enums[type_name] = [(name, value)]

    for name in fake_enums.keys():
        flags_name = name.replace("FlagBits", "Flags")
        enum_name = name.replace("FlagBits", "Flag")
        f.write("{} :: distinct bit_set[{}; Flags64]\n".format(flags_name, enum_name))
        f.write("{} :: enum Flags64 {{\n".format(name.replace("FlagBits", "Flag")))

        prefix = to_snake_case(name).upper()
        suffix = None
        for ext in ext_suffixes:
            prefix_new = remove_suffix(prefix, "_"+ext)
            assert suffix is None
            if prefix_new != prefix:
                suffix = "_"+ext
                prefix = prefix_new
                break


        prefix = prefix.replace("_FLAG_BITS2", "_2")
        prefix += "_"

        ff = []

        groups = []
        flags = {}

        names_and_values = fake_enums[name]

        for name, value in names_and_values:
            value = value.replace("ULL", "")
            n = fix_enum_name(name, prefix, suffix, True)
            try:
                v = fix_enum_value(value, prefix, suffix, True)
            except FlagError as e:
                v = int(str(e))
                groups.append((n, v))
                continue
            except IgnoreFlagError as e:
                groups.append((n, 0))
                continue

            if n == v:
                continue
            try:
                flags[int(v)] = n
            except ValueError as e:
                pass

            if v == "NONE":
                continue

            ff.append((n, v))
        
        max_flag_value = max([int(v) for n, v in ff if is_int(v)] + [0])
        max_group_value = max([int(v) for n, v in groups if is_int(v)] + [0])
        if max_flag_value < max_group_value:
            if (1<<max_flag_value)+1 < max_group_value:
                ff.append(('_MAX', 31))
                flags[31] = '_MAX'
                pass

        max_len = max([len(n) for n, v in ff] + [0])

        flag_names = set([n for n, v in ff])

        for n, v in ff:
            if not is_int(v) and v not in flag_names:
                print("Ignoring", n, "=", v)
                continue
            f.write("\t{} = {},".format(n.ljust(max_len), v))
            if n == "_MAX":
                f.write(" // Needed for the *_ALL bit set")
            f.write("\n")

        f.write("}\n\n")

def parse_structs(f):
    data = re.findall(r"typedef (struct|union) Vk(\w+?) {(.+?)} \w+?;", src, re.S)
    data += re.findall(r"typedef (struct|union) Std(\w+?) {(.+?)} \w+?;", src, re.S)

    for _type, name, fields in data:
        fields = re.findall(r"\s+(.+?)[\s:]+([_a-zA-Z0-9[\]]+);", fields)
        f.write("{} :: struct ".format(name))
        if _type == "union":
            f.write("#raw_union ")
        f.write("{\n")


        prev_name = ""
        ffields = []
        for type_, fname in fields:

            # If the field name only has a number in it, then it is a C bit field.
            if is_int(fname):
                comment = None
                bit_field = type_.split(' ')
                # Get rid of empty spaces
                bit_field = list(filter(bool, bit_field))
                # [type, fieldname]
                assert len(bit_field) == 2, "Failed to parse the bit field!"


                bit_field_type = ""
                # Right now there are only two ways that C bit fields exist in
                # the header files.

                # First one uses the 8 MOST significant bits for one field, and
                # 24 bits for the other field.
                # In the bindings these two fields are merged into one u32.
                if int(fname) == 24:
                    prev_name = bit_field[1]
                    continue

                elif prev_name:
                    bit_field_type = do_type("uint32_t")
                    bit_field_name = prev_name + "And" + bit_field[1].capitalize()
                    comment = " // Most significant byte is {}".format(bit_field[1])
                    ffields.append(tuple([bit_field_name, bit_field_type, comment]))
                    prev_name = ""
                    continue
                
                # The second way has many fields that are each 1 bit
                elif int(fname) == 1:
                    bit_field_type = do_type(bit_field[0], prev_name, fname)
                    ffields.append(tuple(["bitfield", bit_field_type, comment]))
                    break
                    


            if '[' in fname:
                fname, type_ = parse_array(fname, type_)
            comment = None
            n = fix_arg(fname)
            if "Flag_Bits" in type_:
                comment = " // only single bit set"
            t = do_type(type_, prev_name, fname)
            if n == "matrix":
                n = "mat"

            ffields.append(tuple([n, t, comment]))
            prev_name = fname

        max_len = max([len(n) for n, _, _ in ffields], default=0)

        for n, t, comment in ffields:
            k = max_len-len(n)+len(t)
            f.write("\t{}: {},{}\n".format(n, t.rjust(k), comment or ""))


        f.write("}\n\n")

    f.write("// Opaque structs\n")
    f.write(OPAQUE_STRUCTS)

    f.write("// Aliases\n")
    data = re.findall(r"typedef Vk(\w+?) Vk(\w+?);", src, re.S)
    aliases = []
    for _type, name in data:
        if _type == "Flags":
            continue
        name = name.replace("FlagBits", "Flag")
        _type = _type.replace("FlagBits", "Flag")

        if name.endswith("Flag2") or name.endswith("Flags2"):
            continue

        aliases.append((name, _type))

    max_len = max([len(n) for n, _ in aliases] + [0])
    for n, t in aliases:
        k = max_len
        f.write("{} :: {}\n".format(n.ljust(k), t))



procedure_map = {}

def parse_procedures(f):
    data = re.findall(r"typedef (\w+\*?) \(\w+ \*(\w+)\)\((.+?)\);", src, re.S)

    group_ff = {"Loader":[], "Misc":[], "Instance":[], "Device":[]}

    for rt, name, fields in data:
        proc_name = no_vk(name)
        pf = []
        prev_name = ""
        for type_, fname, array_len in re.findall(r"(?:\s*|)(.+?)\s*(\w+)(?:\[(\d+)\])?(?:,|$)", fields):
            curr_name = fix_arg(fname)
            ty = do_type(type_, prev_name, curr_name)
            if array_len != "":
                ty = f"^[{array_len}]{ty}"
            pf.append((ty, curr_name))
            prev_name = curr_name

        data_fields = ', '.join(["{}: {}".format(n, t) for t, n in pf if t != ""])

        ts = "proc \"c\" ({})".format(data_fields)
        rt_str = do_type(rt)
        if rt_str != "void":
            ts += " -> {}".format(rt_str)

        procedure_map[proc_name] = ts

        fields_types_name = [do_type(t) for t in re.findall(r"(?:\s*|)(.+?)\s*\w+(?:,|$)", fields)]
        table_name = fields_types_name[0]
        nn = (proc_name, ts)
        if table_name in ('Device', 'Queue', 'CommandBuffer') and proc_name != 'GetDeviceProcAddr':
            group_ff["Device"].append(nn)
        elif table_name in ('Instance', 'PhysicalDevice') or proc_name == 'GetDeviceProcAddr':
            group_ff["Instance"].append(nn)
        elif table_name in ('rawptr', '', 'DebugReportFlagsEXT') or proc_name == 'GetInstanceProcAddr':
            group_ff["Misc"].append(nn)
        else:
            group_ff["Loader"].append(nn)


    f.write("import \"core:c\"\n\n")
    for group_name, ff in group_ff.items():
        ff.sort()
        f.write("// {} Procedure Types\n".format(group_name))
        max_len = max(len(n) for n, t in ff)
        for n, t in ff:
            f.write("{} :: #type {}\n".format(n.ljust(max_len), t.replace('"c"', '"system"')))
        f.write("\n")

def group_functions(f):
    data = re.findall(r"typedef (\w+\*?) \(\w+ \*(\w+)\)\((.+?)\);", src, re.S)
    group_map = {"Loader":[], "Instance":[], "Device":[]}

    for rt, vkname, fields in data:
        fields_types_name = [do_type(t) for t in re.findall(r"(?:\s*|)(.+?)\s*\w+(?:,|$)", fields)]
        table_name = fields_types_name[0]
        name = no_vk(vkname)

        nn = (fix_arg(name), fix_ext_suffix(name))

        if table_name in ('Device', 'Queue', 'CommandBuffer') and name != 'GetDeviceProcAddr':
            group_map["Device"].append(nn)
        elif table_name in ('Instance', 'PhysicalDevice') and name != 'ProcGetInstanceProcAddr' or name == 'GetDeviceProcAddr':
            group_map["Instance"].append(nn)
        elif table_name in ('rawptr', '', 'DebugReportFlagsEXT') or name == 'GetInstanceProcAddr':
            # Skip the allocation function and the dll entry point
            pass
        else:
            group_map["Loader"].append(nn)
    for _, group in group_map.items():
        group.sort()

    for group_name, group_lines in group_map.items():
        f.write("// {} Procedures\n".format(group_name))
        max_len = max(len(name) for name, _ in group_lines)
        for name, vk_name in group_lines:
            type_str = procedure_map[vk_name]
            f.write('{}: {}\n'.format(remove_prefix(name, "Proc"), name.rjust(max_len)))
        f.write("\n")

    f.write("load_proc_addresses_custom :: proc(set_proc_address: SetProcAddressType) {\n")
    for group_name, group_lines in group_map.items():
        f.write("\t// {} Procedures\n".format(group_name))
        max_len = max(len(name) for name, _ in group_lines)
        for name, vk_name in group_lines:
            k = max_len - len(name)
            f.write('\tset_proc_address(&{}, {}"vk{}")\n'.format(
                remove_prefix(name, 'Proc'),
                "".ljust(k),
                remove_prefix(vk_name, 'Proc'),
            ))
        f.write("\n")
    f.write("}\n\n")

    f.write("// Device Procedure VTable\n")
    f.write("Device_VTable :: struct {\n")
    max_len = max(len(name) for name, _ in group_map["Device"])
    for name, vk_name in group_map["Device"]:
        f.write('\t{}: {},\n'.format(remove_prefix(name, "Proc"), name.rjust(max_len)))
    f.write("}\n\n")

    f.write("load_proc_addresses_device_vtable :: proc(device: Device, vtable: ^Device_VTable) {\n")
    for name, vk_name in group_map["Device"]:
        k = max_len - len(name)
        f.write('\tvtable.{}{} = auto_cast GetDeviceProcAddr(device, "vk{}")\n'.format(
            remove_prefix(name, 'Proc'),
            "".ljust(k),
            remove_prefix(vk_name, 'Proc'),
        ))
    f.write("}\n\n")

    f.write("load_proc_addresses_device :: proc(device: Device) {\n")
    max_len = max(len(name) for name, _ in group_map["Device"])
    for name, vk_name in group_map["Device"]:
        k = max_len - len(name)
        f.write('\t{}{} = auto_cast GetDeviceProcAddr(device, "vk{}")\n'.format(
            remove_prefix(name, 'Proc'),
            "".ljust(k),
            remove_prefix(vk_name, 'Proc'),
        ))
    f.write("}\n\n")

    f.write("load_proc_addresses_instance :: proc(instance: Instance) {\n")
    max_len = max(len(name) for name, _ in group_map["Instance"])
    for name, vk_name in group_map["Instance"]:
        k = max_len - len(name)
        f.write('\t{}{} = auto_cast GetInstanceProcAddr(instance, "vk{}")\n'.format(
            remove_prefix(name, 'Proc'),
            "".ljust(k),
            remove_prefix(vk_name, 'Proc'),
        ))
    f.write("\n\t// Device Procedures (may call into dispatch)\n")
    max_len = max(len(name) for name, _ in group_map["Device"])
    for name, vk_name in group_map["Device"]:
        k = max_len - len(name)
        f.write('\t{}{} = auto_cast GetInstanceProcAddr(instance, "vk{}")\n'.format(
            remove_prefix(name, 'Proc'),
            "".ljust(k),
            remove_prefix(vk_name, 'Proc'),
        ))
    f.write("}\n\n")

    f.write("load_proc_addresses_global :: proc(vk_get_instance_proc_addr: rawptr) {\n")
    f.write("\tGetInstanceProcAddr = auto_cast vk_get_instance_proc_addr\n\n")
    max_len = max(len(name) for name, _ in group_map["Loader"])
    for name, vk_name in group_map["Loader"]:
        k = max_len - len(name)
        f.write('\t{}{} = auto_cast GetInstanceProcAddr(nil, "vk{}")\n'.format(
            remove_prefix(name, 'Proc'),
            "".ljust(k),
            remove_prefix(vk_name, 'Proc'),
        ))
    f.write("}\n\n")

    f.write("""
load_proc_addresses :: proc{
\tload_proc_addresses_global,
\tload_proc_addresses_instance,
\tload_proc_addresses_device,
\tload_proc_addresses_device_vtable,
\tload_proc_addresses_custom,
}\n
"""[1::])



BASE = """
//
// Vulkan wrapper generated from "https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/master/include/vulkan/vulkan_core.h"
//
package vulkan
"""[1::]


with open("../core.odin", 'w', encoding='utf-8') as f:
    f.write(BASE)
    f.write("""
API_VERSION_1_0 :: (1<<22) | (0<<12) | (0)
API_VERSION_1_1 :: (1<<22) | (1<<12) | (0)
API_VERSION_1_2 :: (1<<22) | (2<<12) | (0)
API_VERSION_1_3 :: (1<<22) | (3<<12) | (0)

MAKE_VERSION :: proc(major, minor, patch: u32) -> u32 {
\treturn (major<<22) | (minor<<12) | (patch)
}

// Base types
Flags         :: distinct u32
Flags64       :: distinct u64
DeviceSize    :: distinct u64
DeviceAddress :: distinct u64
SampleMask    :: distinct u32

Handle                :: distinct rawptr
NonDispatchableHandle :: distinct u64

SetProcAddressType :: #type proc(p: rawptr, name: cstring)


RemoteAddressNV :: distinct rawptr // Declared inline before MemoryGetRemoteAddressInfoNV

// Base constants
LOD_CLAMP_NONE                        :: 1000.0
REMAINING_MIP_LEVELS                  :: ~u32(0)
REMAINING_ARRAY_LAYERS                :: ~u32(0)
WHOLE_SIZE                            :: ~u64(0)
ATTACHMENT_UNUSED                     :: ~u32(0)
TRUE                                  :: 1
FALSE                                 :: 0
QUEUE_FAMILY_IGNORED                  :: ~u32(0)
SUBPASS_EXTERNAL                      :: ~u32(0)
MAX_PHYSICAL_DEVICE_NAME_SIZE         :: 256
MAX_SHADER_MODULE_IDENTIFIER_SIZE_EXT :: 32
UUID_SIZE                             :: 16
MAX_MEMORY_TYPES                      :: 32
MAX_MEMORY_HEAPS                      :: 16
MAX_EXTENSION_NAME_SIZE               :: 256
MAX_DESCRIPTION_SIZE                  :: 256
MAX_DEVICE_GROUP_SIZE                 :: 32
LUID_SIZE_KHX                         :: 8
LUID_SIZE                             :: 8
MAX_QUEUE_FAMILY_EXTERNAL             :: ~u32(1)
MAX_GLOBAL_PRIORITY_SIZE_EXT          :: 16
QUEUE_FAMILY_EXTERNAL                 :: MAX_QUEUE_FAMILY_EXTERNAL

"""[1::])
    parse_constants(f)
    parse_handles_def(f)
    f.write("\n\n")
    parse_flags_def(f)
with open("../enums.odin", 'w', encoding='utf-8') as f:
    f.write(BASE)
    f.write("\n")
    parse_enums(f)
    parse_fake_enums(f)
    f.write("\n\n")
with open("../structs.odin", 'w', encoding='utf-8') as f:
    f.write(BASE)
    f.write("""
import "core:c"

import win32 "core:sys/windows"
_ :: win32
when ODIN_OS == .Windows {
\tHINSTANCE           :: win32.HINSTANCE
\tHWND                :: win32.HWND
\tHMONITOR            :: win32.HMONITOR
\tHANDLE              :: win32.HANDLE
\tLPCWSTR             :: win32.LPCWSTR
\tSECURITY_ATTRIBUTES :: win32.SECURITY_ATTRIBUTES
\tDWORD               :: win32.DWORD
\tLONG                :: win32.LONG
\tLUID                :: win32.LUID
} else {
\tHINSTANCE           :: distinct rawptr
\tHWND                :: distinct rawptr
\tHMONITOR            :: distinct rawptr
\tHANDLE              :: distinct rawptr
\tLPCWSTR             :: ^u16
\tSECURITY_ATTRIBUTES :: struct {}
\tDWORD               :: u32
\tLONG                :: c.long
\tLUID :: struct {
\t\tLowPart:  DWORD,
\t\tHighPart: LONG,
\t}
}

CAMetalLayer :: struct {}

MTLBuffer_id       :: rawptr
MTLTexture_id      :: rawptr
MTLSharedEvent_id  :: rawptr
MTLDevice_id       :: rawptr
MTLCommandQueue_id :: rawptr

/********************************/
""")
    f.write("\n")
    parse_structs(f)
    f.write("\n\n")
with open("../procedures.odin", 'w', encoding='utf-8') as f:
    f.write(BASE)
    f.write("\n")
    parse_procedures(f)
    f.write("\n")
    group_functions(f)
