/*
 * Copyright 2012 Stefan Leichter
 * Copyright 2012 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 "atlbase.h"
#include "atlcom.h"

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

WINE_DEFAULT_DEBUG_CHANNEL(atl);

#define ATLVer1Size FIELD_OFFSET(_ATL_MODULEW, dwAtlBuildVer)

HINSTANCE atl_instance;

typedef unsigned char cpp_bool;

static ICatRegister *catreg;

/***********************************************************************
 *           AtlAdvise         [atl100.@]
 */
HRESULT WINAPI AtlAdvise(IUnknown *pUnkCP, IUnknown *pUnk, const IID *iid, DWORD *pdw)
{
    IConnectionPointContainer *container;
    IConnectionPoint *cp;
    HRESULT hres;

    TRACE("%p %p %p %p\n", pUnkCP, pUnk, iid, pdw);

    if(!pUnkCP)
        return E_INVALIDARG;

    hres = IUnknown_QueryInterface(pUnkCP, &IID_IConnectionPointContainer, (void**)&container);
    if(FAILED(hres))
        return hres;

    hres = IConnectionPointContainer_FindConnectionPoint(container, iid, &cp);
    IConnectionPointContainer_Release(container);
    if(FAILED(hres))
        return hres;

    hres = IConnectionPoint_Advise(cp, pUnk, pdw);
    IConnectionPoint_Release(cp);
    return hres;
}

/***********************************************************************
 *           AtlUnadvise         [atl100.@]
 */
HRESULT WINAPI AtlUnadvise(IUnknown *pUnkCP, const IID *iid, DWORD dw)
{
    IConnectionPointContainer *container;
    IConnectionPoint *cp;
    HRESULT hres;

    TRACE("%p %p %d\n", pUnkCP, iid, dw);

    if(!pUnkCP)
        return E_INVALIDARG;

    hres = IUnknown_QueryInterface(pUnkCP, &IID_IConnectionPointContainer, (void**)&container);
    if(FAILED(hres))
        return hres;

    hres = IConnectionPointContainer_FindConnectionPoint(container, iid, &cp);
    IConnectionPointContainer_Release(container);
    if(FAILED(hres))
        return hres;

    hres = IConnectionPoint_Unadvise(cp, dw);
    IConnectionPoint_Release(cp);
    return hres;
}

/***********************************************************************
 *           AtlFreeMarshalStream         [atl100.@]
 */
HRESULT WINAPI AtlFreeMarshalStream(IStream *stm)
{
    FIXME("%p\n", stm);
    return S_OK;
}

/***********************************************************************
 *           AtlMarshalPtrInProc         [atl100.@]
 */
HRESULT WINAPI AtlMarshalPtrInProc(IUnknown *pUnk, const IID *iid, IStream **pstm)
{
    FIXME("%p %p %p\n", pUnk, iid, pstm);
    return E_FAIL;
}

/***********************************************************************
 *           AtlUnmarshalPtr              [atl100.@]
 */
HRESULT WINAPI AtlUnmarshalPtr(IStream *stm, const IID *iid, IUnknown **ppUnk)
{
    FIXME("%p %p %p\n", stm, iid, ppUnk);
    return E_FAIL;
}

/***********************************************************************
 *           AtlCreateTargetDC         [atl100.@]
 */
HDC WINAPI AtlCreateTargetDC( HDC hdc, DVTARGETDEVICE *dv )
{
    static const WCHAR displayW[] = {'d','i','s','p','l','a','y',0};
    const WCHAR *driver = NULL, *device = NULL, *port = NULL;
    DEVMODEW *devmode = NULL;

    TRACE( "(%p, %p)\n", hdc, dv );

    if (dv)
    {
        if (dv->tdDriverNameOffset) driver  = (WCHAR *)((char *)dv + dv->tdDriverNameOffset);
        if (dv->tdDeviceNameOffset) device  = (WCHAR *)((char *)dv + dv->tdDeviceNameOffset);
        if (dv->tdPortNameOffset)   port    = (WCHAR *)((char *)dv + dv->tdPortNameOffset);
        if (dv->tdExtDevmodeOffset) devmode = (DEVMODEW *)((char *)dv + dv->tdExtDevmodeOffset);
    }
    else
    {
        if (hdc) return hdc;
        driver = displayW;
    }
    return CreateDCW( driver, device, port, devmode );
}

/***********************************************************************
 *           AtlHiMetricToPixel              [atl100.@]
 */
void WINAPI AtlHiMetricToPixel(const SIZEL* lpHiMetric, SIZEL* lpPix)
{
    HDC dc = GetDC(NULL);
    lpPix->cx = lpHiMetric->cx * GetDeviceCaps( dc, LOGPIXELSX ) / 100;
    lpPix->cy = lpHiMetric->cy * GetDeviceCaps( dc, LOGPIXELSY ) / 100;
    ReleaseDC( NULL, dc );
}

/***********************************************************************
 *           AtlPixelToHiMetric              [atl100.@]
 */
void WINAPI AtlPixelToHiMetric(const SIZEL* lpPix, SIZEL* lpHiMetric)
{
    HDC dc = GetDC(NULL);
    lpHiMetric->cx = 100 * lpPix->cx / GetDeviceCaps( dc, LOGPIXELSX );
    lpHiMetric->cy = 100 * lpPix->cy / GetDeviceCaps( dc, LOGPIXELSY );
    ReleaseDC( NULL, dc );
}

/***********************************************************************
 *           AtlComPtrAssign              [atl100.@]
 */
IUnknown* WINAPI AtlComPtrAssign(IUnknown** pp, IUnknown *p)
{
    TRACE("(%p %p)\n", pp, p);

    if (p) IUnknown_AddRef(p);
    if (*pp) IUnknown_Release(*pp);
    *pp = p;
    return p;
}

/***********************************************************************
 *           AtlComQIPtrAssign              [atl100.@]
 */
IUnknown* WINAPI AtlComQIPtrAssign(IUnknown** pp, IUnknown *p, REFIID riid)
{
    IUnknown *new_p = NULL;

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

    if (p) IUnknown_QueryInterface(p, riid, (void **)&new_p);
    if (*pp) IUnknown_Release(*pp);
    *pp = new_p;
    return new_p;
}

/***********************************************************************
 *           AtlInternalQueryInterface     [atl100.@]
 */
HRESULT WINAPI AtlInternalQueryInterface(void* this, const _ATL_INTMAP_ENTRY* pEntries,  REFIID iid, void** ppvObject)
{
    int i = 0;
    HRESULT rc = E_NOINTERFACE;
    TRACE("(%p, %p, %s, %p)\n",this, pEntries, debugstr_guid(iid), ppvObject);

    if (IsEqualGUID(iid,&IID_IUnknown))
    {
        TRACE("Returning IUnknown\n");
        *ppvObject = ((LPSTR)this+pEntries[0].dw);
        IUnknown_AddRef((IUnknown*)*ppvObject);
        return S_OK;
    }

    while (pEntries[i].pFunc != 0)
    {
        TRACE("Trying entry %i (%s %i %p)\n",i,debugstr_guid(pEntries[i].piid),
              pEntries[i].dw, pEntries[i].pFunc);

        if (!pEntries[i].piid || IsEqualGUID(iid,pEntries[i].piid))
        {
            TRACE("MATCH\n");
            if (pEntries[i].pFunc == (_ATL_CREATORARGFUNC*)1)
            {
                TRACE("Offset\n");
                *ppvObject = ((LPSTR)this+pEntries[i].dw);
                IUnknown_AddRef((IUnknown*)*ppvObject);
                return S_OK;
            }
            else
            {
                TRACE("Function\n");
                rc = pEntries[i].pFunc(this, iid, ppvObject, pEntries[i].dw);
                if(rc==S_OK || pEntries[i].piid)
                    return rc;
            }
        }
        i++;
    }
    TRACE("Done returning (0x%x)\n",rc);
    return rc;
}

/***********************************************************************
 *           AtlIPersistStreamInit_Load      [atl100.@]
 */
HRESULT WINAPI AtlIPersistStreamInit_Load( LPSTREAM pStm, ATL_PROPMAP_ENTRY *pMap,
                                           void *pThis, IUnknown *pUnk)
{
    FIXME("(%p, %p, %p, %p)\n", pStm, pMap, pThis, pUnk);

    return S_OK;
}

/***********************************************************************
 *           AtlIPersistStreamInit_Save      [atl100.@]
 */
HRESULT WINAPI AtlIPersistStreamInit_Save(LPSTREAM pStm, BOOL fClearDirty,
                                          ATL_PROPMAP_ENTRY *pMap, void *pThis,
                                          IUnknown *pUnk)
{
    FIXME("(%p, %d, %p, %p, %p)\n", pStm, fClearDirty, pMap, pThis, pUnk);

    return S_OK;
}

/***********************************************************************
 *           AtlIPersistPropertyBag_Load      [atl100.@]
 */
HRESULT WINAPI AtlIPersistPropertyBag_Load(LPPROPERTYBAG pPropBag, LPERRORLOG pErrorLog,
                                           ATL_PROPMAP_ENTRY *pMap, void *pThis,
                                           IUnknown *pUnk)
{
    FIXME("(%p, %p, %p, %p, %p)\n", pPropBag, pErrorLog, pMap, pThis, pUnk);

    return S_OK;
}

/***********************************************************************
 *           AtlIPersistPropertyBag_Save     [atl100.@]
 */
HRESULT WINAPI AtlIPersistPropertyBag_Save(LPPROPERTYBAG pPropBag, BOOL fClearDirty,
                                           BOOL fSaveAll, ATL_PROPMAP_ENTRY *pMap,
                                           void *pThis, IUnknown *pUnk)
{
    FIXME("(%p, %d, %d, %p, %p, %p)\n", pPropBag, fClearDirty, fSaveAll, pMap, pThis, pUnk);

    return S_OK;
}

/***********************************************************************
 *           AtlModuleAddTermFunc            [atl100.@]
 */
HRESULT WINAPI AtlModuleAddTermFunc(_ATL_MODULE *pM, _ATL_TERMFUNC *pFunc, DWORD_PTR dw)
{
    _ATL_TERMFUNC_ELEM *termfunc_elem;

    TRACE("version %04x (%p %p %ld)\n", _ATL_VER, pM, pFunc, dw);

    if (_ATL_VER > _ATL_VER_30 || pM->cbSize > ATLVer1Size) {
        termfunc_elem = HeapAlloc(GetProcessHeap(), 0, sizeof(_ATL_TERMFUNC_ELEM));
        termfunc_elem->pFunc = pFunc;
        termfunc_elem->dw = dw;
        termfunc_elem->pNext = pM->m_pTermFuncs;

        pM->m_pTermFuncs = termfunc_elem;
    }

    return S_OK;
}

#if _ATL_VER > _ATL_VER_30

/***********************************************************************
 *           AtlCallTermFunc              [atl100.@]
 */
void WINAPI AtlCallTermFunc(_ATL_MODULE *pM)
{
    _ATL_TERMFUNC_ELEM *iter = pM->m_pTermFuncs, *tmp;

    TRACE("(%p)\n", pM);

    while(iter) {
        iter->pFunc(iter->dw);
        tmp = iter;
        iter = iter->pNext;
        HeapFree(GetProcessHeap(), 0, tmp);
    }

    pM->m_pTermFuncs = NULL;
}

#endif

/***********************************************************************
 *           AtlLoadTypeLib             [atl100.56]
 */
HRESULT WINAPI AtlLoadTypeLib(HINSTANCE inst, LPCOLESTR lpszIndex,
        BSTR *pbstrPath, ITypeLib **ppTypeLib)
{
    size_t path_len, index_len;
    ITypeLib *typelib = NULL;
    WCHAR *path;
    HRESULT hres;

    static const WCHAR tlb_extW[] = {'.','t','l','b',0};

    TRACE("(%p %s %p %p)\n", inst, debugstr_w(lpszIndex), pbstrPath, ppTypeLib);

    index_len = lpszIndex ? strlenW(lpszIndex) : 0;
    path = heap_alloc((MAX_PATH+index_len)*sizeof(WCHAR) + sizeof(tlb_extW));
    if(!path)
        return E_OUTOFMEMORY;

    path_len = GetModuleFileNameW(inst, path, MAX_PATH);
    if(!path_len) {
        heap_free(path);
        return HRESULT_FROM_WIN32(GetLastError());
    }

    if(index_len)
        memcpy(path+path_len, lpszIndex, (index_len+1)*sizeof(WCHAR));

    hres = LoadTypeLib(path, &typelib);
    if(FAILED(hres)) {
        WCHAR *ptr;

        for(ptr = path+path_len-1; ptr > path && *ptr != '\\' && *ptr != '.'; ptr--);
        if(*ptr != '.')
            ptr = path+path_len;
        memcpy(ptr, tlb_extW, sizeof(tlb_extW));
        hres = LoadTypeLib(path, &typelib);
    }

    if(SUCCEEDED(hres)) {
        *pbstrPath = SysAllocString(path);
        if(!*pbstrPath) {
            ITypeLib_Release(typelib);
            hres = E_OUTOFMEMORY;
        }
    }

    heap_free(path);
    if(FAILED(hres))
        return hres;

    *ppTypeLib = typelib;
    return S_OK;
}

#if _ATL_VER <= _ATL_VER_80

/***********************************************************************
 *           AtlRegisterTypeLib         [atl80.19]
 */
HRESULT WINAPI AtlRegisterTypeLib(HINSTANCE inst, const WCHAR *index)
{
    ITypeLib *typelib;
    BSTR path;
    HRESULT hres;

    TRACE("(%p %s)\n", inst, debugstr_w(index));

    hres = AtlLoadTypeLib(inst, index, &path, &typelib);
    if(FAILED(hres))
        return hres;

    hres = RegisterTypeLib(typelib, path, NULL); /* FIXME: pass help directory */
    ITypeLib_Release(typelib);
    SysFreeString(path);
    return hres;
}

#endif

#if _ATL_VER > _ATL_VER_30

/***********************************************************************
 *           AtlWinModuleInit                          [atl100.65]
 */
HRESULT WINAPI AtlWinModuleInit(_ATL_WIN_MODULE *winmod)
{
    TRACE("(%p)\n", winmod);

    if(winmod->cbSize != sizeof(*winmod))
        return E_INVALIDARG;

    InitializeCriticalSection(&winmod->m_csWindowCreate);
    winmod->m_pCreateWndList = NULL;
    return S_OK;
}

/***********************************************************************
 *           AtlWinModuleAddCreateWndData              [atl100.43]
 */
void WINAPI AtlWinModuleAddCreateWndData(_ATL_WIN_MODULE *pM, _AtlCreateWndData *pData, void *pvObject)
{
    TRACE("(%p, %p, %p)\n", pM, pData, pvObject);

    pData->m_pThis = pvObject;
    pData->m_dwThreadID = GetCurrentThreadId();

    EnterCriticalSection(&pM->m_csWindowCreate);
    pData->m_pNext = pM->m_pCreateWndList;
    pM->m_pCreateWndList = pData;
    LeaveCriticalSection(&pM->m_csWindowCreate);
}

/***********************************************************************
 *           AtlWinModuleExtractCreateWndData          [atl100.44]
 */
void* WINAPI AtlWinModuleExtractCreateWndData(_ATL_WIN_MODULE *winmod)
{
    _AtlCreateWndData *iter, *prev = NULL;
    DWORD thread_id;

    TRACE("(%p)\n", winmod);

    thread_id = GetCurrentThreadId();

    EnterCriticalSection(&winmod->m_csWindowCreate);

    for(iter = winmod->m_pCreateWndList; iter && iter->m_dwThreadID != thread_id; iter = iter->m_pNext)
        prev = iter;
    if(iter) {
        if(prev)
            prev->m_pNext = iter->m_pNext;
        else
            winmod->m_pCreateWndList = iter->m_pNext;
    }

    LeaveCriticalSection(&winmod->m_csWindowCreate);

    return iter ? iter->m_pThis : NULL;
}

/***********************************************************************
 *           AtlComModuleGetClassObject                [atl100.15]
 */
#if _ATL_VER < _ATL_VER_110
HRESULT WINAPI AtlComModuleGetClassObject(_ATL_COM_MODULE *pm, REFCLSID rclsid, REFIID riid, void **ppv)
{
    _ATL_OBJMAP_ENTRY **iter;
    HRESULT hres;

    TRACE("(%p %s %s %p)\n", pm, debugstr_guid(rclsid), debugstr_guid(riid), ppv);

    if(!pm)
        return E_INVALIDARG;

    for(iter = pm->m_ppAutoObjMapFirst; iter < pm->m_ppAutoObjMapLast; iter++) {
        if(IsEqualCLSID((*iter)->pclsid, rclsid) && (*iter)->pfnGetClassObject) {
            if(!(*iter)->pCF)
                hres = (*iter)->pfnGetClassObject((*iter)->pfnCreateInstance, &IID_IUnknown, (void**)&(*iter)->pCF);
            if((*iter)->pCF)
                hres = IUnknown_QueryInterface((*iter)->pCF, riid, ppv);
            TRACE("returning %p (%08x)\n", *ppv, hres);
            return hres;
        }
    }

    WARN("Class %s not found\n", debugstr_guid(rclsid));
    return CLASS_E_CLASSNOTAVAILABLE;
}
#else
HRESULT WINAPI AtlComModuleGetClassObject(_ATL_COM_MODULE *pm, REFCLSID rclsid, REFIID riid, void **ppv)
{
    _ATL_OBJMAP_ENTRY_EX **iter;
    HRESULT hres;

    TRACE("(%p %s %s %p)\n", pm, debugstr_guid(rclsid), debugstr_guid(riid), ppv);

    if(!pm)
        return E_INVALIDARG;

    for(iter = pm->m_ppAutoObjMapFirst; iter < pm->m_ppAutoObjMapLast; iter++) {
        if(IsEqualCLSID((*iter)->pclsid, rclsid) && (*iter)->pfnGetClassObject) {
            if(!(*iter)->pCache->pCF)
                hres = (*iter)->pfnGetClassObject((*iter)->pfnCreateInstance, &IID_IUnknown, (void**)&(*iter)->pCache->pCF);
            if((*iter)->pCache->pCF)
                hres = IUnknown_QueryInterface((*iter)->pCache->pCF, riid, ppv);
            TRACE("returning %p (%08x)\n", *ppv, hres);
            return hres;
        }
    }

    WARN("Class %s not found\n", debugstr_guid(rclsid));
    return CLASS_E_CLASSNOTAVAILABLE;
}
#endif

/***********************************************************************
 *           AtlComModuleRegisterClassObjects   [atl100.17]
 */
#if _ATL_VER < _ATL_VER_110
HRESULT WINAPI AtlComModuleRegisterClassObjects(_ATL_COM_MODULE *module, DWORD context, DWORD flags)
{
    _ATL_OBJMAP_ENTRY **iter;
    IUnknown *unk;
    HRESULT hres;

    TRACE("(%p %x %x)\n", module, context, flags);

    if(!module)
        return E_INVALIDARG;

    for(iter = module->m_ppAutoObjMapFirst; iter < module->m_ppAutoObjMapLast; iter++) {
        if(!(*iter)->pfnGetClassObject)
            continue;

        hres = (*iter)->pfnGetClassObject((*iter)->pfnCreateInstance, &IID_IUnknown, (void**)&unk);
        if(FAILED(hres))
            return hres;

        hres = CoRegisterClassObject((*iter)->pclsid, unk, context, flags, &(*iter)->dwRegister);
        IUnknown_Release(unk);
        if(FAILED(hres))
            return hres;
    }

   return S_OK;
}
#else
HRESULT WINAPI AtlComModuleRegisterClassObjects(_ATL_COM_MODULE *module, DWORD context, DWORD flags)
{
    _ATL_OBJMAP_ENTRY_EX **iter;
    IUnknown *unk;
    HRESULT hres;

    TRACE("(%p %x %x)\n", module, context, flags);

    if(!module)
        return E_INVALIDARG;

    for(iter = module->m_ppAutoObjMapFirst; iter < module->m_ppAutoObjMapLast; iter++) {
        if(!(*iter)->pfnGetClassObject)
            continue;

        hres = (*iter)->pfnGetClassObject((*iter)->pfnCreateInstance, &IID_IUnknown, (void**)&unk);
        if(FAILED(hres))
            return hres;

        hres = CoRegisterClassObject((*iter)->pclsid, unk, context, flags, &(*iter)->pCache->dwRegister);
        IUnknown_Release(unk);
        if(FAILED(hres))
            return hres;
    }

   return S_OK;
}
#endif

/***********************************************************************
 *           AtlComModuleRevokeClassObjects   [atl100.20]
 */
#if _ATL_VER < _ATL_VER_110
HRESULT WINAPI AtlComModuleRevokeClassObjects(_ATL_COM_MODULE *module)
{
    _ATL_OBJMAP_ENTRY **iter;
    HRESULT hres;

    TRACE("(%p)\n", module);

    if(!module)
        return E_INVALIDARG;

    for(iter = module->m_ppAutoObjMapFirst; iter < module->m_ppAutoObjMapLast; iter++) {
        hres = CoRevokeClassObject((*iter)->dwRegister);
        if(FAILED(hres))
            return hres;
    }

    return S_OK;
}
#else
HRESULT WINAPI AtlComModuleRevokeClassObjects(_ATL_COM_MODULE *module)
{
    _ATL_OBJMAP_ENTRY_EX **iter;
    HRESULT hres;

    TRACE("(%p)\n", module);

    if(!module)
        return E_INVALIDARG;

    for(iter = module->m_ppAutoObjMapFirst; iter < module->m_ppAutoObjMapLast; iter++) {
        hres = CoRevokeClassObject((*iter)->pCache->dwRegister);
        if(FAILED(hres))
            return hres;
    }

    return S_OK;
}
#endif

/***********************************************************************
 *           AtlComModuleUnregisterServer       [atl100.22]
 */
#if _ATL_VER < _ATL_VER_110
HRESULT WINAPI AtlComModuleUnregisterServer(_ATL_COM_MODULE *mod, BOOL bRegTypeLib, const CLSID *clsid)
{
    const struct _ATL_CATMAP_ENTRY *catmap;
    _ATL_OBJMAP_ENTRY **iter;
    HRESULT hres;

    TRACE("(%p %x %s)\n", mod, bRegTypeLib, debugstr_guid(clsid));

    for(iter = mod->m_ppAutoObjMapFirst; iter < mod->m_ppAutoObjMapLast; iter++) {
        if(!*iter || (clsid && !IsEqualCLSID((*iter)->pclsid, clsid)))
            continue;

        TRACE("Unregistering clsid %s\n", debugstr_guid((*iter)->pclsid));

        catmap = (*iter)->pfnGetCategoryMap();
        if(catmap) {
            hres = AtlRegisterClassCategoriesHelper((*iter)->pclsid, catmap, FALSE);
            if(FAILED(hres))
                return hres;
        }

        hres = (*iter)->pfnUpdateRegistry(FALSE);
        if(FAILED(hres))
            return hres;
    }

    if(bRegTypeLib) {
        ITypeLib *typelib;
        TLIBATTR *attr;
        BSTR path;

        hres = AtlLoadTypeLib(mod->m_hInstTypeLib, NULL, &path, &typelib);
        if(FAILED(hres))
            return hres;

        SysFreeString(path);
        hres = ITypeLib_GetLibAttr(typelib, &attr);
        if(SUCCEEDED(hres)) {
            hres = UnRegisterTypeLib(&attr->guid, attr->wMajorVerNum, attr->wMinorVerNum, attr->lcid, attr->syskind);
            ITypeLib_ReleaseTLibAttr(typelib, attr);
        }
        ITypeLib_Release(typelib);
        if(FAILED(hres))
            return hres;
    }

    return S_OK;
}
#else
HRESULT WINAPI AtlComModuleUnregisterServer(_ATL_COM_MODULE *mod, BOOL bRegTypeLib, const CLSID *clsid)
{
    const struct _ATL_CATMAP_ENTRY *catmap;
    _ATL_OBJMAP_ENTRY_EX **iter;
    HRESULT hres;

    TRACE("(%p %x %s)\n", mod, bRegTypeLib, debugstr_guid(clsid));

    for(iter = mod->m_ppAutoObjMapFirst; iter < mod->m_ppAutoObjMapLast; iter++) {
        if(!*iter || (clsid && !IsEqualCLSID((*iter)->pclsid, clsid)))
            continue;

        TRACE("Unregistering clsid %s\n", debugstr_guid((*iter)->pclsid));

        catmap = (*iter)->pfnGetCategoryMap();
        if(catmap) {
            hres = AtlRegisterClassCategoriesHelper((*iter)->pclsid, catmap, FALSE);
            if(FAILED(hres))
                return hres;
        }

        hres = (*iter)->pfnUpdateRegistry(FALSE);
        if(FAILED(hres))
            return hres;
    }

    if(bRegTypeLib) {
        ITypeLib *typelib;
        TLIBATTR *attr;
        BSTR path;

        hres = AtlLoadTypeLib(mod->m_hInstTypeLib, NULL, &path, &typelib);
        if(FAILED(hres))
            return hres;

        SysFreeString(path);
        hres = ITypeLib_GetLibAttr(typelib, &attr);
        if(SUCCEEDED(hres)) {
            hres = UnRegisterTypeLib(&attr->guid, attr->wMajorVerNum, attr->wMinorVerNum, attr->lcid, attr->syskind);
            ITypeLib_ReleaseTLibAttr(typelib, attr);
        }
        ITypeLib_Release(typelib);
        if(FAILED(hres))
            return hres;
    }

    return S_OK;
}
#endif

#endif

/***********************************************************************
 *           AtlRegisterClassCategoriesHelper          [atl100.49]
 */
HRESULT WINAPI AtlRegisterClassCategoriesHelper(REFCLSID clsid, const struct _ATL_CATMAP_ENTRY *catmap, BOOL reg)
{
    const struct _ATL_CATMAP_ENTRY *iter;
    HRESULT hres;

    TRACE("(%s %p %x)\n", debugstr_guid(clsid), catmap, reg);

    if(!catmap)
        return S_OK;

    if(!catreg) {
        ICatRegister *new_catreg;

        hres = CoCreateInstance(&CLSID_StdComponentCategoriesMgr, NULL, CLSCTX_INPROC_SERVER,
                &IID_ICatRegister, (void**)&new_catreg);
        if(FAILED(hres))
            return hres;

        if(InterlockedCompareExchangePointer((void**)&catreg, new_catreg, NULL))
            ICatRegister_Release(new_catreg);
    }

    for(iter = catmap; iter->iType != _ATL_CATMAP_ENTRY_END; iter++) {
        CATID catid = *iter->pcatid; /* For stupid lack of const in ICatRegister declaration. */

        if(iter->iType == _ATL_CATMAP_ENTRY_IMPLEMENTED) {
            if(reg)
                hres = ICatRegister_RegisterClassImplCategories(catreg, clsid, 1, &catid);
            else
                hres = ICatRegister_UnRegisterClassImplCategories(catreg, clsid, 1, &catid);
        }else {
            if(reg)
                hres = ICatRegister_RegisterClassReqCategories(catreg, clsid, 1, &catid);
            else
                hres = ICatRegister_UnRegisterClassReqCategories(catreg, clsid, 1, &catid);
        }
        if(FAILED(hres))
            return hres;
    }

    if(!reg) {
        WCHAR reg_path[256] = {'C','L','S','I','D','\\'}, *ptr = reg_path+6;

        static const WCHAR implemented_catW[] =
            {'I','m','p','l','e','m','e','n','t','e','d',' ','C','a','t','e','g','o','r','i','e','s',0};
        static const WCHAR required_catW[] =
            {'R','e','q','u','i','r','e','d',' ','C','a','t','e','g','o','r','i','e','s',0};

        ptr += StringFromGUID2(clsid, ptr, 64)-1;
        *ptr++ = '\\';

        memcpy(ptr, implemented_catW, sizeof(implemented_catW));
        RegDeleteKeyW(HKEY_CLASSES_ROOT, reg_path);

        memcpy(ptr, required_catW, sizeof(required_catW));
        RegDeleteKeyW(HKEY_CLASSES_ROOT, reg_path);
    }

    return S_OK;
}

/***********************************************************************
 *           AtlWaitWithMessageLoop     [atl100.24]
 */
BOOL WINAPI AtlWaitWithMessageLoop(HANDLE handle)
{
    MSG msg;
    DWORD res;

    TRACE("(%p)\n", handle);

    while(1) {
        res = MsgWaitForMultipleObjects(1, &handle, FALSE, INFINITE, QS_ALLINPUT);
        switch(res) {
        case WAIT_OBJECT_0:
            return TRUE;
        case WAIT_OBJECT_0+1:
            if(GetMessageW(&msg, NULL, 0, 0) < 0)
                return FALSE;

            TranslateMessage(&msg);
            DispatchMessageW(&msg);
            break;
        default:
            return FALSE;
        }
    }
}

static HRESULT get_default_source(ITypeLib *typelib, const CLSID *clsid, IID *iid)
{
    ITypeInfo *typeinfo, *src_typeinfo = NULL;
    TYPEATTR *attr;
    int type_flags;
    unsigned i;
    HRESULT hres;

    hres = ITypeLib_GetTypeInfoOfGuid(typelib, clsid, &typeinfo);
    if(FAILED(hres))
        return hres;

    hres = ITypeInfo_GetTypeAttr(typeinfo, &attr);
    if(FAILED(hres)) {
        ITypeInfo_Release(typeinfo);
        return hres;
    }

    for(i=0; i < attr->cImplTypes; i++) {
        hres = ITypeInfo_GetImplTypeFlags(typeinfo, i, &type_flags);
        if(SUCCEEDED(hres) && type_flags == (IMPLTYPEFLAG_FSOURCE|IMPLTYPEFLAG_FDEFAULT)) {
            HREFTYPE ref;

            hres = ITypeInfo_GetRefTypeOfImplType(typeinfo, i, &ref);
            if(SUCCEEDED(hres))
                hres = ITypeInfo_GetRefTypeInfo(typeinfo, ref, &src_typeinfo);
            break;
        }
    }

    ITypeInfo_ReleaseTypeAttr(typeinfo, attr);
    ITypeInfo_Release(typeinfo);
    if(FAILED(hres))
        return hres;

    if(!src_typeinfo) {
        *iid = IID_NULL;
        return S_OK;
    }

    hres = ITypeInfo_GetTypeAttr(src_typeinfo, &attr);
    if(SUCCEEDED(hres)) {
        *iid = attr->guid;
        ITypeInfo_ReleaseTypeAttr(src_typeinfo, attr);
    }
    ITypeInfo_Release(src_typeinfo);
    return hres;
}

/***********************************************************************
 *           AtlGetObjectSourceInterface    [atl100.54]
 */
HRESULT WINAPI AtlGetObjectSourceInterface(IUnknown *unk, GUID *libid, IID *iid, unsigned short *major, unsigned short *minor)
{
    IProvideClassInfo2 *classinfo;
    ITypeInfo *typeinfo;
    ITypeLib *typelib;
    IPersist *persist;
    IDispatch *disp;
    HRESULT hres;

    TRACE("(%p %p %p %p %p)\n", unk, libid, iid, major, minor);

    hres = IUnknown_QueryInterface(unk, &IID_IDispatch, (void**)&disp);
    if(FAILED(hres))
        return hres;

    hres = IDispatch_GetTypeInfo(disp, 0, 0, &typeinfo);
    IDispatch_Release(disp);
    if(FAILED(hres))
        return hres;

    hres = ITypeInfo_GetContainingTypeLib(typeinfo, &typelib, 0);
    ITypeInfo_Release(typeinfo);
    if(SUCCEEDED(hres)) {
        TLIBATTR *attr;

        hres = ITypeLib_GetLibAttr(typelib, &attr);
        if(SUCCEEDED(hres)) {
            *libid = attr->guid;
            *major = attr->wMajorVerNum;
            *minor = attr->wMinorVerNum;
            ITypeLib_ReleaseTLibAttr(typelib, attr);
        }else {
            ITypeLib_Release(typelib);
        }
    }
    if(FAILED(hres))
        return hres;

    hres = IUnknown_QueryInterface(unk, &IID_IProvideClassInfo2, (void**)&classinfo);
    if(SUCCEEDED(hres)) {
        hres = IProvideClassInfo2_GetGUID(classinfo, GUIDKIND_DEFAULT_SOURCE_DISP_IID, iid);
        IProvideClassInfo2_Release(classinfo);
        ITypeLib_Release(typelib);
        return hres;
    }

    hres = IUnknown_QueryInterface(unk, &IID_IPersist, (void**)&persist);
    if(SUCCEEDED(hres)) {
        CLSID clsid;

        hres = IPersist_GetClassID(persist, &clsid);
        if(SUCCEEDED(hres))
            hres = get_default_source(typelib, &clsid, iid);
        IPersist_Release(persist);
    }

    return hres;
}

#if _ATL_VER >= _ATL_VER90

/***********************************************************************
 *           AtlSetPerUserRegistration [atl100.67]
 */
HRESULT WINAPI AtlSetPerUserRegistration(cpp_bool bEnable)
{
    FIXME("stub: bEnable: %d\n", bEnable);
    return E_NOTIMPL;
}

/***********************************************************************
 *           AtlGetPerUserRegistration  [atl100.68]
 */
HRESULT WINAPI AtlGetPerUserRegistration(cpp_bool *pbEnabled)
{
    FIXME("stub: returning false\n");
    *pbEnabled = 0;
    return S_OK;
}

#endif

/***********************************************************************
 *           AtlGetVersion              [atl100.@]
 */
DWORD WINAPI AtlGetVersion(void *pReserved)
{
    TRACE("version %04x (%p)\n", _ATL_VER, pReserved);
    return _ATL_VER;
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    TRACE("(0x%p, %d, %p)\n", hinstDLL, fdwReason, lpvReserved);

    switch(fdwReason) {
    case DLL_PROCESS_ATTACH:
        atl_instance = hinstDLL;
        DisableThreadLibraryCalls(hinstDLL);
        break;
    case DLL_PROCESS_DETACH:
        if (lpvReserved) break;
        if(catreg)
            ICatRegister_Release(catreg);
    }

    return TRUE;
}