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

#include "config.h"

#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <ctype.h>
#include <time.h>

#include "widl.h"
#include "windef.h"
#include "winbase.h"

#include "typelib.h"
#include "typelib_struct.h"
#include "utils.h"
#include "header.h"
#include "typetree.h"

static const GUID sltg_library_guid = { 0x204ff,0,0,{ 0xc0,0,0,0,0,0,0,0x46 } };

struct sltg_data
{
    int size, allocated;
    char *data;
};

struct sltg_library
{
    short name;
    char *helpstring;
    char *helpfile;
    int helpcontext;
    int syskind;
    LCID lcid;
    int libflags;
    int version;
    GUID uuid;
};

struct sltg_block
{
    int length;
    int index_string;
    void *data;
};

struct sltg_typelib
{
    typelib_t *typelib;
    struct sltg_data index;
    struct sltg_data name_table;
    struct sltg_library library;
    struct sltg_block *blocks;
    int block_count;
    int first_block;
    short typeinfo_count;
    int typeinfo_size;
    struct sltg_block *typeinfo;
};

struct sltg_hrefmap
{
    int href_count;
    int *href;
};

#pragma pack(push,1)
struct sltg_typeinfo_header
{
    short magic;
    int href_offset;
    int res06;
    int member_offset;
    int res0e;
    int version;
    int res16;
    struct
    {
        unsigned unknown1  : 3;
        unsigned flags     : 13;
        unsigned unknown2  : 8;
        unsigned typekind  : 8;
    } misc;
    int res1e;
};

struct sltg_member_header
{
    short res00;
    short res02;
    char res04;
    int extra;
};

struct sltg_variable
{
    char magic; /* 0x0a */
    char flags;
    short next;
    short name;
    short byte_offs; /* pos in struct, or offset to const type or const data (if flags & 0x08) */
    short type; /* if flags & 0x02 this is the type, else offset to type */
    int memid;
    short helpcontext;
    short helpstring;
    short varflags; /* only present if magic & 0x20 */
};

struct sltg_tail
{
    short cFuncs;
    short cVars;
    short cImplTypes;
    short res06; /* always 0000 */
    short funcs_off; /* offset to functions (starting from the member header) */
    short vars_off; /* offset to vars (starting from the member header) */
    short impls_off; /* offset to implemented types (starting from the member header) */
    short funcs_bytes; /* bytes used by function data */
    short vars_bytes; /* bytes used by var data */
    short impls_bytes; /* bytes used by implemented type data */
    short tdescalias_vt; /* for TKIND_ALIAS */
    short res16; /* always ffff */
    short res18; /* always 0000 */
    short res1a; /* always 0000 */
    short simple_alias; /* tdescalias_vt is a vt rather than an offset? */
    short res1e; /* always 0000 */
    short cbSizeInstance;
    short cbAlignment;
    short res24;
    short res26;
    short cbSizeVft;
    short res2a; /* always ffff */
    short res2c; /* always ffff */
    short res2e; /* always ffff */
    short res30; /* always ffff */
    short res32; /* unknown */
    short type_bytes; /* bytes used by type descriptions */
};

struct sltg_hrefinfo
{
    char magic; /* 0xdf */
    char res01; /* 0x00 */
    char res02[0x42]; /* 0xff... */
    int number; /* this is 8 times the number of refs */
    /* Now we have number bytes (8 for each ref) of SLTG_UnknownRefInfo */

    short res50;/* 0xffff */
    char res52; /* 0x01 */
    int res53;  /* 0x00000000 */
    /*    Now we have number/8 SLTG_Names (first WORD is no of bytes in the ascii
     *    string).  Strings look like "*\Rxxxx*#n".  If xxxx == ffff then the
     *    ref refers to the nth type listed in this library (0 based).  Else
     *    the xxxx (which maybe fewer than 4 digits) is the offset into the name
     *    table to a string "*\G{<guid>}#1.0#0#C:\WINNT\System32\stdole32.tlb#"
     *    The guid is the typelib guid; the ref again refers to the nth type of
     *    the imported typelib.
     */

    char resxx; /* 0xdf */
};

struct sltg_function
{
    char magic; /* 0x4c, 0xcb or 0x8b with optional SLTG_FUNCTION_FLAGS_PRESENT flag */
    char flags; /* high nibble is INVOKE_KIND, low nibble = 2 */
    short next; /* byte offset from beginning of group to next fn */
    short name; /* Offset within name table to name */
    int dispid; /* dispid */
    short helpcontext; /* helpcontext (again 1 is special) */
    short helpstring; /* helpstring offset to offset */
    short arg_off; /* offset to args from start of block */
    char nacc; /* lowest 3bits are CALLCONV, rest are no of args */
    char retnextopt; /* if 0x80 bit set ret type follows else next WORD
                        is offset to ret type. No of optional args is
                        middle 6 bits */
    short rettype; /* return type VT_?? or offset to ret type */
    short vtblpos; /* position in vtbl? */
    short funcflags; /* present if magic & 0x20 */
/* Param list starts, repeat next two as required */
#if 0
    WORD name; /* offset to 2nd letter of name */
    WORD+ type; /* VT_ of param */
#endif
};

struct sltg_impl_info
{
    short res00;
    short next;
    short res04;
    char impltypeflags;
    char res07;
    short res08;
    short ref;
    short res0c;
    short res0e;
    short res10;
    short res12;
    short pos;
};

#pragma pack(pop)

static void add_structure_typeinfo(struct sltg_typelib *typelib, type_t *type);
static void add_interface_typeinfo(struct sltg_typelib *typelib, type_t *type);
static void add_enum_typeinfo(struct sltg_typelib *typelib, type_t *type);
static void add_union_typeinfo(struct sltg_typelib *typelib, type_t *type);
static void add_coclass_typeinfo(struct sltg_typelib *typelib, type_t *type);

static void init_sltg_data(struct sltg_data *data)
{
    data->size = 0;
    data->allocated = 0;
    data->data = NULL;
}

static int add_index(struct sltg_data *index, const char *name)
{
    int name_offset = index->size;
    int new_size = index->size + strlen(name) + 1;

    chat("add_index: name_offset %d, \"%s\"\n", name_offset, name);

    if (new_size > index->allocated)
    {
        index->allocated = index->allocated ? max(index->allocated * 2, new_size) : new_size;
        index->data = xrealloc(index->data, index->allocated);
    }

    strcpy(index->data + index->size, name);
    index->size = new_size;

    return name_offset;
}

static void init_index(struct sltg_data *index)
{
    static const char compobj[] = { 1,'C','o','m','p','O','b','j',0 };

    init_sltg_data(index);

    add_index(index, compobj);
}

static int add_name(struct sltg_data *name_table, const char *name)
{
    int name_offset = name_table->size;
    int new_size = name_table->size + strlen(name) + 1 + 8;
    int aligned_size;

    chat("add_name: %s\n", name);

    aligned_size = (new_size + 0x1f) & ~0x1f;
    if (aligned_size - new_size < 4)
        new_size = aligned_size;
    else
        new_size = (new_size + 1) & ~1;

    if (new_size > name_table->allocated)
    {
        name_table->allocated = name_table->allocated ? max(name_table->allocated * 2, new_size) : new_size;
        name_table->data = xrealloc(name_table->data, name_table->allocated);
    }

    memset(name_table->data + name_table->size, 0xff, 8);
    strcpy(name_table->data + name_table->size + 8, name);
    name_table->size = new_size;
    name_table->data[name_table->size - 1] = 0; /* clear alignment */

    return name_offset;
}

static void init_name_table(struct sltg_data *name_table)
{
    init_sltg_data(name_table);
}

static void init_library(struct sltg_typelib *sltg)
{
    const attr_t *attr;

    sltg->library.name = add_name(&sltg->name_table, sltg->typelib->name);
    sltg->library.helpstring = NULL;
    sltg->library.helpcontext = 0;
    sltg->library.syskind = SYS_WIN32;
    sltg->library.lcid = 0x0409;
    sltg->library.libflags = 0;
    sltg->library.version = 0;
    sltg->library.helpfile = NULL;
    memset(&sltg->library.uuid, 0, sizeof(sltg->library.uuid));

    if (!sltg->typelib->attrs) return;

    LIST_FOR_EACH_ENTRY(attr, sltg->typelib->attrs, const attr_t, entry)
    {
        const expr_t *expr;

        switch (attr->type)
        {
        case ATTR_VERSION:
        {
            const version_t *version = attr->u.pval;
            unsigned short major = version ? version->major : 0, minor = version ? version->minor : 0;

            sltg->library.version = (minor << 16) | major;
            break;
        }
        case ATTR_HELPSTRING:
            sltg->library.helpstring = attr->u.pval;
            break;
        case ATTR_HELPFILE:
            sltg->library.helpfile = attr->u.pval;
            break;
        case ATTR_UUID:
            sltg->library.uuid = *(GUID *)attr->u.pval;
            break;
        case ATTR_HELPCONTEXT:
            expr = attr->u.pval;
            sltg->library.helpcontext = expr->cval;
            break;
        case ATTR_LIBLCID:
            expr = attr->u.pval;
            sltg->library.lcid = expr->cval;
            break;
        case ATTR_CONTROL:
            sltg->library.libflags |= 0x02; /* LIBFLAG_FCONTROL */
            break;
        case ATTR_HIDDEN:
            sltg->library.libflags |= 0x04; /* LIBFLAG_FHIDDEN */
            break;
        case ATTR_RESTRICTED:
            sltg->library.libflags |= 0x01; /* LIBFLAG_FRESTRICTED */
            break;
        default:
            break;
        }
    }
}

static void add_block_index(struct sltg_typelib *sltg, void *data, int length, int index)
{
    sltg->blocks = xrealloc(sltg->blocks, sizeof(sltg->blocks[0]) * (sltg->block_count + 1));
    sltg->blocks[sltg->block_count].length = length;
    sltg->blocks[sltg->block_count].data = data;
    sltg->blocks[sltg->block_count].index_string = index;
    sltg->block_count++;
}

static void add_block(struct sltg_typelib *sltg, void *data, int size, const char *name)
{
    struct sltg_block *block = xmalloc(sizeof(*block));
    int index;

    chat("add_block: %p,%d,\"%s\"\n", data, size, name);

    index = add_index(&sltg->index, name);

    add_block_index(sltg, data, size, index);
}

static void *create_library_block(struct sltg_typelib *typelib, int *size, int *index)
{
    void *block;
    short *p;

    *size = sizeof(short) * 9 + sizeof(int) * 3 + sizeof(GUID);
    if (typelib->library.helpstring) *size += strlen(typelib->library.helpstring);
    if (typelib->library.helpfile) *size += strlen(typelib->library.helpfile);

    block = xmalloc(*size);
    p = block;
    *p++ = 0x51cc; /* magic */
    *p++ = 3; /* res02 */
    *p++ = typelib->library.name;
    *p++ = 0xffff; /* res06 */
    if (typelib->library.helpstring)
    {
        *p++ = strlen(typelib->library.helpstring);
        strcpy((char *)p, typelib->library.helpstring);
        p = (short *)((char *)p + strlen(typelib->library.helpstring));
    }
    else
        *p++ = 0xffff;
    if (typelib->library.helpfile)
    {
        *p++ = strlen(typelib->library.helpfile);
        strcpy((char *)p, typelib->library.helpfile);
        p = (short *)((char *)p + strlen(typelib->library.helpfile));
    }
    else
        *p++ = 0xffff;
    *(int *)p = typelib->library.helpcontext;
    p += 2;
    *p++ = typelib->library.syskind;
    *p++ = typelib->library.lcid;
    *(int *)p = 0; /* res12 */
    p += 2;
    *p++ = typelib->library.libflags;
    *(int *)p = typelib->library.version;
    p += 2;
    *(GUID *)p = typelib->library.uuid;

    *index = add_index(&typelib->index, "dir");

    return block;
}

static const char *new_index_name(void)
{
    static char name[11] = "AAAAAAAAAA";
    static int pos = 0;
    char *new_name;

    if (name[pos] == 'Z')
    {
        pos++;
        if (pos > 9)
            error("too many index names\n");
    }

    name[pos]++;

    new_name = xmalloc(sizeof(name));
    strcpy(new_name, name);
    return new_name;
}

static void sltg_add_typeinfo(struct sltg_typelib *sltg, void *data, int length, const char *name)
{
    chat("sltg_add_typeinfo: %p,%d,%s\n", data, length, name);

    sltg->typeinfo = xrealloc(sltg->typeinfo, sizeof(sltg->typeinfo[0]) * (sltg->typeinfo_count + 1));
    sltg->typeinfo[sltg->typeinfo_count].length = length;
    sltg->typeinfo[sltg->typeinfo_count].data = data;
    sltg->typeinfo[sltg->typeinfo_count].index_string = 0;
    sltg->typeinfo_count++;
    sltg->typeinfo_size += length;
}

static void append_data(struct sltg_data *block, const void *data, int size)
{
    int new_size = block->size + size;

    if (new_size > block->allocated)
    {
        block->allocated = max(block->allocated * 2, new_size);
        block->data = xrealloc(block->data, block->allocated);
    }

    memcpy(block->data + block->size, data, size);
    block->size = new_size;
}

static void add_module_typeinfo(struct sltg_typelib *typelib, type_t *type)
{
    error("add_module_typeinfo: %s not implemented\n", type->name);
}

static const char *add_typeinfo_block(struct sltg_typelib *typelib, const type_t *type, short kind)
{
    struct sltg_data block;
    const char *index_name, *other_name;
    short val;
    void *p;
    int helpcontext = 0;
    GUID guid = { 0 };
    const expr_t *expr;

    index_name = new_index_name();
    other_name = new_index_name();

    expr = get_attrp(type->attrs, ATTR_HELPCONTEXT);
    if (expr) helpcontext = expr->cval;

    p = get_attrp(type->attrs, ATTR_UUID);
    if (p) guid = *(GUID *)p;

    init_sltg_data(&block);

    val = strlen(index_name);
    append_data(&block, &val, sizeof(val));
    append_data(&block, index_name, val);
    val = strlen(other_name);
    append_data(&block, &val, sizeof(val));
    append_data(&block, other_name, val);
    val = -1; /* res1a */
    append_data(&block, &val, sizeof(val));
    val = add_name(&typelib->name_table, type->name); /* name offset */
    append_data(&block, &val, sizeof(val));
    val = 0; /* FIXME: helpstring */
    append_data(&block, &val, sizeof(val));
    val = -1; /* res20 */
    append_data(&block, &val, sizeof(val));
    append_data(&block, &helpcontext, sizeof(helpcontext));
    val = -1; /* res26 */
    append_data(&block, &val, sizeof(val));
    append_data(&block, &guid, sizeof(guid));
    append_data(&block, &kind, sizeof(kind));

    sltg_add_typeinfo(typelib, block.data, block.size, index_name);

    return index_name;
}

static void init_typeinfo(struct sltg_typeinfo_header *ti, const type_t *type, short kind,
                          const struct sltg_hrefmap *hrefmap)
{
    unsigned short major, minor;

    get_version( type->attrs, &major, &minor );

    ti->magic = 0x0501;
    ti->href_offset = -1;
    ti->res06 = -1;
    ti->member_offset = sizeof(*ti);
    ti->res0e = -1;
    ti->version = minor << 16 | major;
    ti->res16 = 0xfffe0000;
    ti->misc.unknown1 = 0x02;
    ti->misc.flags = 0; /* FIXME */
    ti->misc.unknown2 = 0x02;
    ti->misc.typekind = kind;
    ti->res1e = 0;

    if (hrefmap->href_count)
    {
        char name[64];
        int i, hrefinfo_size;

        hrefinfo_size = sizeof(struct sltg_hrefinfo);

        for (i = 0; i < hrefmap->href_count; i++)
        {
            snprintf(name, sizeof(name), "*\\Rffff*#%x", hrefmap->href[i]);
            hrefinfo_size += 8 + 2 + strlen(name);
        }

        ti->href_offset = ti->member_offset;
        ti->member_offset += hrefinfo_size;
    }
}

static void write_hrefmap(struct sltg_data *data, const struct sltg_hrefmap *hrefmap)
{
    struct sltg_hrefinfo hrefinfo;
    char name[64];
    int i;

    if (!hrefmap->href_count) return;

    hrefinfo.magic = 0xdf;
    hrefinfo.res01 = 0;
    memset(hrefinfo.res02, 0xff, sizeof(hrefinfo.res02));
    hrefinfo.number = hrefmap->href_count * 8;
    hrefinfo.res50 = -1;
    hrefinfo.res52 = 1;
    hrefinfo.res53 = 0;
    hrefinfo.resxx = 0xdf;

    append_data(data, &hrefinfo, offsetof(struct sltg_hrefinfo, res50));

    for (i = 0; i < hrefmap->href_count; i++)
        append_data(data, "\xff\xff\xff\xff\xff\xff\xff\xff", 8);

    append_data(data, &hrefinfo.res50, 7);

    for (i = 0; i < hrefmap->href_count; i++)
    {
        short len;

        snprintf(name, sizeof(name), "*\\Rffff*#%x", hrefmap->href[i]);
        len = strlen(name);

        append_data(data, &len, sizeof(len));
        append_data(data, name, len);
    }

    append_data(data, &hrefinfo.resxx, sizeof(hrefinfo.resxx));
}

static void dump_var_desc(const char *data, int size)
{
    const unsigned char *p = (const unsigned char *)data;
    int i;

    if (!(debuglevel & (DEBUGLEVEL_TRACE | DEBUGLEVEL_CHAT))) return;

    chat("dump_var_desc: size %d bytes\n", size);

    for (i = 0; i < size; i++)
        fprintf(stderr, " %02x", *p++);

    fprintf(stderr, "\n");
}

static int get_element_size(type_t *type)
{
    int vt = get_type_vt(type);

    switch (vt)
    {
    case VT_I1:
    case VT_UI1:
        return 1;

    case VT_UI2:
    case VT_I2:
    case VT_BOOL:
        return 2;

    case VT_INT:
    case VT_UINT:
    case VT_I4:
    case VT_UI4:
    case VT_R4:
    case VT_ERROR:
    case VT_HRESULT:
        return 4;

    case VT_R8:
    case VT_I8:
    case VT_UI8:
    case VT_CY:
    case VT_DATE:
        return 8;

    case VT_DECIMAL:
        return 16;

    case VT_PTR:
    case VT_UNKNOWN:
    case VT_DISPATCH:
    case VT_BSTR:
    case VT_LPSTR:
    case VT_LPWSTR:
        return 4;

    case VT_VOID:
        return 0;

    case VT_VARIANT:
        return 16;

    case VT_USERDEFINED:
        return 0;

    default:
        error("get_element_size: unrecognized vt %d\n", vt);
        break;
    }

    return 0;
}

static int local_href(struct sltg_hrefmap *hrefmap, int typelib_href)
{
    int i, href = -1;

    for (i = 0; i < hrefmap->href_count; i++)
    {
        if (hrefmap->href[i] == typelib_href)
        {
            href = i;
            break;
        }
    }

    if (href == -1)
    {
        href = hrefmap->href_count;

        if (hrefmap->href)
            hrefmap->href = xrealloc(hrefmap->href, sizeof(*hrefmap->href) * (hrefmap->href_count + 1));
        else
            hrefmap->href = xmalloc(sizeof(*hrefmap->href));

        hrefmap->href[hrefmap->href_count] = typelib_href;
        hrefmap->href_count++;
    }

    chat("typelib href %d mapped to local href %d\n", typelib_href, href);

    return href << 2;
}

static short write_var_desc(struct sltg_typelib *typelib, struct sltg_data *data, type_t *type, short param_flags,
                            short flags, short base_offset, int *size_instance, struct sltg_hrefmap *hrefmap)
{
    short vt, vt_flags, desc_offset;

    chat("write_var_desc: type %p, type->name %s\n",
         type, type->name ? type->name : "NULL");

    if (is_array(type) && !type_array_is_decl_as_ptr(type))
    {
        int num_dims, elements, array_start, size, array_size;
        type_t *atype;
        struct
        {
            short cDims;
            short fFeatures;
            int cbElements;
            int cLocks;
            int pvData;
            int bound[2];
        } *array;
        int *bound;
        short vt_off[2];

        elements = 1;
        num_dims = 0;

        atype = type;

        while (is_array(atype) && !type_array_is_decl_as_ptr(atype))
        {
            num_dims++;
            elements *= type_array_get_dim(atype);

            atype = type_array_get_element_type(atype);
        }

        chat("write_var_desc: VT_CARRAY: %d dimensions, %d elements\n", num_dims, elements);

        array_start = data->size;

        size = sizeof(*array) + (num_dims - 1) * 8 /* sizeof(SAFEARRAYBOUND) */;
        array = xmalloc(size);

        array->cDims = num_dims;
        array->fFeatures = 0x0004; /* FADF_EMBEDDED */
        array->cbElements = get_element_size(atype);
        array->cLocks = 0;
        array->pvData = 0;

        bound = array->bound;

        array_size = array->cbElements;
        atype = type;

        while (is_array(atype) && !type_array_is_decl_as_ptr(atype))
        {
            bound[0] = type_array_get_dim(atype);
            array_size *= bound[0];
            bound[1] = 0;
            bound += 2;

            atype = type_array_get_element_type(atype);
        }

        if (size_instance)
        {
            *size_instance += array_size;
            size_instance = NULL; /* don't account for element size */
        }

        append_data(data, array, size);

        desc_offset = data->size;

        vt_off[0] = VT_CARRAY;
        vt_off[1] = array_start + base_offset;
        append_data(data, vt_off, sizeof(vt_off));

        /* fall through to write array element description */
        type = atype;
    }
    else
        desc_offset = data->size;

    vt = get_type_vt(type);

    if (vt == VT_PTR)
    {
        type_t *ref = is_ptr(type) ? type_pointer_get_ref_type(type) : type_array_get_element_type(type);

        if (is_ptr(ref))
        {
            chat("write_var_desc: vt VT_PTR | 0x0400 | %04x\n",  param_flags);
            vt = VT_PTR | 0x0400 | param_flags;
            append_data(data, &vt, sizeof(vt));
            write_var_desc(typelib, data, ref, 0, 0, base_offset, size_instance, hrefmap);
        }
        else
            write_var_desc(typelib, data, ref, param_flags, 0x0e00, base_offset, size_instance, hrefmap);
        return desc_offset;
    }

    chat("write_var_desc: vt %d, flags %04x\n", vt, flags);

    vt_flags = vt | flags | param_flags;
    append_data(data, &vt_flags, sizeof(vt_flags));

    if (vt == VT_USERDEFINED)
    {
        short href;

        while (type->typelib_idx < 0 && type_is_alias(type))
            type = type_alias_get_aliasee_type(type);

        chat("write_var_desc: VT_USERDEFINED, type %p, name %s, real type %d, href %d\n",
             type, type->name, type_get_type(type), type->typelib_idx);

        if (type->typelib_idx == -1)
        {
            chat("write_var_desc: trying to ref not added type\n");

            switch (type_get_type(type))
            {
            case TYPE_STRUCT:
                add_structure_typeinfo(typelib, type);
                break;
            case TYPE_INTERFACE:
                add_interface_typeinfo(typelib, type);
                break;
            case TYPE_ENUM:
                add_enum_typeinfo(typelib, type);
                break;
            case TYPE_UNION:
                add_union_typeinfo(typelib, type);
                break;
            case TYPE_COCLASS:
                add_coclass_typeinfo(typelib, type);
                break;
            default:
                error("write_var_desc: VT_USERDEFINED - unhandled type %d\n",
                      type_get_type(type));
            }
        }

        if (type->typelib_idx == -1)
            error("write_var_desc: trying to ref not added type\n");

        href = local_href(hrefmap, type->typelib_idx);
        chat("write_var_desc: VT_USERDEFINED, local href %d\n", href);

        append_data(data, &href, sizeof(href));
    }

    if (size_instance)
        *size_instance += get_element_size(type);

    return desc_offset;
}

static void init_sltg_tail(struct sltg_tail *tail)
{
    tail->cFuncs = 0;
    tail->cVars = 0;
    tail->cImplTypes = 0;
    tail->res06 = 0;
    tail->funcs_off = -1;
    tail->vars_off = -1;
    tail->impls_off = -1;
    tail->funcs_bytes = -1;
    tail->vars_bytes = -1;
    tail->impls_bytes = -1;
    tail->tdescalias_vt = -1;
    tail->res16 = -1;
    tail->res18 = 0;
    tail->res1a = 0;
    tail->simple_alias = 0;
    tail->res1e = 0;
    tail->cbSizeInstance = 0;
    tail->cbAlignment = 4;
    tail->res24 = -1;
    tail->res26 = -1;
    tail->cbSizeVft = 0;
    tail->res2a = -1;
    tail->res2c = -1;
    tail->res2e = -1;
    tail->res30 = -1;
    tail->res32 = 0;
    tail->type_bytes = 0;
}

static void add_structure_typeinfo(struct sltg_typelib *typelib, type_t *type)
{
    struct sltg_data data, *var_data = NULL;
    struct sltg_hrefmap hrefmap;
    const char *index_name;
    struct sltg_typeinfo_header ti;
    struct sltg_member_header member;
    struct sltg_tail tail;
    int member_offset, var_count = 0, var_data_size = 0, size_instance = 0;
    short *type_desc_offset = NULL;

    if (type->typelib_idx != -1) return;

    chat("add_structure_typeinfo: type %p, type->name %s\n", type, type->name);

    type->typelib_idx = typelib->block_count;

    hrefmap.href_count = 0;
    hrefmap.href = NULL;

    if (type_struct_get_fields(type))
    {
        int i = 0;
        var_t *var;

        var_count = list_count(type_struct_get_fields(type));

        var_data = xmalloc(var_count * sizeof(*var_data));
        type_desc_offset = xmalloc(var_count * sizeof(*type_desc_offset));

        LIST_FOR_EACH_ENTRY(var, type_struct_get_fields(type), var_t, entry)
        {
            short base_offset;

            chat("add_structure_typeinfo: var %p (%s), type %p (%s)\n",
                 var, var->name, var->declspec.type, var->declspec.type->name);

            init_sltg_data(&var_data[i]);

            base_offset = var_data_size + (i + 1) * sizeof(struct sltg_variable);
            type_desc_offset[i] = write_var_desc(typelib, &var_data[i], var->declspec.type, 0, 0,
                                                 base_offset, &size_instance, &hrefmap);
            dump_var_desc(var_data[i].data, var_data[i].size);

            if (var_data[i].size > sizeof(short))
                var_data_size += var_data[i].size;
            i++;
        }
    }

    init_sltg_data(&data);

    index_name = add_typeinfo_block(typelib, type, TKIND_RECORD);

    init_typeinfo(&ti, type, TKIND_RECORD, &hrefmap);
    append_data(&data, &ti, sizeof(ti));

    write_hrefmap(&data, &hrefmap);

    member_offset = data.size;

    member.res00 = 0x0001;
    member.res02 = 0xffff;
    member.res04 = 0x01;
    member.extra = var_data_size + var_count * sizeof(struct sltg_variable);
    append_data(&data, &member, sizeof(member));

    var_data_size = 0;

    if (type_struct_get_fields(type))
    {
        int i = 0;
        short next = member_offset;
        var_t *var;

        LIST_FOR_EACH_ENTRY(var, type_struct_get_fields(type), var_t, entry)
        {
            struct sltg_variable variable;

            next += sizeof(variable);

            variable.magic = 0x2a; /* always write flags to simplify calculations */
            variable.name = add_name(&typelib->name_table, var->name);
            variable.byte_offs = 0;
            if (var_data[i].size > sizeof(short))
            {
                variable.flags = 0;
                var_data_size = next - member_offset + type_desc_offset[i];
                variable.type = var_data_size;
                next += var_data[i].size;
            }
            else
            {
                variable.flags = 0x02;
                variable.type = *(short *)var_data[i].data;
            }
            variable.next = i < var_count - 1 ? next - member_offset : -1;
            variable.memid = 0x40000000 + i;
            variable.helpcontext = -2; /* 0xfffe */
            variable.helpstring = -1;
            variable.varflags = 0;

            append_data(&data, &variable, sizeof(variable));
            if (var_data[i].size > sizeof(short))
                append_data(&data, var_data[i].data, var_data[i].size);

            i++;
        }
    }

    init_sltg_tail(&tail);
    tail.cVars = var_count;
    tail.vars_off = 0;
    tail.vars_bytes = var_data_size;
    tail.cbSizeInstance = size_instance;
    tail.type_bytes = data.size - member_offset - sizeof(member);
    append_data(&data, &tail, sizeof(tail));

    add_block(typelib, data.data, data.size, index_name);
}

static importinfo_t *find_importinfo(typelib_t *typelib, const char *name)
{
    importlib_t *importlib;

    LIST_FOR_EACH_ENTRY(importlib, &typelib->importlibs, importlib_t, entry)
    {
        int i;

        for (i = 0; i < importlib->ntypeinfos; i++)
        {
            if (!strcmp(name, importlib->importinfos[i].name))
            {
                chat("Found %s in importlib list\n", name);
                return &importlib->importinfos[i];
            }
        }
    }

    return NULL;
}

static int get_func_flags(const var_t *func, int *dispid, int *invokekind, int *helpcontext, const char **helpstring)
{
    const attr_t *attr;
    int flags;

    *invokekind = 1 /* INVOKE_FUNC */;
    *helpcontext = -2;
    *helpstring = NULL;

    if (!func->attrs) return 0;

    flags = 0;

    LIST_FOR_EACH_ENTRY(attr, func->attrs, const attr_t, entry)
    {
        expr_t *expr = attr->u.pval;
        switch(attr->type)
        {
        case ATTR_BINDABLE:
            flags |= 0x4; /* FUNCFLAG_FBINDABLE */
            break;
        case ATTR_DEFAULTBIND:
            flags |= 0x20; /* FUNCFLAG_FDEFAULTBIND */
            break;
        case ATTR_DEFAULTCOLLELEM:
            flags |= 0x100; /* FUNCFLAG_FDEFAULTCOLLELEM */
            break;
        case ATTR_DISPLAYBIND:
            flags |= 0x10; /* FUNCFLAG_FDISPLAYBIND */
            break;
        case ATTR_HELPCONTEXT:
            *helpcontext = expr->u.integer.value;
            break;
        case ATTR_HELPSTRING:
            *helpstring = attr->u.pval;
            break;
        case ATTR_HIDDEN:
            flags |= 0x40; /* FUNCFLAG_FHIDDEN */
            break;
        case ATTR_ID:
            *dispid = expr->cval;
            break;
        case ATTR_IMMEDIATEBIND:
            flags |= 0x1000; /* FUNCFLAG_FIMMEDIATEBIND */
            break;
        case ATTR_NONBROWSABLE:
            flags |= 0x400; /* FUNCFLAG_FNONBROWSABLE */
            break;
        case ATTR_PROPGET:
            *invokekind = 0x2; /* INVOKE_PROPERTYGET */
            break;
        case ATTR_PROPPUT:
            *invokekind = 0x4; /* INVOKE_PROPERTYPUT */
            break;
        case ATTR_PROPPUTREF:
            *invokekind = 0x8; /* INVOKE_PROPERTYPUTREF */
            break;
        /* FIXME: FUNCFLAG_FREPLACEABLE */
        case ATTR_REQUESTEDIT:
            flags |= 0x8; /* FUNCFLAG_FREQUESTEDIT */
            break;
        case ATTR_RESTRICTED:
            flags |= 0x1; /* FUNCFLAG_FRESTRICTED */
            break;
        case ATTR_SOURCE:
            flags |= 0x2; /* FUNCFLAG_FSOURCE */
            break;
        case ATTR_UIDEFAULT:
            flags |= 0x200; /* FUNCFLAG_FUIDEFAULT */
            break;
        case ATTR_USESGETLASTERROR:
            flags |= 0x80; /* FUNCFLAG_FUSESGETLASTERROR */
            break;
        default:
            break;
        }
    }

    return flags;
}

static int get_param_flags(const var_t *param)
{
    const attr_t *attr;
    int flags, in, out;

    if (!param->attrs) return 0;

    flags = 0;
    in = out = 0;

    LIST_FOR_EACH_ENTRY(attr, param->attrs, const attr_t, entry)
    {
        switch(attr->type)
        {
        case ATTR_IN:
            in++;
            break;
        case ATTR_OUT:
            out++;
            break;
        case ATTR_PARAMLCID:
            flags |= 0x2000;
            break;
        case ATTR_RETVAL:
            flags |= 0x80;
            break;
        default:
            chat("unhandled param attr %d\n", attr->type);
            break;
        }
    }

    if (out)
        flags |= in ? 0x8000 : 0x4000;
    else if (!in)
        flags |= 0xc000;

    return flags;
}


static int add_func_desc(struct sltg_typelib *typelib, struct sltg_data *data, var_t *func,
                         int idx, int dispid, short base_offset, struct sltg_hrefmap *hrefmap)
{
    struct sltg_data ret_data, *arg_data;
    int arg_count = 0, arg_data_size, optional = 0, old_size;
    int funcflags = 0, invokekind = 1 /* INVOKE_FUNC */, helpcontext;
    const char *helpstring;
    const var_t *arg;
    short ret_desc_offset, *arg_desc_offset, arg_offset;
    struct sltg_function func_desc;

    chat("add_func_desc: %s, idx %#x, dispid %#x\n", func->name, idx, dispid);

    old_size = data->size;

    init_sltg_data(&ret_data);
    ret_desc_offset = write_var_desc(typelib, &ret_data, type_function_get_rettype(func->declspec.type),
                                     0, 0, base_offset, NULL, hrefmap);
    dump_var_desc(ret_data.data, ret_data.size);

    arg_data_size = 0;
    arg_offset = base_offset + sizeof(struct sltg_function);

    if (ret_data.size > sizeof(short))
    {
        arg_data_size += ret_data.size;
        arg_offset += ret_data.size;
    }

    if (type_function_get_args(func->declspec.type))
    {
        int i = 0;

        arg_count = list_count(type_function_get_args(func->declspec.type));

        arg_data = xmalloc(arg_count * sizeof(*arg_data));
        arg_desc_offset = xmalloc(arg_count * sizeof(*arg_desc_offset));

        arg_offset += arg_count * 2 * sizeof(short);

        LIST_FOR_EACH_ENTRY(arg, type_function_get_args(func->declspec.type), const var_t, entry)
        {
            const attr_t *attr;
            short param_flags = get_param_flags(arg);

            chat("add_func_desc: arg[%d] %p (%s), type %p (%s)\n",
                 i, arg, arg->name, arg->declspec.type, arg->declspec.type->name);

            init_sltg_data(&arg_data[i]);


            arg_desc_offset[i] = write_var_desc(typelib, &arg_data[i], arg->declspec.type, param_flags, 0,
                                                arg_offset, NULL, hrefmap);
            dump_var_desc(arg_data[i].data, arg_data[i].size);

            if (arg_data[i].size > sizeof(short))
            {
                arg_data_size += arg_data[i].size;
                arg_offset += arg_data[i].size;
            }

            i++;

            if (!arg->attrs) continue;

            LIST_FOR_EACH_ENTRY(attr, arg->attrs, const attr_t, entry)
            {
                if (attr->type == ATTR_OPTIONAL)
                    optional++;
            }
        }
    }

    funcflags = get_func_flags(func, &dispid, &invokekind, &helpcontext, &helpstring);

    if (base_offset != -1)
        chat("add_func_desc: flags %#x, dispid %#x, invokekind %d, helpcontext %#x, helpstring %s\n",
             funcflags, dispid, invokekind, helpcontext, helpstring);

    func_desc.magic = 0x6c; /* always write flags to simplify calculations */
    func_desc.flags = (invokekind << 4) | 0x02;
    if (idx & 0x80000000)
    {
        func_desc.next = -1;
        idx &= ~0x80000000;
    }
    else
        func_desc.next = base_offset + sizeof(func_desc) + arg_data_size + arg_count * 2 * sizeof(short);
    func_desc.name = base_offset != -1 ? add_name(&typelib->name_table, func->name) : -1;
    func_desc.dispid = dispid;
    func_desc.helpcontext = helpcontext;
    func_desc.helpstring = (helpstring && base_offset != -1) ? add_name(&typelib->name_table, helpstring) : -1;
    func_desc.arg_off = arg_count ? base_offset + sizeof(func_desc) : -1;
    func_desc.nacc = (arg_count << 3) | 4 /* CC_STDCALL */;
    func_desc.retnextopt = (optional << 1);
    if (ret_data.size > sizeof(short))
    {
        func_desc.rettype = base_offset + sizeof(func_desc) + ret_desc_offset;
        if (arg_count)
            func_desc.arg_off += ret_data.size;
    }
    else
    {
        func_desc.retnextopt |= 0x80;
        func_desc.rettype = *(short *)ret_data.data;
    }
    func_desc.vtblpos = idx * pointer_size;
    func_desc.funcflags = funcflags;

    append_data(data, &func_desc, sizeof(func_desc));

    arg_offset = base_offset + sizeof(struct sltg_function);

    if (ret_data.size > sizeof(short))
    {
        append_data(data, ret_data.data, ret_data.size);
        func_desc.arg_off += ret_data.size;
        arg_offset += ret_data.size;
    }

    if (arg_count)
    {
        int i = 0;

        arg_offset += arg_count * 2 * sizeof(short);

        LIST_FOR_EACH_ENTRY(arg, type_function_get_args(func->declspec.type), const var_t, entry)
        {
            short name, type_offset;

            name = base_offset != -1 ? add_name(&typelib->name_table, arg->name) : -1;

            if (arg_data[i].size > sizeof(short))
            {
                type_offset = (arg_offset + arg_desc_offset[i]);
                arg_offset += arg_data[i].size;
            }
            else
            {
                name |= 1;
                type_offset = *(short *)arg_data[i].data;
            }

            append_data(data, &name, sizeof(name));
            append_data(data, &type_offset, sizeof(type_offset));

            if (base_offset != -1)
                chat("add_func_desc: arg[%d] - name %s (%#x), type_offset %#x\n",
                     i, arg->name, name, type_offset);

            i++;
        }

        for (i = 0; i < arg_count; i++)
        {
            if (arg_data[i].size > sizeof(short))
                append_data(data, arg_data[i].data, arg_data[i].size);
        }
    }

    return data->size - old_size;
}

static void write_impl_href(struct sltg_data *data, short href)
{
    struct sltg_impl_info impl_info;

    impl_info.res00 = 0x004a;
    impl_info.next = -1;
    impl_info.res04 = -1;
    impl_info.impltypeflags = 0;
    impl_info.res07 = 0x80;
    impl_info.res08 = 0x0012;
    impl_info.ref = href;
    impl_info.res0c = 0x4001;
    impl_info.res0e = -2; /* 0xfffe */
    impl_info.res10 = -1;
    impl_info.res12 = 0x001d;
    impl_info.pos = 0;

    append_data(data, &impl_info, sizeof(impl_info));
}

static void add_interface_typeinfo(struct sltg_typelib *typelib, type_t *iface)
{
    const statement_t *stmt_func;
    importinfo_t *ref_importinfo = NULL;
    short inherit_href = -1;
    struct sltg_data data;
    struct sltg_hrefmap hrefmap;
    const char *index_name;
    struct sltg_typeinfo_header ti;
    struct sltg_member_header member;
    struct sltg_tail tail;
    int member_offset, base_offset, func_data_size, i;
    int func_count, inherited_func_count = 0;
    int dispid, inherit_level = 0;

    if (iface->typelib_idx != -1) return;

    chat("add_interface_typeinfo: type %p, type->name %s\n", iface, iface->name);

    if (!iface->details.iface)
    {
        error("interface %s is referenced but not defined\n", iface->name);
        return;
    }

    if (is_attr(iface->attrs, ATTR_DISPINTERFACE))
    {
        error("support for dispinterface %s is not implemented\n", iface->name);
        return;
    }

    hrefmap.href_count = 0;
    hrefmap.href = NULL;

    if (type_iface_get_inherit(iface))
    {
        type_t *inherit;

        inherit = type_iface_get_inherit(iface);

        chat("add_interface_typeinfo: inheriting from base interface %s\n", inherit->name);

        ref_importinfo = find_importinfo(typelib->typelib, inherit->name);

        if (!ref_importinfo && type_iface_get_inherit(inherit))
            add_interface_typeinfo(typelib, inherit);

        if (ref_importinfo)
            error("support for imported interfaces is not implemented\n");

        inherit_href = local_href(&hrefmap, inherit->typelib_idx);

        while (inherit)
        {
            inherit_level++;
            inherited_func_count += list_count(type_iface_get_stmts(inherit));
            inherit = type_iface_get_inherit(inherit);
        }
    }

    /* check typelib_idx again, it could have been added while resolving the parent interface */
    if (iface->typelib_idx != -1) return;

    iface->typelib_idx = typelib->block_count;

    /* pass 1: calculate function descriptions data size */
    init_sltg_data(&data);

    STATEMENTS_FOR_EACH_FUNC(stmt_func, type_iface_get_stmts(iface))
    {
        add_func_desc(typelib, &data, stmt_func->u.var, -1, -1, -1, &hrefmap);
    }

    func_data_size = data.size;

    /* pass 2: write function descriptions */
    init_sltg_data(&data);

    func_count = list_count(type_iface_get_stmts(iface));

    index_name = add_typeinfo_block(typelib, iface, TKIND_INTERFACE);

    init_typeinfo(&ti, iface, TKIND_INTERFACE, &hrefmap);
    append_data(&data, &ti, sizeof(ti));

    write_hrefmap(&data, &hrefmap);

    member_offset = data.size;
    base_offset = 0;

    member.res00 = 0x0001;
    member.res02 = 0xffff;
    member.res04 = 0x01;
    member.extra = func_data_size;
    if (inherit_href != -1)
    {
        member.extra += sizeof(struct sltg_impl_info);
        base_offset += sizeof(struct sltg_impl_info);
    }
    append_data(&data, &member, sizeof(member));

    if (inherit_href != -1)
        write_impl_href(&data, inherit_href);

    i = 0;
    dispid = 0x60000000 | (inherit_level << 16);

    STATEMENTS_FOR_EACH_FUNC(stmt_func, type_iface_get_stmts(iface))
    {
        int idx = inherited_func_count + i;

        if (i == func_count - 1) idx |= 0x80000000;

        base_offset += add_func_desc(typelib, &data, stmt_func->u.var,
                                     idx, dispid + i, base_offset, &hrefmap);
        i++;
    }

    init_sltg_tail(&tail);

    tail.cFuncs = func_count;
    tail.funcs_off = 0;
    tail.funcs_bytes = func_data_size;
    tail.cbSizeInstance = pointer_size;
    tail.cbAlignment = pointer_size;
    tail.cbSizeVft = (inherited_func_count + func_count) * pointer_size;
    tail.type_bytes = data.size - member_offset - sizeof(member);
    tail.res24 = 0;
    tail.res26 = 0;
    if (inherit_href != -1)
    {
        tail.cImplTypes++;
        tail.impls_off = 0;
        tail.impls_bytes = 0;

        tail.funcs_off += sizeof(struct sltg_impl_info);
    }
    append_data(&data, &tail, sizeof(tail));

    add_block(typelib, data.data, data.size, index_name);
}

static void add_enum_typeinfo(struct sltg_typelib *typelib, type_t *type)
{
    struct sltg_data data, *var_data = NULL;
    struct sltg_hrefmap hrefmap;
    const char *index_name;
    struct sltg_typeinfo_header ti;
    struct sltg_member_header member;
    struct sltg_tail tail;
    int member_offset, var_count = 0;
    short *type_desc_offset = NULL;

    if (type->typelib_idx != -1) return;

    chat("add_enum_typeinfo: type %p, type->name %s\n", type, type->name);

    type->typelib_idx = typelib->block_count;

    hrefmap.href_count = 0;
    hrefmap.href = NULL;

    if (type_enum_get_values(type))
    {
        int i = 0;
        var_t *var;

        var_count = list_count(type_enum_get_values(type));

        var_data = xmalloc(var_count * sizeof(*var_data));
        type_desc_offset = xmalloc(var_count * sizeof(*type_desc_offset));

        LIST_FOR_EACH_ENTRY(var, type_enum_get_values(type), var_t, entry)
        {
            short base_offset;

            chat("add_enum_typeinfo: var %p (%s), type %p (%s)\n",
                 var, var->name, var->declspec.type, var->declspec.type->name);

            init_sltg_data(&var_data[i]);

            base_offset = (i + 1) * (sizeof(struct sltg_variable) + sizeof(int));
            type_desc_offset[i] = write_var_desc(typelib, &var_data[i], var->declspec.type, 0, 0,
                                                 base_offset, NULL, &hrefmap);
            dump_var_desc(var_data[i].data, var_data[i].size);
            i++;
        }
    }

    init_sltg_data(&data);

    index_name = add_typeinfo_block(typelib, type, TKIND_ENUM);

    init_typeinfo(&ti, type, TKIND_ENUM, &hrefmap);
    append_data(&data, &ti, sizeof(ti));

    write_hrefmap(&data, &hrefmap);

    member_offset = data.size;

    member.res00 = 0x0001;
    member.res02 = 0xffff;
    member.res04 = 0x01;
    member.extra = var_count * (sizeof(struct sltg_variable) + sizeof(int));
    append_data(&data, &member, sizeof(member));

    if (type_enum_get_values(type))
    {
        int i = 0;
        short next = member_offset;
        var_t *var;

        LIST_FOR_EACH_ENTRY(var, type_enum_get_values(type), var_t, entry)
        {
            struct sltg_variable variable;

            next += sizeof(variable);

            variable.magic = 0x2a; /* always write flags to simplify calculations */
            variable.name = add_name(&typelib->name_table, var->name);
            variable.byte_offs = next - member_offset;
            variable.flags = 0x12;
            variable.type = VT_INT;
            variable.next = i < var_count - 1 ? variable.byte_offs + sizeof(int) : -1;
            variable.memid = 0x40000000 + i;
            variable.helpcontext = -2; /* 0xfffe */
            variable.helpstring = -1;
            variable.varflags = 0;

            append_data(&data, &variable, sizeof(variable));
            append_data(&data, &var->eval->cval, sizeof(var->eval->cval));

            next += sizeof(int);
            i++;
        }
    }

    init_sltg_tail(&tail);
    tail.cVars = var_count;
    tail.vars_off = 0;
    tail.vars_bytes = 0;
    tail.cbSizeInstance = sizeof(int);
    tail.type_bytes = data.size - member_offset - sizeof(member);
    append_data(&data, &tail, sizeof(tail));

    add_block(typelib, data.data, data.size, index_name);
}

static void add_union_typeinfo(struct sltg_typelib *typelib, type_t *type)
{
    struct sltg_data data, *var_data = NULL;
    struct sltg_hrefmap hrefmap;
    const char *index_name;
    struct sltg_typeinfo_header ti;
    struct sltg_member_header member;
    struct sltg_tail tail;
    int member_offset, var_count = 0, var_data_size = 0, size_instance = 0;
    short *type_desc_offset = NULL;

    if (type->typelib_idx != -1) return;

    chat("add_union_typeinfo: type %p, type->name %s\n", type, type->name);

    type->typelib_idx = typelib->block_count;

    hrefmap.href_count = 0;
    hrefmap.href = NULL;

    if (type_union_get_cases(type))
    {
        int i = 0;
        var_t *var;

        var_count = list_count(type_union_get_cases(type));

        var_data = xmalloc(var_count * sizeof(*var_data));
        type_desc_offset = xmalloc(var_count * sizeof(*type_desc_offset));

        LIST_FOR_EACH_ENTRY(var, type_union_get_cases(type), var_t, entry)
        {
            short base_offset;

            chat("add_union_typeinfo: var %p (%s), type %p (%s)\n",
                 var, var->name, var->declspec.type, var->declspec.type->name);

            init_sltg_data(&var_data[i]);

            base_offset = var_data_size + (i + 1) * sizeof(struct sltg_variable);
            type_desc_offset[i] = write_var_desc(typelib, &var_data[i], var->declspec.type, 0, 0,
                                                 base_offset, &size_instance, &hrefmap);
            dump_var_desc(var_data[i].data, var_data[i].size);

            if (var_data[i].size > sizeof(short))
                var_data_size += var_data[i].size;
            i++;
        }
    }

    init_sltg_data(&data);

    index_name = add_typeinfo_block(typelib, type, TKIND_UNION);

    init_typeinfo(&ti, type, TKIND_UNION, &hrefmap);
    append_data(&data, &ti, sizeof(ti));

    write_hrefmap(&data, &hrefmap);

    member_offset = data.size;

    member.res00 = 0x0001;
    member.res02 = 0xffff;
    member.res04 = 0x01;
    member.extra = var_data_size + var_count * sizeof(struct sltg_variable);
    append_data(&data, &member, sizeof(member));

    if (type_union_get_cases(type))
    {
        int i = 0;
        short next = member_offset;
        var_t *var;

        LIST_FOR_EACH_ENTRY(var, type_union_get_cases(type), var_t, entry)
        {
            struct sltg_variable variable;

            next += sizeof(variable);

            variable.magic = 0x2a; /* always write flags to simplify calculations */
            variable.name = add_name(&typelib->name_table, var->name);
            variable.byte_offs = 0;
            if (var_data[i].size > sizeof(short))
            {
                variable.flags = 0;
                variable.type = next - member_offset + type_desc_offset[i];
                next += var_data[i].size;
            }
            else
            {
                variable.flags = 0x02;
                variable.type = *(short *)var_data[i].data;
            }
            variable.next = i < var_count - 1 ? next - member_offset : -1;
            variable.memid = 0x40000000 + i;
            variable.helpcontext = -2; /* 0xfffe */
            variable.helpstring = -1;
            variable.varflags = 0;

            append_data(&data, &variable, sizeof(variable));
            if (var_data[i].size > sizeof(short))
                append_data(&data, var_data[i].data, var_data[i].size);

            i++;
        }
    }

    init_sltg_tail(&tail);
    tail.cVars = var_count;
    tail.vars_off = 0;
    tail.vars_bytes = 0;
    tail.cbSizeInstance = size_instance;
    tail.type_bytes = data.size - member_offset - sizeof(member);
    append_data(&data, &tail, sizeof(tail));

    add_block(typelib, data.data, data.size, index_name);
}

static void add_coclass_typeinfo(struct sltg_typelib *typelib, type_t *type)
{
    error("add_coclass_typeinfo: %s not implemented\n", type->name);
}

static void add_type_typeinfo(struct sltg_typelib *typelib, type_t *type)
{
    chat("add_type_typeinfo: adding %s, type %d\n", type->name, type_get_type(type));

    switch (type_get_type(type))
    {
    case TYPE_INTERFACE:
        add_interface_typeinfo(typelib, type);
        break;
    case TYPE_STRUCT:
        add_structure_typeinfo(typelib, type);
        break;
    case TYPE_ENUM:
        add_enum_typeinfo(typelib, type);
        break;
    case TYPE_UNION:
        add_union_typeinfo(typelib, type);
        break;
    case TYPE_COCLASS:
        add_coclass_typeinfo(typelib, type);
        break;
    case TYPE_BASIC:
    case TYPE_POINTER:
        break;
    default:
        error("add_type_typeinfo: unhandled type %d for %s\n", type_get_type(type), type->name);
        break;
    }
}

static void add_statement(struct sltg_typelib *typelib, const statement_t *stmt)
{
    switch(stmt->type)
    {
    case STMT_LIBRARY:
    case STMT_IMPORT:
    case STMT_PRAGMA:
    case STMT_CPPQUOTE:
    case STMT_DECLARATION:
        /* not included in typelib */
        break;
    case STMT_IMPORTLIB:
        /* not processed here */
        break;

    case STMT_TYPEDEF:
    {
        typeref_t *ref;

        if (!stmt->u.type_list)
            break;

        LIST_FOR_EACH_ENTRY(ref, stmt->u.type_list, typeref_t, entry)
        {
            /* in old style typelibs all types are public */
            add_type_typeinfo(typelib, ref->type);
        }
        break;
    }

    case STMT_MODULE:
        add_module_typeinfo(typelib, stmt->u.type);
        break;

    case STMT_TYPE:
    case STMT_TYPEREF:
    {
        type_t *type = stmt->u.type;
        add_type_typeinfo(typelib, type);
        break;
    }

    default:
        error("add_statement: unhandled statement type %d\n", stmt->type);
        break;
    }
}

static void sltg_write_header(struct sltg_typelib *sltg, int *library_block_start)
{
    char pad[0x40];
    struct sltg_header
    {
        int magic;
        short block_count;
        short res06;
        short size_of_index;
        short first_blk;
        GUID uuid;
        int res1c;
        int res20;
    } header;
    struct sltg_block_entry
    {
        int length;
        short index_string;
        short next;
    } entry;
    int i;

    header.magic = 0x47544c53;
    header.block_count = sltg->block_count + 1; /* 1-based */
    header.res06 = 9;
    header.size_of_index = sltg->index.size;
    header.first_blk = 1; /* 1-based */
    header.uuid = sltg_library_guid;
    header.res1c = 0x00000044;
    header.res20 = 0xffff0000;

    put_data(&header, sizeof(header));

    /* library block is written separately */
    for (i = 0; i < sltg->block_count - 1; i++)
    {
        entry.length = sltg->blocks[i].length;
        entry.index_string = sltg->blocks[i].index_string;
        entry.next = header.first_blk + i + 1; /* point to next block */
        chat("sltg_write_header: writing block entry %d: length %#x, index_string %#x, next %#x\n",
             i, entry.length, entry.index_string, entry.next);
        put_data(&entry, sizeof(entry));
    }

    /* library block length includes helpstrings and name table */
    entry.length = sltg->blocks[sltg->block_count - 1].length + 0x40 /* pad after library block */ +
                   sizeof(sltg->typeinfo_count) + sltg->typeinfo_size + 4 /* library block size */ + 6 /* dummy help strings */ +
                   12 /* name table header */ + 0x200 /* name table hash */ +
                   sizeof(sltg->name_table.size) + sltg->name_table.size +
                   4 /* 0x01ffff01 */ + 4 /* 0 */;
    entry.index_string = sltg->blocks[sltg->block_count - 1].index_string;
    entry.next = 0;
    chat("sltg_write_header: writing library block entry %d: length %#x, index_string %#x, next %#x\n",
         i, entry.length, entry.index_string, entry.next);
    put_data(&entry, sizeof(entry));

    chat("sltg_write_header: writing index: %d bytes\n", sltg->index.size);
    put_data(sltg->index.data, sltg->index.size);
    memset(pad, 0, 9);
    put_data(pad, 9);

    /* library block is written separately */
    for (i = 0; i < sltg->block_count - 1; i++)
    {
        chat("sltg_write_header: writing block %d: %d bytes\n", i, sltg->blocks[i].length);
        put_data(sltg->blocks[i].data, sltg->blocks[i].length);
    }

    /* library block */
    chat("library_block_start = %#x\n", (int)output_buffer_pos);
    *library_block_start = output_buffer_pos;
    chat("sltg_write_header: writing library block %d: %d bytes\n", i, sltg->blocks[i].length);
    put_data(sltg->blocks[sltg->block_count - 1].data, sltg->blocks[sltg->block_count - 1].length);

    chat("sltg_write_header: writing pad 0x40 bytes\n");
    memset(pad, 0xff, 0x40);
    put_data(pad, 0x40);
}

static void sltg_write_typeinfo(struct sltg_typelib *typelib)
{
    short i;

    put_data(&typelib->typeinfo_count, sizeof(typelib->typeinfo_count));

    for (i = 0; i < typelib->typeinfo_count; i++)
    {
        chat("sltg_write_typeinfo: writing block %d: %d bytes\n", i, typelib->typeinfo[i].length);
        put_data(typelib->typeinfo[i].data, typelib->typeinfo[i].length);
    }
}

static void sltg_write_helpstrings(struct sltg_typelib *typelib)
{
    static const char dummy[6];

    chat("sltg_write_helpstrings: writing dummy 6 bytes\n");

    put_data(dummy, sizeof(dummy));
}

static void sltg_write_nametable(struct sltg_typelib *typelib)
{
    static const short dummy[6] = { 0xffff,1,2,0xff00,0xffff,0xffff };
    char pad[0x200];

    chat("sltg_write_nametable: writing 12+0x200+%d bytes\n", typelib->name_table.size);

    put_data(dummy, sizeof(dummy));
    memset(pad, 0xff, 0x200);
    put_data(pad, 0x200);
    put_data(&typelib->name_table.size, sizeof(typelib->name_table.size));
    put_data(typelib->name_table.data, typelib->name_table.size);
}

static void sltg_write_remainder(void)
{
    static const short dummy1[] = { 1,0xfffe,0x0a03,0,0xffff,0xffff };
    static const short dummy2[] = { 0xffff,0xffff,0x0200,0,0,0 };
    static const char dummy3[] = { 0xf4,0x39,0xb2,0x71,0,0,0,0,0,0,0,0,0,0,0,0 };
    static const char TYPELIB[] = { 8,0,0,0,'T','Y','P','E','L','I','B',0 };
    int pad;

    pad = 0x01ffff01;
    put_data(&pad, sizeof(pad));
    pad = 0;
    put_data(&pad, sizeof(pad));

    put_data(dummy1, sizeof(dummy1));

    put_data(&sltg_library_guid, sizeof(sltg_library_guid));

    put_data(TYPELIB, sizeof(TYPELIB));

    put_data(dummy2, sizeof(dummy2));
    put_data(dummy3, sizeof(dummy3));
}

static void save_all_changes(struct sltg_typelib *typelib)
{
    int library_block_start;
    int *name_table_offset;

    sltg_write_header(typelib, &library_block_start);
    sltg_write_typeinfo(typelib);

    name_table_offset = (int *)(output_buffer + output_buffer_pos);
    chat("name_table_offset = %#x\n", (int)output_buffer_pos);
    put_data(&library_block_start, sizeof(library_block_start));

    sltg_write_helpstrings(typelib);

    *name_table_offset = output_buffer_pos - library_block_start;
    chat("*name_table_offset = %#x\n", *name_table_offset);

    sltg_write_nametable(typelib);
    sltg_write_remainder();

    if (strendswith(typelib_name, ".res")) /* create a binary resource file */
    {
        char typelib_id[13] = "#1";

        expr_t *expr = get_attrp(typelib->typelib->attrs, ATTR_ID);
        if (expr)
            snprintf(typelib_id, sizeof(typelib_id), "#%d", expr->cval);
        add_output_to_resources("TYPELIB", typelib_id);
        if (strendswith(typelib_name, "_t.res"))  /* add typelib registration */
            output_typelib_regscript(typelib->typelib);
    }
    else flush_output_buffer(typelib_name);
}

int create_sltg_typelib(typelib_t *typelib)
{
    struct sltg_typelib sltg;
    const statement_t *stmt;
    void *library_block;
    int library_block_size, library_block_index;

    if (pointer_size != 4)
        error("Only 32-bit platform is supported\n");

    sltg.typelib = typelib;
    sltg.typeinfo_count = 0;
    sltg.typeinfo_size = 0;
    sltg.typeinfo = NULL;
    sltg.blocks = NULL;
    sltg.block_count = 0;
    sltg.first_block = 1;

    init_index(&sltg.index);
    init_name_table(&sltg.name_table);
    init_library(&sltg);

    library_block = create_library_block(&sltg, &library_block_size, &library_block_index);

    if (typelib->stmts)
        LIST_FOR_EACH_ENTRY(stmt, typelib->stmts, const statement_t, entry)
            add_statement(&sltg, stmt);

    add_block_index(&sltg, library_block, library_block_size, library_block_index);

    save_all_changes(&sltg);

    return 1;
}
