'''
This module contains the classes which represent XCB data types.
'''
import sys
from xcbgen.expr import Field, Expression
from xcbgen.align import Alignment, AlignmentLog

if sys.version_info[:2] >= (3, 3):
    from xml.etree.ElementTree import SubElement
else:
    from xml.etree.cElementTree import SubElement

import __main__

verbose_align_log = False
true_values = ['true', '1', 'yes']

class Type(object):
    '''
    Abstract base class for all XCB data types.
    Contains default fields, and some abstract methods.
    '''
    def __init__(self, name):
        '''
        Default structure initializer.  Sets up default fields.

        Public fields:
        name is a tuple of strings specifying the full type name.
        size is the size of the datatype in bytes, or None if variable-sized.
        nmemb is 1 for non-list types, None for variable-sized lists, otherwise number of elts.
        booleans for identifying subclasses, because I can't figure out isinstance().
        '''
        self.name = name
        self.size = None
        self.nmemb = None
        self.resolved = False

        # Screw isinstance().
        self.is_simple = False
        self.is_list = False
        self.is_expr = False
        self.is_container = False
        self.is_reply = False
        self.is_union = False
        self.is_pad = False
        self.is_eventstruct = False
        self.is_event = False
        self.is_switch = False
        self.is_case_or_bitcase = False
        self.is_bitcase = False
        self.is_case = False
        self.is_fd = False
        self.required_start_align = Alignment()

        # the biggest align value of an align-pad contained in this type
        self.max_align_pad = 1

    def resolve(self, module):
        '''
        Abstract method for resolving a type.
        This should make sure any referenced types are already declared.
        '''
        raise Exception('abstract resolve method not overridden!')

    def out(self, name):
        '''
        Abstract method for outputting code.
        These are declared in the language-specific modules, and
        there must be a dictionary containing them declared when this module is imported!
        '''
        raise Exception('abstract out method not overridden!')

    def fixed_size(self):
        '''
        Abstract method for determining if the data type is fixed-size.
        '''
        raise Exception('abstract fixed_size method not overridden!')

    def make_member_of(self, module, complex_type, field_type, field_name, visible, wire, auto, enum=None, is_fd=False):
        '''
        Default method for making a data type a member of a structure.
        Extend this if the data type needs to add an additional length field or something.

        module is the global module object.
        complex_type is the structure object.
        see Field for the meaning of the other parameters.
        '''
        new_field = Field(self, field_type, field_name, visible, wire, auto, enum, is_fd)

        # We dump the _placeholder_byte if any fields are added.
        for (idx, field) in enumerate(complex_type.fields):
            if field == _placeholder_byte:
                complex_type.fields[idx] = new_field
                return

        complex_type.fields.append(new_field)
        new_field.parent = complex_type

    def make_fd_of(self, module, complex_type, fd_name):
        '''
        Method for making a fd member of a structure.
        '''
        new_fd = Field(self, module.get_type_name('INT32'), fd_name, True, False, False, None, True)
        # We dump the _placeholder_byte if any fields are added.
        for (idx, field) in enumerate(complex_type.fields):
            if field == _placeholder_byte:
                complex_type.fields[idx] = new_fd
                return

        complex_type.fields.append(new_fd)


    def get_total_size(self):
        '''
        get the total size of this type if it is fixed-size, otherwise None
        '''
        if self.fixed_size():
            if self.nmemb is None:
                return self.size
            else:
                return self.size * self.nmemb
        else:
            return None

    def get_align_offset(self):
        if self.required_start_align is None:
            return 0
        else:
            return self.required_start_align.offset

    def is_acceptable_start_align(self, start_align, callstack, log):
        return self.get_alignment_after(start_align, callstack, log) is not None

    def get_alignment_after(self, start_align, callstack, log):
        '''
        get the alignment after this type based on the given start_align.
        the start_align is checked for compatibility with the
        internal start align. If it is not compatible, then None is returned
        '''
        if self.required_start_align is None or self.required_start_align.is_guaranteed_at(start_align):
            return self.unchecked_get_alignment_after(start_align, callstack, log)
        else:
            if log is not None:
                log.fail(start_align, "", self, callstack + [self],
                    "start_align is incompatible with required_start_align %s"
                    % (str(self.required_start_align)))
            return None

    def unchecked_get_alignment_after(self, start_align, callstack, log):
        '''
        Abstract method for geting the alignment after this type
        when the alignment at the start is given, and when this type
        has variable size.
        '''
        raise Exception('abstract unchecked_get_alignment_after method not overridden!')


    @staticmethod
    def type_name_to_str(type_name):
        if isinstance(type_name, str):
            #already a string
            return type_name
        else:
            return ".".join(type_name)


    def __str__(self):
        return type(self).__name__ + " \"" + Type.type_name_to_str(self.name) + "\""

class PrimitiveType(Type):

    def __init__(self, name, size):
        Type.__init__(self, name)
        self.size = size
        self.nmemb = 1

        # compute the required start_alignment based on the size of the type
        self.required_start_align = Alignment.for_primitive_type(self.size)

    def unchecked_get_alignment_after(self, start_align, callstack, log):
        my_callstack = callstack + [self];
        after_align = start_align.align_after_fixed_size(self.size)

        if log is not None:
            if after_align is None:
                log.fail(start_align, "", self, my_callstack,
                "align after fixed size %d failed" % self.size)
            else:
                log.ok(start_align, "", self, my_callstack, after_align)

        return after_align

    def fixed_size(self):
        return True

class SimpleType(PrimitiveType):
    '''
    Derived class which represents a cardinal type like CARD32 or char.
    Any type which is typedef'ed to cardinal will be one of these.

    Public fields added:
    xml_type is the original string describing the type in the XML
    '''
    def __init__(self, name, size, xml_type=None):
        PrimitiveType.__init__(self, name, size)
        self.is_simple = True
        self.xml_type = xml_type

    def resolve(self, module):
        self.resolved = True

    out = __main__.output['simple']


# Cardinal datatype globals.  See module __init__ method.
tcard8 = SimpleType(('uint8_t',), 1, 'CARD8')
tcard16 = SimpleType(('uint16_t',), 2, 'CARD16')
tcard32 = SimpleType(('uint32_t',), 4, 'CARD32')
tcard64 = SimpleType(('uint64_t',), 8, 'CARD64')
tint8 =  SimpleType(('int8_t',), 1, 'INT8')
tint16 = SimpleType(('int16_t',), 2, 'INT16')
tint32 = SimpleType(('int32_t',), 4, 'INT32')
tint64 = SimpleType(('int64_t',), 8, 'INT64')
tchar =  SimpleType(('char',), 1, 'char')
tfloat = SimpleType(('float',), 4, 'float')
tdouble = SimpleType(('double',), 8, 'double')
tbyte = SimpleType(('uint8_t',), 1, 'BYTE')
tbool = SimpleType(('uint8_t',), 1, 'BOOL')
tvoid = SimpleType(('uint8_t',), 1, 'void')

class FileDescriptor(SimpleType):
    '''
    Derived class which represents a file descriptor.
    '''
    def __init__(self):
        SimpleType.__init__(self, ('int'), 4, 'fd')
        self.is_fd = True

    def fixed_size(self):
        return True

    out = __main__.output['simple']

class Enum(SimpleType):
    '''
    Derived class which represents an enum.  Fixed-size.

    Public fields added:
    values contains a list of (name, value) tuples.  value is empty, or a number.
    bits contains a list of (name, bitnum) tuples.  items only appear if specified as a bit. bitnum is a number.
    '''
    def __init__(self, name, elt):
        SimpleType.__init__(self, name, 4, 'enum')
        self.values = []
        self.bits = []
        self.doc = None
        for item in list(elt):
            if item.tag == 'doc':
                self.doc = Doc(name, item)

            # First check if we're using a default value
            if len(list(item)) == 0:
                self.values.append((item.get('name'), ''))
                continue

            # An explicit value or bit was specified.
            value = list(item)[0]
            if value.tag == 'value':
                self.values.append((item.get('name'), value.text))
            elif value.tag == 'bit':
                self.values.append((item.get('name'), '%u' % (1 << int(value.text, 0))))
                self.bits.append((item.get('name'), value.text))

    def resolve(self, module):
        self.resolved = True

    def fixed_size(self):
        return True

    out = __main__.output['enum']


class ListType(Type):
    '''
    Derived class which represents a list of some other datatype.  Fixed- or variable-sized.

    Public fields added:
    member is the datatype of the list elements.
    parent is the structure type containing the list.
    expr is an Expression object containing the length information, for variable-sized lists.
    '''
    def __init__(self, elt, member, *parent):
        Type.__init__(self, member.name)
        self.is_list = True
        self.member = member
        self.parents = list(parent)
        lenfield_name = False

        if elt.tag == 'list':
            elts = list(elt)
            self.expr = Expression(elts[0] if len(elts) else elt, self)
            is_list_in_parent = self.parents[0].elt.tag in ('request', 'event', 'reply', 'error')
            if not len(elts) and is_list_in_parent:
                self.expr = Expression(elt,self)
                self.expr.op = 'calculate_len'
            else:
                self.expr = Expression(elts[0] if len(elts) else elt, self)

        self.size = member.size if member.fixed_size() else None
        self.nmemb = self.expr.nmemb if self.expr.fixed_size() else None

        self.required_start_align = self.member.required_start_align

    def make_member_of(self, module, complex_type, field_type, field_name, visible, wire, auto, enum=None):
        if not self.fixed_size():
            # We need a length field.
            # Ask our Expression object for it's name, type, and whether it's on the wire.
            lenfid = self.expr.lenfield_type
            lenfield_name = self.expr.lenfield_name
            lenwire = self.expr.lenwire
            needlen = True

            # See if the length field is already in the structure.
            for parent in self.parents:
                for field in parent.fields:
                    if field.field_name == lenfield_name:
                        needlen = False

            # It isn't, so we need to add it to the structure ourself.
            if needlen:
                type = module.get_type(lenfid)
                lenfield_type = module.get_type_name(lenfid)
                type.make_member_of(module, complex_type, lenfield_type, lenfield_name, True, lenwire, False, enum)

        # Add ourself to the structure by calling our original method.
        if self.member.is_fd:
            wire = False
        Type.make_member_of(self, module, complex_type, field_type, field_name, visible, wire, auto, enum, self.member.is_fd)

    def resolve(self, module):
        if self.resolved:
            return
        self.member.resolve(module)
        self.expr.resolve(module, self.parents)

        # resolve() could have changed the size (ComplexType starts with size 0)
        self.size = self.member.size if self.member.fixed_size() else None

        self.required_start_align = self.member.required_start_align

        # Find my length field again.  We need the actual Field object in the expr.
        # This is needed because we might have added it ourself above.
        if not self.fixed_size():
            for parent in self.parents:
                for field in parent.fields:
                    if field.field_name == self.expr.lenfield_name and field.wire:
                        self.expr.lenfield = field
                        break

        self.resolved = True

    def fixed_size(self):
        return self.member.fixed_size() and self.expr.fixed_size()

    def unchecked_get_alignment_after(self, start_align, callstack, log):
        my_callstack = callstack[:]
        my_callstack.append(self)
        if start_align is None:
            log.fail(start_align, "", self, my_callstack, "start_align is None")
            return None
        if self.expr.fixed_size():
            # fixed number of elements
            num_elements = self.nmemb
            prev_alignment = None
            alignment = start_align
            while num_elements > 0:
                if alignment is None:
                    if log is not None:
                        log.fail(start_align, "", self, my_callstack,
                            ("fixed size list with size %d after %d iterations"
                            + ", at transition from alignment \"%s\"")
                            % (self.nmemb,
                               (self.nmemb - num_elements),
                               str(prev_alignment)))
                    return None
                prev_alignment = alignment
                alignment = self.member.get_alignment_after(prev_alignment, my_callstack, log)
                num_elements -= 1
            if log is not None:
                log.ok(start_align, "", self, my_callstack, alignment)
            return alignment
        else:
            # variable number of elements
            # check whether the number of elements is a multiple
            multiple = self.expr.get_multiple()
            assert multiple > 0

            # iterate until the combined alignment does not change anymore
            alignment = start_align
            while True:
                prev_multiple_alignment = alignment
                # apply "multiple" amount of changes sequentially
                prev_alignment = alignment
                for multiple_count in range(0, multiple):

                    after_alignment = self.member.get_alignment_after(prev_alignment, my_callstack, log)
                    if after_alignment is None:
                        if log is not None:
                            log.fail(start_align, "", self, my_callstack,
                                ("variable size list "
                                + "at transition from alignment \"%s\"")
                                % (str(prev_alignment)))
                        return None

                    prev_alignment = after_alignment

                # combine with the cumulatively combined alignment
                # (to model the variable number of entries)
                alignment = prev_multiple_alignment.combine_with(after_alignment)

                if alignment == prev_multiple_alignment:
                    # does not change anymore by adding more potential elements
                    # -> finished
                    if log is not None:
                        log.ok(start_align, "", self, my_callstack, alignment)
                    return alignment

class ExprType(PrimitiveType):
    '''
    Derived class which represents an exprfield.  Fixed size.

    Public fields added:
    expr is an Expression object containing the value of the field.
    '''
    def __init__(self, elt, member, *parents):
        PrimitiveType.__init__(self, member.name, member.size)
        self.is_expr = True
        self.member = member
        self.parents = parents

        self.expr = Expression(list(elt)[0], self)

    def resolve(self, module):
        if self.resolved:
            return
        self.member.resolve(module)
        self.resolved = True


class PadType(Type):
    '''
    Derived class which represents a padding field.
    '''
    def __init__(self, elt):
        Type.__init__(self, tcard8.name)
        self.is_pad = True
        self.size = 1
        self.nmemb = 1
        self.align = 1
        if elt != None:
            self.nmemb = int(elt.get('bytes', "1"), 0)
            self.align = int(elt.get('align', "1"), 0)
            self.serialize = elt.get('serialize', "false").lower() in true_values

        # pads don't require any alignment at their start
        self.required_start_align = Alignment(1,0)

    def resolve(self, module):
        self.resolved = True

    def fixed_size(self):
        return self.align <= 1

    def unchecked_get_alignment_after(self, start_align, callstack, log):
        if self.align <= 1:
            # fixed size pad
            after_align = start_align.align_after_fixed_size(self.get_total_size())
            if log is not None:
                if after_align is None:
                    log.fail(start_align, "", self, callstack,
                    "align after fixed size pad of size %d failed" % self.size)
                else:
                    log.ok(start_align, "", self, callstack, after_align)

            return after_align

        # align-pad
        assert self.align > 1
        assert self.size == 1
        assert self.nmemb == 1
        if (start_align.offset == 0
           and self.align <= start_align.align
           and start_align.align % self.align == 0):
            # the alignment pad is size 0 because the start_align
            # is already sufficiently aligned -> return the start_align
            after_align = start_align
        else:
            # the alignment pad has nonzero size -> return the alignment
            # that is guaranteed by it, independently of the start_align
            after_align = Alignment(self.align, 0)

        if log is not None:
            log.ok(start_align, "", self, callstack, after_align)

        return after_align
    
class ComplexType(Type):
    '''
    Derived class which represents a structure.  Base type for all structure types.

    Public fields added:
    fields is an array of Field objects describing the structure fields.
    length_expr is an expression that defines the length of the structure.

    '''
    def __init__(self, name, elt):
        Type.__init__(self, name)
        self.is_container = True
        self.elt = elt
        self.fields = []
        self.nmemb = 1
        self.size = 0
        self.lenfield_parent = [self]
        self.length_expr = None

        # get required_start_alignment
        required_start_align_element = elt.find("required_start_align")
        if required_start_align_element is None:
            # unknown -> mark for autocompute
            self.required_start_align = None
        else:
            self.required_start_align = Alignment(
                int(required_start_align_element.get('align', "4"), 0),
                int(required_start_align_element.get('offset', "0"), 0))
            if verbose_align_log:
                print ("Explicit start-align for %s: %s\n" % (self, self.required_start_align))

    def resolve(self, module):
        if self.resolved:
            return

        # Resolve all of our field datatypes.
        for child in list(self.elt):
            enum = None
            if child.tag == 'pad':
                field_name = 'pad' + str(module.pads)
                fkey = 'CARD8'
                type = PadType(child)
                module.pads = module.pads + 1
                visible = False
            elif child.tag == 'field':
                field_name = child.get('name')
                enum = child.get('enum')
                fkey = child.get('type')
                type = module.get_type(fkey)
                visible = True
            elif child.tag == 'exprfield':
                field_name = child.get('name')
                fkey = child.get('type')
                type = ExprType(child, module.get_type(fkey), *self.lenfield_parent)
                visible = False
            elif child.tag == 'list':
                field_name = child.get('name')
                fkey = child.get('type')
                if fkey == 'fd':
                    ftype = FileDescriptor()
                    fkey = 'INT32'
                else:
                    ftype = module.get_type(fkey)
                type = ListType(child, ftype, *self.lenfield_parent)
                visible = True
            elif child.tag == 'switch':
                field_name = child.get('name')
                # construct the switch type name from the parent type and the field name
                field_type = self.name + (field_name,)
                type = SwitchType(field_type, child, *self.lenfield_parent)
                visible = True
                type.make_member_of(module, self, field_type, field_name, visible, True, False)
                type.resolve(module)
                continue
            elif child.tag == 'fd':
                fd_name = child.get('name')
                type = module.get_type('INT32')
                type.make_fd_of(module, self, fd_name)
                continue
            elif child.tag == 'length':
                self.length_expr = Expression(list(child)[0], self)
                continue
            else:
                # Hit this on Reply
                continue

            # Get the full type name for the field
            field_type = module.get_type_name(fkey)
            # Add the field to ourself
            type.make_member_of(module, self, field_type, field_name, visible, True, False, enum)
            # Recursively resolve the type (could be another structure, list)
            type.resolve(module)

            # Compute the size of the maximally contain align-pad
            if type.max_align_pad > self.max_align_pad:
                self.max_align_pad = type.max_align_pad

        self.check_implicit_fixed_size_part_aligns();

        self.calc_size() # Figure out how big we are
        self.calc_or_check_required_start_align()

        self.resolved = True

    def calc_size(self):
        self.size = 0
        for m in self.fields:
            if not m.wire:
                continue
            if m.type.fixed_size():
                self.size = self.size + m.type.get_total_size()
            else:
                self.size = None
                break

    def calc_or_check_required_start_align(self):
        if self.required_start_align is None:
            # no required-start-align configured -> calculate it
            log = AlignmentLog()
            callstack = []
            self.required_start_align = self.calc_minimally_required_start_align(callstack, log)
            if self.required_start_align is None:
                print ("ERROR: could not calc required_start_align of %s\nDetails:\n%s"
                    % (str(self), str(log)))
            else:
                if verbose_align_log:
                    print ("calc_required_start_align: %s has start-align %s"
                        % (str(self), str(self.required_start_align)))
                    print ("Details:\n" + str(log))
                if self.required_start_align.offset != 0:
                    print (("WARNING: %s\n\thas start-align with non-zero offset: %s"
                        + "\n\tsuggest to add explicit definition with:"
                        + "\n\t\t<required_start_align align=\"%d\" offset=\"%d\" />"
                        + "\n\tor to fix the xml so that zero offset is ok\n")
                        % (str(self), self.required_start_align,
                           self.required_start_align.align,
                           self.required_start_align.offset))
        else:
            # required-start-align configured -> check it
            log = AlignmentLog()
            callstack = []
            if not self.is_possible_start_align(self.required_start_align, callstack, log):
                print ("ERROR: required_start_align %s of %s causes problems\nDetails:\n%s"
                    % (str(self.required_start_align), str(self), str(log)))


    def calc_minimally_required_start_align(self, callstack, log):
        # calculate the minimally required start_align that causes no
        # align errors
        best_log = None
        best_failed_align = None
        for align in [1,2,4,8]:
            for offset in range(0,align):
                align_candidate = Alignment(align, offset)
                if verbose_align_log:
                    print ("trying %s for %s" % (str(align_candidate), str(self)))
                my_log = AlignmentLog()
                if self.is_possible_start_align(align_candidate, callstack, my_log):
                    log.append(my_log)
                    if verbose_align_log:
                        print ("found start-align %s for %s" % (str(align_candidate), str(self)))
                    return align_candidate
                else:
                    my_ok_count = my_log.ok_count()
                    if (best_log is None
                       or my_ok_count > best_log.ok_count()
                       or (my_ok_count == best_log.ok_count()
                          and align_candidate.align > best_failed_align.align)
                          and align_candidate.align != 8):
                        best_log = my_log
                        best_failed_align = align_candidate



        # none of the candidates applies
        # this type has illegal internal aligns for all possible start_aligns
        if verbose_align_log:
            print ("didn't find start-align for %s" % str(self))
        log.append(best_log)
        return None

    def is_possible_start_align(self, align, callstack, log):
        if align is None:
            return False
        if (self.max_align_pad > align.align
           or align.align % self.max_align_pad != 0):
            # our align pad implementation depends on known alignment
            # at the start of our type
            return False

        return self.get_alignment_after(align, callstack, log) is not None

    def fixed_size(self):
        for m in self.fields:
            if not m.type.fixed_size():
                return False
        return True


    # default impls of polymorphic methods which assume sequential layout of fields
    # (like Struct or CaseOrBitcaseType)
    def check_implicit_fixed_size_part_aligns(self):
        # find places where the implementation of the C-binding would
        # create code that makes the compiler add implicit alignment.
        # make these places explicit, so we have
        # consistent behaviour for all bindings
        size = 0
        for field in self.fields:
            if not field.wire:
                continue
            if not field.type.fixed_size():
                # end of fixed-size part
                break
            required_field_align = field.type.required_start_align
            if required_field_align is None:
                raise Exception(
                    "field \"%s\" in \"%s\" has not required_start_align"
                    % (field.field_name, self.name)
                )
            mis_align = (size + required_field_align.offset) % required_field_align.align
            if mis_align != 0:
                # implicit align pad is required
                padsize = required_field_align.align - mis_align
                raise Exception(
                    "C-compiler would insert implicit alignpad of size %d before field \"%s\" in \"%s\""
                    % (padsize, field.field_name, self.name)
                )

    def unchecked_get_alignment_after(self, start_align, callstack, log):
        # default impl assumes sequential layout of fields
        # (like Struct or CaseOrBitcaseType)
        my_align = start_align
        if my_align is None:
            return None

        for field in self.fields:
            if not field.wire:
                continue
            my_callstack = callstack[:]
            my_callstack.extend([self, field])

            prev_align = my_align
            my_align = field.type.get_alignment_after(my_align, my_callstack, log)
            if my_align is None:
                if log is not None:
                    log.fail(prev_align, field.field_name, self, my_callstack,
                        "alignment is incompatible with this field")
                return None
            else:
                if log is not None:
                    log.ok(prev_align, field.field_name, self, my_callstack, my_align)

        if log is not None:
            my_callstack = callstack[:]
            my_callstack.append(self)
            log.ok(start_align, "", self, my_callstack, my_align)
        return my_align


class SwitchType(ComplexType):
    '''
    Derived class which represents a List of Items.  

    Public fields added:
    bitcases is an array of Bitcase objects describing the list items
    '''

    def __init__(self, name, elt, *parents):
        ComplexType.__init__(self, name, elt)
        self.parents = parents
        # FIXME: switch cannot store lenfields, so it should just delegate the parents
        self.lenfield_parent = list(parents) + [self]
        # self.fields contains all possible fields collected from the Bitcase objects, 
        # whereas self.items contains the Bitcase objects themselves
        self.bitcases = []

        self.is_switch = True
        elts = list(elt)
        self.expr = Expression(elts[0] if len(elts) else elt, self)

    def resolve(self, module):
        if self.resolved:
            return

        parents = list(self.parents) + [self]

        # Resolve all of our field datatypes.
        for index, child in enumerate(list(self.elt)):
            if child.tag == 'bitcase' or child.tag == 'case':
                field_name = child.get('name')
                if field_name is None:
                    field_type = self.name + ('%s%d' % ( child.tag, index ),)
                else:
                    field_type = self.name + (field_name,)

                # use self.parent to indicate anchestor, 
                # as switch does not contain named fields itself
                if child.tag == 'bitcase':
                    type = BitcaseType(index, field_type, child, *parents)
                else:
                    type = CaseType(index, field_type, child, *parents)

                # construct the switch type name from the parent type and the field name
                if field_name is None:
                    type.has_name = False
                    # Get the full type name for the field
                    field_type = type.name               
                visible = True

                # add the field to ourself
                type.make_member_of(module, self, field_type, field_name, visible, True, False)

                # recursively resolve the type (could be another structure, list)
                type.resolve(module)
                inserted = False
                for new_field in type.fields:
                    # We dump the _placeholder_byte if any fields are added.
                    for (idx, field) in enumerate(self.fields):
                        if field == _placeholder_byte:
                            self.fields[idx] = new_field
                            inserted = True
                            break
                    if False == inserted:
                        self.fields.append(new_field)

        self.calc_size() # Figure out how big we are
        self.calc_or_check_required_start_align()
        self.resolved = True

    def make_member_of(self, module, complex_type, field_type, field_name, visible, wire, auto, enum=None):
        if not self.fixed_size():
            # We need a length field.
            # Ask our Expression object for it's name, type, and whether it's on the wire.
            lenfid = self.expr.lenfield_type
            lenfield_name = self.expr.lenfield_name
            lenwire = self.expr.lenwire
            needlen = True

            # See if the length field is already in the structure.
            for parent in self.parents:
                for field in parent.fields:
                    if field.field_name == lenfield_name:
                        needlen = False

            # It isn't, so we need to add it to the structure ourself.
            if needlen:
                type = module.get_type(lenfid)
                lenfield_type = module.get_type_name(lenfid)
                type.make_member_of(module, complex_type, lenfield_type, lenfield_name, True, lenwire, False, enum)

        # Add ourself to the structure by calling our original method.
        Type.make_member_of(self, module, complex_type, field_type, field_name, visible, wire, auto, enum)

    # size for switch can only be calculated at runtime
    def calc_size(self):
        pass

    # note: switch is _always_ of variable size, but we indicate here wether 
    # it contains elements that are variable-sized themselves
    def fixed_size(self):
        return False
#        for m in self.fields:
#            if not m.type.fixed_size():
#                return False
#        return True



    def check_implicit_fixed_size_part_aligns(self):
        # this is done for the CaseType or BitCaseType
        return

    def unchecked_get_alignment_after(self, start_align, callstack, log):
        # we assume that BitCases can appear in any combination,
        # and that at most one Case can appear
        # (assuming that Cases are mutually exclusive)

        # get all Cases (we assume that at least one case is selected if there are cases)
        case_fields = []
        for field in self.bitcases:
            if field.type.is_case:
                case_fields.append(field)

        if not case_fields:
            # there are no case-fields -> check without case-fields
            case_fields = [None]

        my_callstack = callstack[:]
        my_callstack.append(self)
        #
        total_align = None
        first = True
        for case_field in case_fields:
            my2_callstack = my_callstack[:]
            if case_field is not None:
                my2_callstack.append(case_field)

            case_align = self.get_align_for_selected_case_field(
                             case_field, start_align, my2_callstack, log)


            if case_align is None:
                if log is not None:
                    if case_field is None:
                        log.fail(start_align, "", self, my2_callstack,
                            "alignment without cases (only bitcases) failed")
                    else:
                        log.fail(start_align, "", self, my2_callstack + [case_field],
                            "alignment for selected case %s failed"
                            % case_field.field_name)
                return None
            if first:
                total_align = case_align
            else:
                total_align = total_align.combine_with(case_align)

            if log is not None:
                if case_field is None:
                    log.ok(
                        start_align,
                        "without cases (only arbitrary bitcases)",
                        self, my2_callstack, case_align)
                else:
                    log.ok(
                        start_align,
                        "case %s and arbitrary bitcases" % case_field.field_name,
                        self, my2_callstack, case_align)


        if log is not None:
            log.ok(start_align, "", self, my_callstack, total_align)
        return total_align

    # aux function for unchecked_get_alignment_after
    def get_align_for_selected_case_field(self, case_field, start_align, callstack, log):
        if verbose_align_log:
            print ("get_align_for_selected_case_field: %s, case_field = %s" % (str(self), str(case_field)))
        total_align = start_align
        for field in self.bitcases:
            my_callstack = callstack[:]
            my_callstack.append(field)

            if not field.wire:
                continue
            if field is case_field:
                # assume that this field is active -> no combine_with to emulate optional
                after_field_align = field.type.get_alignment_after(total_align, my_callstack, log)

                if log is not None:
                    if after_field_align is None:
                        log.fail(total_align, field.field_name, field.type, my_callstack,
                            "invalid aligment for this case branch")
                    else:
                        log.ok(total_align, field.field_name, field.type, my_callstack,
                            after_field_align)

                total_align = after_field_align
            elif field.type.is_bitcase:
                after_field_align = field.type.get_alignment_after(total_align, my_callstack, log)
                # we assume that this field is optional, therefore combine
                # alignment after the field with the alignment before the field.
                if after_field_align is None:
                    if log is not None:
                        log.fail(total_align, field.field_name, field.type, my_callstack,
                            "invalid aligment for this bitcase branch")
                    total_align = None
                else:
                    if log is not None:
                        log.ok(total_align, field.field_name, field.type, my_callstack,
                            after_field_align)

                    # combine with the align before the field because
                    # the field is optional
                    total_align = total_align.combine_with(after_field_align)
            else:
                # ignore other fields as they are irrelevant for alignment
                continue

            if total_align is None:
                break

        return total_align


class Struct(ComplexType):
    '''
    Derived class representing a struct data type.
    '''
    out = __main__.output['struct']


class Union(ComplexType):
    '''
    Derived class representing a union data type.
    '''
    def __init__(self, name, elt):
        ComplexType.__init__(self, name, elt)
        self.is_union = True

    out = __main__.output['union']


    def calc_size(self):
        self.size = 0
        for m in self.fields:
            if not m.wire:
                continue
            if m.type.fixed_size():
                self.size = max(self.size, m.type.get_total_size())
            else:
                self.size = None
                break


    def check_implicit_fixed_size_part_aligns(self):
        # a union does not have implicit aligns because all fields start
        # at the start of the union
        return


    def unchecked_get_alignment_after(self, start_align, callstack, log):
        my_callstack = callstack[:]
        my_callstack.append(self)

        after_align = None
        if self.fixed_size():

            #check proper alignment for all members
            start_align_ok = all(
                [field.type.is_acceptable_start_align(start_align, my_callstack + [field], log)
                for field in self.fields])

            if start_align_ok:
                #compute the after align from the start_align
                after_align = start_align.align_after_fixed_size(self.get_total_size())
            else:
                after_align = None

            if log is not None and after_align is not None:
                log.ok(start_align, "fixed sized union", self, my_callstack, after_align)

        else:
            if start_align is None:
                if log is not None:
                    log.fail(start_align, "", self, my_callstack,
                        "missing start_align for union")
                return None

            after_align = reduce(
                lambda x, y: None if x is None or y is None else x.combine_with(y),
                [field.type.get_alignment_after(start_align, my_callstack + [field], log)
                 for field in self.fields])

            if log is not None and after_align is not None:
                log.ok(start_align, "var sized union", self, my_callstack, after_align)


        if after_align is None and log is not None:
            log.fail(start_align, "", self, my_callstack, "start_align is not ok for all members")

        return after_align

class CaseOrBitcaseType(ComplexType):
    '''
    Derived class representing a case or bitcase.
    '''
    def __init__(self, index, name, elt, *parent):
        elts = list(elt)
        self.expr = []
        for sub_elt in elts:
            if sub_elt.tag == 'enumref':
                self.expr.append(Expression(sub_elt, self))
                elt.remove(sub_elt)
        ComplexType.__init__(self, name, elt)
        self.has_name = True
        self.index = 1
        self.lenfield_parent = list(parent) + [self]
        self.parents = list(parent)
        self.is_case_or_bitcase = True

    def make_member_of(self, module, switch_type, field_type, field_name, visible, wire, auto, enum=None):
        '''
        register BitcaseType with the corresponding SwitchType

        module is the global module object.
        complex_type is the structure object.
        see Field for the meaning of the other parameters.
        '''
        new_field = Field(self, field_type, field_name, visible, wire, auto, enum)

        # We dump the _placeholder_byte if any bitcases are added.
        for (idx, field) in enumerate(switch_type.bitcases):
            if field == _placeholder_byte:
                switch_type.bitcases[idx] = new_field
                return

        switch_type.bitcases.append(new_field)

    def resolve(self, module):
        if self.resolved:
            return

        for e in self.expr:
            e.resolve(module, self.parents+[self])

        # Resolve the bitcase expression
        ComplexType.resolve(self, module)

        #calculate alignment
        self.calc_or_check_required_start_align()


class BitcaseType(CaseOrBitcaseType):
    '''
    Derived class representing a bitcase.
    '''
    def __init__(self, index, name, elt, *parent):
        CaseOrBitcaseType.__init__(self, index, name, elt, *parent)
        self.is_bitcase = True

class CaseType(CaseOrBitcaseType):
    '''
    Derived class representing a case.
    '''
    def __init__(self, index, name, elt, *parent):
        CaseOrBitcaseType.__init__(self, index, name, elt, *parent)
        self.is_case = True


class Reply(ComplexType):
    '''
    Derived class representing a reply.  Only found as a field of Request.
    '''
    def __init__(self, name, elt):
        ComplexType.__init__(self, name, elt)
        self.is_reply = True
        self.doc = None
        if self.required_start_align is None:
            self.required_start_align = Alignment(4,0)

        for child in list(elt):
            if child.tag == 'doc':
                self.doc = Doc(name, child)

    def resolve(self, module):
        if self.resolved:
            return
        # Reset pads count
        module.pads = 0
        # Add the automatic protocol fields
        self.fields.append(Field(tcard8, tcard8.name, 'response_type', False, True, True))
        self.fields.append(_placeholder_byte)
        self.fields.append(Field(tcard16, tcard16.name, 'sequence', False, True, True))
        self.fields.append(Field(tcard32, tcard32.name, 'length', False, True, True))
        ComplexType.resolve(self, module)
        

class Request(ComplexType):
    '''
    Derived class representing a request.

    Public fields added:
    reply contains the reply datatype or None for void requests.
    opcode contains the request number.
    '''
    def __init__(self, name, elt):
        ComplexType.__init__(self, name, elt)
        self.reply = None
        self.doc = None
        self.opcode = elt.get('opcode')
        if self.required_start_align is None:
            self.required_start_align = Alignment(4,0)

        for child in list(elt):
            if child.tag == 'reply':
                self.reply = Reply(name, child)
            if child.tag == 'doc':
                self.doc = Doc(name, child)

    def resolve(self, module):
        if self.resolved:
            return
        # Add the automatic protocol fields
        if module.namespace.is_ext:
            self.fields.append(Field(tcard8, tcard8.name, 'major_opcode', False, True, True))
            self.fields.append(Field(tcard8, tcard8.name, 'minor_opcode', False, True, True))
            self.fields.append(Field(tcard16, tcard16.name, 'length', False, True, True))
            ComplexType.resolve(self, module)
        else:
            self.fields.append(Field(tcard8, tcard8.name, 'major_opcode', False, True, True))
            self.fields.append(_placeholder_byte)
            self.fields.append(Field(tcard16, tcard16.name, 'length', False, True, True))
            ComplexType.resolve(self, module)

        if self.reply:
            self.reply.resolve(module)

    out = __main__.output['request']


class EventStructAllowedRule:

    def __init__(self, parent, elt):
        self.elt = elt
        self.extension = elt.get('extension')
        self.ge_events = elt.get('xge') == "true"
        self.min_opcode = int( elt.get('opcode-min') )
        self.max_opcode = int( elt.get('opcode-max') )

    def resolve(self, parent, module):
        # get the namespace of the specified extension
        extension_namespace = module.get_namespace( self.extension )
        if extension_namespace is None:
            raise Exception( "EventStructAllowedRule.resolve: cannot find extension \"" + self.extension + "\"" )
            return

        # find and add the selected events
        for opcode in range(self.min_opcode, self.max_opcode):
            name_and_event = extension_namespace.get_event_by_opcode( opcode, self.ge_events )
            if name_and_event is None:
                # could not find event -> error handling
                if self.ge_events:
                    raise Exception("EventStructAllowedRule.resolve: cannot find xge-event with opcode " + str(opcode) + " in extension " + self.extension )
                else:
                    raise Exception("EventStructAllowedRule.resolve: cannot find oldstyle-event with opcode " + str(opcode) + " in extension " + self.extension )
                return

            ( name, event ) = name_and_event
            # add event to EventStruct
            parent.add_event( module, self.extension, opcode, name, event )


class EventStruct(Union):
    '''
    Derived class representing an event-use-as-struct data type.
    '''

    def __init__(self, name, elt):
        Union.__init__(self, name, elt)
        self.is_eventstruct = True
        self.events = []
        self.allowedRules = []
        self.contains_ge_events = False
        for item in list(elt):
            if item.tag == 'allowed':
                allowedRule = EventStructAllowedRule(self, item)
                self.allowedRules.append( allowedRule )
                if allowedRule.ge_events:
                    self.contains_ge_events = True

    out = __main__.output['eventstruct']

    def resolve(self, module):
        if self.resolved:
            return
        for allowedRule in self.allowedRules:
            allowedRule.resolve(self, module)
        Union.resolve(self,module)
        self.resolved = True

    # add event. called by resolve
    def add_event(self, module, extension, opcode, name, event_type ):
        self.events.append( (extension, opcode, name, event_type) )
        # Add the field to ourself
        event_type.make_member_of(module, self, name, name[-1], True, True, False)
        # Recursively resolve the event (could be another structure, list)
        event_type.resolve(module)

    def fixed_size(self):
        is_fixed_size = True
        for extension, opcode, name, event in self.events:
            if not event.fixed_size():
                is_fixed_size = False
        return is_fixed_size


class Event(ComplexType):
    '''
    Derived class representing an event data type.

    Public fields added:
    opcodes is a dictionary of name -> opcode number, for eventcopies.
    '''
    def __init__(self, name, elt):
        ComplexType.__init__(self, name, elt)

        if self.required_start_align is None:
            self.required_start_align = Alignment(4,0)

        self.opcodes = {}

        self.has_seq = not bool(elt.get('no-sequence-number'))

        self.is_ge_event = bool(elt.get('xge'))

        self.is_event = True

        self.doc = None
        for item in list(elt):
            if item.tag == 'doc':
                self.doc = Doc(name, item)

    def add_opcode(self, opcode, name, main):
        self.opcodes[name] = opcode
        if main:
            self.name = name

    def get_name_for_opcode(self, opcode):
        for name, my_opcode in self.opcodes.items():
            if int(my_opcode) == opcode:
                return name
        else:
            return None

    def resolve(self, module):
        def add_event_header():
            self.fields.append(Field(tcard8, tcard8.name, 'response_type', False, True, True))
            if self.has_seq:
                self.fields.append(_placeholder_byte)
                self.fields.append(Field(tcard16, tcard16.name, 'sequence', False, True, True))

        def add_ge_event_header():
            self.fields.append(Field(tcard8,  tcard8.name,  'response_type', False, True, True))
            self.fields.append(Field(tcard8,  tcard8.name,  'extension', False, True, True))
            self.fields.append(Field(tcard16, tcard16.name, 'sequence', False, True, True))
            self.fields.append(Field(tcard32, tcard32.name, 'length', False, True, True))
            self.fields.append(Field(tcard16, tcard16.name, 'event_type', False, True, True))

        if self.resolved:
            return

        # Add the automatic protocol fields
        if self.is_ge_event:
            add_ge_event_header()
        else:
            add_event_header()

        ComplexType.resolve(self, module)

    out = __main__.output['event']


class Error(ComplexType):
    '''
    Derived class representing an error data type.

    Public fields added:
    opcodes is a dictionary of name -> opcode number, for errorcopies.
    '''
    def __init__(self, name, elt):
        ComplexType.__init__(self, name, elt)
        self.opcodes = {}
        if self.required_start_align is None:
            self.required_start_align = Alignment(4,0)

        # All errors are basically the same, but they still got different XML
        # for historic reasons. This 'invents' the missing parts.
        if len(self.elt) < 1:
            SubElement(self.elt, "field", type="CARD32", name="bad_value")
        if len(self.elt) < 2:
            SubElement(self.elt, "field", type="CARD16", name="minor_opcode")
        if len(self.elt) < 3:
            SubElement(self.elt, "field", type="CARD8", name="major_opcode")

    def add_opcode(self, opcode, name, main):
        self.opcodes[name] = opcode
        if main:
            self.name = name

    def resolve(self, module):
        if self.resolved:
            return

        # Add the automatic protocol fields
        self.fields.append(Field(tcard8, tcard8.name, 'response_type', False, True, True))
        self.fields.append(Field(tcard8, tcard8.name, 'error_code', False, True, True))
        self.fields.append(Field(tcard16, tcard16.name, 'sequence', False, True, True))
        ComplexType.resolve(self, module)

    out = __main__.output['error']


class Doc(object):
    '''
    Class representing a <doc> tag.
    '''
    def __init__(self, name, elt):
        self.name = name
        self.description = None
        self.brief = 'BRIEF DESCRIPTION MISSING'
        self.fields = {}
        self.errors = {}
        self.see = {}
        self.example = None

        for child in list(elt):
            text = child.text if child.text else ''
            if child.tag == 'description':
                self.description = text.strip()
            if child.tag == 'brief':
                self.brief = text.strip()
            if child.tag == 'field':
                self.fields[child.get('name')] = text.strip()
            if child.tag == 'error':
                self.errors[child.get('type')] = text.strip()
            if child.tag == 'see':
                self.see[child.get('name')] = child.get('type')
            if child.tag == 'example':
                self.example = text.strip()



_placeholder_byte = Field(PadType(None), tcard8.name, 'pad0', False, True, False)
