// File: connpnts.cpp
//
// CConnectionPoint
// CConnectionPointContainer
// CEnumConnections
///////////////////////////////////////////////////////////////////////////

#include "precomp.h"
#include "connpnts.h"

#include <olectl.h>


/*  C  C O N N E C T I O N  P O I N T  */
/*-------------------------------------------------------------------------
    %%Function: CConnectionPoint

-------------------------------------------------------------------------*/
CConnectionPoint::CConnectionPoint(const IID *pIID, IConnectionPointContainer *pCPCInit) :
	m_riid(*pIID),
	m_pCPC(pCPCInit),
	m_cSinks(0),
    m_cAllocatedSinks(0),
    m_rgSinks(NULL)
{
	TRACE_OUT(("CConnectionPoint - Constructed(%08X)", this));
}

CConnectionPoint::~CConnectionPoint (void)
{
    for (ULONG x = 0; x < m_cAllocatedSinks; x += 1)
    {
        if (m_rgSinks[x] != NULL)
        {
		    IUnknown *pUnk = (IUnknown *)m_rgSinks[x];
			pUnk->Release();
        }
    }

    if (m_cAllocatedSinks != 0)
    {
        HeapFree(GetProcessHeap(), 0, m_rgSinks);
    }

	TRACE_OUT(("CConnectionPoint - Destructed(%p)", this));
}

STDMETHODIMP_(ULONG) CConnectionPoint::AddRef(void)
{
	return RefCount::AddRef();
}
	
STDMETHODIMP_(ULONG) CConnectionPoint::Release(void)
{
	return RefCount::Release();
}

STDMETHODIMP CConnectionPoint::QueryInterface(REFIID riid, void **ppv)
{
	HRESULT hr = S_OK;

	if ((riid == IID_IUnknown) || (riid == IID_IConnectionPoint))
	{
		*ppv = (IConnectionPoint *)this;
		TRACE_OUT(("CConnectionPoint::QueryInterface(): Returning IConnectionPoint."));
	}
	else
	{
		hr = E_NOINTERFACE;
		*ppv = NULL;
		TRACE_OUT(("CConnectionPoint::QueryInterface(): Called on unknown interface."));
	}

	if (S_OK == hr)
	{
		AddRef();
	}

	return hr;
}


/*  N O T I F Y  */
/*-------------------------------------------------------------------------
    %%Function: Notify

-------------------------------------------------------------------------*/
STDMETHODIMP CConnectionPoint::Notify(void *pv, CONN_NOTIFYPROC pfn)
{
    //
	// Enumerate each connection
	//

	AddRef();
    for (ULONG x = 0; x < m_cAllocatedSinks; x += 1)
    {
		if (m_rgSinks[x] != NULL)
		{
		    IUnknown *pUnk = (IUnknown *)m_rgSinks[x];
			pUnk->AddRef();
			(*pfn)(pUnk, pv, m_riid);
			pUnk->Release();
		}
    }

	Release();
	return S_OK;
}

/*  G E T  C O N N E C T I O N  I N T E R F A C E  */
/*-------------------------------------------------------------------------
    %%Function: GetConnectionInterface

-------------------------------------------------------------------------*/
STDMETHODIMP CConnectionPoint::GetConnectionInterface(IID *pIID)
{
	// Validate the parameter
	//
	if (pIID == NULL)
		return E_POINTER;

	// Support only one connection interface
	//
	*pIID = m_riid;
	return S_OK;
}

STDMETHODIMP CConnectionPoint::GetConnectionPointContainer(IConnectionPointContainer **ppCPC)
{
	// Validate the parameter
	//
	if (ppCPC == NULL)
		return E_POINTER;

	// Return the container and add its reference count
	//
	*ppCPC = m_pCPC;

	if (m_pCPC != NULL)
	{
		// The container is still alive
		//
		m_pCPC->AddRef();
		return S_OK;
	}
	else
	{
		// The container no longer exists
		//
		return E_FAIL;
	}
}

/*  A D V I S E  */
/*-------------------------------------------------------------------------
    %%Function: Advise

-------------------------------------------------------------------------*/
STDMETHODIMP CConnectionPoint::Advise(IUnknown *pUnk, DWORD *pdwCookie)
{
	IUnknown *pSinkInterface;

	// Validate the parameter
	//
	if (pdwCookie == NULL)
		return E_POINTER;

	*pdwCookie = 0;
	if (pUnk == NULL)
		return E_INVALIDARG;

	HRESULT hr = CONNECT_E_CANNOTCONNECT;

    //
	// Get the sink interface
	//

	if (SUCCEEDED(pUnk->QueryInterface(m_riid, (void **)&pSinkInterface)))
	{

        //
        // If the number of active sinks is less than the number of allocated
        // sinks, then there is a free slot in the sink table. Otherwise, the
        // table must be expanded.
        //

        ULONG x = m_cAllocatedSinks;
        if (m_cSinks < m_cAllocatedSinks)
        {
            for (x = 0; x < m_cAllocatedSinks; x += 1)
            {
                if (m_rgSinks[x] == NULL)
                {
                    break;
                }
            }
        }

        //
        // If a free slot was found in the table, then use the slot. Otherwise,
        // expand the sink table.
        //

        if (x == m_cAllocatedSinks)
        {
            IUnknown **rgSinks = (IUnknown **)HeapAlloc(GetProcessHeap(),
                                                        HEAP_ZERO_MEMORY,
                                                        (m_cAllocatedSinks + 8) * sizeof(IUnknown *));

            if (rgSinks == NULL)
            {
			    pSinkInterface->Release();
                return E_OUTOFMEMORY;
            }

            for (ULONG z = 0; z < m_cAllocatedSinks; z += 1)
            {
                rgSinks[z] = m_rgSinks[z];
            }

            m_cAllocatedSinks += 8;
            if (m_rgSinks != NULL) {
                HeapFree(GetProcessHeap(), 0, m_rgSinks);
            }

            m_rgSinks = rgSinks;
        }

        //
		// Add new sink to the table.
		//

        m_rgSinks[x] = pSinkInterface;
		m_cSinks += 1;
		*pdwCookie = x + 1;
		hr = S_OK;
	}

	return hr;
}

/*  U N A D V I S E  */
/*-------------------------------------------------------------------------
    %%Function: Unadvise

-------------------------------------------------------------------------*/
STDMETHODIMP CConnectionPoint::Unadvise(DWORD dwCookie)
{
	HRESULT hr = CONNECT_E_NOCONNECTION;

    //
	// Traverse the sink list to find the specified sink object
	//

    if ((dwCookie != 0) &&
        (dwCookie <= m_cAllocatedSinks) &&
        (m_rgSinks[dwCookie - 1] != NULL))
    {
		IUnknown *pUnk = (IUnknown *) m_rgSinks[dwCookie - 1];
	    pUnk->Release();
        m_rgSinks[dwCookie - 1] = NULL;
        m_cSinks -= 1;
		hr = S_OK;
    }

	return hr;
}

STDMETHODIMP CConnectionPoint::EnumConnections(IEnumConnections **ppEnum)
{
	HRESULT hr = E_POINTER;

	// Validate parameters
	//
	if (ppEnum == NULL)
	{
		// Create an enumerator
		//
		*ppEnum = new CEnumConnections(m_rgSinks, m_cSinks, m_cAllocatedSinks);
		hr = (NULL != *ppEnum) ? S_OK : E_OUTOFMEMORY;
	}

	return hr;
}


///////////////////////////////////////////////////////////////////////////

/*  C  E N U M  C O N N E C T I O N S  */
/*-------------------------------------------------------------------------
    %%Function: CEnumConnections

-------------------------------------------------------------------------*/
CEnumConnections::CEnumConnections(IUnknown **pSinks, ULONG cSinks, ULONG cAllocatedSinks) :
	m_iIndex(0),
	m_cConnections(0),
	m_pCD(NULL)
{
	// Snapshot the connection list
	//
	if (cSinks > 0)
	{
		m_pCD = new CONNECTDATA[cSinks];
		if (NULL != m_pCD)
		{
            for (ULONG x = 0; x < cAllocatedSinks; x += 1)
            {
                if (pSinks[x] != NULL) {
				    IUnknown *pUnk = (IUnknown *) pSinks[x];
                    pUnk->AddRef();
					m_pCD[m_cConnections++].pUnk = pUnk;
					m_pCD[m_cConnections++].dwCookie = x + 1;
                }
            }
		}
	}

	TRACE_OUT(("CEnumConnections - Constructed(%p)", this));
}

CEnumConnections::~CEnumConnections(void)
{
	if (m_pCD != NULL)
	{
		for (int i = 0; i < m_cConnections; i++)
		{
			m_pCD[i].pUnk->Release();
		};
		delete [] m_pCD;
	};

	TRACE_OUT(("CEnumConnections - Destructed(%08X)", this));
}

STDMETHODIMP_(ULONG) CEnumConnections::AddRef(void)
{
	return RefCount::AddRef();
}
	
STDMETHODIMP_(ULONG) CEnumConnections::Release(void)
{
	return RefCount::Release();
}

STDMETHODIMP CEnumConnections::QueryInterface(REFIID riid, void **ppv)
{
	HRESULT hr = S_OK;

	if ((riid == IID_IEnumConnections) || (riid == IID_IUnknown))
	{
		*ppv = (IEnumConnections *)this;
		TRACE_OUT(("CEnumConnections::QueryInterface(): Returning IEnumConnections."));
	}
	else
	{
		hr = E_NOINTERFACE;
		*ppv = NULL;
		TRACE_OUT(("CEnumConnections::QueryInterface(): Called on unknown interface."));
	}

	if (S_OK == hr)
	{
		AddRef();
	}

	return hr;
}

STDMETHODIMP CEnumConnections::Next(ULONG cConnections, CONNECTDATA *rgpcd, ULONG *pcFetched)
{
	ULONG cCopied = 0;

	if ((0 == cConnections) && (NULL == rgpcd) && (NULL != pcFetched))
	{
		// Return the number of remaining elements
		*pcFetched = m_cConnections - m_iIndex;
		return S_OK;
	}
	
	if ((NULL == rgpcd) || ((NULL == pcFetched) && (cConnections != 1)))
		return E_POINTER;

	if (NULL != m_pCD)
	{
		while ((cCopied < cConnections) && (m_iIndex < m_cConnections))
		{
			*rgpcd = m_pCD[m_iIndex];
			(*rgpcd).pUnk->AddRef();
			rgpcd++;
			cCopied++;
			m_iIndex++;
		}
	}

	if (pcFetched != NULL)
		*pcFetched = cCopied;

	return (cConnections == cCopied) ? S_OK : S_FALSE;
}

STDMETHODIMP CEnumConnections::Skip(ULONG cConnections)
{
    m_iIndex += cConnections;
	if (m_iIndex >= m_cConnections)
	{
		// Past the end of the list
		m_iIndex = m_cConnections;
		return S_FALSE;
	}

	return S_OK;
}


STDMETHODIMP CEnumConnections::Reset(void)
{
	m_iIndex = 0;
	return S_OK;
}

STDMETHODIMP CEnumConnections::Clone(IEnumConnections **ppEnum)
{
	// Validate parameters
	//
	if (ppEnum != NULL)
		return E_POINTER;

	HRESULT hr = S_OK;
	CEnumConnections * pEnum = new CEnumConnections(NULL, 0, 0);
	if (NULL == pEnum)
	{
		hr = E_OUTOFMEMORY;
	}
	else if (NULL != m_pCD)
	{
		pEnum->m_pCD = new CONNECTDATA[m_cConnections];
		if (NULL == pEnum->m_pCD)
		{
			delete pEnum;
			pEnum = NULL;
    		hr = E_OUTOFMEMORY;
		}
		else
		{
			pEnum->m_iIndex = m_iIndex;
			pEnum->m_cConnections = m_cConnections;

            for (int i = 0; i < m_cConnections; ++i)
            {
        		m_pCD[i].pUnk->AddRef();
                pEnum->m_pCD[i] = m_pCD[i];
            }
		}
	}

	*ppEnum = pEnum;
	return hr;
}


///////////////////////////////////////////////////////////////////////////

/*  C  C O N N E C T I O N  P O I N T  C O N T A I N E R  */
/*-------------------------------------------------------------------------
    %%Function: CConnectionPointContainer

-------------------------------------------------------------------------*/
CConnectionPointContainer::CConnectionPointContainer(const IID **ppiid, int cCp) :
	m_ppCp(NULL),
	m_cCp(0)
{
	m_ppCp = new CConnectionPoint* [cCp];
	if (NULL != m_ppCp)
	{
		for (int i = 0; i < cCp; ++i)
		{
			CConnectionPoint *pCp = new CConnectionPoint(ppiid[i], this);
			if (NULL != pCp)
			{
				m_ppCp[m_cCp++] = pCp;
			}
		}
	}
}

CConnectionPointContainer::~CConnectionPointContainer()
{
	if (NULL != m_ppCp)
	{
		for (int i = 0; i < m_cCp; ++i)
		{
			CConnectionPoint *pCp = m_ppCp[i];
			if (NULL != pCp)
			{
				pCp->ContainerReleased();
				pCp->Release();
			}
		}
		delete[] m_ppCp;
	}
}


HRESULT STDMETHODCALLTYPE
CConnectionPointContainer::NotifySink(void *pv, CONN_NOTIFYPROC pfn)
{
	if (NULL != m_ppCp)
	{
		for (int i = 0; i < m_cCp; ++i)
		{
			m_ppCp[i]->Notify(pv, pfn);
		}
	}

	return S_OK;
}

STDMETHODIMP
CConnectionPointContainer::EnumConnectionPoints(IEnumConnectionPoints **ppEnum)
{
	if (ppEnum == NULL)
		return E_POINTER;

	// Create an enumerator
	*ppEnum = new CEnumConnectionPoints(m_ppCp, m_cCp);

	return (NULL != *ppEnum) ? S_OK : E_OUTOFMEMORY;
}

STDMETHODIMP
CConnectionPointContainer::FindConnectionPoint(REFIID riid, IConnectionPoint **ppCp)
{
	HRESULT hr = E_POINTER;
	if (NULL != ppCp)
	{
		hr = CONNECT_E_NOCONNECTION;
		*ppCp = NULL;

		if (NULL != m_ppCp)
		{
			for (int i = 0; i < m_cCp; ++i)
			{
				IID iid;
				IConnectionPoint *pCp = m_ppCp[i];
				if (S_OK == pCp->GetConnectionInterface(&iid))
				{
					if (riid == iid)
					{
						pCp->AddRef();
						*ppCp = pCp;
						hr = S_OK;
						break;
					}
				}
			}
		}
	}

	return hr;
}



