#!/usr/bin/env python3

#
# esx_vi_generator.py: generates most of the SOAP type mapping code
#
# Copyright (C) 2014 Red Hat, Inc.
# Copyright (C) 2010-2012 Matthias Bolte <matthias.bolte@googlemail.com>
# Copyright (C) 2013 Ata E Husain Bohra <ata.husain@hotmail.com>
#
# 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, see
# <http://www.gnu.org/licenses/>.
#

import sys
import os
import os.path


OCCURRENCE__REQUIRED_ITEM = "r"
OCCURRENCE__REQUIRED_LIST = "rl"
OCCURRENCE__OPTIONAL_ITEM = "o"
OCCURRENCE__OPTIONAL_LIST = "ol"
OCCURRENCE__IGNORED = "i"

valid_occurrences = [OCCURRENCE__REQUIRED_ITEM,
                     OCCURRENCE__REQUIRED_LIST,
                     OCCURRENCE__OPTIONAL_ITEM,
                     OCCURRENCE__OPTIONAL_LIST,
                     OCCURRENCE__IGNORED]

autobind_names = set()

separator = "/* " + ("* " * 37) + "*\n"


def aligned(left, right, length=59):
    return left.ljust(length, ' ') + right


class Member:
    def __init__(self, type, occurrence):
        self.type = type
        self.occurrence = occurrence

    def is_enum(self):
        return self.type in predefined_enums or self.type in enums_by_name

    def is_object(self):
        return self.type in predefined_objects or self.type in objects_by_name

    def is_type_generated(self):
        return self.type in enums_by_name or self.type in objects_by_name

    def get_occurrence_comment(self):
        occurrence_map = {
            OCCURRENCE__REQUIRED_ITEM: "/* required */",
            OCCURRENCE__REQUIRED_LIST: "/* required, list */",
            OCCURRENCE__OPTIONAL_ITEM: "/* optional */",
            OCCURRENCE__OPTIONAL_LIST: "/* optional, list */"
        }
        try:
            return occurrence_map[self.occurrence]
        except KeyError:
            raise ValueError("unknown occurrence value '%s'" % self.occurrence)


class Parameter(Member):
    def __init__(self, type, name, occurrence):
        Member.__init__(self, type, occurrence)

        if ':' in name and name.startswith("_this"):
            self.name, self.autobind_name = name.split(":")
        else:
            self.name = name
            self.autobind_name = None

    def generate_parameter(self, is_last=False, is_header=True, offset=0):
        if self.occurrence == OCCURRENCE__IGNORED:
            raise ValueError("invalid function parameter occurrence value '%s'"
                             % self.occurrence)
        elif self.autobind_name is not None:
            return ""
        else:
            string = "       "
            string += " " * offset
            string += "%s%s" % (self.get_type_string(), self.name)

            if is_last:
                if is_header:
                    string += "); "
                else:
                    string += "), "
            else:
                string += ", "

            return aligned(string, self.get_occurrence_comment() + "\n")

    def generate_return(self, offset=0, end_of_line=";"):
        if self.occurrence == OCCURRENCE__IGNORED:
            raise ValueError("invalid function parameter occurrence value '%s'"
                             % self.occurrence)
        else:
            string = "       "
            string += " " * offset
            string += "%s%s)%s" \
                      % (self.get_type_string(True), self.name, end_of_line)

            return aligned(string, self.get_occurrence_comment() + "\n")

    def generate_require_code(self):
        if self.occurrence in [OCCURRENCE__REQUIRED_ITEM,
                               OCCURRENCE__REQUIRED_LIST]:
            return "    ESX_VI__METHOD__PARAMETER__REQUIRE(%s)\n" % self.name
        else:
            return ""

    def generate_serialize_code(self):
        if self.occurrence in [OCCURRENCE__REQUIRED_LIST,
                               OCCURRENCE__OPTIONAL_LIST]:
            return "    ESX_VI__METHOD__PARAMETER__SERIALIZE_LIST(%s, %s)\n" \
                   % (self.type, self.name)
        elif self.type == "String":
            return "    ESX_VI__METHOD__PARAMETER__SERIALIZE_VALUE(String, %s)\n" \
                   % self.name
        else:
            return "    ESX_VI__METHOD__PARAMETER__SERIALIZE(%s, %s)\n" \
                   % (self.type, self.name)

    def get_type_string(self, as_return_value=False):
        string = ""

        if self.type == "String" and \
           self.occurrence not in [OCCURRENCE__REQUIRED_LIST,
                                   OCCURRENCE__OPTIONAL_LIST]:
            if as_return_value:
                string += "char *"
            else:
                string += "const char *"
        elif self.is_enum():
            string += "esxVI_%s " % self.type
        else:
            string += "esxVI_%s *" % self.type

        if as_return_value:
            string += "*"

        return string

    def get_occurrence_short_enum(self):
        if self.occurrence == OCCURRENCE__REQUIRED_ITEM:
            return "RequiredItem"
        elif self.occurrence == OCCURRENCE__REQUIRED_LIST:
            return "RequiredList"
        elif self.occurrence == OCCURRENCE__OPTIONAL_ITEM:
            return "OptionalItem"
        elif self.occurrence == OCCURRENCE__OPTIONAL_LIST:
            return "OptionalList"

        raise ValueError("unknown occurrence value '%s'" % self.occurrence)


class Method:
    def __init__(self, name, parameters, returns):
        self.name = name
        self.parameters = []
        self.autobind_parameter = None
        self.returns = returns

        for parameter in parameters:
            if parameter.autobind_name is None:
                self.parameters.append(parameter)
            else:
                self.autobind_parameter = parameter

    def generate_header(self):
        header = "int esxVI_%s\n" % self.name
        header += "      (esxVI_Context *ctx"

        if len(self.parameters) > 0 or self.returns is not None:
            header += ",\n"

            for parameter in self.parameters[:-1]:
                header += parameter.generate_parameter()

            if self.returns is None:
                header += self.parameters[-1].generate_parameter(is_last=True)
            else:
                header += self.parameters[-1].generate_parameter()
                header += self.returns.generate_return()
        else:
            header += ");\n"

        header += "\n"

        return header

    def generate_source(self):
        source = "/* esxVI_%s */\n" % self.name
        source += "ESX_VI__METHOD(%s," % self.name

        if self.autobind_parameter is not None:
            autobind_names.add(self.autobind_parameter.autobind_name)
            source += " %s,\n" % self.autobind_parameter.autobind_name
        else:
            source += " /* explicit _this */,\n"

        source += "               (esxVI_Context *ctx"

        if len(self.parameters) > 0 or self.returns is not None:
            source += ",\n"

            for parameter in self.parameters[:-1]:
                source += parameter.generate_parameter(is_header=False,
                                                       offset=9)

            if self.returns is None:
                source += self.parameters[-1].generate_parameter(is_last=True,
                                                                 is_header=False,
                                                                 offset=9)
            else:
                source += self.parameters[-1].generate_parameter(is_header=False,
                                                                 offset=9)
                source += self.returns.generate_return(offset=9,
                                                       end_of_line=",")
        else:
            source += "),\n"

        if self.returns is None:
            source += "               void, /* nothing */, None,\n"
        elif self.returns.type == "String":
            source += "               String, Value, %s,\n" \
                      % self.returns.get_occurrence_short_enum()
        else:
            source += "               %s, /* nothing */, %s,\n" \
                      % (self.returns.type,
                         self.returns.get_occurrence_short_enum())

        source += "{\n"

        if self.autobind_parameter is not None:
            source += self.autobind_parameter.generate_require_code()

        for parameter in self.parameters:
            source += parameter.generate_require_code()

        source += "},\n"
        source += "{\n"

        if self.autobind_parameter is not None:
            source += self.autobind_parameter.generate_serialize_code()

        for parameter in self.parameters:
            source += parameter.generate_serialize_code()

        source += "})\n\n\n\n"

        return source


class Property(Member):
    def __init__(self, type, name, occurrence):
        Member.__init__(self, type, occurrence)

        self.name = name

    def generate_struct_member(self):
        if self.occurrence == OCCURRENCE__IGNORED:
            return "    /* FIXME: %s is currently ignored */\n" % self.name
        else:
            string = "    %s%s; " % (self.get_type_string(), self.name)

            return aligned(string, self.get_occurrence_comment() + "\n")

    def generate_free_code(self):
        if self.type == "String" and \
           self.occurrence not in [OCCURRENCE__REQUIRED_LIST,
                                   OCCURRENCE__OPTIONAL_LIST,
                                   OCCURRENCE__IGNORED]:
            return "    VIR_FREE(item->%s);\n" % self.name
        elif self.is_enum():
            return ""
        else:
            if self.occurrence == OCCURRENCE__IGNORED:
                return "    /* FIXME: %s is currently ignored */\n" % self.name
            else:
                return "    esxVI_%s_Free(&item->%s);\n" % (self.type, self.name)

    def generate_validate_code(self, managed=False):
        if managed:
            macro = "ESX_VI__TEMPLATE__PROPERTY__MANAGED_REQUIRE"
        else:
            macro = "ESX_VI__TEMPLATE__PROPERTY__REQUIRE"

        if self.occurrence in [OCCURRENCE__REQUIRED_ITEM,
                               OCCURRENCE__REQUIRED_LIST]:
            return "    %s(%s)\n" % (macro, self.name)
        elif self.occurrence == OCCURRENCE__IGNORED:
            return "    /* FIXME: %s is currently ignored */\n" % self.name
        else:
            return ""

    def generate_deep_copy_code(self):
        if self.occurrence == OCCURRENCE__IGNORED:
            return "    /* FIXME: %s is currently ignored */\n" % self.name
        elif self.occurrence in [OCCURRENCE__REQUIRED_LIST,
                                 OCCURRENCE__OPTIONAL_LIST]:
            return "    ESX_VI__TEMPLATE__PROPERTY__DEEP_COPY_LIST(%s, %s)\n" \
                   % (self.type, self.name)
        elif self.type == "String":
            return "    ESX_VI__TEMPLATE__PROPERTY__DEEP_COPY_VALUE(String, %s)\n" \
                   % self.name
        elif self.is_enum():
            return "    (*dest)->%s = src->%s;\n" % (self.name, self.name)
        else:
            return "    ESX_VI__TEMPLATE__PROPERTY__DEEP_COPY(%s, %s)\n" \
                   % (self.type, self.name)

    def generate_serialize_code(self):
        if self.occurrence == OCCURRENCE__IGNORED:
            return "    /* FIXME: %s is currently ignored */\n" % self.name
        elif self.occurrence in [OCCURRENCE__REQUIRED_LIST,
                                 OCCURRENCE__OPTIONAL_LIST]:
            return "    ESX_VI__TEMPLATE__PROPERTY__SERIALIZE_LIST(%s, %s)\n" \
                   % (self.type, self.name)
        elif self.type == "String":
            return "    ESX_VI__TEMPLATE__PROPERTY__SERIALIZE_VALUE(String, %s)\n" \
                   % self.name
        else:
            return "    ESX_VI__TEMPLATE__PROPERTY__SERIALIZE(%s, %s)\n" \
                   % (self.type, self.name)

    def generate_deserialize_code(self):
        if self.occurrence == OCCURRENCE__IGNORED:
            return "    ESX_VI__TEMPLATE__PROPERTY__DESERIALIZE_IGNORE(%s) /* FIXME */\n" \
                   % self.name
        elif self.occurrence in [OCCURRENCE__REQUIRED_LIST,
                                 OCCURRENCE__OPTIONAL_LIST]:
            return "    ESX_VI__TEMPLATE__PROPERTY__DESERIALIZE_LIST(%s, %s)\n" \
                   % (self.type, self.name)
        elif self.type == "String":
            return "    ESX_VI__TEMPLATE__PROPERTY__DESERIALIZE_VALUE(String, %s)\n" \
                   % self.name
        else:
            return "    ESX_VI__TEMPLATE__PROPERTY__DESERIALIZE(%s, %s)\n" \
                   % (self.type, self.name)

    def generate_lookup_code(self):
        if self.occurrence == OCCURRENCE__IGNORED:
            return "    ESX_VI__TEMPLATE__PROPERTY__CAST_FROM_ANY_TYPE_IGNORE(%s) /* FIXME */\n" \
                   % self.name
        elif self.occurrence in [OCCURRENCE__REQUIRED_LIST,
                                 OCCURRENCE__OPTIONAL_LIST]:
            return "    ESX_VI__TEMPLATE__PROPERTY__CAST_LIST_FROM_ANY_TYPE(%s, %s)\n" \
                   % (self.type, self.name)
        elif self.type == "String":
            return "    ESX_VI__TEMPLATE__PROPERTY__CAST_VALUE_FROM_ANY_TYPE(String, %s)\n" \
                   % self.name
        else:
            return "    ESX_VI__TEMPLATE__PROPERTY__CAST_FROM_ANY_TYPE(%s, %s)\n" \
                   % (self.type, self.name)

    def get_type_string(self):
        if self.type == "String" and \
           self.occurrence not in [OCCURRENCE__REQUIRED_LIST,
                                   OCCURRENCE__OPTIONAL_LIST]:
            return "char *"
        elif self.is_enum():
            return "esxVI_%s " % self.type
        else:
            return "esxVI_%s *" % self.type


class Type:
    def __init__(self, kind, name):
        self.kind = kind
        self.name = name

    def generate_typedef(self):
        return "typedef %s _esxVI_%s esxVI_%s;\n" \
               % (self.kind, self.name, self.name)

    def generate_typeenum(self):
        return "    esxVI_Type_%s,\n" % self.name

    def generate_typetostring(self):
        string = "          case esxVI_Type_%s:\n" % self.name
        string += "            return \"%s\";\n\n" % self.name

        return string

    def generate_typefromstring(self):
        string = "           if (STREQ(type, \"%s\"))\n" % self.name
        string += "               return esxVI_Type_%s;\n" % self.name

        return string


class GenericObject(Type):
    FEATURE__DYNAMIC_CAST = (1 << 1)
    FEATURE__LIST = (1 << 2)
    FEATURE__DEEP_COPY = (1 << 3)
    FEATURE__ANY_TYPE = (1 << 4)
    FEATURE__SERIALIZE = (1 << 5)
    FEATURE__DESERIALIZE = (1 << 6)

    def __init__(self, name, category, managed, generic_objects_by_name):
        Type.__init__(self, "struct", name)
        self.category = category
        self.managed = managed
        self.generic_objects_by_name = generic_objects_by_name

    def generate_comment(self):
        comment = separator
        comment += " * %s: %s\n" % (self.category, self.name)

        if self.extends is not None:
            comment += " * %s  extends %s\n" \
                       % (' ' * len(self.category), self.extends)

        first = True

        if self.extended_by is not None:
            for extended_by in self.extended_by:
                if first:
                    comment += " * %s  extended by %s\n" \
                               % (' ' * len(self.category), extended_by)
                    first = False
                else:
                    comment += " * %s              %s\n" \
                               % (' ' * len(self.category), extended_by)

        comment += " */\n\n"

        return comment

    def generate_struct_members(self, add_banner=False, struct_gap=False):
        members = ""

        if struct_gap:
            members += "\n"

        if self.extends is not None:
            obj = self.generic_objects_by_name[self.extends]
            members += obj.generate_struct_members(add_banner=True,
                                                   struct_gap=False) + "\n"

        if self.extends is not None or add_banner:
            members += "    /* %s */\n" % self.name

        for property in self.properties:
            members += property.generate_struct_member()

        if len(self.properties) < 1:
            members += "    /* no properties */\n"

        return members

    def generate_dispatch(self, suffix, is_first=True):
        source = ""

        if self.extended_by is not None:
            if not is_first:
                source += "\n"

            source += "    /* %s */\n" % self.name

            for extended_by in self.extended_by:
                source += "    ESX_VI__TEMPLATE__DISPATCH__%s(%s)\n" \
                          % (suffix, extended_by)

            for extended_by in self.extended_by:
                obj = self.generic_objects_by_name[extended_by]
                source += obj.generate_dispatch(suffix, False)

        return source

    def generate_free_code(self, add_banner=False):
        source = ""

        if self.extends is not None:
            obj = self.generic_objects_by_name[self.extends]
            source += obj.generate_free_code(add_banner=True) + "\n"

        if self.extends is not None or add_banner:
            source += "    /* %s */\n" % self.name

        if len(self.properties) < 1:
            source += "    /* no properties */\n"
        else:
            string = ""

            for property in self.properties:
                string += property.generate_free_code()

            if len(string) < 1:
                source += "    /* no properties to be freed */\n"
            else:
                source += string

        return source

    def generate_validate_code(self, add_banner=False):
        source = ""

        if self.extends is not None:
            obj = self.generic_objects_by_name[self.extends]
            source += obj.generate_validate_code(add_banner=True) + "\n"

        if self.extends is not None or add_banner:
            source += "    /* %s */\n" % self.name

        if len(self.properties) < 1:
            source += "    /* no properties */\n"
        else:
            string = ""

            for property in self.properties:
                string += property.generate_validate_code(self.managed)

            if len(string) < 1:
                source += "    /* no required properties */\n"
            else:
                source += string

        return source


class Object(GenericObject):
    def __init__(self, name, extends, properties, features=0, extended_by=None):
        GenericObject.__init__(self, name, 'VI Object', False, objects_by_name)
        self.extends = extends
        self.features = features
        self.properties = properties
        self.extended_by = extended_by
        self.candidate_for_dynamic_cast = False

        if self.extended_by is not None:
            self.extended_by.sort()

    def generate_dynamic_cast_code(self, is_first=True):
        source = ""

        if self.extended_by is not None:
            if not is_first:
                source += "\n"

            source += "    /* %s */\n" % self.name

            for extended_by in self.extended_by:
                source += "    ESX_VI__TEMPLATE__DYNAMIC_CAST__ACCEPT(%s)\n" \
                          % extended_by

            for extended_by in self.extended_by:
                obj = objects_by_name[extended_by]
                source += obj.generate_dynamic_cast_code(False)

        return source

    def generate_deep_copy_code(self, add_banner=False):
        source = ""

        if self.extends is not None:
            obj = objects_by_name[self.extends]
            source += obj.generate_deep_copy_code(add_banner=True) + "\n"

        if self.extends is not None or add_banner:
            source += "    /* %s */\n" % self.name

        if len(self.properties) < 1:
            source += "    /* no properties */\n"
        else:
            string = ""

            for property in self.properties:
                string += property.generate_deep_copy_code()

            if len(string) < 1:
                source += "    /* no properties to be deep copied */\n"
            else:
                source += string

        return source

    def generate_serialize_code(self, add_banner=False):
        source = ""

        if self.extends is not None:
            obj = objects_by_name[self.extends]
            source += obj.generate_serialize_code(add_banner=True) + "\n"

        if self.extends is not None or add_banner:
            source += "    /* %s */\n" % self.name

        if len(self.properties) < 1:
            source += "    /* no properties */\n"
        else:
            for property in self.properties:
                source += property.generate_serialize_code()

        return source

    def generate_deserialize_code(self, add_banner=False):
        source = ""

        if self.extends is not None:
            obj = objects_by_name[self.extends]
            source += obj.generate_deserialize_code(add_banner=True) + "\n"

        if self.extends is not None or add_banner:
            source += "    /* %s */\n" % self.name

        if len(self.properties) < 1:
            source += "    /* no properties */\n"
        else:
            for property in self.properties:
                source += property.generate_deserialize_code()

        return source

    def generate_header(self):
        header = self.generate_comment()

        # struct
        header += "struct _esxVI_%s {\n" % self.name

        if self.features & Object.FEATURE__LIST:
            header += aligned("    esxVI_%s *_next; " % self.name,
                              "/* optional */\n")
        else:
            header += aligned("    esxVI_%s *_unused; " % self.name,
                              "/* optional */\n")

        header += aligned("    esxVI_Type _type; ", "/* required */\n")
        header += self.generate_struct_members(struct_gap=True)
        header += "};\n\n"

        # functions
        header += "int esxVI_%s_Alloc(esxVI_%s **item);\n" \
                  % (self.name, self.name)
        header += "void esxVI_%s_Free(esxVI_%s **item);\n" \
                  % (self.name, self.name)
        header += "int esxVI_%s_Validate(esxVI_%s *item);\n" \
                  % (self.name, self.name)

        if self.features & Object.FEATURE__DYNAMIC_CAST:
            if self.extended_by is not None or self.extends is not None:
                header += "esxVI_%s *esxVI_%s_DynamicCast(void *item);\n" \
                          % (self.name, self.name)
            else:
                report_error("cannot add dynamic cast support for an untyped object")

        if self.features & Object.FEATURE__LIST:
            header += "int esxVI_%s_AppendToList(esxVI_%s **list, esxVI_%s *item);\n" \
                      % (self.name, self.name, self.name)

        if self.features & Object.FEATURE__DEEP_COPY:
            header += "int esxVI_%s_DeepCopy(esxVI_%s **dst, esxVI_%s *src);\n" \
                      % (self.name, self.name, self.name)

            if self.features & Object.FEATURE__LIST:
                header += ((
                    "int esxVI_%s_DeepCopyList(esxVI_%s **dstList, "
                    "                          esxVI_%s *srcList);\n") %
                    (self.name, self.name, self.name))

        if self.features & Object.FEATURE__ANY_TYPE:
            header += ((
                "int esxVI_%s_CastFromAnyType(esxVI_AnyType *anyType, "
                "                             esxVI_%s **item);\n") %
                (self.name, self.name))

            if self.features & Object.FEATURE__LIST:
                header += ((
                    "int esxVI_%s_CastListFromAnyType(esxVI_AnyType *anyType, "
                    "                                 esxVI_%s **list);\n") %
                    (self.name, self.name))

        if self.features & Object.FEATURE__SERIALIZE:
            header += ((
                "int esxVI_%s_Serialize(esxVI_%s *item, "
                "                       const char *element, "
                "                       virBufferPtr output);\n") %
                (self.name, self.name))

            if self.features & Object.FEATURE__LIST:
                header += ((
                    "int esxVI_%s_SerializeList(esxVI_%s *list, "
                    "                           const char *element, "
                    "                           virBufferPtr output);\n") %
                    (self.name, self.name))

        if self.features & Object.FEATURE__DESERIALIZE:
            header += "int esxVI_%s_Deserialize(xmlNodePtr node, esxVI_%s **item);\n" \
                      % (self.name, self.name)

            if self.features & Object.FEATURE__LIST:
                header += ((
                    "int esxVI_%s_DeserializeList(xmlNodePtr node, "
                    "                             esxVI_%s **list);\n") %
                    (self.name, self.name))

        header += "\n\n\n"

        return header

    def generate_source(self):
        source = separator
        source += " * VI Object: %s\n" % self.name

        if self.extends is not None:
            source += " *            extends %s\n" % self.extends

        first = True

        if self.extended_by is not None:
            for extended_by in self.extended_by:
                if first:
                    source += " *            extended by %s\n" % extended_by
                    first = False
                else:
                    source += " *                        %s\n" % extended_by

        source += " */\n\n"

        # functions
        source += "/* esxVI_%s_Alloc */\n" % self.name
        source += "ESX_VI__TEMPLATE__ALLOC(%s)\n\n" % self.name

        # free
        source += "/* esxVI_%s_Free */\n" % self.name

        if self.extended_by is None:
            source += "ESX_VI__TEMPLATE__FREE(%s,\n" % self.name
        else:
            source += "ESX_VI__TEMPLATE__DYNAMIC_FREE(%s,\n" % self.name
            source += "{\n"
            source += self.generate_dispatch('FREE')
            source += "},\n"

        source += "{\n"

        if self.features & Object.FEATURE__LIST:
            base_class = get_base_class(self)
            if base_class:
                # avoid "dereferencing type-punned pointer will break
                # strict-aliasing rules" warnings
                source += "    esxVI_%s *baseNext = (esxVI_%s *)item->_next;\n" \
                          % (base_class, base_class)
                source += "    esxVI_%s_Free(&baseNext);\n\n" % base_class
            else:
                source += "    esxVI_%s_Free(&item->_next);\n\n" % self.name

        source += self.generate_free_code()
        source += "})\n\n"

        # validate
        source += "/* esxVI_%s_Validate */\n" % self.name
        source += "ESX_VI__TEMPLATE__VALIDATE(%s,\n" % self.name
        source += "{\n"
        source += self.generate_validate_code()
        source += "})\n\n"

        # dynamic cast
        if self.features & Object.FEATURE__DYNAMIC_CAST:
            if self.extended_by is not None or self.extends is not None:
                source += "/* esxVI_%s_DynamicCast */\n" % self.name
                source += "ESX_VI__TEMPLATE__DYNAMIC_CAST(%s,\n" % self.name
                source += "{\n"
                source += self.generate_dynamic_cast_code()
                source += "})\n\n"
            else:
                report_error("cannot add dynamic cast support for an untyped object")

        # append to list
        if self.features & Object.FEATURE__LIST:
            source += "/* esxVI_%s_AppendToList */\n" % self.name
            source += "ESX_VI__TEMPLATE__LIST__APPEND(%s)\n\n" % self.name

        # deep copy
        if self.features & Object.FEATURE__DEEP_COPY:
            source += "/* esxVI_%s_DeepCopy */\n" % self.name

            if self.extended_by is None:
                source += "ESX_VI__TEMPLATE__DEEP_COPY(%s,\n" % self.name
            else:
                source += "ESX_VI__TEMPLATE__DYNAMIC_DEEP_COPY(%s,\n" % self.name
                source += "{\n"
                source += self.generate_dispatch('DEEP_COPY')
                source += "},\n"

            source += "{\n"
            source += self.generate_deep_copy_code()
            source += "})\n\n"

            if self.features & Object.FEATURE__LIST:
                source += "/* esxVI_%s_DeepCopyList */\n" % self.name
                source += "ESX_VI__TEMPLATE__LIST__DEEP_COPY(%s)\n\n" \
                          % self.name

        # cast from any type
        if self.features & Object.FEATURE__ANY_TYPE:
            source += "/* esxVI_%s_CastFromAnyType */\n" % self.name

            if self.extended_by is None:
                source += "ESX_VI__TEMPLATE__CAST_FROM_ANY_TYPE(%s)\n\n" \
                          % self.name
            else:
                source += "ESX_VI__TEMPLATE__DYNAMIC_CAST_FROM_ANY_TYPE(%s,\n" \
                          % self.name
                source += "{\n"
                source += self.generate_dispatch('CAST_FROM_ANY_TYPE')
                source += "})\n\n"

            if self.features & Object.FEATURE__LIST:
                source += "/* esxVI_%s_CastListFromAnyType */\n" % self.name
                source += "ESX_VI__TEMPLATE__LIST__CAST_FROM_ANY_TYPE(%s)\n\n" \
                          % self.name

        # serialize
        if self.features & Object.FEATURE__SERIALIZE:
            source += "/* esxVI_%s_Serialize */\n" % self.name

            if self.extended_by is None:
                source += "ESX_VI__TEMPLATE__SERIALIZE(%s,\n" % self.name
            else:
                source += "ESX_VI__TEMPLATE__DYNAMIC_SERIALIZE(%s,\n" % self.name
                source += "{\n"
                source += self.generate_dispatch('SERIALIZE')
                source += "},\n"

            source += "{\n"
            source += self.generate_serialize_code()
            source += "})\n\n"

            if self.features & Object.FEATURE__LIST:
                source += "/* esxVI_%s_SerializeList */\n" % self.name
                source += "ESX_VI__TEMPLATE__LIST__SERIALIZE(%s)\n\n" \
                          % self.name

        # deserialize
        if self.features & Object.FEATURE__DESERIALIZE:
            source += "/* esxVI_%s_Deserialize */\n" % self.name

            if self.extended_by is None:
                source += "ESX_VI__TEMPLATE__DESERIALIZE(%s,\n" % self.name
            else:
                source += "ESX_VI__TEMPLATE__DYNAMIC_DESERIALIZE(%s,\n" \
                          % self.name
                source += "{\n"
                source += self.generate_dispatch('DESERIALIZE')
                source += "},\n"

            source += "{\n"
            source += self.generate_deserialize_code()
            source += "})\n\n"

            if self.features & Object.FEATURE__LIST:
                source += "/* esxVI_%s_DeserializeList */\n" % self.name
                source += "ESX_VI__TEMPLATE__LIST__DESERIALIZE(%s)\n\n" \
                          % self.name

        source += "\n\n"

        return source


class ManagedObject(GenericObject):
    def __init__(self, name, extends, properties, features=0, extended_by=None):
        GenericObject.__init__(self, name, 'VI Managed Object', True,
                               managed_objects_by_name)
        self.extends = extends
        self.features = features
        self.properties = properties
        self.extended_by = extended_by

        if self.extended_by is not None:
            self.extended_by.sort()

    def generate_lookup_code1(self, add_banner=False):
        source = ""

        if self.extends is not None:
            obj = managed_objects_by_name[self.extends]
            source += obj.generate_lookup_code1(add_banner=True) + "\n"

        if self.extends is not None or add_banner:
            source += "    /* %s */\n" % self.name

        if len(self.properties) < 1:
            source += "    /* no properties */\n"
        else:
            string = ""

            for property in self.properties:
                string += "    \"%s\\0\"\n" % property.name

            if len(string) < 1:
                source += "    /* no properties */\n"
            else:
                source += string

        return source

    def generate_lookup_code2(self, add_banner=False):
        source = ""

        if self.extends is not None:
            obj = managed_objects_by_name[self.extends]
            source += obj.generate_lookup_code2(add_banner=True) + "\n"

        if self.extends is not None or add_banner:
            source += "    /* %s */\n" % self.name

        if len(self.properties) < 1:
            source += "    /* no properties */\n"
        else:
            string = ""

            for property in self.properties:
                string += property.generate_lookup_code()

            if len(string) < 1:
                source += "    /* no properties */\n"
            else:
                source += string

        return source

    def generate_header(self):
        header = self.generate_comment()

        # struct
        header += "struct _esxVI_%s {\n" % self.name

        if self.features & Object.FEATURE__LIST:
            header += aligned("    esxVI_%s *_next; " % self.name,
                              "/* optional */\n")
        else:
            header += aligned("    esxVI_%s *_unused; " % self.name,
                              "/* optional */\n")

        header += aligned("    esxVI_Type _type; ", "/* required */\n")
        header += aligned("    esxVI_ManagedObjectReference *_reference; ",
                          "/* required */\n")
        header += "\n"
        header += self.generate_struct_members()
        header += "};\n\n"

        # functions
        header += "int esxVI_%s_Alloc(esxVI_%s **item);\n" % (self.name, self.name)
        header += "void esxVI_%s_Free(esxVI_%s **item);\n" % (self.name, self.name)
        header += ("int esxVI_%s_Validate(esxVI_%s *item, "
                   "                      esxVI_String *selectedPropertyNameList);\n") \
            % (self.name, self.name)

        if self.features & Object.FEATURE__LIST:
            header += "int esxVI_%s_AppendToList(esxVI_%s **list, esxVI_%s *item);\n" \
                % (self.name, self.name, self.name)

        header += "\n\n\n"

        return header

    def generate_helper_header(self):
        # functions
        return (
            "int esxVI_Lookup%(name)s(esxVI_Context *ctx,"
            "                         const char *name,"
            "                         esxVI_ManagedObjectReference *root,"
            "                         esxVI_String *selectedPropertyNameList,"
            "                         esxVI_%(name)s **item,"
            "                         esxVI_Occurrence occurrence);\n\n"
            % {"name": self.name}
        )

    def generate_source(self):
        source = self.generate_comment()

        # functions
        source += "/* esxVI_%s_Alloc */\n" % self.name
        source += "ESX_VI__TEMPLATE__ALLOC(%s)\n\n" % self.name

        # free
        source += "/* esxVI_%s_Free */\n" % self.name

        if self.extended_by is None:
            source += "ESX_VI__TEMPLATE__FREE(%s,\n" % self.name
        else:
            source += "ESX_VI__TEMPLATE__DYNAMIC_FREE(%s,\n" % self.name
            source += "{\n"
            source += self.generate_dispatch('FREE')
            source += "},\n"

        source += "{\n"

        if self.features & ManagedObject.FEATURE__LIST:
            if self.extends is not None:
                # avoid "dereferencing type-punned pointer will break
                # strict-aliasing rules" warnings
                source += "    esxVI_%s *next = (esxVI_%s *)item->_next;\n\n" \
                          % (self.extends, self.extends)
                source += "    esxVI_%s_Free(&next);\n" % self.extends
                source += "    item->_next = (esxVI_%s *)next;\n\n" % self.name
            else:
                source += "    esxVI_%s_Free(&item->_next);\n" % self.name

        source += "    esxVI_ManagedObjectReference_Free(&item->_reference);\n\n"
        source += self.generate_free_code()
        source += "})\n\n"

        # validate
        source += "/* esxVI_%s_Validate */\n" % self.name
        source += "ESX_VI__TEMPLATE__MANAGED_VALIDATE(%s,\n" % self.name
        source += "{\n"

        source += self.generate_validate_code()

        source += "})\n\n"

        # append to list
        if self.features & ManagedObject.FEATURE__LIST:
            source += "/* esxVI_%s_AppendToList */\n" % self.name
            source += "ESX_VI__TEMPLATE__LIST__APPEND(%s)\n\n" % self.name

        source += "\n\n"

        return source

    def generate_helper_source(self):
        # lookup
        return (
            "/* esxVI_Lookup%(name)s */\n"
            "ESX_VI__TEMPLATE__LOOKUP(%(name)s,\n"
            "{\n"
            "%(lookup_code1)s},\n"
            "{\n"
            "%(lookup_code2)s})"
            "\n\n\n\n"
            % {"name": self.name,
               "lookup_code1": self.generate_lookup_code1(),
               "lookup_code2": self.generate_lookup_code2()}
        )


class Enum(Type):
    FEATURE__ANY_TYPE = (1 << 1)
    FEATURE__SERIALIZE = (1 << 2)
    FEATURE__DESERIALIZE = (1 << 3)

    def __init__(self, name, values, features=0):
        Type.__init__(self, "enum", name)
        self.values = values
        self.features = features

    def generate_header(self):
        header = separator
        header += " * VI Enum: %s\n" % self.name
        header += " */\n\n"
        header += "enum _esxVI_%s {\n" % self.name
        header += "    esxVI_%s_Undefined = 0,\n" % self.name

        for value in self.values:
            header += "    esxVI_%s_%s,\n" % (self.name, capitalize_first(value))

        header += "};\n\n"

        # functions
        if self.features & Enum.FEATURE__ANY_TYPE:
            header += ("int esxVI_%s_CastFromAnyType(esxVI_AnyType *anyType, "
                       "                             esxVI_%s *item);\n") \
                % (self.name, self.name)

        if self.features & Enum.FEATURE__SERIALIZE:
            header += ("int esxVI_%s_Serialize(esxVI_%s item, const char *element, "
                       "                       virBufferPtr output);\n") \
                % (self.name, self.name)

        if self.features & Enum.FEATURE__DESERIALIZE:
            header += ("int esxVI_%s_Deserialize(xmlNodePtr node, "
                       "                         esxVI_%s *item);\n") \
                % (self.name, self.name)

        header += "\n\n\n"

        return header

    def generate_source(self):
        source = separator
        source += " * VI Enum: %s\n" % self.name
        source += " */\n\n"
        source += "static const esxVI_Enumeration _esxVI_%s_Enumeration = {\n" \
            % self.name
        source += "    esxVI_Type_%s, {\n" % self.name

        for value in self.values:
            source += "        { \"%s\", esxVI_%s_%s },\n" \
                % (value, self.name, capitalize_first(value))

        source += "        { NULL, -1 },\n"
        source += "    },\n"
        source += "};\n\n"

        # functions
        if self.features & Enum.FEATURE__ANY_TYPE:
            source += "/* esxVI_%s_CastFromAnyType */\n" % self.name
            source += "ESX_VI__TEMPLATE__ENUMERATION__CAST_FROM_ANY_TYPE(%s)\n\n" \
                      % self.name

        if self.features & Enum.FEATURE__SERIALIZE:
            source += "/* esxVI_%s_Serialize */\n" % self.name
            source += "ESX_VI__TEMPLATE__ENUMERATION__SERIALIZE(%s)\n\n" \
                      % self.name

        if self.features & Enum.FEATURE__DESERIALIZE:
            source += "/* esxVI_%s_Deserialize */\n" % self.name
            source += "ESX_VI__TEMPLATE__ENUMERATION__DESERIALIZE(%s)\n\n" \
                      % self.name

        source += "\n\n"

        return source


def report_error(message):
    print("error: " + message)
    sys.exit(1)


def capitalize_first(string):
    return string[:1].upper() + string[1:]


def parse_object(block):
    # expected format: [managed] object <name> [extends <name>]
    header_items = block[0][1].split()
    managed = False

    if header_items[0] == "managed":
        managed = True
        del header_items[0]

    if len(header_items) < 2:
        report_error("line %d: invalid block header" % (number))

    assert header_items[0] == "object"

    name = header_items[1]
    extends = None

    if len(header_items) > 2:
        if header_items[2] != "extends":
            report_error("line %d: invalid block header" % (number))
        else:
            extends = header_items[3]

    properties = []

    for line in block[1:]:
        # expected format: <type> <name> <occurrence>
        items = line[1].split()

        if len(items) != 3:
            report_error("line %d: invalid property" % line[0])

        if items[2] not in valid_occurrences:
            report_error("line %d: invalid occurrence" % line[0])

        properties.append(Property(type=items[0], name=items[1],
                                   occurrence=items[2]))

    if managed:
        return ManagedObject(name=name, extends=extends, properties=properties)
    else:
        return Object(name=name, extends=extends, properties=properties)


def parse_enum(block):
    # expected format: enum <name>
    header_items = block[0][1].split()

    if len(header_items) < 2:
        report_error("line %d: invalid block header" % (number))

    assert header_items[0] == "enum"

    name = header_items[1]

    values = []

    for line in block[1:]:
        # expected format: <value>
        values.append(line[1])

    return Enum(name=name, values=values)


def parse_method(block):
    # expected format: method <name> [returns <type> <occurrence>]
    header_items = block[0][1].split()

    if len(header_items) < 2:
        report_error("line %d: invalid block header" % (number))

    assert header_items[0] == "method"

    name = header_items[1]
    returns = None

    if len(header_items) > 2:
        if header_items[2] != "returns":
            report_error("line %d: invalid block header" % (number))
        else:
            returns = Parameter(type=header_items[3], name="output",
                                occurrence=header_items[4])

    parameters = []

    for line in block[1:]:
        # expected format: <type> <name> <occurrence>
        items = line[1].split()

        if len(items) != 3:
            report_error("line %d: invalid property" % line[0])

        if items[2] not in valid_occurrences:
            report_error("line %d: invalid occurrence" % line[0])

        parameters.append(Parameter(type=items[0], name=items[1],
                                    occurrence=items[2]))

    return Method(name=name, parameters=parameters, returns=returns)


def is_known_type(type):
    return (type in predefined_objects or
            type in predefined_enums or
            type in objects_by_name or
            type in managed_objects_by_name or
            type in enums_by_name)


def get_base_class(obj):
    if not obj.extends:
        return None
    base_class = None
    try:
        base_class = base_class_by_name[obj.extends]
    except KeyError:
        parent = objects_by_name[obj.extends]
        base_class = get_base_class(parent)
        if not base_class:
            base_class = parent.name
        base_class_by_name[name] = base_class
    return base_class


def open_file(filename):
    return open(filename, "wt")


predefined_enums = ["Boolean"]

predefined_objects = ["AnyType",
                      "Byte",
                      "Int",
                      "Long",
                      "String",
                      "DateTime",
                      "MethodFault",
                      "ManagedObjectReference"]

additional_enum_features = {
    "ManagedEntityStatus": Enum.FEATURE__ANY_TYPE,
    "TaskInfoState": Enum.FEATURE__ANY_TYPE,
    "VirtualMachinePowerState": Enum.FEATURE__ANY_TYPE
}

additional_object_features = {
    "AutoStartDefaults": Object.FEATURE__ANY_TYPE,
    "AutoStartPowerInfo": Object.FEATURE__ANY_TYPE,
    "DatastoreHostMount": (Object.FEATURE__DEEP_COPY | Object.FEATURE__LIST |
                           Object.FEATURE__ANY_TYPE),
    "DatastoreInfo": Object.FEATURE__ANY_TYPE | Object.FEATURE__DYNAMIC_CAST,
    "GuestNicInfo": Object.FEATURE__LIST | Object.FEATURE__ANY_TYPE,
    "HostConfigManager": Object.FEATURE__ANY_TYPE,
    "HostCpuIdInfo": Object.FEATURE__LIST | Object.FEATURE__ANY_TYPE,
    "HostDatastoreBrowserSearchResults": (Object.FEATURE__LIST |
                                          Object.FEATURE__ANY_TYPE),
    "HostHostBusAdapter": Object.FEATURE__LIST | Object.FEATURE__ANY_TYPE,
    "HostInternetScsiHba": (Object.FEATURE__DYNAMIC_CAST |
                            Object.FEATURE__DEEP_COPY),
    "HostInternetScsiTargetTransport": Object.FEATURE__DYNAMIC_CAST,
    "HostScsiDisk": (Object.FEATURE__LIST | Object.FEATURE__ANY_TYPE |
                     Object.FEATURE__DYNAMIC_CAST),
    "HostScsiTopologyInterface": (Object.FEATURE__LIST |
                                  Object.FEATURE__ANY_TYPE),
    "HostScsiTopologyLun": (Object.FEATURE__ANY_TYPE | Object.FEATURE__LIST |
                            Object.FEATURE__DEEP_COPY),
    "HostScsiTopologyTarget": Object.FEATURE__ANY_TYPE | Object.FEATURE__LIST,
    "HostPortGroup": Object.FEATURE__LIST | Object.FEATURE__ANY_TYPE,
    "HostVirtualSwitch": (Object.FEATURE__DEEP_COPY | Object.FEATURE__LIST |
                          Object.FEATURE__ANY_TYPE),
    "ManagedObjectReference": Object.FEATURE__ANY_TYPE,
    "ObjectContent": Object.FEATURE__DEEP_COPY,
    "PhysicalNic": (Object.FEATURE__DEEP_COPY | Object.FEATURE__LIST |
                    Object.FEATURE__ANY_TYPE),
    "ResourcePoolResourceUsage": Object.FEATURE__ANY_TYPE,
    "ScsiLun": (Object.FEATURE__LIST | Object.FEATURE__ANY_TYPE |
                Object.FEATURE__DEEP_COPY),
    "ScsiLunDurableName": Object.FEATURE__LIST,
    "ServiceContent": Object.FEATURE__DESERIALIZE,
    "SharesInfo": Object.FEATURE__ANY_TYPE,
    "TaskInfo": Object.FEATURE__LIST | Object.FEATURE__ANY_TYPE,
    "UserSession": Object.FEATURE__ANY_TYPE,
    "VirtualMachineQuestionInfo": Object.FEATURE__ANY_TYPE,
    "VirtualMachineSnapshotTree": (Object.FEATURE__DEEP_COPY |
                                   Object.FEATURE__ANY_TYPE),
    "VmEventArgument": Object.FEATURE__DESERIALIZE
}

removed_object_features = {}

if len(sys.argv) != 4:
    report_error("usage: %s srcdir builddir header" % sys.argv[0])

input_filename = os.path.join(sys.argv[1], "esx/esx_vi_generator.input")
output_dirname = os.path.join(sys.argv[2], "esx")
header = sys.argv[3] == "header"


if header:
    types_typedef = open_file(os.path.join(output_dirname, "esx_vi_types.generated.typedef"))
    types_typeenum = open_file(os.path.join(output_dirname, "esx_vi_types.generated.typeenum"))
    types_header = open_file(os.path.join(output_dirname, "esx_vi_types.generated.h"))
    methods_header = open_file(os.path.join(output_dirname, "esx_vi_methods.generated.h"))
    helpers_header = open_file(os.path.join(output_dirname, "esx_vi.generated.h"))
else:
    types_typetostring = open_file(os.path.join(output_dirname, "esx_vi_types.generated.typetostring"))
    types_typefromstring = open_file(os.path.join(output_dirname, "esx_vi_types.generated.typefromstring"))
    types_source = open_file(os.path.join(output_dirname, "esx_vi_types.generated.c"))
    methods_macro = open_file(os.path.join(output_dirname, "esx_vi_methods.generated.macro"))
    methods_source = open_file(os.path.join(output_dirname, "esx_vi_methods.generated.c"))
    helpers_source = open_file(os.path.join(output_dirname, "esx_vi.generated.c"))


number = 0
objects_by_name = {}
managed_objects_by_name = {}
enums_by_name = {}
methods_by_name = {}
block = None
base_class_by_name = {}


# parse input file
for line in open(input_filename, "rt").readlines():
    number += 1

    if "#" in line:
        line = line[:line.index("#")]

    line = line.lstrip().rstrip()

    if len(line) < 1:
        continue

    if line.startswith("object") or line.startswith("managed object") or \
       line.startswith("enum") or line.startswith("method"):
        if block is not None:
            report_error("line %d: nested block found" % (number))
        else:
            block = []

    if block is not None:
        if line == "end":
            if block[0][1].startswith("object"):
                obj = parse_object(block)
                objects_by_name[obj.name] = obj
            elif block[0][1].startswith("managed object"):
                obj = parse_object(block)
                managed_objects_by_name[obj.name] = obj
            elif block[0][1].startswith("enum"):
                enum = parse_enum(block)
                enums_by_name[enum.name] = enum
            else:
                method = parse_method(block)
                methods_by_name[method.name] = method

            block = None
        else:
            block.append((number, line))


for method in methods_by_name.values():
    # method parameter types must be serializable
    for parameter in method.parameters:
        if not parameter.is_type_generated():
            continue

        if parameter.is_enum():
            enums_by_name[parameter.type].features |= Enum.FEATURE__SERIALIZE
        else:
            objects_by_name[parameter.type].features |= Object.FEATURE__SERIALIZE
            objects_by_name[parameter.type].candidate_for_dynamic_cast = True

        # detect list usage
        if parameter.occurrence == OCCURRENCE__REQUIRED_LIST or \
           parameter.occurrence == OCCURRENCE__OPTIONAL_LIST:
            if parameter.is_enum():
                report_error("unsupported usage of enum '%s' as list in '%s'"
                             % (parameter.type, method.name))
            else:
                objects_by_name[parameter.type].features |= Object.FEATURE__LIST

    # method return types must be deserializable
    if method.returns and method.returns.is_type_generated():
        if method.returns.is_enum():
            enums_by_name[method.returns.type].features |= Enum.FEATURE__DESERIALIZE
        else:
            objects_by_name[method.returns.type].features |= Object.FEATURE__DESERIALIZE
            objects_by_name[method.returns.type].candidate_for_dynamic_cast = True

        # detect list usage
        if method.returns.occurrence == OCCURRENCE__REQUIRED_LIST or \
           method.returns.occurrence == OCCURRENCE__OPTIONAL_LIST:
            if method.returns.is_enum():
                report_error("unsupported usage of enum '%s' as list in '%s'"
                             % (method.returns.type, method.name))
            else:
                objects_by_name[method.returns.type].features |= Object.FEATURE__LIST


for enum in enums_by_name.values():
    # apply additional features
    if enum.name in additional_enum_features:
        enum.features |= additional_enum_features[enum.name]

        if additional_enum_features[enum.name] & Enum.FEATURE__ANY_TYPE:
            enum.features |= Enum.FEATURE__DESERIALIZE


for obj in objects_by_name.values():
    for property in obj.properties:
        if property.occurrence != OCCURRENCE__IGNORED and \
           not is_known_type(property.type):
            report_error("object '%s' contains unknown property type '%s'"
                         % (obj.name, property.type))

    if obj.extends is not None:
        if not is_known_type(obj.extends):
            report_error("object '%s' extends unknown object '%s'"
                         % (obj.name, obj.extends))

    for property in obj.properties:
        if not property.is_type_generated():
            continue

        if property.is_enum():
            enums_by_name[property.type].candidate_for_dynamic_cast = True
        else:
            objects_by_name[property.type].candidate_for_dynamic_cast = True

        # detect list usage
        if property.occurrence == OCCURRENCE__REQUIRED_LIST or \
           property.occurrence == OCCURRENCE__OPTIONAL_LIST:
            if property.is_enum():
                report_error("unsupported usage of enum '%s' as list in '%s'"
                             % (property.type, obj.type))
            else:
                objects_by_name[property.type].features |= Object.FEATURE__LIST

    # apply/remove additional features
    if obj.name in additional_object_features:
        obj.features |= additional_object_features[obj.name]

        if additional_object_features[obj.name] & Object.FEATURE__ANY_TYPE:
            obj.features |= Object.FEATURE__DESERIALIZE

    if obj.name in removed_object_features:
        obj.features &= ~removed_object_features[obj.name]

    # detect extended_by relation
    if obj.extends is not None:
        extended_obj = objects_by_name[obj.extends]

        if extended_obj.extended_by is None:
            extended_obj.extended_by = [obj.name]
        else:
            extended_obj.extended_by.append(obj.name)
            extended_obj.extended_by.sort()


for obj in objects_by_name.values():
    # if an object is a candidate (it is used directly as parameter or return
    # type or is a member of another object) and it is extended by another
    # object then this type needs the dynamic cast feature
    if obj.candidate_for_dynamic_cast and obj.extended_by:
        obj.features |= Object.FEATURE__DYNAMIC_CAST


def propagate_feature(obj, feature):
    global features_have_changed

    if not (obj.features & feature):
        return

    for property in obj.properties:
        if (property.occurrence == OCCURRENCE__IGNORED or
                not property.is_type_generated()):
            continue

        if property.is_enum():
            if (feature == Object.FEATURE__SERIALIZE and
                not (enums_by_name[property.type].features &
                     Enum.FEATURE__SERIALIZE)):
                enums_by_name[property.type].features |= Enum.FEATURE__SERIALIZE
                features_have_changed = True
            elif (feature == Object.FEATURE__DESERIALIZE and
                  not (enums_by_name[property.type].features &
                       Enum.FEATURE__DESERIALIZE)):
                enums_by_name[property.type].features |= Enum.FEATURE__DESERIALIZE
                features_have_changed = True
        elif property.is_object():
            if not (objects_by_name[property.type].features & feature):
                objects_by_name[property.type].features |= feature
                features_have_changed = True

            if obj.name != property.type:
                propagate_feature(objects_by_name[property.type], feature)


def inherit_features(obj):
    global features_have_changed

    if obj.extended_by is not None:
        for extended_by in obj.extended_by:
            previous = objects_by_name[extended_by].features
            objects_by_name[extended_by].features |= obj.features

            if objects_by_name[extended_by].features != previous:
                features_have_changed = True

    if obj.extends is not None:
        previous = objects_by_name[obj.extends].features
        objects_by_name[obj.extends].features |= obj.features

        if objects_by_name[obj.extends].features != previous:
            features_have_changed = True

    if obj.extended_by is not None:
        for extended_by in obj.extended_by:
            inherit_features(objects_by_name[extended_by])


# there are two directions to spread features:
# 1) up and down the inheritance chain
# 2) from object types to their member property types
# spreading needs to be done alternating on both directions because they can
# affect each other
features_have_changed = True

while features_have_changed:
    features_have_changed = False

    for obj in objects_by_name.values():
        propagate_feature(obj, Object.FEATURE__DEEP_COPY)
        propagate_feature(obj, Object.FEATURE__SERIALIZE)
        propagate_feature(obj, Object.FEATURE__DESERIALIZE)

    for obj in objects_by_name.values():
        inherit_features(obj)


for obj in managed_objects_by_name.values():
    for property in obj.properties:
        if property.occurrence != OCCURRENCE__IGNORED and \
           not is_known_type(property.type):
            report_error("object '%s' contains unknown property type '%s'"
                         % (obj.name, property.type))

    if obj.extends is not None:
        if not is_known_type(obj.extends):
            report_error("object '%s' extends unknown object '%s'"
                         % (obj.name, obj.extends))

    # detect extended_by relation
    if obj.extends is not None:
        extended_obj = managed_objects_by_name[obj.extends]

        if extended_obj.extended_by is None:
            extended_obj.extended_by = [obj.name]
        else:
            extended_obj.extended_by.append(obj.name)
            extended_obj.extended_by.sort()


notice = "/* Generated by esx_vi_generator.py */\n\n\n\n"

if (header):
    types_typedef.write(notice)
    types_typeenum.write(notice)
    types_header.write(notice)
    methods_header.write(notice)
    helpers_header.write(notice)
else:
    types_typetostring.write(notice)
    types_typefromstring.write(notice)
    types_source.write(notice)
    methods_macro.write(notice)
    methods_source.write(notice)
    helpers_source.write(notice)


# output enums
if header:
    types_typedef.write(separator +
                        " * VI Enums\n" +
                        " */\n\n")

names = sorted(enums_by_name.keys())

for name in names:
    if header:
        types_typedef.write(enums_by_name[name].generate_typedef())
        types_typeenum.write(enums_by_name[name].generate_typeenum())
        types_header.write(enums_by_name[name].generate_header())
    else:
        types_typetostring.write(enums_by_name[name].generate_typetostring())
        types_typefromstring.write(enums_by_name[name].generate_typefromstring())
        types_source.write(enums_by_name[name].generate_source())


# output objects
if header:
    types_typedef.write("\n\n\n" +
                        separator +
                        " * VI Objects\n" +
                        " */\n\n")
    types_typeenum.write("\n")
else:
    types_typetostring.write("\n")
    types_typefromstring.write("\n")

names = sorted(objects_by_name.keys())

for name in names:
    if header:
        types_typedef.write(objects_by_name[name].generate_typedef())
        types_typeenum.write(objects_by_name[name].generate_typeenum())
        types_header.write(objects_by_name[name].generate_header())
    else:
        types_typetostring.write(objects_by_name[name].generate_typetostring())
        types_typefromstring.write(objects_by_name[name].generate_typefromstring())
        types_source.write(objects_by_name[name].generate_source())


# output managed objects
if header:
    types_typedef.write("\n\n\n" +
                        separator +
                        " * VI Managed Objects\n" +
                        " */\n\n")
    types_typeenum.write("\n")
else:
    types_typetostring.write("\n")
    types_typefromstring.write("\n")

names = sorted(managed_objects_by_name.keys())

for name in names:
    if header:
        types_typedef.write(managed_objects_by_name[name].generate_typedef())
        types_typeenum.write(managed_objects_by_name[name].generate_typeenum())
        types_header.write(managed_objects_by_name[name].generate_header())
    else:
        types_typetostring.write(managed_objects_by_name[name].generate_typetostring())
        types_typefromstring.write(managed_objects_by_name[name].generate_typefromstring())
        types_source.write(managed_objects_by_name[name].generate_source())


# output methods
names = sorted(methods_by_name.keys())

for name in names:
    if header:
        methods_header.write(methods_by_name[name].generate_header())
    else:
        methods_source.write(methods_by_name[name].generate_source())

if not header:
    names = list(autobind_names)
    names.sort()

    for name in names:
        string = aligned("#define ESX_VI__METHOD__PARAMETER__THIS__%s " % name, "\\\n", 78)
        string += "    ESX_VI__METHOD__PARAMETER__THIS_FROM_SERVICE(ManagedObjectReference,      \\\n"
        string += aligned("", "%s)\n\n\n\n" % name, 49)

        methods_macro.write(string)


# output helpers
names = sorted(managed_objects_by_name.keys())

for name in names:
    if header:
        helpers_header.write(managed_objects_by_name[name].generate_helper_header())
    else:
        helpers_source.write(managed_objects_by_name[name].generate_helper_source())
