import sys

from comtypes import automation, typeinfo, COMError
from comtypes.tools import typedesc
from ctypes import c_void_p, sizeof, alignment

try:
    set
except NameError:
    from sets import Set as set

# Is the process 64-bit?
is_64bits = sys.maxsize > 2**32


################################

def PTR(typ):
    return typedesc.PointerType(typ,
                                sizeof(c_void_p)*8,
                                alignment(c_void_p)*8)

# basic C data types, with size and alignment in bits
char_type = typedesc.FundamentalType("char", 8, 8)
uchar_type = typedesc.FundamentalType("unsigned char", 8, 8)
wchar_t_type = typedesc.FundamentalType("wchar_t", 16, 16)
short_type = typedesc.FundamentalType("short int", 16, 16)
ushort_type = typedesc.FundamentalType("short unsigned int", 16, 16)
int_type = typedesc.FundamentalType("int", 32, 32)
uint_type = typedesc.FundamentalType("unsigned int", 32, 32)
long_type = typedesc.FundamentalType("long int", 32, 32)
ulong_type = typedesc.FundamentalType("long unsigned int", 32, 32)
longlong_type = typedesc.FundamentalType("long long int", 64, 64)
ulonglong_type = typedesc.FundamentalType("long long unsigned int", 64, 64)
float_type = typedesc.FundamentalType("float", 32, 32)
double_type = typedesc.FundamentalType("double", 64, 64)

# basic COM data types
BSTR_type = typedesc.Typedef("BSTR", PTR(wchar_t_type))
SCODE_type = typedesc.Typedef("SCODE", int_type)
VARIANT_BOOL_type = typedesc.Typedef("VARIANT_BOOL", short_type)
HRESULT_type = typedesc.Typedef("HRESULT", ulong_type)

VARIANT_type = typedesc.Structure("VARIANT",
                                  align=alignment(automation.VARIANT)*8,
                                  members=[], bases=[],
                                  size=sizeof(automation.VARIANT)*8)
IDISPATCH_type = typedesc.Typedef("IDispatch", None)
IUNKNOWN_type = typedesc.Typedef("IUnknown", None)
DECIMAL_type = typedesc.Structure("DECIMAL",
                                  align=alignment(automation.DECIMAL)*8,
                                  members=[], bases=[],
                                  size=sizeof(automation.DECIMAL)*8)

def midlSAFEARRAY(typ):
    return typedesc.SAFEARRAYType(typ)

# faked COM data types
CURRENCY_type = longlong_type # slightly wrong; should be scaled by 10000 - use subclass of longlong?
DATE_type = double_type # not *that* wrong...

COMTYPES = {
    automation.VT_I2: short_type, # 2
    automation.VT_I4: int_type, # 3
    automation.VT_R4: float_type, # 4
    automation.VT_R8: double_type, # 5
    automation.VT_CY: CURRENCY_type, # 6
    automation.VT_DATE: DATE_type, # 7
    automation.VT_BSTR: BSTR_type, # 8
    automation.VT_DISPATCH: PTR(IDISPATCH_type), # 9
    automation.VT_ERROR: SCODE_type, # 10
    automation.VT_BOOL: VARIANT_BOOL_type, # 11
    automation.VT_VARIANT: VARIANT_type, # 12
    automation.VT_UNKNOWN: PTR(IUNKNOWN_type), # 13
    automation.VT_DECIMAL: DECIMAL_type, # 14

    automation.VT_I1: char_type, # 16
    automation.VT_UI1: uchar_type, # 17
    automation.VT_UI2: ushort_type, # 18
    automation.VT_UI4: ulong_type, # 19
    automation.VT_I8: longlong_type, # 20
    automation.VT_UI8: ulonglong_type, # 21
    automation.VT_INT: int_type, # 22
    automation.VT_UINT: uint_type, # 23
    automation.VT_VOID: typedesc.FundamentalType("void", 0, 0), # 24
    automation.VT_HRESULT: HRESULT_type, # 25
    automation.VT_LPSTR: PTR(char_type), # 30
    automation.VT_LPWSTR: PTR(wchar_t_type), # 31
}

#automation.VT_PTR = 26 # below
#automation.VT_SAFEARRAY = 27
#automation.VT_CARRAY = 28 # below
#automation.VT_USERDEFINED = 29 # below

#automation.VT_RECORD = 36

#automation.VT_ARRAY = 8192
#automation.VT_BYREF = 16384

################################################################

class Parser(object):

    def make_type(self, tdesc, tinfo):
        try:
            return COMTYPES[tdesc.vt]
        except KeyError:
            pass

        if tdesc.vt == automation.VT_CARRAY:
            typ = self.make_type(tdesc._.lpadesc[0].tdescElem, tinfo)
            for i in range(tdesc._.lpadesc[0].cDims):
                typ = typedesc.ArrayType(typ,
                                         tdesc._.lpadesc[0].rgbounds[i].lLbound,
                                         tdesc._.lpadesc[0].rgbounds[i].cElements-1)
            return typ

        elif tdesc.vt == automation.VT_PTR:
            typ = self.make_type(tdesc._.lptdesc[0], tinfo)
            return PTR(typ)

        elif tdesc.vt == automation.VT_USERDEFINED:
            try:
                ti = tinfo.GetRefTypeInfo(tdesc._.hreftype)
            except COMError as details:
                type_name = "__error_hreftype_%d__" % tdesc._.hreftype
                tlib_name = get_tlib_filename(self.tlib)
                if tlib_name is None:
                    tlib_name = "unknown typelib"
                message = "\n\tGetRefTypeInfo failed in %s: %s\n\tgenerating type '%s' instead" % \
                          (tlib_name, details, type_name)
                import warnings
                warnings.warn(message, UserWarning);
                result = typedesc.Structure(type_name,
                                            align=8,
                                            members=[], bases=[],
                                            size=0)
                return result
            result = self.parse_typeinfo(ti)
            assert result is not None, ti.GetDocumentation(-1)[0]
            return result

        elif tdesc.vt == automation.VT_SAFEARRAY:
            # SAFEARRAY(<type>), see Don Box pp.331f
            itemtype = self.make_type(tdesc._.lptdesc[0], tinfo)
            return midlSAFEARRAY(itemtype)

        raise NotImplementedError(tdesc.vt)

    ################################################################

    # TKIND_ENUM = 0
    def ParseEnum(self, tinfo, ta):
        ta = tinfo.GetTypeAttr()
        enum_name = tinfo.GetDocumentation(-1)[0]
        enum = typedesc.Enumeration(enum_name, 32, 32)
        self._register(enum_name, enum)

        for i in range(ta.cVars):
            vd = tinfo.GetVarDesc(i)
            name = tinfo.GetDocumentation(vd.memid)[0]
            assert vd.varkind == typeinfo.VAR_CONST
            num_val = vd._.lpvarValue[0].value
            v = typedesc.EnumValue(name, num_val, enum)
            enum.add_value(v)
        return enum

    # TKIND_RECORD = 1
    def ParseRecord(self, tinfo, ta):
        members = [] # will be filled later
        struct_name, doc, helpcntext, helpfile = tinfo.GetDocumentation(-1)
        struct = typedesc.Structure(struct_name,
                                    align=ta.cbAlignment*8,
                                    members=members,
                                    bases=[],
                                    size=ta.cbSizeInstance*8)
        self._register(struct_name, struct)

        tlib, _ = tinfo.GetContainingTypeLib()
        tlib_ta = tlib.GetLibAttr()
        # If this is a 32-bit typlib being loaded in a 64-bit process, then the
        # size and alignment are incorrect. Set the size to None to disable
        # size checks and correct the alignment.
        if is_64bits and tlib_ta.syskind == typeinfo.SYS_WIN32:
            struct.size = None
            struct.align = 64

        if ta.guid:
            struct._recordinfo_ = (str(tlib_ta.guid),
                                   tlib_ta.wMajorVerNum, tlib_ta.wMinorVerNum,
                                   tlib_ta.lcid,
                                   str(ta.guid))

        for i in range(ta.cVars):
            vd = tinfo.GetVarDesc(i)
            name = tinfo.GetDocumentation(vd.memid)[0]
            offset = vd._.oInst * 8
            assert vd.varkind == typeinfo.VAR_PERINSTANCE
            typ = self.make_type(vd.elemdescVar.tdesc, tinfo)
            field = typedesc.Field(name,
                                   typ,
                                   None, # bits
                                   offset)
            members.append(field)
        return struct

    # TKIND_MODULE = 2
    def ParseModule(self, tinfo, ta):
        assert 0 == ta.cImplTypes
        # functions
        for i in range(ta.cFuncs):
            # We skip all function definitions.  There are several
            # problems with these, and we can, for comtypes, ignore them.
            continue
            fd = tinfo.GetFuncDesc(i)
            dllname, func_name, ordinal = tinfo.GetDllEntry(fd.memid, fd.invkind)
            func_doc = tinfo.GetDocumentation(fd.memid)[1]
            assert 0 == fd.cParamsOpt # XXX
            returns = self.make_type(fd.elemdescFunc.tdesc, tinfo)

            if fd.callconv == typeinfo.CC_CDECL:
                attributes = "__cdecl__"
            elif fd.callconv == typeinfo.CC_STDCALL:
                attributes = "__stdcall__"
            else:
                raise ValueError("calling convention %d" % fd.callconv)

            func = typedesc.Function(func_name, returns, attributes, extern=1)
            if func_doc is not None:
                func.doc = func_doc.encode("mbcs")
            func.dllname = dllname
            self._register(func_name, func)
            for i in range(fd.cParams):
                argtype = self.make_type(fd.lprgelemdescParam[i].tdesc, tinfo)
                func.add_argument(argtype)

        # constants
        for i in range(ta.cVars):
            vd = tinfo.GetVarDesc(i)
            name, var_doc = tinfo.GetDocumentation(vd.memid)[0:2]
            assert vd.varkind == typeinfo.VAR_CONST
            typ = self.make_type(vd.elemdescVar.tdesc, tinfo)
            var_value = vd._.lpvarValue[0].value
            v = typedesc.Constant(name, typ, var_value)
            self._register(name, v)
            if var_doc is not None:
                v.doc = var_doc

    # TKIND_INTERFACE = 3
    def ParseInterface(self, tinfo, ta):
        itf_name, itf_doc = tinfo.GetDocumentation(-1)[0:2]
        assert ta.cImplTypes <= 1
        if ta.cImplTypes == 0 and itf_name != "IUnknown":
            # Windows defines an interface IOleControlTypes in ocidl.idl.
            # Don't known what artefact that is - we ignore it.
            # It's an interface without methods anyway.
            if itf_name != "IOleControlTypes":
                message = "Ignoring interface %s which has no base interface" % itf_name
                import warnings
                warnings.warn(message, UserWarning);
            return None

        itf = typedesc.ComInterface(itf_name,
                                    members=[],
                                    base=None,
                                    iid=str(ta.guid),
                                    idlflags=self.interface_type_flags(ta.wTypeFlags))
        if itf_doc:
            itf.doc = itf_doc
        self._register(itf_name, itf)

        if ta.cImplTypes:
            hr = tinfo.GetRefTypeOfImplType(0)
            tibase = tinfo.GetRefTypeInfo(hr)
            itf.base = self.parse_typeinfo(tibase)

        assert ta.cVars == 0, "vars on an Interface?"

        members = []
        for i in range(ta.cFuncs):
            fd = tinfo.GetFuncDesc(i)
##            func_name = tinfo.GetDocumentation(fd.memid)[0]
            func_name, func_doc = tinfo.GetDocumentation(fd.memid)[:2]
            assert fd.funckind == typeinfo.FUNC_PUREVIRTUAL
            returns = self.make_type(fd.elemdescFunc.tdesc, tinfo)
            names = tinfo.GetNames(fd.memid, fd.cParams+1)
            names.append("rhs")
            names = names[:fd.cParams + 1]
            assert len(names) == fd.cParams + 1
            flags = self.func_flags(fd.wFuncFlags)
            flags += self.inv_kind(fd.invkind)
            mth = typedesc.ComMethod(fd.invkind, fd.memid, func_name, returns, flags, func_doc)
            mth.oVft = fd.oVft
            for p in range(fd.cParams):
                typ = self.make_type(fd.lprgelemdescParam[p].tdesc, tinfo)
                name = names[p+1]
                flags = fd.lprgelemdescParam[p]._.paramdesc.wParamFlags
                if flags & typeinfo.PARAMFLAG_FHASDEFAULT:
                    # XXX should be handled by VARIANT itself
                    var = fd.lprgelemdescParam[p]._.paramdesc.pparamdescex[0].varDefaultValue
                    default = var.value
                else:
                    default = None
                mth.add_argument(typ, name, self.param_flags(flags), default)
            members.append((fd.oVft, mth))
        # Sort the methods by oVft (VTable offset): Some typeinfo
        # don't list methods in VTable order.
        members.sort()
        itf.members.extend([m[1] for m in members])

        return itf

    # TKIND_DISPATCH = 4
    def ParseDispatch(self, tinfo, ta):
        itf_name, doc = tinfo.GetDocumentation(-1)[0:2]
        assert ta.cImplTypes == 1

        hr = tinfo.GetRefTypeOfImplType(0)
        tibase = tinfo.GetRefTypeInfo(hr)
        base = self.parse_typeinfo(tibase)
        members = []
        itf = typedesc.DispInterface(itf_name,
                                     members=members,
                                     base=base,
                                     iid=str(ta.guid),
                                     idlflags=self.interface_type_flags(ta.wTypeFlags))
        if doc is not None:
            itf.doc = str(doc.split("\0")[0])
        self._register(itf_name, itf)

        # This code can only handle pure dispinterfaces.  Dual
        # interfaces are parsed in ParseInterface().
        assert ta.wTypeFlags & typeinfo.TYPEFLAG_FDUAL == 0

        for i in range(ta.cVars):
            vd = tinfo.GetVarDesc(i)
            assert vd.varkind == typeinfo.VAR_DISPATCH
            var_name, var_doc = tinfo.GetDocumentation(vd.memid)[0:2]
            typ = self.make_type(vd.elemdescVar.tdesc, tinfo)
            mth = typedesc.DispProperty(vd.memid, var_name, typ, self.var_flags(vd.wVarFlags), var_doc)
            itf.members.append(mth)

        # At least the EXCEL typelib lists the IUnknown and IDispatch
        # methods even for this kind of interface.  I didn't find any
        # indication about these methods in the various flags, so we
        # have to exclude them by name.
        # CLF: 12/14/2012 Do this in a way that does not exclude other methods.
        #      I have encountered typlibs where only "QueryInterface", "AddRef"
        #      and "Release" are to be skipped.
        ignored_names = set(["QueryInterface", "AddRef", "Release",
                             "GetTypeInfoCount", "GetTypeInfo",
                             "GetIDsOfNames", "Invoke"])

        for i in range(ta.cFuncs):
            fd = tinfo.GetFuncDesc(i)
            func_name, func_doc = tinfo.GetDocumentation(fd.memid)[:2]
            if func_name in ignored_names:
                continue
            assert fd.funckind == typeinfo.FUNC_DISPATCH

            returns = self.make_type(fd.elemdescFunc.tdesc, tinfo)
            names = tinfo.GetNames(fd.memid, fd.cParams+1)
            names.append("rhs")
            names = names[:fd.cParams + 1]
            assert len(names) == fd.cParams + 1 # function name first, then parameter names
            flags = self.func_flags(fd.wFuncFlags)
            flags += self.inv_kind(fd.invkind)
            mth = typedesc.DispMethod(fd.memid, fd.invkind, func_name, returns, flags, func_doc)
            for p in range(fd.cParams):
                typ = self.make_type(fd.lprgelemdescParam[p].tdesc, tinfo)
                name = names[p+1]
                flags = fd.lprgelemdescParam[p]._.paramdesc.wParamFlags
                if flags & typeinfo.PARAMFLAG_FHASDEFAULT:
                    var = fd.lprgelemdescParam[p]._.paramdesc.pparamdescex[0].varDefaultValue
                    default = var.value
                else:
                    default = None
                mth.add_argument(typ, name, self.param_flags(flags), default)
            itf.members.append(mth)

        return itf

    def inv_kind(self, invkind):
        NAMES = {automation.DISPATCH_METHOD: [],
                 automation.DISPATCH_PROPERTYPUT: ["propput"],
                 automation.DISPATCH_PROPERTYPUTREF: ["propputref"],
                 automation.DISPATCH_PROPERTYGET: ["propget"]}
        return NAMES[invkind]

    def func_flags(self, flags):
        # map FUNCFLAGS values to idl attributes
        NAMES = {typeinfo.FUNCFLAG_FRESTRICTED: "restricted",
                 typeinfo.FUNCFLAG_FSOURCE: "source",
                 typeinfo.FUNCFLAG_FBINDABLE: "bindable",
                 typeinfo.FUNCFLAG_FREQUESTEDIT: "requestedit",
                 typeinfo.FUNCFLAG_FDISPLAYBIND: "displaybind",
                 typeinfo.FUNCFLAG_FDEFAULTBIND: "defaultbind",
                 typeinfo.FUNCFLAG_FHIDDEN: "hidden",
                 typeinfo.FUNCFLAG_FUSESGETLASTERROR: "usesgetlasterror",
                 typeinfo.FUNCFLAG_FDEFAULTCOLLELEM: "defaultcollelem",
                 typeinfo.FUNCFLAG_FUIDEFAULT: "uidefault",
                 typeinfo.FUNCFLAG_FNONBROWSABLE: "nonbrowsable",
                 # typeinfo.FUNCFLAG_FREPLACEABLE: "???",
                 typeinfo.FUNCFLAG_FIMMEDIATEBIND: "immediatebind"}
        return [NAMES[bit] for bit in NAMES if bit & flags]

    def param_flags(self, flags):
        # map PARAMFLAGS values to idl attributes
        NAMES = {typeinfo.PARAMFLAG_FIN: "in",
                 typeinfo.PARAMFLAG_FOUT: "out",
                 typeinfo.PARAMFLAG_FLCID: "lcid",
                 typeinfo.PARAMFLAG_FRETVAL: "retval",
                 typeinfo.PARAMFLAG_FOPT: "optional",
                 # typeinfo.PARAMFLAG_FHASDEFAULT: "",
                 # typeinfo.PARAMFLAG_FHASCUSTDATA: "",
                 }
        return [NAMES[bit] for bit in NAMES if bit & flags]

    def coclass_type_flags(self, flags):
        # map TYPEFLAGS values to idl attributes
        NAMES = {typeinfo.TYPEFLAG_FAPPOBJECT: "appobject",
                 # typeinfo.TYPEFLAG_FCANCREATE:
                 typeinfo.TYPEFLAG_FLICENSED: "licensed",
                 # typeinfo.TYPEFLAG_FPREDECLID:
                 typeinfo.TYPEFLAG_FHIDDEN: "hidden",
                 typeinfo.TYPEFLAG_FCONTROL: "control",
                 typeinfo.TYPEFLAG_FDUAL: "dual",
                 typeinfo.TYPEFLAG_FNONEXTENSIBLE: "nonextensible",
                 typeinfo.TYPEFLAG_FOLEAUTOMATION: "oleautomation",
                 typeinfo.TYPEFLAG_FRESTRICTED: "restricted",
                 typeinfo.TYPEFLAG_FAGGREGATABLE: "aggregatable",
                 # typeinfo.TYPEFLAG_FREPLACEABLE:
                 # typeinfo.TYPEFLAG_FDISPATCHABLE # computed, no flag for this
                 typeinfo.TYPEFLAG_FREVERSEBIND: "reversebind",
                 typeinfo.TYPEFLAG_FPROXY: "proxy",
                 }
        NEGATIVE_NAMES = {typeinfo.TYPEFLAG_FCANCREATE: "noncreatable"}
        return [NAMES[bit] for bit in NAMES if bit & flags] + \
               [NEGATIVE_NAMES[bit] for bit in NEGATIVE_NAMES if not (bit & flags)]

    def interface_type_flags(self, flags):
        # map TYPEFLAGS values to idl attributes
        NAMES = {typeinfo.TYPEFLAG_FAPPOBJECT: "appobject",
                 # typeinfo.TYPEFLAG_FCANCREATE:
                 typeinfo.TYPEFLAG_FLICENSED: "licensed",
                 # typeinfo.TYPEFLAG_FPREDECLID:
                 typeinfo.TYPEFLAG_FHIDDEN: "hidden",
                 typeinfo.TYPEFLAG_FCONTROL: "control",
                 typeinfo.TYPEFLAG_FDUAL: "dual",
                 typeinfo.TYPEFLAG_FNONEXTENSIBLE: "nonextensible",
                 typeinfo.TYPEFLAG_FOLEAUTOMATION: "oleautomation",
                 typeinfo.TYPEFLAG_FRESTRICTED: "restricted",
                 typeinfo.TYPEFLAG_FAGGREGATABLE: "aggregatable",
                 # typeinfo.TYPEFLAG_FREPLACEABLE:
                 # typeinfo.TYPEFLAG_FDISPATCHABLE # computed, no flag for this
                 typeinfo.TYPEFLAG_FREVERSEBIND: "reversebind",
                 typeinfo.TYPEFLAG_FPROXY: "proxy",
                 }
        NEGATIVE_NAMES = {}
        return [NAMES[bit] for bit in NAMES if bit & flags] + \
               [NEGATIVE_NAMES[bit] for bit in NEGATIVE_NAMES if not (bit & flags)]

    def var_flags(self, flags):
        NAMES = {typeinfo.VARFLAG_FREADONLY: "readonly",
                 typeinfo.VARFLAG_FSOURCE: "source",
                 typeinfo.VARFLAG_FBINDABLE: "bindable",
                 typeinfo.VARFLAG_FREQUESTEDIT: "requestedit",
                 typeinfo.VARFLAG_FDISPLAYBIND: "displaybind",
                 typeinfo.VARFLAG_FDEFAULTBIND: "defaultbind",
                 typeinfo.VARFLAG_FHIDDEN: "hidden",
                 typeinfo.VARFLAG_FRESTRICTED: "restricted",
                 typeinfo.VARFLAG_FDEFAULTCOLLELEM: "defaultcollelem",
                 typeinfo.VARFLAG_FUIDEFAULT: "uidefault",
                 typeinfo.VARFLAG_FNONBROWSABLE: "nonbrowsable",
                 typeinfo.VARFLAG_FREPLACEABLE: "replaceable",
                 typeinfo.VARFLAG_FIMMEDIATEBIND: "immediatebind"
                 }
        return [NAMES[bit] for bit in NAMES if bit & flags]


    # TKIND_COCLASS = 5
    def ParseCoClass(self, tinfo, ta):
        # possible ta.wTypeFlags: helpstring, helpcontext, licensed,
        #        version, control, hidden, and appobject
        coclass_name, doc = tinfo.GetDocumentation(-1)[0:2]
        tlibattr = tinfo.GetContainingTypeLib()[0].GetLibAttr()
        coclass = typedesc.CoClass(coclass_name,
                                   str(ta.guid),
                                   self.coclass_type_flags(ta.wTypeFlags),
                                   tlibattr)
        if doc is not None:
            coclass.doc = doc
        self._register(coclass_name, coclass)

        for i in range(ta.cImplTypes):
            hr = tinfo.GetRefTypeOfImplType(i)
            ti = tinfo.GetRefTypeInfo(hr)
            itf = self.parse_typeinfo(ti)
            flags = tinfo.GetImplTypeFlags(i)
            coclass.add_interface(itf, flags)
        return coclass

    # TKIND_ALIAS = 6
    def ParseAlias(self, tinfo, ta):
        name = tinfo.GetDocumentation(-1)[0]
        typ = self.make_type(ta.tdescAlias, tinfo)
        alias = typedesc.Typedef(name, typ)
        self._register(name, alias)
        return alias

    # TKIND_UNION = 7
    def ParseUnion(self, tinfo, ta):
        union_name, doc, helpcntext, helpfile = tinfo.GetDocumentation(-1)
        members = []
        union = typedesc.Union(union_name,
                               align=ta.cbAlignment*8,
                               members=members,
                               bases=[],
                               size=ta.cbSizeInstance*8)
        self._register(union_name, union)

        tlib, _ = tinfo.GetContainingTypeLib()
        tlib_ta = tlib.GetLibAttr()
        # If this is a 32-bit typlib being loaded in a 64-bit process, then the
        # size and alignment are incorrect. Set the size to None to disable
        # size checks and correct the alignment.
        if is_64bits and tlib_ta.syskind == typeinfo.SYS_WIN32:
            union.size = None
            union.align = 64

        for i in range(ta.cVars):
            vd = tinfo.GetVarDesc(i)
            name = tinfo.GetDocumentation(vd.memid)[0]
            offset = vd._.oInst * 8
            assert vd.varkind == typeinfo.VAR_PERINSTANCE
            typ = self.make_type(vd.elemdescVar.tdesc, tinfo)
            field = typedesc.Field(name,
                                   typ,
                                   None, # bits
                                   offset)
            members.append(field)
        return union

    ################################################################

    def _typelib_module(self, tlib=None):
        if tlib is None:
            tlib = self.tlib
        # return a string that uniquely identifies a typelib.
        # The string doesn't have any meaning outside this instance.
        return str(tlib.GetLibAttr())

    def _register(self, name, value, tlib=None):
        modname = self._typelib_module(tlib)
        fullname = "%s.%s" % (modname, name)
        if fullname in self.items:
            # XXX Can we really allow this? It happens, at least.
            if isinstance(value, typedesc.External):
                return
            # BUG: We try to register an item that's already registered.
            raise ValueError("Bug: Multiple registered name '%s': %r" % (name, value))
        self.items[fullname] = value

    def parse_typeinfo(self, tinfo):
        name = tinfo.GetDocumentation(-1)[0]
        modname = self._typelib_module()
        try:
            return self.items["%s.%s" % (modname, name)]
        except KeyError:
            pass

        tlib = tinfo.GetContainingTypeLib()[0]
        if tlib != self.tlib:
            ta = tinfo.GetTypeAttr()
            size = ta.cbSizeInstance * 8
            align = ta.cbAlignment * 8
            typ = typedesc.External(tlib,
                                    name,
                                    size,
                                    align,
                                    tlib.GetDocumentation(-1)[:2])
            self._register(name, typ, tlib)
            return typ

        ta = tinfo.GetTypeAttr()
        tkind = ta.typekind

        if tkind == typeinfo.TKIND_ENUM: # 0
            return self.ParseEnum(tinfo, ta)
        elif tkind == typeinfo.TKIND_RECORD: # 1
            return self.ParseRecord(tinfo, ta)
        elif tkind == typeinfo.TKIND_MODULE: # 2
            return self.ParseModule(tinfo, ta)
        elif tkind == typeinfo.TKIND_INTERFACE: # 3
            return self.ParseInterface(tinfo, ta)
        elif tkind == typeinfo.TKIND_DISPATCH: # 4
            try:
                # GetRefTypeOfImplType(-1) returns the custom portion
                # of a dispinterface, if it is dual
                href = tinfo.GetRefTypeOfImplType(-1)
            except COMError:
                # no dual interface
                return self.ParseDispatch(tinfo, ta)
            tinfo = tinfo.GetRefTypeInfo(href)
            ta = tinfo.GetTypeAttr()
            assert ta.typekind == typeinfo.TKIND_INTERFACE
            return self.ParseInterface(tinfo, ta)
        elif tkind == typeinfo.TKIND_COCLASS: # 5
            return self.ParseCoClass(tinfo, ta)
        elif tkind == typeinfo.TKIND_ALIAS: # 6
            return self.ParseAlias(tinfo, ta)
        elif tkind == typeinfo.TKIND_UNION: # 7
            return self.ParseUnion(tinfo, ta)
        else:
            print("NYI", tkind)
##            raise "NYI", tkind

    def parse_LibraryDescription(self):
        la = self.tlib.GetLibAttr()
        name, doc = self.tlib.GetDocumentation(-1)[:2]
        desc = typedesc.TypeLib(name,
                                str(la.guid), la.wMajorVerNum, la.wMinorVerNum,
                                doc)
        self._register(None, desc)

    ################################################################

    def parse(self):
        self.parse_LibraryDescription()

        for i in range(self.tlib.GetTypeInfoCount()):
            tinfo = self.tlib.GetTypeInfo(i)
            self.parse_typeinfo(tinfo)
        return self.items

class TlbFileParser(Parser):
    "Parses a type library from a file"
    def __init__(self, path):
        # XXX DOESN'T LOOK CORRECT: We should NOT register the typelib.
        self.tlib = typeinfo.LoadTypeLibEx(path)#, regkind=typeinfo.REGKIND_REGISTER)
        self.items = {}

class TypeLibParser(Parser):
    def __init__(self, tlib):
        self.tlib = tlib
        self.items = {}

################################################################
# some interesting typelibs

## these do NOT work:
    # XXX infinite loop?
##    path = r"mshtml.tlb" # has propputref

    # has SAFEARRAY
    # HRESULT Run(BSTR, SAFEARRAY(VARIANT)*, VARIANT*)
##    path = "msscript.ocx"

    # has SAFEARRAY
    # HRESULT AddAddress(SAFEARRAY(BSTR)*, SAFEARRAY(BSTR)*)
##    path = r"c:\Programme\Microsoft Office\Office\MSWORD8.OLB" # has propputref

    # has SAFEARRAY:
    # SAFEARRAY(unsigned char) FileSignatureInfo(BSTR, long, MsiSignatureInfo)
##    path = r"msi.dll" # DispProperty

    # fails packing IDLDESC
##    path = r"C:\Dokumente und Einstellungen\thomas\Desktop\tlb\win.tlb"
    # fails packing WIN32_FIND_DATA
##    path = r"C:\Dokumente und Einstellungen\thomas\Desktop\tlb\win32.tlb"
    # has a POINTER(IUnknown) as default parameter value
##    path = r"c:\Programme\Gemeinsame Dateien\Microsoft Shared\Speech\sapi.dll"


##    path = r"hnetcfg.dll"
##    path = r"simpdata.tlb"
##    path = r"nscompat.tlb"
##    path = r"stdole32.tlb"

##    path = r"shdocvw.dll"

##    path = r"c:\Programme\Microsoft Office\Office\MSO97.DLL"
##    path = r"PICCLP32.OCX" # DispProperty
##    path = r"MSHFLXGD.OCX" # DispProperty, propputref
##    path = r"scrrun.dll" # propput AND propputref on IDictionary::Item
##    path = r"C:\Dokumente und Einstellungen\thomas\Desktop\tlb\threadapi.tlb"

##    path = r"..\samples\BITS\bits2_0.tlb"

##    path = r"c:\vc98\include\activscp.tlb"

def get_tlib_filename(tlib):
    # seems if the typelib is not registered, there's no way to
    # determine the filename.
    from ctypes import windll, byref
    from comtypes import BSTR
    la = tlib.GetLibAttr()
    name = BSTR()
    try:
        windll.oleaut32.QueryPathOfRegTypeLib
    except AttributeError:
        # Windows CE doesn't have this function
        return None
    if 0 == windll.oleaut32.QueryPathOfRegTypeLib(byref(la.guid),
                                                  la.wMajorVerNum,
                                                  la.wMinorVerNum,
                                                  0, # lcid
                                                  byref(name)
                                                  ):
        return name.value.split("\0")[0]
    return None

def _py2exe_hint():
    # If the tlbparser is frozen, we need to include these
    import comtypes.persist
    import comtypes.typeinfo
    import comtypes.automation

def generate_module(tlib, ofi, pathname):
    known_symbols = {}
    for name in ("comtypes.persist",
                 "comtypes.typeinfo",
                 "comtypes.automation",
                 "comtypes._others",
                 "comtypes",
                 "ctypes.wintypes",
                 "ctypes"):
        try:
            mod = __import__(name)
        except ImportError:
            if name == "comtypes._others":
                continue
            raise
        for submodule in name.split(".")[1:]:
            mod = getattr(mod, submodule)
        for name in mod.__dict__:
            known_symbols[name] = mod.__name__
    p = TypeLibParser(tlib)
    if pathname is None:
        pathname = get_tlib_filename(tlib)
    items = p.parse()

    from .codegenerator import Generator

    gen = Generator(ofi,
                    known_symbols=known_symbols,
                    )

    gen.generate_code(list(items.values()), filename=pathname)

# -eof-
