//
//  CConnectionPoint
//
//  Common implementation for CConnectionPoint.
//

//
//  Since EnumConnections is called so much, we have a custom
//  enumerator for it which is faster than CStandardEnum and which
//  performs fewer memory allocations.
//

#include "stock.h"
#pragma hdrstop

#include <olectl.h>
#include "ieguidp.h"
#include "cnctnpt.h"

class CConnectionPointEnum : public IEnumConnections
{
public:
    // IUnknown methods
    //
    virtual STDMETHODIMP QueryInterface(REFIID riid, LPVOID * ppvObj);
    virtual STDMETHODIMP_(ULONG) AddRef(void);
    virtual STDMETHODIMP_(ULONG) Release(void);

    // IEnumConnections methods
    //
    STDMETHOD(Next)(ULONG ccd, LPCONNECTDATA rgcd, ULONG *pcdFetched);
    STDMETHOD(Skip)(ULONG ccd) { return Next(ccd, NULL, NULL); }
    STDMETHOD(Reset)(void) { m_iPos = 0; return S_OK; }
    STDMETHOD(Clone)(IEnumConnections **ppecOut);

    friend HRESULT CConnectionPointEnum_Create(CConnectionPoint *pcp, int iPos, IEnumConnections **pecOut);

private:
    CConnectionPointEnum(CConnectionPoint *pcp, int iPos)
    : m_cRef(1), m_pcp(pcp), m_iPos(iPos) { m_pcp->AddRef(); }

    ~CConnectionPointEnum() { m_pcp->Release(); }

    int m_cRef;                         // refcount
    CConnectionPoint *m_pcp;            // my dad
    int m_iPos;                         // enumeration state
};


//
//  When we need to grow the sink array, we grow by this many.
//
#define GROWTH      8

//
//  OLE says that zero is never a valid cookie, so our cookies are
//  the array index biased by unity.
//
#define COOKIEFROMINDEX(i)      ((i) + 1)
#define INDEXFROMCOOKIE(dw)     ((dw) - 1)


//
//  LocalReAllocHelp behaves like IMalloc::Realloc, which
//  is slightly different from LocalRealloc.
//
//  IMalloc::Realloc(NULL, 0) = return NULL
//  IMalloc::Realloc(pv, 0) = IMalloc::Free(pv)
//  IMalloc::Realloc(NULL, cb) = IMalloc::Alloc(cb)
//  IMalloc::Realloc(pv, cb) = LocalRealloc()
//
void *LocalReAllocHelp(void *pv, ULONG cb)
{
    if (cb == 0)
    {
        if (pv)
        {
            LocalFree(pv);
        }
        return NULL;
    }
    else if (pv == NULL)
    {
        return LocalAlloc(LPTR, cb);
    }
    else
    {
        return LocalReAlloc(pv, cb, LMEM_MOVEABLE|LMEM_ZEROINIT);
    }
}

CConnectionPoint::~CConnectionPoint ()
{
    // clean up some memory stuff
    UnadviseAll();
    if (m_rgSinks)
        LocalFree(m_rgSinks);
}


HRESULT CConnectionPoint::UnadviseAll(void)
{
    if (m_rgSinks)
    {
        int x;

        for (x = 0; x < m_cSinksAlloc; x++)
        {
            ATOMICRELEASE(m_rgSinks[x]);
        }
    }

    return S_OK;
}

//
//  For backwards-compatibility with IE4, our superclass is
//  CIE4ConnectionPoint.
//
STDMETHODIMP CConnectionPoint::QueryInterface(REFIID riid, void **ppvObjOut)
{
    if (IsEqualIID(riid, IID_IConnectionPoint) ||
        IsEqualIID(riid, IID_IUnknown))
    {
        *ppvObjOut = SAFECAST(this, IConnectionPoint *);
        AddRef();
        return S_OK;
    }

    *ppvObjOut = NULL;
    return E_NOINTERFACE;
}

STDMETHODIMP CConnectionPoint::GetConnectionInterface(IID *piid)
{
    *piid = *m_piid;

    return S_OK;
}

STDMETHODIMP CConnectionPoint::GetConnectionPointContainer(IConnectionPointContainer **ppCPC)
{
    return m_punk->QueryInterface(IID_IConnectionPointContainer, (void **)ppCPC);
}

STDMETHODIMP CConnectionPoint::Advise(IUnknown *pUnk,DWORD *pdwCookie)
{
    HRESULT    hr;
    IUnknown **rgUnkNew;
    IUnknown  *punkTgt;
    int        i = 0;

    if (!pdwCookie)
        return E_POINTER;

    *pdwCookie = 0;

    // first, make sure everybody's got what they thinks they got

    hr = pUnk->QueryInterface(*m_piid, (LPVOID *)&punkTgt);
    if (SUCCEEDED(hr))
    {
#ifdef DEBUG
        //
        //  If we are not an IPropertyNotifySink, then we had better
        //  be derived from IDispatch.  Try to confirm.
        //
        if (m_piid != &IID_IPropertyNotifySink)
        {
            IDispatch *pdisp;
            if (SUCCEEDED(pUnk->QueryInterface(IID_IDispatch, (LPVOID *)&pdisp)))
            {
                pdisp->Release();
            }
            else
            {
                AssertMsg(0, TEXT("CConnectionPoint: IID %08x not derived from IDispatch"), m_piid->Data1);
            }
        }
#endif
    }
    else
    {
        if (m_piid != &IID_IPropertyNotifySink)
        {
            // This is against spec, but raymondc is guessing that this is done
            // for compatibility with VB or some other scripting language that
            // talks IDispatch but not necessarily the IDispatch-derived
            // thingie that we officially speak.  Since we really source
            // merely IDispatch::Invoke, we can satisfactorily accept any
            // IDispatch as a sink.
            hr = pUnk->QueryInterface(IID_IDispatch, (LPVOID*)&punkTgt);
        }
    }

    if (SUCCEEDED(hr))
    {

        // we no longer optimize the case where there is only one sink
        // because it's rarely the case any more.

        //
        //  If the table is full, then grow it.
        //
        if (m_cSinks >= m_cSinksAlloc)
        {
            //  LocalReAllocHelp is so smart.  If you realloc from NULL, it
            //  means Alloc.  What this means for us?  No special cases!

            rgUnkNew = (IUnknown **)LocalReAllocHelp(m_rgSinks, (m_cSinksAlloc + GROWTH) * sizeof(IUnknown *));
            if (!rgUnkNew)
            {
                punkTgt->Release();
                // GetLastError();
                return E_OUTOFMEMORY;
            }
            m_rgSinks = rgUnkNew;

            //
            //  OLE does not guarantee that the new memory is zero-initialized.
            //
            ZeroMemory(&m_rgSinks[m_cSinksAlloc], GROWTH * sizeof(IUnknown *));

            m_cSinksAlloc += GROWTH;
        }

        //
        //  Look for an empty slot.  There has to be one since we grew the
        //  table if we were full.
        //
        for (i = 0; m_rgSinks[i]; i++) {
            ASSERT(i < m_cSinksAlloc);
        }

        ASSERT(m_rgSinks[i] == NULL);   // Should've found a free slot
        m_rgSinks[i] = punkTgt;

        *pdwCookie = COOKIEFROMINDEX(i);
        m_cSinks++;

        // notify our owner that someone is connecting to us --
        // they may want to hook something up at the last minute
        //
        IConnectionPointCB* pcb;
        if (SUCCEEDED(m_punk->QueryInterface(IID_IConnectionPointCB, (LPVOID*)&pcb)))
        {
            pcb->OnAdvise(*m_piid, m_cSinks, *pdwCookie);
            pcb->Release();
        }
    }
    else
    {
        hr = CONNECT_E_CANNOTCONNECT;
    }

    return hr;
}

STDMETHODIMP CConnectionPoint::Unadvise(DWORD dwCookie)
{
    if (!dwCookie)
        return S_OK;

    int x = INDEXFROMCOOKIE(dwCookie);

    // Validate the cookie.
    if (x >= m_cSinksAlloc || m_rgSinks[x] == NULL)
        return CONNECT_E_NOCONNECTION;

    // notify our owner that someone is disconnecting from us --
    // they may want to clean up from the OnAdvise call
    // Perform the callback while the sink is still alive, in case
    // the callback wants to do some last-minute communication.
    //
    IConnectionPointCB* pcb;
    if (SUCCEEDED(m_punk->QueryInterface(IID_IConnectionPointCB, (LPVOID*)&pcb)))
    {
        pcb->OnUnadvise(*m_piid, m_cSinks - 1, dwCookie);
        pcb->Release();
    }

    // Free up the slot.  We cannot relocate any elements because that
    // would mess up the outstanding cookies.
    ATOMICRELEASE(m_rgSinks[x]);
    m_cSinks--;

    // Don't free the memory on the loss of the last sink; a new one
    // will probably show up soon.

    return S_OK;
}

//=--------------------------------------------------------------------------=
// CConnectionPoint::EnumConnections
//=--------------------------------------------------------------------------=
// enumerates all current connections
//
// Paramters:
//    IEnumConnections ** - [out] new enumerator object
//
// Output:
//    HRESULT
//
// NOtes:
STDMETHODIMP CConnectionPoint::EnumConnections(IEnumConnections **ppEnumOut)
{
#if 1
    return CConnectionPointEnum_Create(this, 0, ppEnumOut);
#else
    CONNECTDATA *rgConnectData = NULL;
    int i, cSinks;

    // CopyAndAddRefObject assumes that the IUnknown comes first
    // So does CStandardEnum
    COMPILETIME_ASSERT(FIELD_OFFSET(CONNECTDATA, pUnk) == 0);

    cSinks = 0;
    if (_HasSinks())
    {
        // allocate some memory big enough to hold all of the sinks.
        //
        // Must use GlobalAlloc because CStandardEnum uses GlobalFree.
        //
        rgConnectData = (CONNECTDATA *)GlobalAlloc(GMEM_FIXED, m_cSinks * sizeof(CONNECTDATA));
        if (!rgConnectData)
            return E_OUTOFMEMORY;

        // fill in the array
        //
        for (i = 0; i < m_cSinksAlloc; i++)
        {
            if (m_rgSinks[i])
            {
                rgConnectData[cSinks].pUnk = m_rgSinks[i];
                rgConnectData[cSinks].dwCookie = i + 1;
                cSinks++;

                // In case m_rgSinks gets out of sync with m_cSinks,
                // just stop when the array gets full.
                if (cSinks >= m_cSinks)
                {
                    break;
                }
            }
        }
        // Make sure we found all the items we should've found
        ASSERT(cSinks == m_cSinks);
    }

    // create a statndard  enumerator object.
    //
    *ppEnumOut = (IEnumConnections *)(IEnumGeneric *)new CStandardEnum(IID_IEnumConnections,
                       TRUE, cSinks, sizeof(CONNECTDATA), rgConnectData, CopyAndAddRefObject);
    if (!*ppEnumOut)
    {
        LocalFree(rgConnectData);
        return E_OUTOFMEMORY;
    }

    return S_OK;
#endif
}

//
// CConnectionPoint::DoInvokeIE4
//
// Calls all sinks' IDispatch::Invoke() with Cancel semantics.
HRESULT CConnectionPoint::DoInvokeIE4(LPBOOL pf, LPVOID *ppv, DISPID dispid, DISPPARAMS *pdispparams)
{
    return IConnectionPoint_InvokeWithCancel(this->CastToIConnectionPoint(),
                                    dispid, pdispparams, pf, ppv);
}

//
//  CConnectionPointEnum
//

HRESULT CConnectionPointEnum_Create(CConnectionPoint *pcp, int iPos, IEnumConnections **ppecOut)
{
    *ppecOut = new CConnectionPointEnum(pcp, iPos);
    return *ppecOut ? S_OK : E_OUTOFMEMORY;
}

STDMETHODIMP CConnectionPointEnum::QueryInterface(REFIID riid, void **ppvObjOut)
{
    if (IsEqualIID(riid, IID_IEnumConnections) ||
        IsEqualIID(riid, IID_IUnknown))
    {
        *ppvObjOut = (IUnknown *)this;
        AddRef();
        return S_OK;
    }

    *ppvObjOut = NULL;
    return E_NOINTERFACE;
}

STDMETHODIMP_(ULONG) CConnectionPointEnum::AddRef()
{
    return ++m_cRef;
}

STDMETHODIMP_(ULONG) CConnectionPointEnum::Release()
{
    ULONG cRef = --m_cRef;
    if (cRef == 0)
        delete this;

    return cRef;
}

//
//  Next also doubles as Skip.  If you pass a NULL output buffer, then
//  nothing gets copied (i.e., you're a Skip).
//
STDMETHODIMP CConnectionPointEnum::Next(ULONG ccd, LPCONNECTDATA rgcd, ULONG *pcdFetched)
{
    ULONG ccdFetched = 0;

    while (ccdFetched < ccd)
    {
        //
        //  Look for the next sink or the end of the array
        //
        while (m_iPos < m_pcp->m_cSinksAlloc && m_pcp->m_rgSinks[m_iPos] == NULL)
        {
            m_iPos++;
        }

        if (m_iPos >= m_pcp->m_cSinksAlloc)
            break;

        if (rgcd)
        {
            //
            //  Copy it to the output buffer
            //
            rgcd->pUnk = m_pcp->m_rgSinks[m_iPos];
            rgcd->dwCookie = COOKIEFROMINDEX(m_iPos);
            rgcd->pUnk->AddRef();
            rgcd++;
        }
        m_iPos++;
        ccdFetched++;
    }

    if (pcdFetched)
        *pcdFetched = ccdFetched;

    return (ccdFetched < ccd) ? S_FALSE : S_OK;
}

//
//  Our clone enumerates the same CConnectionPoint from the same position.
//
STDMETHODIMP CConnectionPointEnum::Clone(IEnumConnections **ppecOut)
{
    return CConnectionPointEnum_Create(m_pcp, m_iPos, ppecOut);
}

