/* igpgme.c - COM+ class IGpgme
 *	Copyright (C) 2001 g10 Code GmbH
 *
 * This file is part of GPGME.
 *
 * GPGME is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * GPGME 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */


#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <time.h>
#include <windows.h>

#include "../gpgme/gpgme.h"

/* FIXME: Put them into an extra header */
void *_gpgme_malloc (size_t n );
void *_gpgme_calloc (size_t n, size_t m );
void *_gpgme_realloc (void *p, size_t n);
char *_gpgme_strdup (const char *p);
void  _gpgme_free ( void *a );



#define INITGUID
#include "igpgme.h"

/*
 * Declare the interface implementation structures
 */
typedef struct IGpgmeImpl IGpgmeImpl;
typedef struct IClassFactoryImpl IClassFactoryImpl;

static HANDLE my_exit_event;

struct IGpgmeImpl {
    /* IUnknown required stuff */
    ICOM_VFIELD (IGpgme);
    DWORD	 ref;
    /* Delegation to IDispatch */
    struct {
        IUnknown *disp;
        ITypeInfo *tinfo;
    } std_disp;
    /* Our stuff */
    GpgmeCtx mainctx;
    GpgmeData plaintext;
    int plaintext_given_as_bstr;
    GpgmeData ciphertext;
    int ciphertext_is_armored;
    GpgmeRecipients rset;
};


struct IClassFactoryImpl {
    /* IUnknown fields */
    ICOM_VFIELD(IClassFactory);
    DWORD       ref;
};

/**********************************************************
 **************  helper functions  ************************
 *********************************************************/
static HRESULT
map_gpgme_error (GpgmeError err)
{
    HRESULT hr;

    if (!err)
        return 0;
    if ( err < 0 || err > 0x1000 ) {
        fprintf (stderr,"*** GpgmeError `%s' mapped to GPGME_General_Error\n",
                 gpgme_strerror (err) );
        err = GPGME_General_Error;
    }
    hr = MAKE_HRESULT (SEVERITY_ERROR, FACILITY_ITF, 0x1000 + err);
    fprintf (stderr,"*** GpgmeError `%s' mapped to %lx\n",
             gpgme_strerror (err), (unsigned long)hr );
    return hr;
}


/**********************************************************
 **************  IGpgme Implementation  *******************
 *********************************************************/

static HRESULT WINAPI
m_IGpgme_QueryInterface (IGpgme *iface, REFIID refiid, LPVOID *obj)
{
    ICOM_THIS (IGpgmeImpl,iface);

    /*fprintf (stderr,"*** m_IGpgme_QueryInterface(%p,%s)",
      This, debugstr_guid(refiid));*/
    if ( IsEqualGUID (&IID_IUnknown, refiid)
         || IsEqualGUID (&IID_IGpgme, refiid) ) {
        *obj = This;
        IGpgme_AddRef (iface);
        fprintf (stderr," -> got %p\n", *obj);
        return 0;
    }
    else if ( IsEqualGUID (&IID_IDispatch, refiid) ) {
        HRESULT hr = IDispatch_QueryInterface (This->std_disp.disp,
                                               refiid, obj);
        /*fprintf (stderr," -> delegated, hr=%lx, got %p\n",
           hr, hr? NULL: *obj);*/
        return hr;
    }
    /*fprintf (stderr," -> none\n");*/
    *obj = NULL;
    return E_NOINTERFACE;
}


static ULONG WINAPI
m_IGpgme_AddRef (IGpgme *iface)
{
    ICOM_THIS (IGpgmeImpl,iface);
	
    return ++This->ref;
}


static ULONG WINAPI
m_IGpgme_Release (IGpgme *iface)
{
    ICOM_THIS (IGpgmeImpl,iface);
	
    if (--This->ref)
        return This->ref;

    gpgme_release (This->mainctx); This->mainctx = NULL;
    gpgme_data_release (This->plaintext); This->plaintext = NULL;
    gpgme_data_release (This->ciphertext); This->ciphertext = NULL;
    gpgme_recipients_release (This->rset); This->rset = NULL;
    if (This->std_disp.disp)
        IDispatch_Release (This->std_disp.disp);
    if (This->std_disp.tinfo)
        ITypeInfo_Release (This->std_disp.tinfo);
    HeapFree(GetProcessHeap(),0,iface);
    {
        ULONG count = CoReleaseServerProcess ();
        if (!count && my_exit_event)
            SetEvent (my_exit_event);
    }
    return 0;
}


static HRESULT WINAPI
m_stub_IDispatch_GetTypeInfoCount (IGpgme *iface, unsigned int *pctinfo)
{
    return E_NOTIMPL;
}

static HRESULT WINAPI
m_stub_IDispatch_GetTypeInfo (IGpgme *iface, UINT iTInfo,
                              LCID lcid, ITypeInfo **ppTInfo)
{
    return E_NOTIMPL;
}

static HRESULT WINAPI 
m_stub_IDispatch_GetIDsOfNames (IGpgme *iface, REFIID riid, 
                                LPOLESTR *rgszNames, UINT cNames, 
                                LCID lcid, DISPID *rgDispId)
{
    return E_NOTIMPL;
}

static HRESULT WINAPI 
m_stub_IDispatch_Invoke (IGpgme *iface, DISPID dispIdMember, 
                         REFIID riid, LCID lcid, WORD wFlags,
                         DISPPARAMS *pDispParams, VARIANT *pVarResult, 
                         EXCEPINFO *pExepInfo,  UINT *puArgErr)
{
  return E_NOTIMPL;
}



static HRESULT WINAPI
m_IGpgme_GetVersion (IGpgme *iface, BSTR *retvat)
{
    return E_NOTIMPL;
}

static HRESULT WINAPI
m_IGpgme_GetEngineInfo (IGpgme *iface, BSTR *retval)
{
    return E_NOTIMPL;
}


static HRESULT WINAPI
m_IGpgme_Cancel (IGpgme *iface)
{
    return E_NOTIMPL;
}


static HRESULT WINAPI
m_IGpgme_SetArmor (IGpgme *iface, BOOL yes)
{
    ICOM_THIS (IGpgmeImpl,iface);

    gpgme_set_armor (This->mainctx, yes);
    return 0;
}

static HRESULT WINAPI
m_IGpgme_GetArmor (IGpgme *iface, BOOL *retval)
{
    ICOM_THIS (IGpgmeImpl,iface);

    *retval = gpgme_get_armor (This->mainctx);
    return 0;
}


static HRESULT WINAPI
m_IGpgme_SetTextmode (IGpgme *iface, BOOL yes)
{
    ICOM_THIS (IGpgmeImpl,iface);

    gpgme_set_textmode (This->mainctx, yes);
    return 0;
}

static HRESULT WINAPI
m_IGpgme_GetTextmode (IGpgme *iface, BOOL *retval)
{
    ICOM_THIS (IGpgmeImpl,iface);

    *retval = gpgme_get_textmode (This->mainctx);
    return 0;
}


/* 
 * Put the data from VAL into a a Gpgme data object, which is passed by
 * reference.  Valid types of the Variant are: BSTR, SAFEARRAY of BYTE and
 * SAFEARRAY of VARIANTS of signed or unsigned integers.
 */
static HRESULT WINAPI
set_data_from_variant (GpgmeData *data, VARIANT val, int *given_as_bstr)
{
    GpgmeError err = 0;
    HRESULT hr;
    unsigned char *buf;
    SAFEARRAY *array;
    size_t len;
    int i;

    if ( val.vt == VT_BSTR) {
        len = bstrtoutf8 (val.u.bstrVal, NULL, 0);
        buf = _gpgme_malloc (len);
        if (!buf) 
            return E_OUTOFMEMORY;
        
        if (bstrtoutf8 (val.u.bstrVal, buf, len) < 0) {
            fprintf (stderr,"problem with bstrtoutf8\n");
            _gpgme_free (buf);
            return E_FAIL;
        }

        #if 0
        fprintf (stderr,"Got a BSTR (utf8):");
        for (i=0; i < len; i++)
            fprintf (stderr, " %0X", buf[i] );
        putc ('\n', stderr);
        #endif
        gpgme_data_release (*data); *data = NULL; 
        err = gpgme_data_new_from_mem (data, buf, len, 0 /*no need to copy*/ );
        if (!err && given_as_bstr)
            *given_as_bstr = 1;
    }
    else if ( val.vt == (VT_ARRAY|VT_UI1)) {
        array = val.u.parray;

        /*fprintf (stderr,"Got an ARRAY of bytes:");*/
        hr = SafeArrayAccessData (array, (void**)&buf);
        if (hr) {
            fprintf (stderr,"*** SafeArrayAccessData failed: hr=%lx\n", hr);
            return hr;
        }
        len = array->rgsabound[0].cElements;
        /*for (i=0; i < len; i++)
          fprintf (stderr, " %0X", buf[i] );
          putc ('\n', stderr);*/
        
        gpgme_data_release (*data); *data = NULL; 
        err = gpgme_data_new_from_mem (data, buf, len, 1 );
        SafeArrayUnaccessData (array);
        if (given_as_bstr)
            *given_as_bstr = 0;
    }
    else if ( val.vt == (VT_ARRAY|VT_VARIANT)) {
        VARIANT *vp;
        array = val.u.parray;

        /*fprintf (stderr,"Got an ARRAY of VARIANTS:");*/
        hr = SafeArrayAccessData (array, (void**)&vp);
        if (hr) {
            fprintf (stderr,"*** SafeArrayAccessData failed: hr=%lx\n", hr);
            return hr;
        }
        len = array->rgsabound[0].cElements;
        /* allocate the array using the gpgme allocator so that we can
         * later use a new without the copy set*/
        buf = _gpgme_malloc (len);
        if (!buf) {
            SafeArrayUnaccessData (array);
            return E_OUTOFMEMORY;
        }
        /* coerce all array elements into rawtext */
        for (i=0; i < len; i++) {
            switch (vp[i].vt) {
              case VT_I1:   buf[i] = (BYTE)vp[i].u.cVal; break; 
              case VT_I2:   buf[i] = ((UINT)vp[i].u.iVal) & 0xff; break; 
              case VT_I4:   buf[i] = ((ULONG)vp[i].u.lVal) & 0xff; break; 
              case VT_INT:  buf[i] = ((UINT)vp[i].u.intVal) & 0xff; break; 
              case VT_UI1:  buf[i] = vp[i].u.bVal; break; 
              case VT_UI2:  buf[i] = vp[i].u.uiVal & 0xff; break; 
              case VT_UI4:  buf[i] = vp[i].u.ulVal & 0xff; break; 
              case VT_UINT: buf[i] = vp[i].u.uintVal & 0xff; break; 
              default: 
                fprintf (stderr, "Invalid value in array as pos %d\n", i);
                _gpgme_free (buf);
                SafeArrayUnaccessData (array);
                return E_INVALIDARG; 
            }
        }

        /*for (i=0; i < len; i++)
          fprintf (stderr, " %0X", buf[i] );
          putc ('\n', stderr);*/
        
        gpgme_data_release (*data); *data = NULL;
        err = gpgme_data_new_from_mem (data, buf, len, 0);
        SafeArrayUnaccessData (array);
        if (given_as_bstr)
            *given_as_bstr = 0;
    }
    else {
        fprintf (stderr, "Got a variant type = %d (0x%x)\n",
                 (int)val.vt, (int)val.vt );
        return E_INVALIDARG; /* not a safearray of bytes */
    }
    return map_gpgme_error (err);
}


static HRESULT WINAPI
set_data_to_variant (GpgmeData data, VARIANT *retval, int use_bstr)
{
    GpgmeError err;
    HRESULT hr;
    SAFEARRAY *array;
    char *p;
    size_t nread, len;
    int i;

    /* Get some info on the data */
    err = gpgme_data_rewind (data);
    if (err ) {
        fprintf (stderr, "*** gpgme_data_rewind failed: %d\n", err);
        return map_gpgme_error (err);
    }
    err = gpgme_data_read (data, NULL, 0, &nread);
    if (err && err != GPGME_EOF ) {
        fprintf (stderr, "*** gpgme_data_read [length] failed: %d\n", err);
        return map_gpgme_error (err);
    }
    len = nread;  /*(eof returns a length of 0)*/
    /*fprintf (stderr,"*** %d bytes are availabe\n", (int)len);*/

    /* convert it to the target data type */
    if (use_bstr) {
        BSTR bs;
        unsigned char *helpbuf;

        /* It is easier to allocate some helper storage */
        helpbuf = _gpgme_malloc (len);
        if (!helpbuf) 
            return E_OUTOFMEMORY;
        err = gpgme_data_read (data, helpbuf, len, &nread);
        if (err ) {
            _gpgme_free (helpbuf);
            fprintf (stderr, "*** gpgme_data_read [data] failed: %d\n", err);
            return map_gpgme_error (err);
        }

        bs = SysAllocStringLen (NULL, len+1);
        if (!bs) {
            _gpgme_free (helpbuf);
            return E_OUTOFMEMORY;
        }

        for (i=0, p=helpbuf; i < len; i++, p++) 
            bs[i] = *p;
        bs[i] = 0;
        _gpgme_free (helpbuf);

        /* Ready */
        VariantInit (retval);
        retval->vt = VT_BSTR;
        retval->u.bstrVal = bs;
    }
#if 0
    else if (use_byte_array) {
        array = SafeArrayCreateVector (VT_UI1, 0, len);
        if (!array)
            return E_OUTOFMEMORY;

        p = NULL;
        hr = SafeArrayAccessData (array, (void**)&p);
        if (hr) {
            fprintf (stderr,"*** SafeArrayAccessData failed: hr=%lx\n", hr);
            SafeArrayDestroyData (array);
            SafeArrayDestroy (array);
            return hr;
        }
        if (len) {
            err = gpgme_data_read (data, p, len, &nread);
            if (err ) {
                SafeArrayUnaccessData (array);
                SafeArrayDestroyData (array);
                SafeArrayDestroy (array);
                fprintf (stderr, "*** gpgme_data_read [data] failed: %d\n",
                         err);
                return map_gpgme_error (err);
            }
        }
        SafeArrayUnaccessData (array);
        
        /* pass the data to the caller */
        VariantInit (retval);
        retval->vt = (VT_ARRAY|VT_UI1);
        retval->u.parray = array;
    }
#endif
    else { /* Create an array of variants of bytes */
        VARIANT *v;
        unsigned char *helpbuf;

        /* It is easier to allocate some helper storage */
        helpbuf = _gpgme_malloc (len);
        if (!helpbuf)
            return E_OUTOFMEMORY;
        err = gpgme_data_read (data, helpbuf, len, &nread);
        if (err ) {
            _gpgme_free (helpbuf);
            fprintf (stderr, "*** gpgme_data_read [data] failed: %d\n", err);
            return map_gpgme_error (err);
        }

        /* The create the array */
        array = SafeArrayCreateVector (VT_VARIANT, 0, len);
        if (!array) {
            _gpgme_free (helpbuf);
            return E_OUTOFMEMORY;
        }
        
        v = NULL;
        hr = SafeArrayAccessData (array, (void**)&v);
        if (hr) {
            fprintf (stderr,"*** SafeArrayAccessData failed: hr=%lx\n", hr);
            _gpgme_free (helpbuf);
            SafeArrayDestroyData (array);
            SafeArrayDestroy (array);
            return hr;
        }

        for (p=helpbuf; len; len--, v++) {
            VariantInit (v);
            v->vt = VT_UI1;
            v->u.bVal = *p;
        }
        SafeArrayUnaccessData (array);
        _gpgme_free (helpbuf);
        
        /* pass the data to the caller */
        VariantInit (retval);
        retval->vt = (VT_ARRAY|VT_VARIANT);
        retval->u.parray = array;
    }
    return 0;
}


static HRESULT WINAPI
m_IGpgme_SetPlaintext (IGpgme *iface, VARIANT val)
{
    ICOM_THIS (IGpgmeImpl,iface);

    return set_data_from_variant (&This->plaintext, val,
                                  &This->plaintext_given_as_bstr); 
}


static HRESULT WINAPI
m_IGpgme_GetPlaintext (IGpgme *iface, VARIANT *retval)
{
    ICOM_THIS (IGpgmeImpl,iface);

    /*fprintf (stderr,"*** " __PRETTY_FUNCTION__ "(%p)\n", This );*/
    return set_data_to_variant (This->plaintext, retval,
                                This->plaintext_given_as_bstr);
}

static HRESULT WINAPI
m_IGpgme_SetCiphertext (IGpgme *iface, VARIANT val)
{
    ICOM_THIS (IGpgmeImpl,iface);

    return set_data_from_variant (&This->ciphertext, val, NULL); 
}

static HRESULT WINAPI
m_IGpgme_GetCiphertext (IGpgme *iface, VARIANT *retval)
{
    ICOM_THIS (IGpgmeImpl,iface);

    return set_data_to_variant (This->ciphertext, retval,
                                This->ciphertext_is_armored);
}

static HRESULT WINAPI
m_IGpgme_ClearRecipients (IGpgme *iface)
{
    ICOM_THIS (IGpgmeImpl,iface);

    gpgme_recipients_release (This->rset); This->rset = NULL;
    return 0;
}


static HRESULT WINAPI
m_IGpgme_AddRecipient (IGpgme *iface, BSTR name, signed short int trust)
{
    GpgmeError err;
    int n;
    char *p;
    ICOM_THIS (IGpgmeImpl,iface);
    
    /*fprintf (stderr,"*** " __PRETTY_FUNCTION__ "(%p, %d)\n",
      This, (int)trust);*/
    if (!This->rset) {
        err = gpgme_recipients_new (&This->rset);
        if (err)
            return map_gpgme_error (err);
    }

    n = bstrtoutf8 (name, NULL, 0);
    p = HeapAlloc (GetProcessHeap(), 0, n );
    if (!p) {
        fprintf (stderr,"HeapAlloc failed: ec=%d\n", (int)GetLastError () );
        return E_OUTOFMEMORY;
    }
    if (bstrtoutf8 (name, p, n) < 0) {
        fprintf (stderr,"problem with bstrtoutf8\n");
        HeapFree (GetProcessHeap(), 0, p);
        return E_FAIL;
    }
    err = gpgme_recipients_add_name (This->rset, p);
    HeapFree (GetProcessHeap(), 0, p);
    return map_gpgme_error (err);
}

static HRESULT WINAPI
m_IGpgme_ResetSignKeys (IGpgme *iface)
{
    return E_NOTIMPL;
}

static HRESULT WINAPI
m_IGpgme_AddSignKey (IGpgme *iface, BSTR name)
{
    return E_NOTIMPL;
}

static HRESULT WINAPI
m_IGpgme_Encrypt (IGpgme *iface)
{
    GpgmeError err;
    ICOM_THIS (IGpgmeImpl,iface);

    gpgme_data_release (This->ciphertext);
    err = gpgme_data_new (&This->ciphertext);
    if (err)
        return map_gpgme_error (err);

    
    This->ciphertext_is_armored = gpgme_get_armor (This->mainctx);
    err = gpgme_op_encrypt (This->mainctx, This->rset,
                            This->plaintext, This->ciphertext);
#if 0
    if (!err ) {
        char buf[100];
        size_t nread;

        err = gpgme_data_rewind ( This->ciphertext );
        if (err ) 
            fprintf (stderr, "*** gpgme_data_rewind failed: %d\n", err);
        while ( !(err = gpgme_data_read ( This->ciphertext,
                                          buf, 100, &nread )) ) {
            fwrite ( buf, nread, 1, stderr );
        }
        if (err != GPGME_EOF) 
            fprintf (stderr, "*** gpgme_data_read failed: %d\n", err);
        err = 0;
    }
#endif

    return map_gpgme_error (err);
}

static HRESULT WINAPI
m_IGpgme_Sign (IGpgme *iface, short int signmode)
{
    ICOM_THIS (IGpgmeImpl,iface);

    fprintf (stderr,"*** " __PRETTY_FUNCTION__ "(%p)\n", This );

    return E_NOTIMPL;
}

static HRESULT WINAPI
m_IGpgme_SignEncrypt (IGpgme *iface, short int signmode)
{
    ICOM_THIS (IGpgmeImpl,iface);

    fprintf (stderr,"*** " __PRETTY_FUNCTION__ "(%p)\n", This );

    return E_NOTIMPL;
}

#if 0
static HRESULT WINAPI
m_IGpgme_GetSigStatus(GpgmeCtx c, int idx,
                                  GpgmeSigStat *r_stat, time_t *r_created );
{
    return 0;
}


static HRESULT WINAPI
m_IGpgme_GetSigKey (GpgmeCtx c, int idx, GpgmeKey *r_key);
{
    return 0;
}

static HRESULT WINAPI
m_IGpgme_GetNotation(IGpgme *c, BSTR *retval)
{
    return 0;
}
#endif


static ICOM_VTABLE(IGpgme) igpgme_vtbl = 
{
    /* IUnknown methods */
    ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
    m_IGpgme_QueryInterface,
    m_IGpgme_AddRef,
    m_IGpgme_Release,
    /* IDispatch methods */
    m_stub_IDispatch_GetTypeInfoCount,
    m_stub_IDispatch_GetTypeInfo,
    m_stub_IDispatch_GetIDsOfNames,
    m_stub_IDispatch_Invoke,
    /* Our methods */
    m_IGpgme_GetVersion,
    m_IGpgme_GetEngineInfo,
    m_IGpgme_Cancel,             
    m_IGpgme_SetArmor,            
    m_IGpgme_GetArmor,            
    m_IGpgme_SetTextmode,         
    m_IGpgme_GetTextmode,         
    m_IGpgme_SetPlaintext,
    m_IGpgme_GetPlaintext,
    m_IGpgme_SetCiphertext,
    m_IGpgme_GetCiphertext,
    m_IGpgme_ClearRecipients,
    m_IGpgme_AddRecipient,
    m_IGpgme_ResetSignKeys,
    m_IGpgme_AddSignKey,
    m_IGpgme_Encrypt, 
    m_IGpgme_Sign, 
    m_IGpgme_SignEncrypt
};



/***************************************************************
 ******************  Gpgme Factory  ****************************
 ***************************************************************/

static HRESULT WINAPI 
m_GpgmeFactory_QueryInterface (IClassFactory *iface,
                               REFIID refiid, LPVOID *obj)
{
    ICOM_THIS (IClassFactoryImpl,iface);

    /*fprintf (stderr,"*** m_GpgmeFactory_QueryInterface(%p,%s)",
      This, debugstr_guid(refiid));*/
    if ( IsEqualGUID (&IID_IUnknown, refiid)
         || IsEqualGUID (&IID_IClassFactory, refiid) ) {
        *obj = This;
        /*fprintf (stderr," -> got %p\n", obj);*/
        return 0;
    }
    *obj = NULL;
    /*fprintf (stderr," -> none\n");*/
    return E_NOINTERFACE;
}

static ULONG WINAPI
m_GpgmeFactory_AddRef (IClassFactory *iface)
{
    ICOM_THIS(IClassFactoryImpl,iface);
    return ++(This->ref);
}

static ULONG WINAPI
m_GpgmeFactory_Release (IClassFactory *iface)
{
    ICOM_THIS(IClassFactoryImpl,iface);
    return --(This->ref);
}

static HRESULT WINAPI
m_GpgmeFactory_CreateInstance (IClassFactory *iface, IUnknown *outer,
                               REFIID refiid, LPVOID *r_obj )
{
    /*ICOM_THIS(IClassFactoryImpl,iface);*/

    fprintf (stderr,"*** m_GpgmeFactory_CreateInstance(%s)",
             debugstr_guid(refiid) );
    if (   IsEqualGUID (&IID_IUnknown, refiid)
        || IsEqualGUID (&IID_IGpgme, refiid) ) {
	IGpgmeImpl *obj;
        GpgmeCtx ctx;
        GpgmeError err;


        err = gpgme_new (&ctx);
        if (err) {
            fprintf (stderr," -> gpgme_new failed: %s\n", gpgme_strerror (err));
            return E_OUTOFMEMORY;
        }

	obj = HeapAlloc (GetProcessHeap(), 0, sizeof *obj );
	if ( !obj) {
            fprintf (stderr," -> out of core\n");
            gpgme_release (ctx);
            return E_OUTOFMEMORY;
        }
        memset (obj, 0, sizeof *obj);

	ICOM_VTBL(obj) = &igpgme_vtbl;
	obj->ref = 1;
        obj->mainctx = ctx;
        {   /* Fixme: need to release some stuff on error */
            HRESULT hr;
            ITypeLib *pTypeLib;

            hr = LoadRegTypeLib (&TLBID_Gpgcom, 1, 0, LANG_NEUTRAL, &pTypeLib);
            if (hr) {
                fprintf (stderr," -> LoadRegTypeLib failed: %lx\n", hr);
                return hr;
            }
            hr = ITypeLib_GetTypeInfoOfGuid (pTypeLib, &IID_IGpgme,
                                             &obj->std_disp.tinfo);
            ITypeLib_Release (pTypeLib);
            if (hr) {
                fprintf (stderr," -> GetTypeInfoOfGuid failed: %lx\n", hr);
                return hr;
            }
            hr = CreateStdDispatch ((IUnknown*)obj, obj, obj->std_disp.tinfo,
                                     &obj->std_disp.disp);
            if (hr) {
                fprintf (stderr," -> CreateStdDispatch failed: %lx\n", hr);
                return hr;
            }
        }

        CoAddRefServerProcess ();
        *r_obj = obj;
        fprintf (stderr," -> created %p\n", obj );
	return 0;
    }
    fprintf (stderr," -> no interface\n" );
    *r_obj = NULL;
    return E_NOINTERFACE;
}

static HRESULT WINAPI
m_GpgmeFactory_LockServer (IClassFactory *iface, BOOL dolock )
{
    if (dolock) {
        CoAddRefServerProcess ();
    }
    else {
        ULONG count = CoReleaseServerProcess ();
        if (!count && my_exit_event)
            SetEvent (my_exit_event);
    }
    return 0;
}

static ICOM_VTABLE(IClassFactory) igpgme_factory_vtbl = {
    ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
    m_GpgmeFactory_QueryInterface,
    m_GpgmeFactory_AddRef,
    m_GpgmeFactory_Release,
    m_GpgmeFactory_CreateInstance,
    m_GpgmeFactory_LockServer
};
static IClassFactoryImpl igpgme_CF = {&igpgme_factory_vtbl, 1 };

void
igpgme_register_exit_event (HANDLE ev)
{
    my_exit_event = ev;
}


IClassFactory *
igpgme_factory_new ( CLSID *r_clsid )
{
    *r_clsid = CLSID_Gpgme;
    IClassFactory_AddRef((IClassFactory*)&igpgme_CF);
    return (IClassFactory*)&igpgme_CF;
}

void
igpgme_factory_release ( IClassFactory *factory )
{
    /* it's static - nothing to do */
}