#!/usr/bin/python3
# Wine Vulkan generator
#
# Copyright 2017-2018 Roderick Colenbrander
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
#  License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#

import argparse
import logging
import re
import sys
import xml.etree.ElementTree as ET
from collections import OrderedDict
from collections.abc import Sequence
from enum import Enum

# This script generates code for a Wine Vulkan ICD driver from Vulkan's vk.xml.
# Generating the code is like 10x worse than OpenGL, which is mostly a calling
# convention passthrough.
#
# The script parses vk.xml and maps functions and types to helper objects. These
# helper objects simplify the xml parsing and map closely to the Vulkan types.
# The code generation utilizes the helper objects during code generation and
# most of the ugly work is carried out by these objects.
#
# Vulkan ICD challenges:
# - Vulkan ICD loader (vulkan-1.dll) relies on a section at the start of
#   'dispatchable handles' (e.g. VkDevice, VkInstance) for it to insert
#   its private data. It uses this area to stare its own dispatch tables
#   for loader internal use. This means any dispatchable objects need wrapping.
#
# - Vulkan structures have different alignment between win32 and 32-bit Linux.
#   This means structures with alignment differences need conversion logic.
#   Often structures are nested, so the parent structure may not need any
#   conversion, but some child may need some.
#
# vk.xml parsing challenges:
# - Contains type data for all platforms (generic Vulkan, Windows, Linux,..).
#   Parsing of extension information required to pull in types and functions
#   we really want to generate. Just tying all the data together is tricky.
#
# - Extensions can affect core types e.g. add new enum values, bitflags or
#   additional structure chaining through 'pNext' / 'sType'.
#
# - Arrays are used all over the place for parameters or for structure members.
#   Array length is often stored in a previous parameter or another structure
#   member and thus needs careful parsing.

LOGGER = logging.Logger("vulkan")
LOGGER.addHandler(logging.StreamHandler())

# Filenames to create.
WINE_VULKAN_H = "../../include/wine/vulkan.h"
WINE_VULKAN_DRIVER_H = "../../include/wine/vulkan_driver.h"
WINE_VULKAN_THUNKS_C = "vulkan_thunks.c"
WINE_VULKAN_THUNKS_H = "vulkan_thunks.h"


# Functions part of our winevulkan graphics driver interface.
# DRIVER_VERSION should be bumped on any change to driver interface
# in FUNCTION_OVERRIDES
DRIVER_VERSION = 1

# Table of functions for which we have a special implementation.
# This are regular device / instance functions for which we need
# to more work compared to a regular thunk  or because they are
# part of the driver interface.
# - dispatch set whether we need a function pointer in the device
#   / instance dispatch table.
# - driver sets whether the api is part of the driver interface.
# - thunk sets whether to create a thunk in vulkan_thunks.c.
FUNCTION_OVERRIDES = {
    # Global functions
    "vkCreateInstance" : {"dispatch" : False, "driver" : True, "thunk" : False},
    "vkEnumerateInstanceExtensionProperties" : {"dispatch" : False, "driver" : True, "thunk" : False},
    "vkGetInstanceProcAddr": {"dispatch" : False, "driver" : True, "thunk" : False},

    # Instance functions
    "vkDestroyInstance" : {"dispatch" : True, "driver" : True, "thunk" : False },
}


class VkBaseType(object):
    def __init__(self, name, _type, requires=None):
        """ Vulkan base type class.

        VkBaseType is mostly used by Vulkan to define its own
        base types like VkFlags through typedef out of e.g. uint32_t.

        Args:
            name (:obj:'str'): Name of the base type.
            _type (:obj:'str'): Underlaying type
            requires (:obj:'str', optional): Other types required.
                Often bitmask values pull in a *FlagBits type.
        """
        self.name = name
        self.type = _type
        self.requires = requires
        self.required = False

    def definition(self):
        text = "typedef {0} {1};\n".format(self.type, self.name)
        return text


class VkConstant(object):
    def __init__(self, name, value):
        self.name = name
        self.value = value

    def definition(self):
        text = "#define {0} {1}\n".format(self.name, self.value)
        return text


class VkDefine(object):
    def __init__(self, name, value):
        self.name = name
        self.value = value

    @staticmethod
    def from_xml(define):
        name_elem = define.find("name")

        if name_elem is None:
            # <type category="define" name="some_name">some_value</type>
            # At the time of writing there is only 1 define of this category
            # 'VK_DEFINE_NON_DISPATCHABLE_HANDLE'.
            name = define.attrib.get("name")

            # We override behavior of VK_DEFINE_NON_DISPATCHABLE handle as the default
            # definition various between 64-bit (uses pointers) and 32-bit (uses uint64_t).
            # This complicates TRACEs in the thunks, so just use uint64_t.
            if name == "VK_DEFINE_NON_DISPATCHABLE_HANDLE":
                value = "#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object;"
            else:
                value = define.text
            return VkDefine(name, value)

        # With a name element the structure is like:
        # <type category="define"><name>some_name</name>some_value</type>
        name = name_elem.text

        # VK_API_VERSION is a deprecated constant and we don't really want to parse it or generate
        # code for it. However the vk.xml feature section still references it.
        if name == "VK_API_VERSION":
            return VkDefine(name, "")

        # The body of the define is basically unstructured C code. It is not meant for easy parsing.
        # Some lines contain deprecated values or comments, which we try to filter out.
        value = ""
        for line in define.text.splitlines():
            # Skip comments or deprecated values.
            if "//" in line:
                continue
            value += line

        for child in define:
            value += child.text
            if child.tail is not None:
                value += child.tail

        return VkDefine(name, value.rstrip(' '))

    def definition(self):
        if self.value is None:
            return ""

        # Nothing to do as the value was already put in the right form during parsing.
        return "{0}\n".format(self.value)


class VkEnum(object):
    def __init__(self, name, values):
        self.name = name
        self.values = values
        self.required = False

    @staticmethod
    def from_xml(enum):
        name = enum.attrib.get("name")
        values = []

        for v in enum.findall("enum"):
            # Value is either a value or a bitpos, only one can exist.
            value = v.attrib.get("value")
            if value is None:
                # bitmask
                value = 1 << int(v.attrib.get("bitpos"))
                values.append(VkEnumValue(v.attrib.get("name"), value, hex=True))
            else:
                # Some values are in hex form. We want to preserve the hex representation
                # at least when we convert back to a string. Internally we want to use int.
                if "0x" in value:
                    values.append(VkEnumValue(v.attrib.get("name"), int(value, 0), hex=True))
                else:
                    values.append(VkEnumValue(v.attrib.get("name"), int(value, 0)))

        # vulkan.h contains a *_MAX_ENUM value set to 32-bit at the time of writing,
        # which is to prepare for extensions as they can add values and hence affect
        # the size definition.
        max_name = re.sub(r'([0-9a-z_])([A-Z0-9])',r'\1_\2',name).upper() + "_MAX_ENUM"
        values.append(VkEnumValue(max_name, 0x7fffffff, hex=True))

        return VkEnum(name, values)

    def add(self, value):
        """ Add a value to enum. """
        self.values.append(value)

    def definition(self):
        text = "typedef enum {0}\n{{\n".format(self.name)

        # Print values sorted, values can have been added in a random order.
        values = sorted(self.values, key=lambda value: value.value)
        for value in values:
            text += "    {0},\n".format(value.definition())
        text += "}} {0};\n\n".format(self.name)
        return text


class VkEnumValue(object):
    def __init__(self, name, value, hex=False):
        self.name = name
        self.value = value
        self.hex = hex

    def __repr__(self):
        return "{0}={1}".format(self.name, self.value)

    def definition(self):
        """ Convert to text definition e.g. VK_FOO = 1 """

        # Hex is commonly used for FlagBits and sometimes within
        # a non-FlagBits enum for a bitmask value as well.
        if self.hex:
            return "{0} = 0x{1:08x}".format(self.name, self.value)
        else:
            return "{0} = {1}".format(self.name, self.value)


class VkFunction(object):
    def __init__(self, _type=None, name=None, params=[], extension=None):
        self.extension = extension
        self.name = name
        self.type = _type
        self.params = params

        # For some functions we need some extra metadata from FUNCTION_OVERRIDES.
        func_info = FUNCTION_OVERRIDES.get(self.name, None)
        self.dispatch = func_info["dispatch"] if func_info is not None else True
        self.driver = func_info["driver"] if func_info is not None else False
        self.thunk_needed = func_info["thunk"] if func_info is not None else True

        # Required is set while parsing which APIs and types are required
        # and is used by the code generation.
        self.required = False

    @staticmethod
    def from_xml(command, types):
        proto = command.find("proto")

        func_name = proto.find("name").text
        func_type = proto.find("type").text

        params = []
        for param in command.findall("param"):
            vk_param = VkParam.from_xml(param, types)
            params.append(vk_param)

        return VkFunction(_type=func_type, name=func_name, params=params)

    def is_device_func(self):
        # If none of the other, it must be a device function
        return not self.is_global_func() and not self.is_instance_func()

    def is_driver_func(self):
        """ Returns if function is part of Wine driver interface. """
        return self.driver

    def is_global_func(self):
        # Treat vkGetInstanceProcAddr as a global function as it
        # can operate with NULL for vkInstance.
        if self.name == "vkGetInstanceProcAddr":
            return True
        # Global functions are not passed a dispatchable object.
        elif self.params[0].is_dispatchable():
            return False
        return True

    def is_instance_func(self):
        # Instance functions are passed VkInstance or VkPhysicalDevice.
        if self.params[0].type in ["VkInstance", "VkPhysicalDevice"]:
            return True
        return False

    def is_required(self):
        return self.required

    def needs_dispatch(self):
        return self.dispatch

    def needs_thunk(self):
        return self.thunk_needed

    def pfn(self, call_conv=None, conv=False):
        """ Create function pointer. """

        if call_conv is not None:
            pfn = "{0} ({1} *p_{2})(".format(self.type, call_conv, self.name)
        else:
            pfn = "{0} (*p_{1})(".format(self.type, self.name)

        for i, param in enumerate(self.params):
            if param.const:
                pfn += param.const + " "

            pfn += param.type
            if conv and param.needs_conversion():
                pfn += "_host"

            if param.is_pointer():
                pfn += " " + param.pointer

            if param.array_len is not None:
                pfn += "[{0}]".format(param.array_len)

            if i < len(self.params) - 1:
                pfn += ", "
        pfn += ")"
        return pfn

    def prototype(self, call_conv=None, prefix=None, postfix=None):
        """ Generate prototype for given function.

        Args:
            call_conv (str, optional): calling convention e.g. WINAPI
            prefix (str, optional): prefix to append prior to function name e.g. vkFoo -> wine_vkFoo
            postfix (str, optional): text to append after function name but prior to semicolon e.g. DECLSPEC_HIDDEN
        """

        proto = "{0}".format(self.type)

        if call_conv is not None:
            proto += " {0}".format(call_conv)

        if prefix is not None:
            proto += " {0}{1}(".format(prefix, self.name)
        else:
            proto += " {0}(".format(self.name)

        # Add all the paremeters.
        proto += ", ".join([p.definition() for p in self.params])

        if postfix is not None:
            proto += ") {0}".format(postfix)
        else:
            proto += ")"

        return proto

    def stub(self, call_conv=None, prefix=None):
        stub = self.prototype(call_conv=call_conv, prefix=prefix)
        stub += "\n{\n"
        stub += "    {0}".format(self.trace(message="stub: ", trace_func="FIXME"))

        if self.type == "VkResult":
            stub += "    return VK_ERROR_OUT_OF_HOST_MEMORY;\n"
        elif self.type == "VkBool32":
            stub += "    return VK_FALSE;\n"

        stub += "}\n\n"
        return stub

    def trace(self, message=None, trace_func=None):
        """ Create a trace string including all parameters.

        Args:
            message (str, optional): text to print at start of trace message e.g. 'stub: '
            trace_func (str, optional): used to override trace function e.g. FIXME, printf, etcetera.
        """
        if trace_func is not None:
            trace = "{0}(\"".format(trace_func)
        else:
            trace = "TRACE(\""

        if message is not None:
            trace += message

        # First loop is for all the format strings.
        trace += ", ".join([p.format_string() for p in self.params])
        trace += "\\n\""

        # Second loop for parameter names and optional conversions.
        for param in self.params:
            if param.format_conv is not None:
                trace += ", " + param.format_conv.format(param.name)
            else:
                trace += ", {0}".format(param.name)
        trace += ");\n"

        return trace


class VkFunctionPointer(object):
    def __init__(self, _type, name, members):
        self.name = name
        self.members = members
        self.type = _type
        self.required = False

    @staticmethod
    def from_xml(funcpointer):
        members = []
        begin = None

        for t in funcpointer.findall("type"):
            # General form:
            # <type>void</type>*       pUserData,
            # Parsing of the tail (anything past </type>) is tricky since there
            # can be other data on the next line like: const <type>int</type>..
            const = begin
            _type = t.text
            lines = t.tail.split(",\n")
            if lines[0][0] == "*":
                pointer = "*"
                name = lines[0][1:].strip()
            else:
                pointer = None
                name = lines[0].strip()

            # Filter out ); if it is contained.
            name = name.partition(");")[0]

            # If tail encompasses multiple lines, assign the second line to begin
            # for the next line.
            try:
                begin = lines[1].strip()
            except IndexError:
                begin = None

            members.append(VkMember(const=const, _type=_type, pointer=pointer, name=name))

        _type = funcpointer.text
        name = funcpointer.find("name").text
        return VkFunctionPointer(_type, name, members)

    def definition(self):
        text = "{0} {1})(\n".format(self.type, self.name)

        first = True
        if len(self.members) > 0:
            for m in self.members:
                if first:
                    text += "    " + m.definition()
                    first = False
                else:
                    text += ",\n    " + m.definition()
        else:
            # Just make the compiler happy by adding a void parameter.
            text += "void"
        text += ");\n"
        return text


class VkHandle(object):
    def __init__(self, name, _type, parent):
        self.name = name
        self.type = _type
        self.parent = parent
        self.required = False

    @staticmethod
    def from_xml(handle):
        name = handle.find("name").text
        _type = handle.find("type").text
        parent = handle.attrib.get("parent") # Most objects have a parent e.g. VkQueue has VkDevice.
        return VkHandle(name, _type, parent)

    def definition(self):
        """ Generates handle definition e.g. VK_DEFINE_HANDLE(vkInstance) """
        return "{0}({1})\n".format(self.type, self.name)

    def is_dispatchable(self):
        """ Some handles like VkInstance, VkDevice are dispatchable objects,
        which means they contain a dispatch table of function pointers.
        """
        return self.type == "VK_DEFINE_HANDLE"


class VkMember(object):
    def __init__(self, const=None, _type=None, pointer=None, name=None, array_len=None, dyn_array_len=None, optional=False,
            extension_structs=None):
        self.const = const
        self.name = name
        self.pointer = pointer
        self.type = _type
        self.type_info = None
        self.array_len = array_len
        self.dyn_array_len = dyn_array_len
        self.optional = optional
        self.extension_structs = extension_structs

    def __eq__(self, other):
        """ Compare member based on name against a string.

        This method is for convenience by VkStruct, which holds a number of members and needs quick checking
        if certain members exist.
        """

        if self.name == other:
            return True

        return False

    def __repr__(self):
        return "{0} {1} {2} {3} {4} {5}".format(self.const, self.type, self.pointer, self.name, self.array_len,
                self.dyn_array_len)

    @staticmethod
    def from_xml(member):
        """ Helper function for parsing a member tag within a struct or union. """

        name_elem = member.find("name")
        type_elem = member.find("type")

        const = member.text.strip() if member.text else None
        member_type = None
        pointer = None
        array_len = None

        if type_elem is not None:
            member_type = type_elem.text
            if type_elem.tail is not None:
                pointer = type_elem.tail.strip() if type_elem.tail.strip() != "" else None

        # Name of other member within, which stores the number of
        # elements pointed to be by this member.
        dyn_array_len = member.get("len", None)

        if "validextensionstructs" in member.attrib:
            extension_structs = member.get("validextensionstructs").split(",")
        else:
            extension_structs = None

        # Some members are optional, which is important for conversion code e.g. not dereference NULL pointer.
        optional = True if member.get("optional") else False

        # Usually we need to allocate memory for dynamic arrays. We need to do the same in a few other cases
        # like for VkCommandBufferBeginInfo.pInheritanceInfo. Just threat such cases as dynamic arrays of
        # size 1 to simplify code generation.
        if dyn_array_len is None and pointer is not None:
            dyn_array_len = 1

        # Some members are arrays, attempt to parse these. Formats include:
        # <member><type>char</type><name>extensionName</name>[<enum>VK_MAX_EXTENSION_NAME_SIZE</enum>]</member>
        # <member><type>uint32_t</type><name>foo</name>[4]</member>
        if name_elem.tail and name_elem.tail[0] == '[':
            LOGGER.debug("Found array type")
            enum_elem = member.find("enum")
            if enum_elem is not None:
                array_len = enum_elem.text
            else:
                # Remove brackets around length
                array_len = name_elem.tail.strip("[]")

        return VkMember(const=const, _type=member_type, pointer=pointer, name=name_elem.text, array_len=array_len,
                dyn_array_len=dyn_array_len, optional=optional, extension_structs=extension_structs)

    def definition(self, align=False, conv=False):
        """ Generate prototype for given function.

        Args:
            align (bool, optional): Enable alignment if a type needs it. This adds WINE_VK_ALIGN(8) to a member.
            conv (bool, optional): Enable conversion if a type needs it. This appends '_host' to the name.
        """

        text = ""
        if self.is_const():
            text += "const "

        if conv and self.is_struct():
            text += "{0}_host".format(self.type)
        else:
            text += self.type

        if self.is_pointer():
            text += " {0}{1}".format(self.pointer, self.name)
        else:
            if align and self.needs_alignment():
                text += " WINE_VK_ALIGN(8) " + self.name
            else:
                text += " " + self.name

        if self.is_static_array():
            text += "[{0}]".format(self.array_len)

        return text

    def is_const(self):
        return self.const is not None

    def is_dynamic_array(self):
        """ Returns if the member is an array element.
        Vulkan uses this for dynamically sized arrays for which
        there is a 'count' parameter.
        """
        return self.dyn_array_len is not None

    def is_handle(self):
        return self.type_info["category"] == "handle"

    def is_pointer(self):
        return self.pointer is not None

    def is_static_array(self):
        """ Returns if the member is an array.
        Vulkan uses this often for fixed size arrays in which the
        length is part of the member.
        """
        return self.array_len is not None

    def is_struct(self):
        return self.type_info["category"] == "struct"

    def is_union(self):
        return self.type_info["category"] == "union"

    def needs_alignment(self):
        """ Check if this member needs alignment for 64-bit data.
        Various structures need alignment on 64-bit variables due
        to compiler differences on 32-bit between Win32 and Linux.
        """

        if self.is_pointer():
            return False
        elif self.type == "size_t":
            return False
        elif self.type in ["uint64_t", "VkDeviceSize"]:
            return True
        elif self.is_struct():
            struct = self.type_info["data"]
            return struct.needs_alignment()
        elif self.is_handle():
            # Dispatchable handles are pointers to objects, while
            # non-dispatchable are uint64_t and hence need alignment.
            handle = self.type_info["data"]
            return False if handle.is_dispatchable() else True
        return False

    def set_type_info(self, type_info):
        """ Helper function to set type information from the type registry.
        This is needed, because not all type data is available at time of
        parsing.
        """
        self.type_info = type_info


class VkParam(object):
    """ Helper class which describes a parameter to a function call. """

    def __init__(self, type_info, const=None, pointer=None, name=None, array_len=None, dyn_array_len=None):
        self.const = const
        self.name = name
        self.array_len = array_len
        self.dyn_array_len = dyn_array_len
        self.pointer = pointer
        self.type_info = type_info
        self.type = type_info["name"] # For convenience
        self.handle = type_info["data"] if type_info["category"] == "handle" else None
        self.struct = type_info["data"] if type_info["category"] == "struct" else None

        self._set_format_string()

    def __repr__(self):
        return "{0} {1} {2} {3} {4}".format(self.const, self.type, self.pointer, self.name, self.array_len, self.dyn_array_len)

    @staticmethod
    def from_xml(param, types):
        """ Helper function to create VkParam from xml. """

        # Parameter parsing is slightly tricky. All the data is contained within
        # a param tag, but some data is within subtags while others are text
        # before or after the type tag.
        # Common structure:
        # <param>const <type>char</type>* <name>pLayerName</name></param>

        name_elem = param.find("name")
        array_len = None
        name = name_elem.text
        # Tail contains array length e.g. for blendConstants param of vkSetBlendConstants
        if name_elem.tail is not None:
            array_len = name_elem.tail.strip("[]")

        # Name of other parameter in function prototype, which stores the number of
        # elements pointed to be by this parameter.
        dyn_array_len = param.get("len", None)

        const = param.text.strip() if param.text else None
        type_elem = param.find("type")
        pointer = type_elem.tail.strip() if type_elem.tail.strip() != "" else None

        # Since we have parsed all types before hand, this should not happen.
        type_info = types.get(type_elem.text, None)
        if type_info is None:
            LOGGER.err("type info not found for: {0}".format(type_elem.text))

        return VkParam(type_info, const=const, pointer=pointer, name=name, array_len=array_len, dyn_array_len=dyn_array_len)

    def _set_format_string(self):
        """ Internal helper function to be used by constructor to set format string. """

        # Determine a format string used by code generation for traces.
        # 64-bit types need a conversion function.
        self.format_conv = None
        if self.is_static_array() or self.is_pointer():
            self.format_str = "%p"
        else:
            if self.type_info["category"] == "bitmask":
                self.format_str = "%#x"
            elif self.type_info["category"] == "enum":
                self.format_str = "%d"
            elif self.is_handle():
                # We use uint64_t for non-dispatchable handles as opposed to pointers
                # for dispatchable handles.
                if self.handle.is_dispatchable():
                    self.format_str = "%p"
                else:
                    self.format_str = "0x%s"
                    self.format_conv = "wine_dbgstr_longlong({0})"
            elif self.type == "float":
                self.format_str = "%f"
            elif self.type == "int":
                self.format_str = "%d"
            elif self.type == "int32_t":
                self.format_str = "%d"
            elif self.type == "size_t":
                self.format_str = "0x%s"
                self.format_conv = "wine_dbgstr_longlong({0})"
            elif self.type in ["uint32_t", "VkBool32"]:
                self.format_str = "%u"
            elif self.type in ["uint64_t", "VkDeviceSize"]:
                self.format_str = "0x%s"
                self.format_conv = "wine_dbgstr_longlong({0})"
            elif self.type == "HANDLE":
                self.format_str = "%p"
            elif self.type in ["VisualID", "xcb_visualid_t", "RROutput"]:
                # Don't care about Linux specific types.
                self.format_str = ""
            else:
                LOGGER.warn("Unhandled type: {0}".format(self.type_info))

    def definition(self, postfix=None):
        """ Return prototype for the parameter. E.g. 'const char *foo' """

        proto = ""
        if self.const:
            proto += self.const + " "

        proto += self.type

        if self.is_pointer():
            proto += " {0}{1}".format(self.pointer, self.name)
        else:
            proto += " " + self.name

        # Allows appeninding something to the variable name useful for
        # win32 to host conversion.
        if postfix is not None:
            proto += postfix

        if self.is_static_array():
            proto += "[{0}]".format(self.array_len)

        return proto

    def direction(self):
        """ Returns parameter direction: input, output, input_output.

        Parameter direction in Vulkan is not straight-forward, which this function determines.
        """

        return self._direction

    def format_string(self):
        return self.format_str

    def is_const(self):
        return self.const is not None

    def is_dynamic_array(self):
        return self.dyn_array_len is not None

    def is_dispatchable(self):
        if not self.is_handle():
            return False

        return self.handle.is_dispatchable()

    def is_handle(self):
        return self.handle is not None

    def is_pointer(self):
        return self.pointer is not None

    def is_static_array(self):
        return self.array_len is not None

    def is_struct(self):
        return self.struct is not None


class VkStruct(Sequence):
    """ Class which represents the type union and struct. """

    def __init__(self, name, members, returnedonly, union=False):
        self.name = name
        self.members = members
        self.returnedonly = returnedonly
        self.required = False
        self.union = union
        self.type_info = None # To be set later.

    def __getitem__(self, i):
        return self.members[i]

    def __len__(self):
        return len(self.members)

    @staticmethod
    def from_xml(struct):
        # Unions and structs are the same parsing wise, but we need to
        # know which one we are dealing with later on for code generation.
        union = True if struct.attrib["category"] == "union" else False

        name = struct.attrib.get("name", None)

        # 'Output' structures for which data is filled in by the API are
        # marked as 'returnedonly'.
        returnedonly = True if struct.attrib.get("returnedonly") else False

        members = []
        for member in struct.findall("member"):
            vk_member = VkMember.from_xml(member)
            members.append(vk_member)

        return VkStruct(name, members, returnedonly, union=union)

    @staticmethod
    def decouple_structs(structs):
        """ Helper function which decouples a list of structs.
        Structures often depend on other structures. To make the C compiler
        happy we need to define 'substructures' first. This function analyzes
        the list of structures and reorders them in such a way that they are
        decoupled.
        """

        tmp_structs = list(structs) # Don't modify the original structures.
        decoupled_structs = []

        while (len(tmp_structs) > 0):
            for struct in tmp_structs:
                dependends = False

                if not struct.required:
                    tmp_structs.remove(struct)
                    continue

                for m in struct:
                    if not (m.is_struct() or m.is_union()):
                        continue

                    found = False
                    # Check if a struct we depend on has already been defined.
                    for s in decoupled_structs:
                        if s.name == m.type:
                            found = True
                            break

                    if not found:
                        # Check if the struct we depend on is even in the list of structs.
                        # If found now, it means we haven't met all dependencies before we
                        # can operate on the current struct.
                        # When generating 'host' structs we may not be able to find a struct
                        # as the list would only contain the structs requiring conversion.
                        for s in tmp_structs:
                            if s.name == m.type:
                                dependends = True
                                break

                if dependends == False:
                    decoupled_structs.append(struct)
                    tmp_structs.remove(struct)

        return decoupled_structs

    def definition(self, align=False, conv=False, postfix=None):
        """ Convert structure to textual definition.

        Args:
            align (bool, optional): enable alignment to 64-bit for win32 struct compatibility.
            conv (bool, optional): enable struct conversion if the struct needs it.
            postfix (str, optional): text to append to end of struct name, useful for struct renaming.
        """

        if self.union:
            text = "typedef union {0}".format(self.name)
        else:
            text = "typedef struct {0}".format(self.name)

        if postfix is not None:
            text += postfix

        text += "\n{\n"

        for m in self:
            if align and m.needs_alignment():
                text += "    {0};\n".format(m.definition(align=align))
            elif conv and m.needs_conversion():
                text += "    {0};\n".format(m.definition(conv=conv))
            else:
                text += "    {0};\n".format(m.definition())

        if postfix is not None:
            text += "}} {0}{1};\n\n".format(self.name, postfix)
        else:
            text += "}} {0};\n\n".format(self.name)
        return text

    def needs_alignment(self):
        """ Check if structure needs alignment for 64-bit data.
        Various structures need alignment on 64-bit variables due
        to compiler differences on 32-bit between Win32 and Linux.
        """

        for m in self.members:
            if m.needs_alignment():
                return True
        return False

    def set_type_info(self, types):
        """ Helper function to set type information from the type registry.
        This is needed, because not all type data is available at time of
        parsing.
        """
        for m in self.members:
            type_info = types[m.type]
            m.set_type_info(type_info)


class VkGenerator(object):
    def __init__(self, registry):
        self.registry = registry

    def generate_thunks_c(self, f, prefix):
        f.write("/* Automatically generated from Vulkan vk.xml; DO NOT EDIT! */\n\n")

        f.write("#include \"config.h\"\n")
        f.write("#include \"wine/port.h\"\n\n")

        f.write("#include \"wine/debug.h\"\n")
        f.write("#include \"wine/vulkan.h\"\n")
        f.write("#include \"wine/vulkan_driver.h\"\n")
        f.write("#include \"vulkan_private.h\"\n\n")

        f.write("WINE_DEFAULT_DEBUG_CHANNEL(vulkan);\n\n")

        # Create thunks for instance functions.
        # Global functions don't go through the thunks.
        for vk_func in self.registry.funcs.values():
            if not vk_func.is_required():
                continue

            if vk_func.is_global_func():
                continue

            # We don't support device functions yet as other plumbing
            # is needed first.
            if vk_func.is_device_func():
                continue

            if not vk_func.needs_thunk():
                continue

            f.write("static " + vk_func.stub(prefix=prefix, call_conv="WINAPI"))

        f.write("static const struct vulkan_func vk_instance_dispatch_table[] =\n{\n")
        for vk_func in self.registry.instance_funcs:
            if not vk_func.is_required():
                continue

            if not vk_func.needs_dispatch():
                LOGGER.debug("skipping {0} in instance dispatch table".format(vk_func.name))
                continue

            f.write("    {{\"{0}\", &{1}{0}}},\n".format(vk_func.name, prefix))
        f.write("};\n\n")

        f.write("void *wine_vk_get_instance_proc_addr(const char *name)\n")
        f.write("{\n")
        f.write("    unsigned int i;\n")
        f.write("    for (i = 0; i < ARRAY_SIZE(vk_instance_dispatch_table); i++)\n")
        f.write("    {\n")
        f.write("        if (strcmp(vk_instance_dispatch_table[i].name, name) == 0)\n")
        f.write("        {\n")
        f.write("            TRACE(\"Found pName=%s in instance table\\n\", name);\n")
        f.write("            return vk_instance_dispatch_table[i].func;\n")
        f.write("        }\n")
        f.write("    }\n")
        f.write("    return NULL;\n")
        f.write("}\n")

    def generate_thunks_h(self, f, prefix):
        f.write("/* Automatically generated from Vulkan vk.xml; DO NOT EDIT! */\n\n")

        f.write("#ifndef __WINE_VULKAN_THUNKS_H\n")
        f.write("#define __WINE_VULKAN_THUNKS_H\n\n")

        f.write("/* For use by vk_icdGetInstanceProcAddr / vkGetInstanceProcAddr */\n")
        f.write("void *wine_vk_get_instance_proc_addr(const char *name) DECLSPEC_HIDDEN;\n\n")

        # Generate prototypes for device and instance functions requiring a custom implementation.
        f.write("/* Functions for which we have custom implementations outside of the thunks. */\n")
        for vk_func in self.registry.funcs.values():
            if not vk_func.is_required() or vk_func.is_global_func() or vk_func.needs_thunk():
                continue

            f.write("{0};\n".format(vk_func.prototype("WINAPI", prefix="wine_", postfix="DECLSPEC_HIDDEN")))
        f.write("\n")

        f.write("/* For use by vkInstance and children */\n")
        f.write("struct vulkan_instance_funcs\n{\n")
        for vk_func in self.registry.instance_funcs:
            if not vk_func.is_required():
                continue

            if not vk_func.needs_dispatch() or vk_func.is_driver_func():
                LOGGER.debug("skipping {0} in vulkan_instance_funcs".format(vk_func.name))
                continue

            f.write("    {0};\n".format(vk_func.pfn(conv=False)))
        f.write("};\n\n")

        f.write("#define ALL_VK_INSTANCE_FUNCS() \\\n")
        first = True
        for vk_func in self.registry.instance_funcs:
            if not vk_func.is_required():
                continue

            if not vk_func.needs_dispatch() or vk_func.is_driver_func():
                LOGGER.debug("skipping {0} in ALL_VK_INSTANCE_FUNCS".format(vk_func.name))
                continue

            if first:
                f.write("    USE_VK_FUNC({0})".format(vk_func.name))
                first = False
            else:
                f.write("\\\n    USE_VK_FUNC({0})".format(vk_func.name))
        f.write("\n\n")

        f.write("#endif /* __WINE_VULKAN_THUNKS_H */\n")

    def generate_vulkan_h(self, f):
        f.write("/* Automatically generated from Vulkan vk.xml; DO NOT EDIT! */\n\n")
        f.write("#ifndef __WINE_VULKAN_H\n")
        f.write("#define __WINE_VULKAN_H\n\n")

        f.write("#include <windef.h>\n")
        f.write("#include <stdint.h>\n\n")

        f.write("#ifndef VKAPI_CALL\n")
        f.write("#define VKAPI_CALL __stdcall\n")
        f.write("#endif\n\n")

        f.write("#ifndef VKAPI_PTR\n")
        f.write("#define VKAPI_PTR VKAPI_CALL\n")
        f.write("#endif\n\n")

        f.write("/* Callers can override WINE_VK_ALIGN if they want 'host' headers. */\n")
        f.write("#ifndef WINE_VK_ALIGN\n")
        f.write("#define WINE_VK_ALIGN DECLSPEC_ALIGN\n")
        f.write("#endif\n\n")

        # The overall strategy is to define independent constants and datatypes,
        # prior to complex structures and function calls to avoid forward declarations.
        for const in self.registry.consts:
            # For now just generate things we may not need. The amount of parsing needed
            # to get some of the info is tricky as you need to figure out which structure
            # references a certain constant.
            f.write(const.definition())
        f.write("\n")

        for define in self.registry.defines:
            f.write(define.definition())

        for handle in self.registry.handles:
            if handle.required:
                 f.write(handle.definition())
        f.write("\n")

        for base_type in self.registry.base_types:
            f.write(base_type.definition())
        f.write("\n")

        for bitmask in self.registry.bitmasks:
            f.write(bitmask.definition())
        f.write("\n")

        # Define enums, this includes values for some of the bitmask types as well.
        for enum in self.registry.enums.values():
            if enum.required:
                f.write(enum.definition())

        for fp in self.registry.funcpointers:
            if fp.required:
                f.write(fp.definition())
        f.write("\n")

        # This generates both structures and unions. Since structures
        # may depend on other structures/unions, we need a list of
        # decoupled structs.
        # Note: unions are stored in structs for dependency reasons,
        # see comment in parsing section.
        structs = VkStruct.decouple_structs(self.registry.structs)
        for struct in structs:
            LOGGER.debug("Generating struct: {0}".format(struct.name))
            f.write(struct.definition(align=True))

        for func in self.registry.funcs.values():
            if not func.is_required():
                LOGGER.debug("Skipping API definition for: {0}".format(func.name))
                continue

            LOGGER.debug("Generating API definition for: {0}".format(func.name))
            f.write("{0};\n".format(func.prototype(call_conv="VKAPI_CALL")))
        f.write("\n")

        f.write("#endif /* __WINE_VULKAN_H */\n")

    def generate_vulkan_driver_h(self, f):
        f.write("/* Automatically generated from Vulkan vk.xml; DO NOT EDIT! */\n\n")
        f.write("#ifndef __WINE_VULKAN_DRIVER_H\n")
        f.write("#define __WINE_VULKAN_DRIVER_H\n\n")

        f.write("/* Wine internal vulkan driver version, needs to be bumped upon vulkan_funcs changes. */\n")
        f.write("#define WINE_VULKAN_DRIVER_VERSION {0}\n\n".format(DRIVER_VERSION))

        f.write("struct vulkan_funcs\n{\n")
        f.write("    /* Vulkan global functions. These are the only calls at this point a graphics driver\n")
        f.write("     * needs to provide. Other function calls will be provided indirectly by dispatch\n")
        f.write("     * tables part of dispatchable Vulkan objects such as VkInstance or vkDevice.\n")
        f.write("     */\n")

        for vk_func in self.registry.funcs.values():
            if not vk_func.is_required() or not vk_func.is_driver_func():
                continue

            pfn = vk_func.pfn()
            # Avoid PFN_vkVoidFunction in driver interface as Vulkan likes to put calling convention
            # stuff in there. For simplicity substitute with "void *".
            pfn = pfn.replace("PFN_vkVoidFunction", "void *")
            f.write("    {0};\n".format(pfn))
        f.write("};\n\n")

        f.write("extern const struct vulkan_funcs * CDECL __wine_get_vulkan_driver(HDC hdc, UINT version);\n\n")
        f.write("#endif /* __WINE_VULKAN_DRIVER_H */\n")


class VkRegistry(object):
    def __init__(self, reg_filename):
        # Used for storage of type information.
        self.base_types = None
        self.bitmasks = None
        self.consts = None
        self.defines = None
        self.enums = None
        self.funcpointers = None
        self.handles = None
        self.structs = None

        # We aggregate all types in here for cross-referencing.
        self.funcs = {}
        self.types = {}

        # Overall strategy for parsing the registry is to first
        # parse all type / function definitions. Then parse
        # features and extensions to decide which types / functions
        # to actually 'pull in' for code generation. For each type or
        # function call we want we set a member 'required' to True.
        tree = ET.parse(reg_filename)
        root = tree.getroot()
        self._parse_enums(root)
        self._parse_types(root)
        self._parse_commands(root)

        # Pull in any required types and functions.
        self._parse_features(root)

    def _mark_command_required(self, command):
        """ Helper function to mark a certain command and the datatypes it needs as required."""
        def mark_bitmask_dependencies(bitmask, types):
            if bitmask.requires is not None:
                types[bitmask.requires]["data"].required = True

        def mark_funcpointer_dependencies(fp, types):
            for m in fp.members:
                type_info = types[m.type]

                # Complex types have a matching definition e.g. VkStruct.
                # Not needed for base types such as uint32_t.
                if "data" in type_info:
                    types[m.type]["data"].required = True

        def mark_struct_dependencies(struct, types):
             for m in struct:
                type_info = types[m.type]

                # Complex types have a matching definition e.g. VkStruct.
                # Not needed for base types such as uint32_t.
                if "data" in type_info:
                    types[m.type]["data"].required = True

                if type_info["category"] == "struct":
                    # Yay, recurse
                    mark_struct_dependencies(type_info["data"], types)
                elif type_info["category"] == "funcpointer":
                    mark_funcpointer_dependencies(type_info["data"], types)
                elif type_info["category"] == "bitmask":
                    mark_bitmask_dependencies(type_info["data"], types)

        func = self.funcs[command]
        func.required = True

        # Pull in return type
        if func.type != "void":
            self.types[func.type]["data"].required = True

        # Analyze parameter dependencies and pull in any type needed.
        for p in func.params:
            type_info = self.types[p.type]

            # Check if we are dealing with a complex type e.g. VkEnum, VkStruct and others.
            if "data" not in type_info:
                continue

            # Mark the complex type as required.
            type_info["data"].required = True
            if type_info["category"] == "struct":
                struct = type_info["data"]
                mark_struct_dependencies(struct, self.types)

    def _parse_commands(self, root):
        """ Parse command section containing the Vulkan function calls. """
        funcs = {}
        commands = root.findall("./commands/")
        for command in commands:
            func = VkFunction.from_xml(command, self.types)
            funcs[func.name] = func

        # To make life easy for the code generation, separate all function
        # calls out in the 3 types of vulkan functions: device, global and instance.
        device_funcs = []
        global_funcs = []
        instance_funcs = []
        for func in funcs.values():
            if func.is_device_func():
                device_funcs.append(func)
            elif func.is_global_func():
                global_funcs.append(func)
            else:
                instance_funcs.append(func)

        # Sort function lists by name and store them.
        self.device_funcs = sorted(device_funcs, key=lambda func: func.name)
        self.global_funcs = sorted(global_funcs, key=lambda func: func.name)
        self.instance_funcs = sorted(instance_funcs, key=lambda func: func.name)

        # The funcs dictionary is used as a convenient way to lookup function
        # calls when needed e.g. to adjust member variables.
        self.funcs = OrderedDict(sorted(funcs.items()))

    def _parse_enums(self, root):
        """ Parse enums section or better described as constants section. """
        enums = {}
        self.consts = []
        for enum in root.findall("./enums"):
            name = enum.attrib.get("name")
            _type = enum.attrib.get("type")

            if _type in ("enum", "bitmask"):
                enums[name] = VkEnum.from_xml(enum)
            else:
                # If no type is set, we are dealing with API constants.
                values = []
                for value in enum.findall("enum"):
                    self.consts.append(VkConstant(value.attrib.get("name"), value.attrib.get("value")))

        self.enums = OrderedDict(sorted(enums.items()))

    def _parse_features(self, root):
        """ Parse the feature section, which describes Core commands and types needed. """
        requires = root.findall("./feature/require")

        for require in requires:
            LOGGER.info("Including features for {0}".format(require.attrib.get("comment")))
            for tag in require:
                # Only deal with command. Other values which appear are enum and type for pulling in some
                # constants and macros. Tricky to parse, so don't bother right now, we will generate them
                # anyway for now.
                name = tag.attrib["name"]
                if tag.tag == "command":
                    self._mark_command_required(name)
                elif tag.tag == "enum":
                    # We could pull in relevant constants here. Unfortunately
                    # this only gets half of them pulled in as others indirectly
                    # get pulled in through structures. Constants don't harm us,
                    # so don't bother.
                    pass
                elif tag.tag == "type":
                    # Pull in types which may not have been pulled in through commands.

                    # Skip pull in for vk_platform.h for now.
                    if name == "vk_platform":
                        continue

                    type_info = self.types[name]
                    type_info["data"].required = True

    def _parse_types(self, root):
        """ Parse types section, which contains all data types e.g. structs, typedefs etcetera. """
        types = root.findall("./types/type")

        base_types = []
        bitmasks = []
        defines = []
        funcpointers = []
        handles = []
        structs = []

        for t in types:
            type_info = {}
            type_info["category"] = t.attrib.get("category", None)

            if type_info["category"] in ["include"]:
                continue

            if type_info["category"] == "basetype":
                name = t.find("name").text
                _type = t.find("type").text
                basetype = VkBaseType(name, _type)
                base_types.append(basetype)
                type_info["data"] = basetype

            if type_info["category"] == "bitmask":
                name = t.find("name").text
                _type = t.find("type").text

                # Most bitmasks have a requires attribute used to pull in
                # required '*FlagBits" enum.
                requires = t.attrib.get("requires")
                bitmask = VkBaseType(name, _type, requires=requires)
                bitmasks.append(bitmask)
                type_info["data"] = bitmask

            if type_info["category"] == "define":
                name = t.attrib.get("name")
                define = VkDefine.from_xml(t)
                defines.append(define)
                type_info["data"] = define

            if type_info["category"] == "enum":
                name = t.attrib.get("name")
                # The type section only contains enum names, not the actual definition.
                # Since we already parsed the enum before, just link it in.
                try:
                    type_info["data"] = self.enums[name]
                except KeyError as e:
                    # Not all enums seem to be defined yet, typically that's for
                    # ones ending in 'FlagBits' where future extensions may add
                    # definitions.
                    type_info["data"] = None

            if type_info["category"] == "funcpointer":
                funcpointer = VkFunctionPointer.from_xml(t)
                funcpointers.append(funcpointer)
                type_info["data"] = funcpointer

            if type_info["category"] == "handle":
                handle = VkHandle.from_xml(t)
                handles.append(handle)
                type_info["data"] = handle

            if type_info["category"] in ["struct", "union"]:
                # We store unions among structs as some structs depend
                # on unions. The types are very similar in parsing and
                # generation anyway. The official vulkan scripts use
                # a similar kind of hack.
                struct = VkStruct.from_xml(t)
                structs.append(struct)
                type_info["data"] = struct

            # Name is in general within a name tag else it is an optional
            # attribute on the type tag.
            name_elem = t.find("name")
            if name_elem is not None:
                type_info["name"] = name_elem.text
            else:
                type_info["name"] = t.attrib.get("name", None)

            # Store all type data in a shared dictionary, so we can easily
            # look up information for a given type. There are no duplicate
            # names.
            self.types[type_info["name"]] = type_info

        # We need detailed type information during code generation
        # on structs for alignment reasons. Unfortunately structs
        # are parsed among other types, so there is no guarantee
        # that any types needed have been parsed already, so set
        # the data now.
        for struct in structs:
            struct.set_type_info(self.types)

        # Guarantee everything is sorted, so code generation doesn't have
        # to deal with this.
        self.base_types = sorted(base_types, key=lambda base_type: base_type.name)
        self.bitmasks = sorted(bitmasks, key=lambda bitmask: bitmask.name)
        self.defines = defines
        self.funcpointers = sorted(funcpointers, key=lambda fp: fp.name)
        self.handles = sorted(handles, key=lambda handle: handle.name)
        self.structs = sorted(structs, key=lambda struct: struct.name)


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("-v", "--verbose", action="count", default=0, help="increase output verbosity")

    args = parser.parse_args()
    if args.verbose == 0:
        LOGGER.setLevel(logging.WARNING)
    elif args.verbose == 1:
        LOGGER.setLevel(logging.INFO)
    else: # > 1
        LOGGER.setLevel(logging.DEBUG)

    registry = VkRegistry("vk.xml")
    generator = VkGenerator(registry)

    with open(WINE_VULKAN_H, "w") as f:
        generator.generate_vulkan_h(f)

    with open(WINE_VULKAN_DRIVER_H, "w") as f:
        generator.generate_vulkan_driver_h(f)

    with open(WINE_VULKAN_THUNKS_H, "w") as f:
        generator.generate_thunks_h(f, "wine_")

    with open(WINE_VULKAN_THUNKS_C, "w") as f:
        generator.generate_thunks_c(f, "wine_")

if __name__ == "__main__":
    main()
