/*
 * Copyright 2017 Nikolay Sivov
 * Copyright 2019 Jacek Caban for CodeWeavers
 *
 * 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
 */
#define COBJMACROS

#include <stdarg.h>

#include "windef.h"
#include "winbase.h"
#include "ole2.h"
#include "olectl.h"
#include "rpcproxy.h"
#include "activscp.h"
#include "dispex.h"
#include "mshtmhst.h"

#include "initguid.h"
#include "scrobj.h"
#include "xmllite.h"

#include "wine/debug.h"
#include "wine/heap.h"
#include "wine/list.h"

WINE_DEFAULT_DEBUG_CHANNEL(scrobj);

#ifdef _WIN64

#define IActiveScriptParse_Release IActiveScriptParse64_Release
#define IActiveScriptParse_InitNew IActiveScriptParse64_InitNew
#define IActiveScriptParse_ParseScriptText IActiveScriptParse64_ParseScriptText

#else

#define IActiveScriptParse_Release IActiveScriptParse32_Release
#define IActiveScriptParse_InitNew IActiveScriptParse32_InitNew
#define IActiveScriptParse_ParseScriptText IActiveScriptParse32_ParseScriptText

#endif

static HINSTANCE scrobj_instance;

enum member_type
{
    MEMBER_METHOD,
    MEMBER_PROPERTY
};

#define PROP_GETTER   0x01
#define PROP_SETTER   0x02

struct scriptlet_member
{
    struct list entry;
    enum member_type type;
    WCHAR *name;
    union
    {
        struct list parameters;
        unsigned int flags;
    } u;
};

struct method_parameter
{
    struct list entry;
    WCHAR *name;
};

struct scriptlet_script
{
    struct list entry;
    WCHAR *language;
    WCHAR *body;
};

struct scriptlet_global
{
    IDispatchEx IDispatchEx_iface;
    LONG ref;
};

struct scriptlet_factory
{
    IClassFactory IClassFactory_iface;

    LONG ref;

    IXmlReader *xml_reader;
    IMoniker *moniker;

    BOOL have_registration;
    BOOL have_public;
    WCHAR *description;
    WCHAR *progid;
    WCHAR *versioned_progid;
    WCHAR *version;
    WCHAR *classid_str;
    CLSID classid;

    struct list hosts;
    struct list members;
    struct list scripts;
};

struct script_reference
{
    struct script_host *host;
    DISPID id;
};

struct object_member
{
    enum member_type type;
    BSTR name;
    union
    {
        struct script_reference method;
        struct
        {
            struct script_reference get;
            struct script_reference put;
        } property;
    } u;
};

struct scriptlet_instance
{
    IDispatchEx IDispatchEx_iface;
    LONG ref;
    struct list hosts;
    struct scriptlet_global *global;
    unsigned member_cnt;
    struct object_member *members;
};

struct script_host
{
    IActiveScriptSite IActiveScriptSite_iface;
    IActiveScriptSiteWindow IActiveScriptSiteWindow_iface;
    IServiceProvider IServiceProvider_iface;

    LONG ref;
    struct list entry;

    WCHAR *language;

    IActiveScript *active_script;
    IActiveScriptParse *parser;
    IDispatchEx *script_dispatch;
    struct scriptlet_instance *object;
    SCRIPTSTATE state;
    BOOL cloned;
};

typedef enum tid_t
{
    NULL_tid,
    IGenScriptletTLib_tid,
    LAST_tid
} tid_t;

static ITypeLib *typelib;
static ITypeInfo *typeinfos[LAST_tid];

static REFIID tid_ids[] = {
    &IID_NULL,
    &IID_IGenScriptletTLib,
};

static HRESULT load_typelib(void)
{
    HRESULT hres;
    ITypeLib *tl;

    if (typelib)
        return S_OK;

    hres = LoadRegTypeLib(&LIBID_Scriptlet, 1, 0, LOCALE_SYSTEM_DEFAULT, &tl);
    if (FAILED(hres))
    {
        ERR("LoadRegTypeLib failed: %08x\n", hres);
        return hres;
    }

    if (InterlockedCompareExchangePointer((void **)&typelib, tl, NULL))
        ITypeLib_Release(tl);
    return hres;
}

static HRESULT get_typeinfo(tid_t tid, ITypeInfo **typeinfo)
{
    HRESULT hres;

    if (FAILED(hres = load_typelib()))
        return hres;

    if (!typeinfos[tid])
    {
        ITypeInfo *ti;

        hres = ITypeLib_GetTypeInfoOfGuid(typelib, tid_ids[tid], &ti);
        if (FAILED(hres)) {
            ERR("GetTypeInfoOfGuid(%s) failed: %08x\n", debugstr_guid(tid_ids[tid]), hres);
            return hres;
        }

        if (InterlockedCompareExchangePointer((void **)(typeinfos+tid), ti, NULL))
            ITypeInfo_Release(ti);
    }

    *typeinfo = typeinfos[tid];
    ITypeInfo_AddRef(typeinfos[tid]);
    return S_OK;
}

static void release_typelib(void)
{
    unsigned i;

    if (!typelib)
        return;

    for (i = 0; i < ARRAY_SIZE(typeinfos); i++)
        if (typeinfos[i])
            ITypeInfo_Release(typeinfos[i]);

    ITypeLib_Release(typelib);
}

static WCHAR *heap_strdupW(const WCHAR *str)
{
    WCHAR *ret;
    size_t size = (wcslen(str) + 1) * sizeof(WCHAR);
    if (!(ret = heap_alloc(size))) return NULL;
    memcpy(ret, str, size);
    return ret;
}

static inline struct scriptlet_global *global_from_IDispatchEx(IDispatchEx *iface)
{
    return CONTAINING_RECORD(iface, struct scriptlet_global, IDispatchEx_iface);
}

static HRESULT WINAPI global_QueryInterface(IDispatchEx *iface, REFIID riid, void **ppv)
{
    struct scriptlet_global *This = global_from_IDispatchEx(iface);

    if (IsEqualGUID(&IID_IUnknown, riid))
    {
        TRACE("(%p)->(IID_IUnknown %p)\n", This, ppv);
        *ppv = &This->IDispatchEx_iface;
    }
    else if (IsEqualGUID(&IID_IDispatch, riid))
    {
        TRACE("(%p)->(IID_IDispatch %p)\n", This, ppv);
        *ppv = &This->IDispatchEx_iface;
    }
    else if (IsEqualGUID(&IID_IDispatchEx, riid))
    {
        TRACE("(%p)->(IID_IDispatchEx %p)\n", This, ppv);
        *ppv = &This->IDispatchEx_iface;
    }
    else
    {
        WARN("(%p)->(%s %p)\n", This, debugstr_guid(riid), ppv);
        *ppv = NULL;
        return E_NOINTERFACE;
    }

    IUnknown_AddRef((IUnknown*)*ppv);
    return S_OK;
}

static ULONG WINAPI global_AddRef(IDispatchEx *iface)
{
    struct scriptlet_global *This = global_from_IDispatchEx(iface);
    ULONG ref = InterlockedIncrement(&This->ref);

    TRACE("(%p) ref=%d\n", This, ref);

    return ref;
}

static ULONG WINAPI global_Release(IDispatchEx *iface)
{
    struct scriptlet_global *This = global_from_IDispatchEx(iface);
    ULONG ref = InterlockedDecrement(&This->ref);

    TRACE("(%p) ref=%d\n", This, ref);

    if (!ref) heap_free(This);
    return ref;
}

static HRESULT WINAPI global_GetTypeInfoCount(IDispatchEx *iface, UINT *pctinfo)
{
    struct scriptlet_global *This = global_from_IDispatchEx(iface);
    FIXME("(%p)->(%p)\n", This, pctinfo);
    return E_NOTIMPL;
}

static HRESULT WINAPI global_GetTypeInfo(IDispatchEx *iface, UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo)
{
    struct scriptlet_global *This = global_from_IDispatchEx(iface);
    FIXME("(%p)->(%u %u %p)\n", This, iTInfo, lcid, ppTInfo);
    return E_NOTIMPL;
}

static HRESULT WINAPI global_GetIDsOfNames(IDispatchEx *iface, REFIID riid,
        LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId)
{
    struct scriptlet_global *This = global_from_IDispatchEx(iface);
    UINT i;
    HRESULT hres;

    TRACE("(%p)->(%s %p %u %u %p)\n", This, debugstr_guid(riid), rgszNames, cNames, lcid, rgDispId);

    for(i=0; i < cNames; i++)
    {
        hres = IDispatchEx_GetDispID(&This->IDispatchEx_iface, rgszNames[i], 0, rgDispId + i);
        if(FAILED(hres))
            return hres;
    }

    return S_OK;
}

static HRESULT WINAPI global_Invoke(IDispatchEx *iface, DISPID dispIdMember,
        REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams,
        VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
{
    struct scriptlet_global *This = global_from_IDispatchEx(iface);
    TRACE("(%p)->(%d %s %d %d %p %p %p %p)\n", This, dispIdMember, debugstr_guid(riid),
          lcid, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr);
    return IDispatchEx_InvokeEx(&This->IDispatchEx_iface, dispIdMember, lcid, wFlags,
            pDispParams, pVarResult, pExcepInfo, NULL);
}

static HRESULT WINAPI global_GetDispID(IDispatchEx *iface, BSTR bstrName, DWORD grfdex, DISPID *pid)
{
    struct scriptlet_global *This = global_from_IDispatchEx(iface);
    FIXME("(%p)->(%s %x %p)\n", This, debugstr_w(bstrName), grfdex, pid);
    return E_NOTIMPL;
}

static HRESULT WINAPI global_InvokeEx(IDispatchEx *iface, DISPID id, LCID lcid, WORD wFlags, DISPPARAMS *pdp,
        VARIANT *pvarRes, EXCEPINFO *pei, IServiceProvider *pspCaller)
{
    struct scriptlet_global *This = global_from_IDispatchEx(iface);
    FIXME("(%p)->(%x %x %x %p %p %p %p)\n", This, id, lcid, wFlags, pdp, pvarRes, pei, pspCaller);
    return E_NOTIMPL;
}

static HRESULT WINAPI global_DeleteMemberByName(IDispatchEx *iface, BSTR bstrName, DWORD grfdex)
{
    struct scriptlet_global *This = global_from_IDispatchEx(iface);
    FIXME("(%p)->(%s %x)\n", This, debugstr_w(bstrName), grfdex);
    return E_NOTIMPL;
}

static HRESULT WINAPI global_DeleteMemberByDispID(IDispatchEx *iface, DISPID id)
{
    struct scriptlet_global *This = global_from_IDispatchEx(iface);
    FIXME("(%p)->(%x)\n", This, id);
    return E_NOTIMPL;
}

static HRESULT WINAPI global_GetMemberProperties(IDispatchEx *iface, DISPID id, DWORD grfdexFetch, DWORD *pgrfdex)
{
    struct scriptlet_global *This = global_from_IDispatchEx(iface);
    FIXME("(%p)->(%x %x %p)\n", This, id, grfdexFetch, pgrfdex);
    return E_NOTIMPL;
}

static HRESULT WINAPI global_GetMemberName(IDispatchEx *iface, DISPID id, BSTR *pbstrName)
{
    struct scriptlet_global *This = global_from_IDispatchEx(iface);
    FIXME("(%p)->(%x %p)\n", This, id, pbstrName);
    return E_NOTIMPL;
}

static HRESULT WINAPI global_GetNextDispID(IDispatchEx *iface, DWORD grfdex, DISPID id, DISPID *pid)
{
    struct scriptlet_global *This = global_from_IDispatchEx(iface);
    FIXME("(%p)->(%x %x %p)\n", This, grfdex, id, pid);
    return E_NOTIMPL;
}

static HRESULT WINAPI global_GetNameSpaceParent(IDispatchEx *iface, IUnknown **ppunk)
{
    struct scriptlet_global *This = global_from_IDispatchEx(iface);
    FIXME("(%p)->(%p)\n", This, ppunk);
    return E_NOTIMPL;
}

static IDispatchExVtbl global_vtbl = {
    global_QueryInterface,
    global_AddRef,
    global_Release,
    global_GetTypeInfoCount,
    global_GetTypeInfo,
    global_GetIDsOfNames,
    global_Invoke,
    global_GetDispID,
    global_InvokeEx,
    global_DeleteMemberByName,
    global_DeleteMemberByDispID,
    global_GetMemberProperties,
    global_GetMemberName,
    global_GetNextDispID,
    global_GetNameSpaceParent
};

static HRESULT set_script_state(struct script_host *host, SCRIPTSTATE state)
{
    HRESULT hres;
    if (state == host->state) return S_OK;
    hres = IActiveScript_SetScriptState(host->active_script, state);
    if (FAILED(hres)) return hres;
    host->state = state;
    return S_OK;
}

static inline struct script_host *impl_from_IActiveScriptSite(IActiveScriptSite *iface)
{
    return CONTAINING_RECORD(iface, struct script_host, IActiveScriptSite_iface);
}

static HRESULT WINAPI ActiveScriptSite_QueryInterface(IActiveScriptSite *iface, REFIID riid, void **ppv)
{
    struct script_host *This = impl_from_IActiveScriptSite(iface);

    if (IsEqualGUID(&IID_IUnknown, riid))
    {
        TRACE("(%p)->(IID_IUnknown %p)\n", This, ppv);
        *ppv = &This->IActiveScriptSite_iface;
    }
    else if (IsEqualGUID(&IID_IActiveScriptSite, riid))
    {
        TRACE("(%p)->(IID_IActiveScriptSite %p)\n", This, ppv);
        *ppv = &This->IActiveScriptSite_iface;
    }
    else if (IsEqualGUID(&IID_IActiveScriptSiteWindow, riid))
    {
        TRACE("(%p)->(IID_IActiveScriptSiteWindow %p)\n", This, ppv);
        *ppv = &This->IActiveScriptSiteWindow_iface;
    }
    else if(IsEqualGUID(&IID_IServiceProvider, riid))
    {
        TRACE("(%p)->(IID_IServiceProvider %p)\n", This, ppv);
        *ppv = &This->IServiceProvider_iface;
    }
    else
    {
        WARN("(%p)->(%s %p) interface not supported\n", This, debugstr_guid(riid), ppv);
        *ppv = NULL;
        return E_NOINTERFACE;
    }

    IUnknown_AddRef((IUnknown*)*ppv);
    return S_OK;
}

static ULONG WINAPI ActiveScriptSite_AddRef(IActiveScriptSite *iface)
{
    struct script_host *This = impl_from_IActiveScriptSite(iface);
    LONG ref = InterlockedIncrement(&This->ref);

    TRACE("(%p) ref=%d\n", This, ref);

    return ref;
}

static ULONG WINAPI ActiveScriptSite_Release(IActiveScriptSite *iface)
{
    struct script_host *This = impl_from_IActiveScriptSite(iface);
    LONG ref = InterlockedDecrement(&This->ref);

    TRACE("(%p) ref=%d\n", This, ref);

    if(!ref) {
        heap_free(This->language);
        heap_free(This);
    }

    return ref;
}

static HRESULT WINAPI ActiveScriptSite_GetLCID(IActiveScriptSite *iface, LCID *lcid)
{
    struct script_host *This = impl_from_IActiveScriptSite(iface);

    TRACE("(%p, %p)\n", This, lcid);

    *lcid = GetUserDefaultLCID();
    return S_OK;
}

static HRESULT WINAPI ActiveScriptSite_GetItemInfo(IActiveScriptSite *iface, LPCOLESTR name, DWORD mask,
    IUnknown **unk, ITypeInfo **ti)
{
    struct script_host *This = impl_from_IActiveScriptSite(iface);

    TRACE("(%p, %s, %#x, %p, %p)\n", This, debugstr_w(name), mask, unk, ti);

    if (mask != SCRIPTINFO_IUNKNOWN)
    {
        FIXME("mask %x not supported\n", mask);
        return E_NOTIMPL;
    }

    if (wcscmp(name, L"scriptlet") && wcscmp(name, L"globals"))
    {
        FIXME("%s not supported\n", debugstr_w(name));
        return E_FAIL;
    }

    if (!This->object) return E_UNEXPECTED;

    *unk = (IUnknown *)&This->object->global->IDispatchEx_iface;
    IUnknown_AddRef(*unk);
    return S_OK;
}

static HRESULT WINAPI ActiveScriptSite_GetDocVersionString(IActiveScriptSite *iface, BSTR *version)
{
    struct script_host *This = impl_from_IActiveScriptSite(iface);
    FIXME("(%p, %p)\n", This, version);
    return E_NOTIMPL;
}

static HRESULT WINAPI ActiveScriptSite_OnScriptTerminate(IActiveScriptSite *iface, const VARIANT *result,
    const EXCEPINFO *ei)
{
    struct script_host *This = impl_from_IActiveScriptSite(iface);
    FIXME("(%p, %s, %p)\n", This, debugstr_variant(result), ei);
    return E_NOTIMPL;
}

static HRESULT WINAPI ActiveScriptSite_OnStateChange(IActiveScriptSite *iface, SCRIPTSTATE state)
{
    struct script_host *This = impl_from_IActiveScriptSite(iface);
    TRACE("(%p, %d)\n", This, state);
    return E_NOTIMPL;
}

static HRESULT WINAPI ActiveScriptSite_OnScriptError(IActiveScriptSite *iface, IActiveScriptError *script_error)
{
    struct script_host *This = impl_from_IActiveScriptSite(iface);
    FIXME("(%p, %p)\n", This, script_error);
    return E_NOTIMPL;
}

static HRESULT WINAPI ActiveScriptSite_OnEnterScript(IActiveScriptSite *iface)
{
    struct script_host *This = impl_from_IActiveScriptSite(iface);
    TRACE("(%p)\n", This);
    return S_OK;
}

static HRESULT WINAPI ActiveScriptSite_OnLeaveScript(IActiveScriptSite *iface)
{
    struct script_host *This = impl_from_IActiveScriptSite(iface);
    TRACE("(%p)\n", This);
    return S_OK;
}

static const IActiveScriptSiteVtbl ActiveScriptSiteVtbl = {
    ActiveScriptSite_QueryInterface,
    ActiveScriptSite_AddRef,
    ActiveScriptSite_Release,
    ActiveScriptSite_GetLCID,
    ActiveScriptSite_GetItemInfo,
    ActiveScriptSite_GetDocVersionString,
    ActiveScriptSite_OnScriptTerminate,
    ActiveScriptSite_OnStateChange,
    ActiveScriptSite_OnScriptError,
    ActiveScriptSite_OnEnterScript,
    ActiveScriptSite_OnLeaveScript
};

static inline struct script_host *impl_from_IActiveScriptSiteWindow(IActiveScriptSiteWindow *iface)
{
    return CONTAINING_RECORD(iface, struct script_host, IActiveScriptSiteWindow_iface);
}

static HRESULT WINAPI ActiveScriptSiteWindow_QueryInterface(IActiveScriptSiteWindow *iface, REFIID riid, void **obj)
{
    struct script_host *This = impl_from_IActiveScriptSiteWindow(iface);
    return IActiveScriptSite_QueryInterface(&This->IActiveScriptSite_iface, riid, obj);
}

static ULONG WINAPI ActiveScriptSiteWindow_AddRef(IActiveScriptSiteWindow *iface)
{
    struct script_host *This = impl_from_IActiveScriptSiteWindow(iface);
    return IActiveScriptSite_AddRef(&This->IActiveScriptSite_iface);
}

static ULONG WINAPI ActiveScriptSiteWindow_Release(IActiveScriptSiteWindow *iface)
{
    struct script_host *This = impl_from_IActiveScriptSiteWindow(iface);
    return IActiveScriptSite_Release(&This->IActiveScriptSite_iface);
}

static HRESULT WINAPI ActiveScriptSiteWindow_GetWindow(IActiveScriptSiteWindow *iface, HWND *hwnd)
{
    struct script_host *This = impl_from_IActiveScriptSiteWindow(iface);
    FIXME("(%p, %p)\n", This, hwnd);
    return E_NOTIMPL;
}

static HRESULT WINAPI ActiveScriptSiteWindow_EnableModeless(IActiveScriptSiteWindow *iface, BOOL enable)
{
    struct script_host *This = impl_from_IActiveScriptSiteWindow(iface);
    FIXME("(%p, %d)\n", This, enable);
    return E_NOTIMPL;
}

static const IActiveScriptSiteWindowVtbl ActiveScriptSiteWindowVtbl = {
    ActiveScriptSiteWindow_QueryInterface,
    ActiveScriptSiteWindow_AddRef,
    ActiveScriptSiteWindow_Release,
    ActiveScriptSiteWindow_GetWindow,
    ActiveScriptSiteWindow_EnableModeless
};

static inline struct script_host *impl_from_IServiceProvider(IServiceProvider *iface)
{
    return CONTAINING_RECORD(iface, struct script_host, IServiceProvider_iface);
}

static HRESULT WINAPI ServiceProvider_QueryInterface(IServiceProvider *iface, REFIID riid, void **obj)
{
    struct script_host *This = impl_from_IServiceProvider(iface);
    return IActiveScriptSite_QueryInterface(&This->IActiveScriptSite_iface, riid, obj);
}

static ULONG WINAPI ServiceProvider_AddRef(IServiceProvider *iface)
{
    struct script_host *This = impl_from_IServiceProvider(iface);
    return IActiveScriptSite_AddRef(&This->IActiveScriptSite_iface);
}

static ULONG WINAPI ServiceProvider_Release(IServiceProvider *iface)
{
    struct script_host *This = impl_from_IServiceProvider(iface);
    return IActiveScriptSite_Release(&This->IActiveScriptSite_iface);
}

static HRESULT WINAPI ServiceProvider_QueryService(IServiceProvider *iface, REFGUID service,
    REFIID riid, void **obj)
{
    struct script_host *This = impl_from_IServiceProvider(iface);
    FIXME("(%p)->(%s %s %p)\n", This, debugstr_guid(service), debugstr_guid(riid), obj);
    return E_NOTIMPL;
}

static const IServiceProviderVtbl ServiceProviderVtbl = {
    ServiceProvider_QueryInterface,
    ServiceProvider_AddRef,
    ServiceProvider_Release,
    ServiceProvider_QueryService
};

static struct script_host *find_script_host(struct list *hosts, const WCHAR *language)
{
    struct script_host *host;
    LIST_FOR_EACH_ENTRY(host, hosts, struct script_host, entry)
    {
        if (!wcscmp(host->language, language)) return host;
    }
    return NULL;
}

static HRESULT parse_scripts(struct scriptlet_factory *factory, struct list *hosts, BOOL start)
{
    DWORD parse_flags = SCRIPTTEXT_ISVISIBLE;
    struct scriptlet_script *script;
    struct script_host *host;
    HRESULT hres;

    if (!start) parse_flags |= SCRIPTITEM_ISPERSISTENT;

    LIST_FOR_EACH_ENTRY(script, &factory->scripts, struct scriptlet_script, entry)
    {
        host = find_script_host(hosts, script->language);

        if (start && host->state != SCRIPTSTATE_STARTED)
        {
            hres = set_script_state(host, SCRIPTSTATE_STARTED);
            if (FAILED(hres)) return hres;
        }

        if (host->cloned) continue;

        hres = IActiveScriptParse_ParseScriptText(host->parser, script->body, NULL, NULL, NULL, 0, 0 /* FIXME */,
                                                  parse_flags, NULL, NULL);
        if (FAILED(hres))
        {
            WARN("ParseScriptText failed: %08x\n", hres);
            return hres;
        }
    }
    if (!start)
    {
        LIST_FOR_EACH_ENTRY(host, hosts, struct script_host, entry)
        {
            if (host->state != SCRIPTSTATE_UNINITIALIZED)
                set_script_state(host, SCRIPTSTATE_UNINITIALIZED);
        }
    }
    return S_OK;
}

static HRESULT lookup_script_properties(struct scriptlet_instance *object, BSTR name, struct script_reference *ret)
{
    struct script_host *host;
    HRESULT hres;

    LIST_FOR_EACH_ENTRY(host, &object->hosts, struct script_host, entry)
    {
        hres = IDispatchEx_GetDispID(host->script_dispatch, name, 0, &ret->id);
        if (FAILED(hres)) continue;
        ret->host = host;
        return S_OK;
    }

    FIXME("Could not find %s in scripts\n", debugstr_w(name));
    return E_FAIL;
}

static HRESULT lookup_object_properties(struct scriptlet_instance *object, struct scriptlet_factory *factory)
{
    struct scriptlet_member *member;
    unsigned i = 0, member_cnt = 0;
    size_t len;
    BSTR name;
    HRESULT hres;

    LIST_FOR_EACH_ENTRY(member, &factory->members, struct scriptlet_member, entry)
        member_cnt++;

    if (!member_cnt) return S_OK;

    if (!(object->members = heap_alloc_zero(member_cnt * sizeof(*object->members)))) return E_OUTOFMEMORY;
    object->member_cnt = member_cnt;

    LIST_FOR_EACH_ENTRY(member, &factory->members, struct scriptlet_member, entry)
    {
        object->members[i].type = member->type;
        if (!(object->members[i].name = SysAllocString(member->name))) return E_OUTOFMEMORY;
        switch (member->type)
        {
        case MEMBER_METHOD:
            hres = lookup_script_properties(object, object->members[i].name, &object->members[i].u.method);
            if (FAILED(hres)) return hres;
            break;
        case MEMBER_PROPERTY:
            len = wcslen(member->name);
            if (!(name = SysAllocStringLen(NULL, len + 4))) return E_OUTOFMEMORY;
            wcscpy(name, L"get_");
            wcscat(name, member->name);
            hres = lookup_script_properties(object, name, &object->members[i].u.property.get);
            if (SUCCEEDED(hres))
            {
                memcpy(name, L"put", 3 * sizeof(WCHAR));
                hres = lookup_script_properties(object, name, &object->members[i].u.property.put);
            }
            SysFreeString(name);
            if (FAILED(hres)) return hres;
            break;
        }
        i++;
    }

    return S_OK;
}

static HRESULT init_script_host(struct script_host *host, IActiveScript *clone)
{
    HRESULT hres;

    if (!clone)
    {
        IClassFactoryEx *factory_ex;
        IClassFactory *factory;
        IUnknown *unk;
        CLSID clsid;

        if (FAILED(hres = CLSIDFromProgID(host->language, &clsid)))
        {
            WARN("Could not find script engine for %s\n", debugstr_w(host->language));
            return hres;
        }

        hres = CoGetClassObject(&clsid, CLSCTX_INPROC_SERVER|CLSCTX_INPROC_HANDLER, NULL,
                                &IID_IClassFactory, (void**)&factory);
        if (FAILED(hres)) return hres;

        hres = IClassFactory_QueryInterface(factory, &IID_IClassFactoryEx, (void**)&factory_ex);
        if (SUCCEEDED(hres))
        {
            FIXME("Use IClassFactoryEx\n");
            IClassFactoryEx_Release(factory_ex);
        }

        hres = IClassFactory_CreateInstance(factory, NULL, &IID_IUnknown, (void**)&unk);
        IClassFactory_Release(factory);
        if (FAILED(hres)) return hres;

        hres = IUnknown_QueryInterface(unk, &IID_IActiveScript, (void**)&host->active_script);
        IUnknown_Release(unk);
        if (FAILED(hres)) return hres;
    }
    else
    {
        IActiveScript_AddRef(clone);
        host->active_script = clone;
        host->cloned = TRUE;
    }

    hres = IActiveScript_QueryInterface(host->active_script, &IID_IActiveScriptParse, (void**)&host->parser);
    if (FAILED(hres)) return hres;

    if (!clone)
    {
        hres = IActiveScriptParse_InitNew(host->parser);
        if (FAILED(hres))
        {
            IActiveScriptParse_Release(host->parser);
            host->parser = NULL;
            return hres;
        }
    }

    return IActiveScript_SetScriptSite(host->active_script, &host->IActiveScriptSite_iface);
}

static HRESULT create_script_host(const WCHAR *language, IActiveScript *origin_script, struct list *hosts, struct script_host **ret)
{
    IActiveScript *clone = NULL;
    struct script_host *host;
    HRESULT hres;

    if (!(host = heap_alloc_zero(sizeof(*host)))) return E_OUTOFMEMORY;

    host->IActiveScriptSite_iface.lpVtbl = &ActiveScriptSiteVtbl;
    host->IActiveScriptSiteWindow_iface.lpVtbl = &ActiveScriptSiteWindowVtbl;
    host->IServiceProvider_iface.lpVtbl = &ServiceProviderVtbl;
    host->ref = 1;
    host->state = SCRIPTSTATE_CLOSED;

    if (!(host->language = heap_strdupW(language)))
    {
        IActiveScriptSite_Release(&host->IActiveScriptSite_iface);
        return E_OUTOFMEMORY;
    }

    if (origin_script)
    {
        hres = IActiveScript_Clone(origin_script, &clone);
        if (FAILED(hres)) clone = NULL;
    }

    list_add_tail(hosts, &host->entry);
    hres = init_script_host(host, clone);
    if (clone) IActiveScript_Release(clone);
    if (FAILED(hres)) return hres;
    if (ret) *ret = host;
    return S_OK;
}

static void detach_script_hosts(struct list *hosts)
{
    while (!list_empty(hosts))
    {
        struct script_host *host = LIST_ENTRY(list_head(hosts), struct script_host, entry);
        if (host->state != SCRIPTSTATE_UNINITIALIZED) set_script_state(host, SCRIPTSTATE_UNINITIALIZED);
        list_remove(&host->entry);
        host->object = NULL;
        if (host->script_dispatch)
        {
            IDispatchEx_Release(host->script_dispatch);
            host->script_dispatch = NULL;
        }
        if (host->parser)
        {
            IActiveScript_Close(host->active_script);
            IActiveScriptParse_Release(host->parser);
            host->parser = NULL;
        }
        if (host->active_script)
        {
            IActiveScript_Release(host->active_script);
            host->active_script = NULL;
        }
        IActiveScriptSite_Release(&host->IActiveScriptSite_iface);
    }
}

static HRESULT create_scriptlet_hosts(struct scriptlet_factory *factory, struct list *hosts)
{
    struct scriptlet_script *script;
    HRESULT hres;

    LIST_FOR_EACH_ENTRY(script, &factory->scripts, struct scriptlet_script, entry)
    {
        if (find_script_host(hosts, script->language)) continue;
        hres = create_script_host(script->language, NULL, hosts, NULL);
        if (FAILED(hres))
        {
            detach_script_hosts(hosts);
            return hres;
        }
    }

    return S_OK;
}

static inline struct scriptlet_instance *impl_from_IDispatchEx(IDispatchEx *iface)
{
    return CONTAINING_RECORD(iface, struct scriptlet_instance, IDispatchEx_iface);
}

static HRESULT WINAPI scriptlet_QueryInterface(IDispatchEx *iface, REFIID riid, void **ppv)
{
    struct scriptlet_instance *This = impl_from_IDispatchEx(iface);

    if (IsEqualGUID(&IID_IUnknown, riid))
    {
        TRACE("(%p)->(IID_IUnknown %p)\n", This, ppv);
        *ppv = &This->IDispatchEx_iface;
    }
    else if (IsEqualGUID(&IID_IDispatch, riid))
    {
        TRACE("(%p)->(IID_IDispatch %p)\n", This, ppv);
        *ppv = &This->IDispatchEx_iface;
    }
    else if (IsEqualGUID(&IID_IDispatchEx, riid))
    {
        TRACE("(%p)->(IID_IDispatchEx %p)\n", This, ppv);
        *ppv = &This->IDispatchEx_iface;
    }
    else
    {
        WARN("(%p)->(%s %p)\n", This, debugstr_guid(riid), ppv);
        *ppv = NULL;
        return E_NOINTERFACE;
    }

    IUnknown_AddRef((IUnknown*)*ppv);
    return S_OK;
}

static ULONG WINAPI scriptlet_AddRef(IDispatchEx *iface)
{
    struct scriptlet_instance *This = impl_from_IDispatchEx(iface);
    ULONG ref = InterlockedIncrement(&This->ref);

    TRACE("(%p) ref=%d\n", This, ref);

    return ref;
}

static ULONG WINAPI scriptlet_Release(IDispatchEx *iface)
{
    struct scriptlet_instance *This = impl_from_IDispatchEx(iface);
    ULONG ref = InterlockedDecrement(&This->ref);

    TRACE("(%p) ref=%d\n", This, ref);

    if (!ref)
    {
        unsigned i;
        for (i = 0; i < This->member_cnt; i++)
            SysFreeString(This->members[i].name);
        heap_free(This->members);
        detach_script_hosts(&This->hosts);
        if (This->global) IDispatchEx_Release(&This->global->IDispatchEx_iface);
        heap_free(This);
    }
    return ref;
}

static HRESULT WINAPI scriptlet_GetTypeInfoCount(IDispatchEx *iface, UINT *pctinfo)
{
    struct scriptlet_instance *This = impl_from_IDispatchEx(iface);
    FIXME("(%p)->(%p)\n", This, pctinfo);
    return E_NOTIMPL;
}

static HRESULT WINAPI scriptlet_GetTypeInfo(IDispatchEx *iface, UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo)
{
    struct scriptlet_instance *This = impl_from_IDispatchEx(iface);
    FIXME("(%p)->(%u %u %p)\n", This, iTInfo, lcid, ppTInfo);
    return E_NOTIMPL;
}

static HRESULT WINAPI scriptlet_GetIDsOfNames(IDispatchEx *iface, REFIID riid,
        LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId)
{
    struct scriptlet_instance *This = impl_from_IDispatchEx(iface);
    UINT i;
    HRESULT hres;

    TRACE("(%p)->(%s %p %u %u %p)\n", This, debugstr_guid(riid), rgszNames, cNames,
          lcid, rgDispId);

    for(i=0; i < cNames; i++)
    {
        hres = IDispatchEx_GetDispID(&This->IDispatchEx_iface, rgszNames[i], 0, rgDispId + i);
        if(FAILED(hres))
            return hres;
    }

    return S_OK;
}

static HRESULT WINAPI scriptlet_Invoke(IDispatchEx *iface, DISPID dispIdMember,
        REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams,
        VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
{
    struct scriptlet_instance *This = impl_from_IDispatchEx(iface);
    TRACE("(%p)->(%d %s %d %d %p %p %p %p)\n", This, dispIdMember, debugstr_guid(riid),
          lcid, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr);
    return IDispatchEx_InvokeEx(&This->IDispatchEx_iface, dispIdMember, lcid, wFlags,
            pDispParams, pVarResult, pExcepInfo, NULL);
}

static HRESULT WINAPI scriptlet_GetDispID(IDispatchEx *iface, BSTR bstrName, DWORD grfdex, DISPID *pid)
{
    struct scriptlet_instance *This = impl_from_IDispatchEx(iface);
    unsigned i;

    TRACE("(%p)->(%s %x %p)\n", This, debugstr_w(bstrName), grfdex, pid);

    if (grfdex & ~(fdexNameCaseInsensitive|fdexNameCaseSensitive))
        FIXME("Unsupported grfdex %x\n", grfdex);

    for (i = 0; i < This->member_cnt; i++)
    {
        if (grfdex & fdexNameCaseInsensitive)
        {
            if (wcsicmp(This->members[i].name, bstrName)) continue;
        }
        else if (wcscmp(This->members[i].name, bstrName)) continue;
        *pid = i + 1;
        return S_OK;
    }

    WARN("Unknown property %s\n", debugstr_w(bstrName));
    return DISP_E_UNKNOWNNAME;
}

static HRESULT WINAPI scriptlet_InvokeEx(IDispatchEx *iface, DISPID id, LCID lcid, WORD flags, DISPPARAMS *pdp,
        VARIANT *res, EXCEPINFO *pei, IServiceProvider *caller)
{
    struct scriptlet_instance *This = impl_from_IDispatchEx(iface);
    struct object_member *member;

    TRACE("(%p)->(%x %x %x %p %p %p %p)\n", This, id, lcid, flags, pdp, res, pei, caller);

    if (id < 1 || id > This->member_cnt)
    {
        WARN("Unknown id %xu\n", id);
        return DISP_E_MEMBERNOTFOUND;
    }
    member = &This->members[id - 1];

    switch (member->type)
    {
    case MEMBER_METHOD:
        if ((flags & ~DISPATCH_PROPERTYGET) != DISPATCH_METHOD)
        {
            FIXME("unsupported method flags %x\n", flags);
            return DISP_E_MEMBERNOTFOUND;
        }
        return IDispatchEx_InvokeEx(member->u.method.host->script_dispatch, member->u.method.id, lcid,
                                    DISPATCH_METHOD, pdp, res, pei, caller);
    case MEMBER_PROPERTY:
        if (flags & DISPATCH_PROPERTYGET)
        {
            if (!member->u.property.get.host)
            {
                FIXME("No %s getter\n", debugstr_w(member->name));
                return DISP_E_MEMBERNOTFOUND;
            }
            return IDispatchEx_InvokeEx(member->u.property.get.host->script_dispatch, member->u.property.get.id, lcid,
                                        DISPATCH_METHOD, pdp, res, pei, caller);
        }
        if (flags & DISPATCH_PROPERTYPUT)
        {
            DISPPARAMS dp;

            if (!member->u.property.put.host)
            {
                FIXME("No %s setter\n", debugstr_w(member->name));
                return DISP_E_MEMBERNOTFOUND;
            }
            if (pdp->cNamedArgs != 1 || pdp->rgdispidNamedArgs[0] != DISPID_PROPERTYPUT)
            {
                FIXME("no propput argument\n");
                return E_FAIL;
            }
            dp = *pdp;
            dp.cNamedArgs = 0;
            return IDispatchEx_InvokeEx(member->u.property.put.host->script_dispatch, member->u.property.put.id, lcid,
                                        DISPATCH_METHOD, &dp, res, pei, caller);
        }

        FIXME("unsupported flags %x\n", flags);
    }

    return DISP_E_MEMBERNOTFOUND;
}

static HRESULT WINAPI scriptlet_DeleteMemberByName(IDispatchEx *iface, BSTR bstrName, DWORD grfdex)
{
    struct scriptlet_instance *This = impl_from_IDispatchEx(iface);
    FIXME("(%p)->(%s %x)\n", This, debugstr_w(bstrName), grfdex);
    return E_NOTIMPL;
}

static HRESULT WINAPI scriptlet_DeleteMemberByDispID(IDispatchEx *iface, DISPID id)
{
    struct scriptlet_instance *This = impl_from_IDispatchEx(iface);
    FIXME("(%p)->(%x)\n", This, id);
    return E_NOTIMPL;
}

static HRESULT WINAPI scriptlet_GetMemberProperties(IDispatchEx *iface, DISPID id, DWORD grfdexFetch, DWORD *pgrfdex)
{
    struct scriptlet_instance *This = impl_from_IDispatchEx(iface);
    FIXME("(%p)->(%x %x %p)\n", This, id, grfdexFetch, pgrfdex);
    return E_NOTIMPL;
}

static HRESULT WINAPI scriptlet_GetMemberName(IDispatchEx *iface, DISPID id, BSTR *pbstrName)
{
    struct scriptlet_instance *This = impl_from_IDispatchEx(iface);
    FIXME("(%p)->(%x %p)\n", This, id, pbstrName);
    return E_NOTIMPL;
}

static HRESULT WINAPI scriptlet_GetNextDispID(IDispatchEx *iface, DWORD grfdex, DISPID id, DISPID *pid)
{
    struct scriptlet_instance *This = impl_from_IDispatchEx(iface);
    FIXME("(%p)->(%x %x %p)\n", This, grfdex, id, pid);
    return E_NOTIMPL;
}

static HRESULT WINAPI scriptlet_GetNameSpaceParent(IDispatchEx *iface, IUnknown **ppunk)
{
    struct scriptlet_instance *This = impl_from_IDispatchEx(iface);
    FIXME("(%p)->(%p)\n", This, ppunk);
    return E_NOTIMPL;
}

static IDispatchExVtbl DispatchExVtbl = {
    scriptlet_QueryInterface,
    scriptlet_AddRef,
    scriptlet_Release,
    scriptlet_GetTypeInfoCount,
    scriptlet_GetTypeInfo,
    scriptlet_GetIDsOfNames,
    scriptlet_Invoke,
    scriptlet_GetDispID,
    scriptlet_InvokeEx,
    scriptlet_DeleteMemberByName,
    scriptlet_DeleteMemberByDispID,
    scriptlet_GetMemberProperties,
    scriptlet_GetMemberName,
    scriptlet_GetNextDispID,
    scriptlet_GetNameSpaceParent
};

static HRESULT create_scriptlet_instance(struct scriptlet_factory *factory, IDispatchEx **disp)
{
    struct script_host *factory_host, *host;
    struct scriptlet_instance *obj;
    IDispatch *script_dispatch;
    HRESULT hres;

    if (!(obj = heap_alloc_zero(sizeof(*obj)))) return E_OUTOFMEMORY;

    obj->IDispatchEx_iface.lpVtbl = &DispatchExVtbl;
    obj->ref = 1;
    list_init(&obj->hosts);

    if (!(obj->global = heap_alloc(sizeof(*obj->global))))
    {
        IDispatchEx_Release(&obj->IDispatchEx_iface);
        return E_OUTOFMEMORY;
    }
    obj->global->IDispatchEx_iface.lpVtbl = &global_vtbl;
    obj->global->ref = 1;

    LIST_FOR_EACH_ENTRY(factory_host, &factory->hosts, struct script_host, entry)
    {
        hres = create_script_host(factory_host->language, factory_host->active_script, &obj->hosts, &host);
        if (FAILED(hres)) break;
        host->object = obj;

        hres = IActiveScript_AddNamedItem(host->active_script, L"scriptlet",
                                          SCRIPTITEM_ISVISIBLE | SCRIPTITEM_GLOBALMEMBERS);
        if (FAILED(hres)) break;

        hres = IActiveScript_AddNamedItem(host->active_script, L"globals", SCRIPTITEM_ISVISIBLE);
        if (FAILED(hres)) break;

        hres = IActiveScript_GetScriptDispatch(host->active_script, NULL, &script_dispatch);
        if (FAILED(hres)) return hres;

        hres = IDispatch_QueryInterface(script_dispatch, &IID_IDispatchEx, (void**)&host->script_dispatch);
        IDispatch_Release(script_dispatch);
        if (FAILED(hres))
        {
            FIXME("IDispatchEx not supported by script engine\n");
            return hres;
        }
    }

    if (SUCCEEDED(hres)) hres = parse_scripts(factory, &obj->hosts, TRUE);
    if (SUCCEEDED(hres)) hres = lookup_object_properties(obj, factory);
    if (FAILED(hres))
    {
        IDispatchEx_Release(&obj->IDispatchEx_iface);
        return hres;
    }

    *disp = &obj->IDispatchEx_iface;
    return S_OK;
}

static const char *debugstr_xml_name(struct scriptlet_factory *factory)
{
    const WCHAR *str;
    UINT len;
    HRESULT hres;
    hres = IXmlReader_GetLocalName(factory->xml_reader, &str, &len);
    if (FAILED(hres)) return "#err";
    return debugstr_wn(str, len);
}

static HRESULT next_xml_node(struct scriptlet_factory *factory, XmlNodeType *node_type)
{
    HRESULT hres;
    for (;;)
    {
        hres = IXmlReader_Read(factory->xml_reader, node_type);
        if (FAILED(hres)) break;
        if (*node_type == XmlNodeType_Whitespace) continue;
        if (*node_type == XmlNodeType_ProcessingInstruction)
        {
            FIXME("Ignoring processing instruction\n");
            continue;
        }
        break;
    }
    return hres;
}

static BOOL is_xml_name(struct scriptlet_factory *factory, const WCHAR *name)
{
    const WCHAR *qname;
    UINT len;
    HRESULT hres;
    hres = IXmlReader_GetQualifiedName(factory->xml_reader, &qname, &len);
    return hres == S_OK && len == wcslen(name) && !memcmp(qname, name, len * sizeof(WCHAR));
}

static HRESULT expect_end_element(struct scriptlet_factory *factory)
{
    XmlNodeType node_type;
    HRESULT hres;
    hres = next_xml_node(factory, &node_type);
    if (hres != S_OK || node_type != XmlNodeType_EndElement)
    {
        FIXME("Unexpected node %u %s\n", node_type, debugstr_xml_name(factory));
        return E_FAIL;
    }
    return S_OK;
}

static HRESULT expect_no_attributes(struct scriptlet_factory *factory)
{
    UINT count;
    HRESULT hres;
    hres = IXmlReader_GetAttributeCount(factory->xml_reader, &count);
    if (FAILED(hres)) return hres;
    if (!count) return S_OK;
    FIXME("Unexpected attributes\n");
    return E_FAIL;
}

static BOOL is_case_xml_name(struct scriptlet_factory *factory, const WCHAR *name)
{
    const WCHAR *qname;
    UINT len;
    HRESULT hres;
    hres = IXmlReader_GetQualifiedName(factory->xml_reader, &qname, &len);
    return hres == S_OK && len == wcslen(name) && !memicmp(qname, name, len * sizeof(WCHAR));
}

static HRESULT read_xml_value(struct scriptlet_factory *factory, WCHAR **ret)
{
    const WCHAR *str;
    UINT len;
    HRESULT hres;
    hres = IXmlReader_GetValue(factory->xml_reader, &str, &len);
    if (FAILED(hres)) return hres;
    if (!(*ret = heap_alloc((len + 1) * sizeof(WCHAR)))) return E_OUTOFMEMORY;
    memcpy(*ret, str, len * sizeof(WCHAR));
    (*ret)[len] = 0;
    return S_OK;
}

static HRESULT parse_scriptlet_registration(struct scriptlet_factory *factory)
{
    HRESULT hres;

    TRACE("\n");

    if (factory->have_registration)
    {
        FIXME("duplicated registration element\n");
        return E_FAIL;
    }
    factory->have_registration = TRUE;

    for (;;)
    {
        hres = IXmlReader_MoveToNextAttribute(factory->xml_reader);
        if (hres != S_OK) break;

        if (is_xml_name(factory, L"description"))
        {
            if (factory->description)
            {
                FIXME("Duplicated description\n");
                return E_FAIL;
            }
            hres = read_xml_value(factory, &factory->description);
            if (FAILED(hres)) return hres;
        }
        else if (is_case_xml_name(factory, L"progid"))
        {
            if (factory->progid)
            {
                FIXME("Duplicated progid\n");
                return E_FAIL;
            }
            hres = read_xml_value(factory, &factory->progid);
            if (FAILED(hres)) return hres;
        }
        else if (is_xml_name(factory, L"version"))
        {
            if (factory->version)
            {
                FIXME("Duplicated version\n");
                return E_FAIL;
            }
            hres = read_xml_value(factory, &factory->version);
            if (FAILED(hres)) return hres;
        }
        else if (is_xml_name(factory, L"classid"))
        {
            if (factory->classid_str)
            {
                FIXME("Duplicated classid attribute\n");
                return E_FAIL;
            }
            hres = read_xml_value(factory, &factory->classid_str);
            if (FAILED(hres)) return hres;
            hres = IIDFromString(factory->classid_str, &factory->classid);
            if (FAILED(hres))
            {
                FIXME("Invalid classid %s\n", debugstr_w(factory->classid_str));

                return E_FAIL;
            }
        }
        else
        {
            FIXME("Unexpected attribute\n");
            return E_NOTIMPL;
        }
    }

    if (!factory->progid && !factory->classid_str)
    {
        FIXME("Incomplet registration element\n");
        return E_FAIL;
    }

    if (factory->progid)
    {
        size_t progid_len = wcslen(factory->progid);
        size_t version_len = wcslen(factory->version);

        if (!(factory->versioned_progid = heap_alloc((progid_len + version_len + 2) * sizeof(WCHAR))))
            return E_OUTOFMEMORY;

        memcpy(factory->versioned_progid, factory->progid, (progid_len + 1) * sizeof(WCHAR));
        if (version_len)
        {
            factory->versioned_progid[progid_len++] = '.';
            wcscpy(factory->versioned_progid + progid_len, factory->version);
        }
        else factory->versioned_progid[progid_len] = 0;
    }

    return expect_end_element(factory);
}

static HRESULT parse_scriptlet_public(struct scriptlet_factory *factory)
{
    struct scriptlet_member *member, *member_iter;
    enum member_type member_type;
    XmlNodeType node_type;
    BOOL empty;
    HRESULT hres;

    TRACE("\n");

    if (factory->have_public)
    {
        FIXME("duplicated public element\n");
        return E_FAIL;
    }
    factory->have_public = TRUE;

    for (;;)
    {
        hres = next_xml_node(factory, &node_type);
        if (FAILED(hres)) return hres;
        if (node_type == XmlNodeType_EndElement) break;
        if (node_type != XmlNodeType_Element)
        {
            FIXME("Unexpected node type %u\n", node_type);
            return E_FAIL;
        }

        if (is_xml_name(factory, L"method"))
            member_type = MEMBER_METHOD;
        else if (is_xml_name(factory, L"property"))
            member_type = MEMBER_PROPERTY;
        else
        {
            FIXME("Unexpected element %s\n", debugstr_xml_name(factory));
            return E_NOTIMPL;
        }

        empty = IXmlReader_IsEmptyElement(factory->xml_reader);

        hres = IXmlReader_MoveToAttributeByName(factory->xml_reader, L"name", NULL);
        if (hres != S_OK)
        {
            FIXME("Missing method name\n");
            return E_FAIL;
        }

        if (!(member = heap_alloc(sizeof(*member)))) return E_OUTOFMEMORY;
        member->type = member_type;

        hres = read_xml_value(factory, &member->name);
        if (FAILED(hres))
        {
            heap_free(member);
            return hres;
        }

        LIST_FOR_EACH_ENTRY(member_iter, &factory->members, struct scriptlet_member, entry)
        {
            if (!wcsicmp(member_iter->name, member->name))
            {
                FIXME("Duplicated member %s\n", debugstr_w(member->name));
                heap_free(member);
                return E_FAIL;
            }
        }
        list_add_tail(&factory->members, &member->entry);

        switch (member_type)
        {
        case MEMBER_METHOD:
            list_init(&member->u.parameters);

            if (empty) break;
            for (;;)
            {
                struct method_parameter *parameter;

                hres = next_xml_node(factory, &node_type);
                if (FAILED(hres)) return hres;
                if (node_type == XmlNodeType_EndElement) break;
                if (node_type != XmlNodeType_Element)
                {
                    FIXME("Unexpected node type %u\n", node_type);
                    return E_FAIL;
                }
                if (!is_case_xml_name(factory, L"parameter"))
                {
                    FIXME("Unexpected method element\n");
                    return E_FAIL;
                }

                empty = IXmlReader_IsEmptyElement(factory->xml_reader);

                hres = IXmlReader_MoveToAttributeByName(factory->xml_reader, L"name", NULL);
                if (hres != S_OK)
                {
                    FIXME("Missing parameter name\n");
                    return E_FAIL;
                }

                if (!(parameter = heap_alloc_zero(sizeof(*parameter)))) return E_OUTOFMEMORY;
                list_add_tail(&member->u.parameters, &parameter->entry);

                hres = read_xml_value(factory, &parameter->name);
                if (FAILED(hres)) return hres;
                if (!empty && FAILED(hres = expect_end_element(factory))) return hres;
            }
            break;

        case MEMBER_PROPERTY:
            member->u.flags = 0;

            if (empty) break;
            for (;;)
            {
                hres = next_xml_node(factory, &node_type);
                if (FAILED(hres)) return hres;
                if (node_type == XmlNodeType_EndElement) break;
                if (node_type != XmlNodeType_Element)
                {
                    FIXME("Unexpected node type %u\n", node_type);
                    return E_FAIL;
                }
                if (is_case_xml_name(factory, L"get"))
                {
                    if (member->u.flags & PROP_GETTER)
                    {
                        FIXME("Duplicated getter\n");
                        return E_FAIL;
                    }
                    member->u.flags |= PROP_GETTER;
                }
                else if (is_case_xml_name(factory, L"put"))
                {
                    if (member->u.flags & PROP_SETTER)
                    {
                        FIXME("Duplicated setter\n");
                        return E_FAIL;
                    }
                    member->u.flags |= PROP_SETTER;
                }
                else
                {
                    FIXME("Unexpected property element %s\n", debugstr_xml_name(factory));
                    return E_FAIL;
                }

                empty = IXmlReader_IsEmptyElement(factory->xml_reader);
                if (!empty && FAILED(hres = expect_end_element(factory))) return hres;
            }
            if (!member->u.flags)
            {
                FIXME("No getter or setter\n");
                return E_NOTIMPL;
            }
            break;
        }
        if (FAILED(hres)) return hres;
    }

    return S_OK;
}

static HRESULT parse_scriptlet_script(struct scriptlet_factory *factory, struct scriptlet_script *script)
{
    XmlNodeType node_type;
    size_t buf_size, size;
    WCHAR *new_body;
    DWORD read;
    HRESULT hres;

    TRACE("\n");

    for (;;)
    {
        hres = IXmlReader_MoveToNextAttribute(factory->xml_reader);
        if (hres != S_OK) break;

        if (is_xml_name(factory, L"language"))
        {
            if (script->language)
            {
                FIXME("Duplicated language\n");
                return E_FAIL;
            }
            hres = read_xml_value(factory, &script->language);
            if (FAILED(hres)) return hres;
        }
        else
        {
            FIXME("Unexpected attribute\n");
            return E_NOTIMPL;
        }
    }

    if (!script->language)
    {
        FIXME("Language not specified\n");
        return E_FAIL;
    }

    if (FAILED(hres = next_xml_node(factory, &node_type))) return hres;

    if (node_type != XmlNodeType_Text && node_type != XmlNodeType_CDATA)
    {
        FIXME("Unexpected node type %u\n", node_type);
        return E_FAIL;
    }

    if (!(script->body = heap_alloc((buf_size = 1024) * sizeof(WCHAR)))) return E_OUTOFMEMORY;
    size = 0;

    for (;;)
    {
        read = 0;
        hres = IXmlReader_ReadValueChunk(factory->xml_reader, script->body + size, buf_size - size - 1, &read);
        if (FAILED(hres)) return hres;
        size += read;
        if (hres == S_FALSE) break;
        if (size + 1 == buf_size)
        {
            if (!(new_body = heap_realloc(script->body, (buf_size *= 2) * sizeof(WCHAR)))) return E_OUTOFMEMORY;
            script->body = new_body;
        }
    }
    script->body[size++] = 0;
    if (size != buf_size) script->body = heap_realloc(script->body, size * sizeof(WCHAR));

    TRACE("body %s\n", debugstr_w(script->body));
    return expect_end_element(factory);
}

static HRESULT parse_scriptlet_file(struct scriptlet_factory *factory, const WCHAR *url)
{
    XmlNodeType node_type;
    IBindCtx *bind_ctx;
    IStream *stream;
    HRESULT hres;

    hres = CreateURLMoniker(NULL, url, &factory->moniker);
    if (FAILED(hres))
    {
        WARN("CreateURLMoniker failed: %08x\n", hres);
        return hres;
    }

    hres = CreateBindCtx(0, &bind_ctx);
    if(FAILED(hres))
        return hres;

    hres = IMoniker_BindToStorage(factory->moniker, bind_ctx, NULL, &IID_IStream, (void**)&stream);
    IBindCtx_Release(bind_ctx);
    if (FAILED(hres))
        return hres;

    hres = CreateXmlReader(&IID_IXmlReader, (void**)&factory->xml_reader, NULL);
    if(SUCCEEDED(hres))
        hres = IXmlReader_SetInput(factory->xml_reader, (IUnknown*)stream);
    IStream_Release(stream);
    if (FAILED(hres))
        return hres;

    hres = next_xml_node(factory, &node_type);
    if (hres == S_OK && node_type == XmlNodeType_XmlDeclaration)
        hres = next_xml_node(factory, &node_type);
    if (FAILED(hres))
        return hres;

    if (node_type != XmlNodeType_Element || !is_xml_name(factory, L"component"))
    {
        FIXME("Unexpected %s element\n", debugstr_xml_name(factory));
        return E_FAIL;
    }

    hres = expect_no_attributes(factory);
    if (FAILED(hres)) return hres;

    for (;;)
    {
        hres = next_xml_node(factory, &node_type);
        if (FAILED(hres)) return hres;
        if (node_type == XmlNodeType_EndElement) break;
        if (node_type != XmlNodeType_Element)
        {
            FIXME("Unexpected node type %u\n", node_type);
            return E_FAIL;
        }

        if (is_xml_name(factory, L"registration"))
            hres = parse_scriptlet_registration(factory);
        else if (is_xml_name(factory, L"public"))
            hres = parse_scriptlet_public(factory);
        else if (is_xml_name(factory, L"script"))
        {
            struct scriptlet_script *script;
            if (!(script = heap_alloc_zero(sizeof(*script)))) return E_OUTOFMEMORY;
            list_add_tail(&factory->scripts, &script->entry);
            hres = parse_scriptlet_script(factory, script);
            if (FAILED(hres)) return hres;
        }
        else
        {
            FIXME("Unexpected element %s\n", debugstr_xml_name(factory));
            return E_NOTIMPL;
        }
        if (FAILED(hres)) return hres;
    }

    hres = next_xml_node(factory, &node_type);
    if (hres != S_FALSE)
    {
        FIXME("Unexpected node type %u\n", node_type);
        return E_FAIL;
    }
    return S_OK;
}

static inline struct scriptlet_factory *impl_from_IClassFactory(IClassFactory *iface)
{
    return CONTAINING_RECORD(iface, struct scriptlet_factory, IClassFactory_iface);
}

static HRESULT WINAPI scriptlet_factory_QueryInterface(IClassFactory *iface, REFIID riid, void **ppv)
{
    struct scriptlet_factory *This = impl_from_IClassFactory(iface);

    if (IsEqualGUID(&IID_IUnknown, riid))
    {
        TRACE("(%p)->(IID_IUnknown %p)\n", iface, ppv);
        *ppv = &This->IClassFactory_iface;
    }
    else if (IsEqualGUID(&IID_IClassFactory, riid))
    {
        TRACE("(%p)->(IID_IClassFactory %p)\n", iface, ppv);
        *ppv = &This->IClassFactory_iface;
    }
    else
    {
        WARN("(%p)->(%s %p)\n", iface, debugstr_guid(riid), ppv);
        *ppv = NULL;
        return E_NOINTERFACE;
    }

    IUnknown_AddRef((IUnknown *)*ppv);
    return S_OK;
}

static ULONG WINAPI scriptlet_factory_AddRef(IClassFactory *iface)
{
    struct scriptlet_factory *This = impl_from_IClassFactory(iface);
    ULONG ref = InterlockedIncrement(&This->ref);

    TRACE("(%p) ref=%d\n", This, ref);

    return ref;
}

static ULONG WINAPI scriptlet_factory_Release(IClassFactory *iface)
{
    struct scriptlet_factory *This = impl_from_IClassFactory(iface);
    ULONG ref = InterlockedDecrement(&This->ref);

    TRACE("(%p) ref=%d\n", This, ref);

    if (!ref)
    {
        if (This->moniker) IMoniker_Release(This->moniker);
        if (This->xml_reader) IXmlReader_Release(This->xml_reader);
        detach_script_hosts(&This->hosts);
        while (!list_empty(&This->members))
        {
            struct scriptlet_member *member = LIST_ENTRY(list_head(&This->members), struct scriptlet_member, entry);
            list_remove(&member->entry);
            heap_free(member->name);
            if (member->type == MEMBER_METHOD)
            {
                while (!list_empty(&member->u.parameters))
                {
                    struct method_parameter *parameter = LIST_ENTRY(list_head(&member->u.parameters), struct method_parameter, entry);
                    list_remove(&parameter->entry);
                    heap_free(parameter->name);
                    heap_free(parameter);
                }
            }
            heap_free(member);
        }
        while (!list_empty(&This->scripts))
        {
            struct scriptlet_script *script = LIST_ENTRY(list_head(&This->scripts), struct scriptlet_script, entry);
            list_remove(&script->entry);
            heap_free(script->language);
            heap_free(script->body);
            heap_free(script);
        }
        heap_free(This->classid_str);
        heap_free(This->description);
        heap_free(This->versioned_progid);
        heap_free(This->progid);
        heap_free(This->version);
        heap_free(This);
    }
    return ref;
}

static HRESULT WINAPI scriptlet_factory_CreateInstance(IClassFactory *iface, IUnknown *outer, REFIID riid, void **ppv)
{
    struct scriptlet_factory *This = impl_from_IClassFactory(iface);
    IDispatchEx *disp;
    HRESULT hres;

    TRACE("(%p)->(%p %s %p)\n", This, outer, debugstr_guid(riid), ppv);

    if (outer) FIXME("outer not supported\n");

    if (list_empty(&This->hosts))
    {
        hres = create_scriptlet_hosts(This, &This->hosts);
        if (FAILED(hres)) return hres;

        hres = parse_scripts(This, &This->hosts, FALSE);
        if (FAILED(hres))
        {
            detach_script_hosts(&This->hosts);
            return hres;
        }
    }

    hres = create_scriptlet_instance(This, &disp);
    if (FAILED(hres)) return hres;

    hres = IDispatchEx_QueryInterface(disp, riid, ppv);
    IDispatchEx_Release(disp);
    return hres;
}

static HRESULT WINAPI scriptlet_factory_LockServer(IClassFactory *iface, BOOL fLock)
{
    struct scriptlet_factory *This = impl_from_IClassFactory(iface);
    TRACE("(%p)->(%x)\n", This, fLock);
    return S_OK;
}

static const struct IClassFactoryVtbl scriptlet_factory_vtbl =
{
    scriptlet_factory_QueryInterface,
    scriptlet_factory_AddRef,
    scriptlet_factory_Release,
    scriptlet_factory_CreateInstance,
    scriptlet_factory_LockServer
};

static HRESULT create_scriptlet_factory(const WCHAR *url, struct scriptlet_factory **ret)
{
    struct scriptlet_factory *factory;
    HRESULT hres;

    TRACE("%s\n", debugstr_w(url));

    if (!(factory = heap_alloc_zero(sizeof(*factory))))
    {
        IClassFactory_Release(&factory->IClassFactory_iface);
        return E_OUTOFMEMORY;
    }

    factory->IClassFactory_iface.lpVtbl = &scriptlet_factory_vtbl;
    factory->ref = 1;
    list_init(&factory->hosts);
    list_init(&factory->members);
    list_init(&factory->scripts);

    hres = parse_scriptlet_file(factory, url);
    if (FAILED(hres))
    {
        IClassFactory_Release(&factory->IClassFactory_iface);
        return hres;
    }

    *ret = factory;
    return S_OK;
}

static HRESULT register_scriptlet(struct scriptlet_factory *factory)
{
    HKEY key, clsid_key, obj_key;
    LSTATUS status;
    HRESULT hres;

    if (factory->classid_str)
    {
        WCHAR *url;

        status = RegCreateKeyW(HKEY_CLASSES_ROOT, L"CLSID", &clsid_key);
        if (status) return E_ACCESSDENIED;

        status = RegCreateKeyW(clsid_key, factory->classid_str, &obj_key);
        RegCloseKey(clsid_key);
        if (status) return E_ACCESSDENIED;

        hres = IMoniker_GetDisplayName(factory->moniker, NULL, NULL, &url);
        if (FAILED(hres))
        {
            RegCloseKey(obj_key);
            return hres;
        }
        status = RegCreateKeyW(obj_key, L"ScriptletURL", &key);
        if (!status)
        {
            status = RegSetValueExW(key, NULL, 0, REG_SZ, (BYTE*)url,
                                    (wcslen(url) + 1) * sizeof(WCHAR));
            RegCloseKey(key);
        }
        CoTaskMemFree(url);

        if (factory->description)
            status = RegSetValueExW(obj_key, NULL, 0, REG_SZ, (BYTE*)factory->description,
                                    (wcslen(factory->description) + 1) * sizeof(WCHAR));

        if (!status)
        {
            status = RegCreateKeyW(obj_key, L"InprocServer32", &key);
            if (!status)
            {
                WCHAR str[MAX_PATH];
                GetModuleFileNameW(scrobj_instance, str, MAX_PATH);
                status = RegSetValueExW(key, NULL, 0, REG_SZ, (BYTE*)str, (wcslen(str) + 1) * sizeof(WCHAR));
                RegCloseKey(key);
            }
        }

        if (!status && factory->versioned_progid)
        {
            status = RegCreateKeyW(obj_key, L"ProgID", &key);
            if (!status)
            {
                status = RegSetValueExW(key, NULL, 0, REG_SZ, (BYTE*)factory->versioned_progid,
                                        (wcslen(factory->versioned_progid) + 1) * sizeof(WCHAR));
                RegCloseKey(key);
            }
        }

        if (!status && factory->progid)
        {
            status = RegCreateKeyW(obj_key, L"VersionIndependentProgID", &key);
            if (!status)
            {
                status = RegSetValueExW(key, NULL, 0, REG_SZ, (BYTE*)factory->progid,
                                        (wcslen(factory->progid) + 1) * sizeof(WCHAR));
                RegCloseKey(key);
            }
        }

        RegCloseKey(obj_key);
        if (status) return E_ACCESSDENIED;
    }

    if (factory->progid)
    {
        status = RegCreateKeyW(HKEY_CLASSES_ROOT, factory->progid, &key);
        if (status) return E_ACCESSDENIED;

        if (factory->description)
            status = RegSetValueExW(key, NULL, 0, REG_SZ, (BYTE*)factory->description,
                                    (wcslen(factory->description) + 1) * sizeof(WCHAR));

        if (!status && factory->classid_str)
        {
            status = RegCreateKeyW(key, L"CLSID", &clsid_key);
            if (!status)
            {
                status = RegSetValueExW(clsid_key, NULL, 0, REG_SZ, (BYTE*)factory->classid_str,
                                        (wcslen(factory->classid_str) + 1) * sizeof(WCHAR));
                RegCloseKey(clsid_key);
            }
        }

        RegCloseKey(key);
        if (status) return E_ACCESSDENIED;
    }

    if (factory->versioned_progid)
    {
        status = RegCreateKeyW(HKEY_CLASSES_ROOT, factory->versioned_progid, &key);
        if (status) return E_ACCESSDENIED;

        if (factory->description)
            status = RegSetValueExW(key, NULL, 0, REG_SZ, (BYTE*)factory->description,
                                    (wcslen(factory->description) + 1) * sizeof(WCHAR));

        if (!status && factory->classid_str)
        {
            status = RegCreateKeyW(key, L"CLSID", &clsid_key);
            if (!status)
            {
                status = RegSetValueExW(clsid_key, NULL, 0, REG_SZ, (BYTE*)factory->classid_str,
                                        (wcslen(factory->classid_str) + 1) * sizeof(WCHAR));
                RegCloseKey(clsid_key);
            }
        }

        RegCloseKey(key);
        if (status) return E_ACCESSDENIED;
    }

    return S_OK;
}

static HRESULT unregister_scriptlet(struct scriptlet_factory *factory)
{
    LSTATUS status;

    if (factory->classid_str)
    {
        HKEY clsid_key;
        status = RegCreateKeyW(HKEY_CLASSES_ROOT, L"CLSID", &clsid_key);
        if (!status)
        {
            RegDeleteTreeW(clsid_key, factory->classid_str);
            RegCloseKey(clsid_key);
        }
    }

    if (factory->progid) RegDeleteTreeW(HKEY_CLASSES_ROOT, factory->progid);
    if (factory->versioned_progid) RegDeleteTreeW(HKEY_CLASSES_ROOT, factory->versioned_progid);
    return S_OK;
}

struct scriptlet_typelib
{
    IGenScriptletTLib IGenScriptletTLib_iface;
    LONG ref;

    BSTR guid;
};

static inline struct scriptlet_typelib *impl_from_IGenScriptletTLib(IGenScriptletTLib *iface)
{
    return CONTAINING_RECORD(iface, struct scriptlet_typelib, IGenScriptletTLib_iface);
}

static HRESULT WINAPI scriptlet_typelib_QueryInterface(IGenScriptletTLib *iface, REFIID riid, void **obj)
{
    struct scriptlet_typelib *This = impl_from_IGenScriptletTLib(iface);

    TRACE("(%p, %s, %p)\n", This, debugstr_guid(riid), obj);

    if (IsEqualIID(riid, &IID_IGenScriptletTLib) ||
            IsEqualIID(riid, &IID_IDispatch) ||
            IsEqualIID(riid, &IID_IUnknown))
    {
        *obj = iface;
        IGenScriptletTLib_AddRef(iface);
        return S_OK;
    }

    *obj = NULL;
    return E_NOINTERFACE;
}

static ULONG WINAPI scriptlet_typelib_AddRef(IGenScriptletTLib *iface)
{
    struct scriptlet_typelib *This = impl_from_IGenScriptletTLib(iface);
    ULONG ref = InterlockedIncrement(&This->ref);
    TRACE("(%p)->(%u)\n", This, ref);
    return ref;
}

static ULONG WINAPI scriptlet_typelib_Release(IGenScriptletTLib *iface)
{
    struct scriptlet_typelib *This = impl_from_IGenScriptletTLib(iface);
    LONG ref = InterlockedDecrement(&This->ref);

    TRACE("(%p)->(%u)\n", This, ref);

    if (!ref)
    {
        SysFreeString(This->guid);
        HeapFree(GetProcessHeap(), 0, This);
    }

    return ref;
}

static HRESULT WINAPI scriptlet_typelib_GetTypeInfoCount(IGenScriptletTLib *iface, UINT *count)
{
    struct scriptlet_typelib *This = impl_from_IGenScriptletTLib(iface);

    TRACE("(%p, %p)\n", This, count);

    *count = 1;
    return S_OK;
}

static HRESULT WINAPI scriptlet_typelib_GetTypeInfo(IGenScriptletTLib *iface, UINT index, LCID lcid,
    ITypeInfo **tinfo)
{
    struct scriptlet_typelib *This = impl_from_IGenScriptletTLib(iface);

    TRACE("(%p, %u, %x, %p)\n", This, index, lcid, tinfo);

    return get_typeinfo(IGenScriptletTLib_tid, tinfo);
}

static HRESULT WINAPI scriptlet_typelib_GetIDsOfNames(IGenScriptletTLib *iface, REFIID riid, LPOLESTR *names,
    UINT cNames, LCID lcid, DISPID *dispid)
{
    struct scriptlet_typelib *This = impl_from_IGenScriptletTLib(iface);
    ITypeInfo *typeinfo;
    HRESULT hr;

    TRACE("(%p, %s, %p, %u, %x, %p)\n", This, debugstr_guid(riid), names, cNames, lcid, dispid);

    hr = get_typeinfo(IGenScriptletTLib_tid, &typeinfo);
    if (SUCCEEDED(hr))
    {
        hr = ITypeInfo_GetIDsOfNames(typeinfo, names, cNames, dispid);
        ITypeInfo_Release(typeinfo);
    }

    return hr;
}

static HRESULT WINAPI scriptlet_typelib_Invoke(IGenScriptletTLib *iface, DISPID dispid, REFIID riid,
    LCID lcid, WORD flags, DISPPARAMS *params, VARIANT *result, EXCEPINFO *ei, UINT *argerr)
{
    struct scriptlet_typelib *This = impl_from_IGenScriptletTLib(iface);
    ITypeInfo *typeinfo;
    HRESULT hr;

    TRACE("(%p, %d, %s, %x, %x, %p, %p, %p, %p)\n", This, dispid, debugstr_guid(riid), lcid, flags,
        params, result, ei, argerr);

    hr = get_typeinfo(IGenScriptletTLib_tid, &typeinfo);
    if (SUCCEEDED(hr))
    {
        hr = ITypeInfo_Invoke(typeinfo, &This->IGenScriptletTLib_iface, dispid, flags,
                params, result, ei, argerr);
        ITypeInfo_Release(typeinfo);
    }

    return hr;
}

static HRESULT WINAPI scriptlet_typelib_AddURL(IGenScriptletTLib *iface, BSTR url)
{
    struct scriptlet_typelib *This = impl_from_IGenScriptletTLib(iface);

    FIXME("(%p, %s): stub\n", This, debugstr_w(url));

    return E_NOTIMPL;
}

static HRESULT WINAPI scriptlet_typelib_put_Path(IGenScriptletTLib *iface, BSTR path)
{
    struct scriptlet_typelib *This = impl_from_IGenScriptletTLib(iface);

    FIXME("(%p, %s): stub\n", This, debugstr_w(path));

    return E_NOTIMPL;
}

static HRESULT WINAPI scriptlet_typelib_get_Path(IGenScriptletTLib *iface, BSTR *path)
{
    struct scriptlet_typelib *This = impl_from_IGenScriptletTLib(iface);

    FIXME("(%p, %p): stub\n", This, path);

    return E_NOTIMPL;
}

static HRESULT WINAPI scriptlet_typelib_put_Doc(IGenScriptletTLib *iface, BSTR doc)
{
    struct scriptlet_typelib *This = impl_from_IGenScriptletTLib(iface);

    FIXME("(%p, %s): stub\n", This, debugstr_w(doc));

    return E_NOTIMPL;
}

static HRESULT WINAPI scriptlet_typelib_get_Doc(IGenScriptletTLib *iface, BSTR *doc)
{
    struct scriptlet_typelib *This = impl_from_IGenScriptletTLib(iface);

    FIXME("(%p, %p): stub\n", This, doc);

    return E_NOTIMPL;
}

static HRESULT WINAPI scriptlet_typelib_put_Name(IGenScriptletTLib *iface, BSTR name)
{
    struct scriptlet_typelib *This = impl_from_IGenScriptletTLib(iface);

    FIXME("(%p, %s): stub\n", This, debugstr_w(name));

    return E_NOTIMPL;
}

static HRESULT WINAPI scriptlet_typelib_get_Name(IGenScriptletTLib *iface, BSTR *name)
{
    struct scriptlet_typelib *This = impl_from_IGenScriptletTLib(iface);

    FIXME("(%p, %p): stub\n", This, name);

    return E_NOTIMPL;
}

static HRESULT WINAPI scriptlet_typelib_put_MajorVersion(IGenScriptletTLib *iface, WORD version)
{
    struct scriptlet_typelib *This = impl_from_IGenScriptletTLib(iface);

    FIXME("(%p, %x): stub\n", This, version);

    return E_NOTIMPL;
}

static HRESULT WINAPI scriptlet_typelib_get_MajorVersion(IGenScriptletTLib *iface, WORD *version)
{
    struct scriptlet_typelib *This = impl_from_IGenScriptletTLib(iface);

    FIXME("(%p, %p): stub\n", This, version);

    return E_NOTIMPL;
}

static HRESULT WINAPI scriptlet_typelib_put_MinorVersion(IGenScriptletTLib *iface, WORD version)
{
    struct scriptlet_typelib *This = impl_from_IGenScriptletTLib(iface);

    FIXME("(%p, %x): stub\n", This, version);

    return E_NOTIMPL;
}

static HRESULT WINAPI scriptlet_typelib_get_MinorVersion(IGenScriptletTLib *iface, WORD *version)
{
    struct scriptlet_typelib *This = impl_from_IGenScriptletTLib(iface);

    FIXME("(%p, %p): stub\n", This, version);

    return E_NOTIMPL;
}

static HRESULT WINAPI scriptlet_typelib_Write(IGenScriptletTLib *iface)
{
    struct scriptlet_typelib *This = impl_from_IGenScriptletTLib(iface);

    FIXME("(%p): stub\n", This);

    return E_NOTIMPL;
}

static HRESULT WINAPI scriptlet_typelib_Reset(IGenScriptletTLib *iface)
{
    struct scriptlet_typelib *This = impl_from_IGenScriptletTLib(iface);

    FIXME("(%p): stub\n", This);

    return E_NOTIMPL;
}

static HRESULT WINAPI scriptlet_typelib_put_GUID(IGenScriptletTLib *iface, BSTR guid)
{
    struct scriptlet_typelib *This = impl_from_IGenScriptletTLib(iface);

    FIXME("(%p, %s): stub\n", This, debugstr_w(guid));

    return E_NOTIMPL;
}

static HRESULT WINAPI scriptlet_typelib_get_GUID(IGenScriptletTLib *iface, BSTR *ret)
{
    struct scriptlet_typelib *This = impl_from_IGenScriptletTLib(iface);

    TRACE("(%p, %p)\n", This, ret);

    *ret = NULL;

    if (!This->guid)
    {
        WCHAR guidW[39];
        GUID guid;
        HRESULT hr;

        hr = CoCreateGuid(&guid);
        if (FAILED(hr))
            return hr;

        hr = StringFromGUID2(&guid, guidW, ARRAY_SIZE(guidW));
        if (FAILED(hr))
            return hr;

        if (!(This->guid = SysAllocString(guidW)))
            return E_OUTOFMEMORY;
    }

    *ret = SysAllocString(This->guid);
    return *ret ? S_OK : E_OUTOFMEMORY;
}

static const IGenScriptletTLibVtbl scriptlet_typelib_vtbl =
{
    scriptlet_typelib_QueryInterface,
    scriptlet_typelib_AddRef,
    scriptlet_typelib_Release,
    scriptlet_typelib_GetTypeInfoCount,
    scriptlet_typelib_GetTypeInfo,
    scriptlet_typelib_GetIDsOfNames,
    scriptlet_typelib_Invoke,
    scriptlet_typelib_AddURL,
    scriptlet_typelib_put_Path,
    scriptlet_typelib_get_Path,
    scriptlet_typelib_put_Doc,
    scriptlet_typelib_get_Doc,
    scriptlet_typelib_put_Name,
    scriptlet_typelib_get_Name,
    scriptlet_typelib_put_MajorVersion,
    scriptlet_typelib_get_MajorVersion,
    scriptlet_typelib_put_MinorVersion,
    scriptlet_typelib_get_MinorVersion,
    scriptlet_typelib_Write,
    scriptlet_typelib_Reset,
    scriptlet_typelib_put_GUID,
    scriptlet_typelib_get_GUID
};

BOOL WINAPI DllMain(HINSTANCE hinst, DWORD reason, void *reserved)
{
    TRACE("%p, %u, %p\n", hinst, reason, reserved);

    switch (reason)
    {
        case DLL_PROCESS_ATTACH:
            DisableThreadLibraryCalls(hinst);
            scrobj_instance = hinst;
            break;
        case DLL_PROCESS_DETACH:
            if (reserved) break;
            release_typelib();
            break;
    }
    return TRUE;
}

/***********************************************************************
 *      DllInstall (scrobj.@)
 */
HRESULT WINAPI DllInstall(BOOL install, const WCHAR *arg)
{
    struct scriptlet_factory *factory;
    HRESULT hres;

    if (install)
    {
        hres = DllRegisterServer();
        if (!arg || FAILED(hres)) return hres;
    }
    else if (!arg)
        return DllUnregisterServer();

    hres = create_scriptlet_factory(arg, &factory);
    if (SUCCEEDED(hres))
    {
        if (factory->have_registration)
        {
            /* validate scripts */
            hres = create_scriptlet_hosts(factory, &factory->hosts);
            if (SUCCEEDED(hres)) hres = parse_scripts(factory, &factory->hosts, FALSE);
        }
        else
        {
            FIXME("No registration info\n");
            hres = E_FAIL;
        }
        if (SUCCEEDED(hres))
        {
            if (install)
                hres = register_scriptlet(factory);
            else
                hres = unregister_scriptlet(factory);
        }
        IClassFactory_Release(&factory->IClassFactory_iface);
    }

    return hres;
}

static HRESULT WINAPI scriptlet_typelib_CreateInstance(IClassFactory *factory, IUnknown *outer, REFIID riid, void **obj)
{
    struct scriptlet_typelib *This;
    HRESULT hr;

    TRACE("(%p, %p, %s, %p)\n", factory, outer, debugstr_guid(riid), obj);

    *obj = NULL;

    This = HeapAlloc(GetProcessHeap(), 0, sizeof(*This));
    if (!This)
        return E_OUTOFMEMORY;

    This->IGenScriptletTLib_iface.lpVtbl = &scriptlet_typelib_vtbl;
    This->ref = 1;
    This->guid = NULL;

    hr = IGenScriptletTLib_QueryInterface(&This->IGenScriptletTLib_iface, riid, obj);
    IGenScriptletTLib_Release(&This->IGenScriptletTLib_iface);
    return hr;
}

static HRESULT WINAPI scrruncf_QueryInterface(IClassFactory *iface, REFIID riid, void **ppv)
{
    *ppv = NULL;

    if (IsEqualGUID(&IID_IUnknown, riid))
    {
        TRACE("(%p)->(IID_IUnknown %p)\n", iface, ppv);
        *ppv = iface;
    }
    else if (IsEqualGUID(&IID_IClassFactory, riid))
    {
        TRACE("(%p)->(IID_IClassFactory %p)\n", iface, ppv);
        *ppv = iface;
    }

    if (*ppv)
    {
        IUnknown_AddRef((IUnknown *)*ppv);
        return S_OK;
    }

    WARN("(%p)->(%s %p)\n", iface, debugstr_guid(riid), ppv);
    return E_NOINTERFACE;
}

static ULONG WINAPI scrruncf_AddRef(IClassFactory *iface)
{
    TRACE("(%p)\n", iface);
    return 2;
}

static ULONG WINAPI scrruncf_Release(IClassFactory *iface)
{
    TRACE("(%p)\n", iface);
    return 1;
}

static HRESULT WINAPI scrruncf_LockServer(IClassFactory *iface, BOOL fLock)
{
    TRACE("(%p)->(%x)\n", iface, fLock);
    return S_OK;
}

static const struct IClassFactoryVtbl scriptlet_typelib_factory_vtbl =
{
    scrruncf_QueryInterface,
    scrruncf_AddRef,
    scrruncf_Release,
    scriptlet_typelib_CreateInstance,
    scrruncf_LockServer
};

static IClassFactory scriptlet_typelib_factory = { &scriptlet_typelib_factory_vtbl };

/***********************************************************************
 *      DllGetClassObject (scrobj.@)
 */
HRESULT WINAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv)
{
    WCHAR key_name[128], *p;
    LONG size = 0;
    LSTATUS status;

    if (IsEqualGUID(&CLSID_TypeLib, rclsid))
    {
        TRACE("(Scriptlet.TypeLib %s %p)\n", debugstr_guid(riid), ppv);
        return IClassFactory_QueryInterface(&scriptlet_typelib_factory, riid, ppv);
    }

    wcscpy(key_name, L"CLSID\\");
    p = key_name + wcslen(key_name);
    p += StringFromGUID2(rclsid, p, ARRAY_SIZE(key_name) - (p - key_name)) - 1;
    wcscpy(p, L"\\ScriptletURL");
    status = RegQueryValueW(HKEY_CLASSES_ROOT, key_name, NULL, &size);
    if (!status)
    {
        struct scriptlet_factory *factory;
        WCHAR *url;
        HRESULT hres;

        if (!(url = heap_alloc(size * sizeof(WCHAR)))) return E_OUTOFMEMORY;
        status = RegQueryValueW(HKEY_CLASSES_ROOT, key_name, url, &size);

        hres = create_scriptlet_factory(url, &factory);
        heap_free(url);
        if (FAILED(hres)) return hres;

        hres = IClassFactory_QueryInterface(&factory->IClassFactory_iface, riid, ppv);
        IClassFactory_Release(&factory->IClassFactory_iface);
        return hres;
    }

    FIXME("%s %s %p\n", debugstr_guid(rclsid), debugstr_guid(riid), ppv);
    return CLASS_E_CLASSNOTAVAILABLE;
}
