/*++

Copyright (C) 1993-1999 Microsoft Corporation

Module Name:

    iconnpt.cpp

Abstract:

    Implementation of CImpIConnectionPoint for the Polyline object
    as well as CConnectionPoint.

--*/

#include "polyline.h"
#include "iconnpt.h"
#include "unkhlpr.h"


static const IID *apIIDConnectPt [CONNECTION_POINT_CNT] = {
                &IID_ISystemMonitorEvents,  
                &DIID_DISystemMonitorEvents
                };

// CImpIConnPt interface implementation
    IMPLEMENT_CONTAINED_IUNKNOWN(CImpIConnPtCont)

/*
 * CImpIConnPtCont::CImpIConnPtCont
 *
 * Purpose:
 *  Constructor.
 *
 * Return Value:
 */

CImpIConnPtCont::CImpIConnPtCont ( PCPolyline pObj, LPUNKNOWN pUnkOuter)
    :   m_cRef(0),
        m_pObj(pObj),
        m_pUnkOuter(pUnkOuter)
{
    return; 
}

/*
 * CImpIConnPtCont::~CImpIConnPtCont
 *
 * Purpose:
 *  Destructor.
 *
 * Return Value:
 */

CImpIConnPtCont::~CImpIConnPtCont( void ) 
{   
    return; 
}

/*
 * CImpIConnPtCont::EnumConnectionPoints
 *
 * Purpose:
 *  Not implemented.
 *
 * Return Value:
 *  HRESULT         E_NOTIMPL
 */

STDMETHODIMP 
CImpIConnPtCont::EnumConnectionPoints (
    OUT LPENUMCONNECTIONPOINTS *ppIEnum
    )
{
    CImpIEnumConnPt *pEnum;

    if (ppIEnum == NULL)
        return E_POINTER;

    *ppIEnum = NULL;

    pEnum = new CImpIEnumConnPt(this, apIIDConnectPt, CONNECTION_POINT_CNT);
    if (pEnum == NULL)
        return E_OUTOFMEMORY;

    return pEnum->QueryInterface(IID_IEnumConnectionPoints, (PPVOID)ppIEnum);   
}



/*
 * CImpIConnPtCont::FindConnectionPoint
 *
 * Purpose:
 *  Returns a pointer to the IConnectionPoint for a given
 *  outgoing IID.
 *
 * Parameters:
 *  riid            REFIID of the outgoing interface for which
 *                  a connection point is desired.
 *  ppCP            IConnectionPoint ** in which to return
 *                  the pointer after calling AddRef.
 *
 * Return Value:
 *  HRESULT         NOERROR if the connection point is found,
 *                  E_NOINTERFACE if it's not supported.
 */

STDMETHODIMP 
CImpIConnPtCont::FindConnectionPoint (
    IN  REFIID riid,
    OUT IConnectionPoint **ppCP
    )
{

    PCImpIConnectionPoint pConnPt;
    
    *ppCP=NULL;

    // if request matches one of our connection IDs
    if (IID_ISystemMonitorEvents == riid)
        pConnPt = &m_pObj->m_ConnectionPoint[eConnectionPointDirect];
    else if (DIID_DISystemMonitorEvents == riid)
        pConnPt = &m_pObj->m_ConnectionPoint[eConnectionPointDispatch];
    else
        return ResultFromScode(E_NOINTERFACE);

    // Return the IConnectionPoint interface
    return pConnPt->QueryInterface(IID_IConnectionPoint, (PPVOID)ppCP); 
}


/*
 * CImpIConnectionPoint constructor
 */
CImpIConnectionPoint::CImpIConnectionPoint (
    void
    )
    :   m_cRef(0),
        m_pObj(NULL),
        m_pUnkOuter(NULL),
        m_hEventEventSink(NULL),
        m_lSendEventRefCount(0),
        m_lUnadviseRefCount(0)
{
    m_Connection.pIDirect = NULL;
    m_Connection.pIDispatch = NULL;
}


/*
 * CImpIConnectionPoint destructor
 */
CImpIConnectionPoint::~CImpIConnectionPoint (
    void
    )
{
    DeinitEventSinkLock();
}

/*
 * CImpIConnectionPoint::QueryInterface
 * CImpIConnectionPoint::AddRef
 * CCImpIonnectionPoint::Release
 *
 */

STDMETHODIMP 
CImpIConnectionPoint::QueryInterface ( 
    IN  REFIID riid,
    OUT LPVOID *ppv
    )
{
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IConnectionPoint==riid)
        *ppv=(LPVOID)this;

    if (NULL != *ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
}


STDMETHODIMP_(ULONG) 
CImpIConnectionPoint::AddRef(
    void
    )
{   
    ++m_cRef;
    return m_pUnkOuter->AddRef();
}



STDMETHODIMP_(ULONG) 
CImpIConnectionPoint::Release (
    void
    )
{
    --m_cRef;
    return m_pUnkOuter->Release();
}



/*
 * CImpIConnectionPoint::Init
 *
 * Purpose:
 * Set back-pointers and connection type.
 *
 * Paramters:
 *  pObj            Containing Object
 *  pUnkOuter       Controlling Object
 *  iConnectType    Connection point type
 */
void
CImpIConnectionPoint::Init (
    IN PCPolyline   pObj,
    IN LPUNKNOWN    pUnkOuter,
    IN INT          iConnPtType
    )
{
    DWORD dwStat = 0;

    m_pObj = pObj;
    m_pUnkOuter = pUnkOuter;
    m_iConnPtType = iConnPtType;

    dwStat = InitEventSinkLock();
}


/*
 * CImpIConnectionPoint::GetConnectionInterface
 *
 * Purpose:
 *  Returns the IID of the outgoing interface supported through
 *  this connection point.
 *
 * Parameters:
 *  pIID            IID * in which to store the IID.
 */

STDMETHODIMP 
CImpIConnectionPoint::GetConnectionInterface (
    OUT IID *pIID
    )
{
    if (NULL == pIID)
        return ResultFromScode(E_POINTER);

    *pIID = *apIIDConnectPt[m_iConnPtType];

    return NOERROR;
}



/*
 * CImpIConnectionPoint::GetConnectionPointContainer
 *
 * Purpose:
 *  Returns a pointer to the IConnectionPointContainer that
 *  is manageing this connection point.
 *
 * Parameters:
 *  ppCPC           IConnectionPointContainer ** in which to return
 *                  the pointer after calling AddRef.
 */

STDMETHODIMP 
CImpIConnectionPoint::GetConnectionPointContainer (
    OUT IConnectionPointContainer **ppCPC
    )
{
    return m_pObj->QueryInterface(IID_IConnectionPointContainer, (void **)ppCPC);
}



/*
 * CImpIConnectionPoint::Advise
 *
 * Purpose:
 *  Provides this connection point with a notification sink to
 *  call whenever the appropriate outgoing function/event occurs.
 *
 * Parameters:
 *  pUnkSink        LPUNKNOWN to the sink to notify.  The connection
 *                  point must QueryInterface on this pointer to obtain
 *                  the proper interface to call.  The connection
 *                  point must also insure that any pointer held has
 *                  a reference count (QueryInterface will do it).
 *  pdwCookie       DWORD * in which to store the connection key for
 *                  later calls to Unadvise.
 */

STDMETHODIMP 
CImpIConnectionPoint::Advise (
    IN  LPUNKNOWN pUnkSink,
    OUT DWORD *pdwCookie )
{
    HRESULT hr;

    *pdwCookie = 0;

    // Can only support one connection
    if (NULL != m_Connection.pIDirect) {
        hr = ResultFromScode(CONNECT_E_ADVISELIMIT);
    } else {
        // Get interface from sink
        if (FAILED(pUnkSink->QueryInterface(*apIIDConnectPt[m_iConnPtType], (PPVOID)&m_Connection)))
            hr = ResultFromScode(CONNECT_E_CANNOTCONNECT);
        else {
        // Return our cookie
            *pdwCookie = eAdviseKey;
            hr = NOERROR;
        }
    }
    
    return hr;
}



/*
 * CImpIConnectionPoint::SendEvent
 *
 * Purpose:
 *  Sends an event to the attached event sink
 *
 * Parameters:
 *  uEventType     Event code
 *  dwParam        Parameter to send with event
 *
 */
void
CImpIConnectionPoint::SendEvent (
    IN UINT uEventType,
    IN DWORD dwParam
    )
{

    // If not connected, just return.

    if ( EnterSendEvent() ) {
        if (m_Connection.pIDirect != NULL) {

            // For direct connection, call the method
            if (m_iConnPtType == eConnectionPointDirect) {

                switch (uEventType) {

                case eEventOnCounterSelected:
                    m_Connection.pIDirect->OnCounterSelected((INT)dwParam);
                    break;

                case eEventOnCounterAdded:
                    m_Connection.pIDirect->OnCounterAdded((INT)dwParam);
                    break;

                case eEventOnCounterDeleted:
                    m_Connection.pIDirect->OnCounterDeleted((INT)dwParam);
                    break;

                case eEventOnSampleCollected:
                    m_Connection.pIDirect->OnSampleCollected();
                    break;

                case eEventOnDblClick:
                    m_Connection.pIDirect->OnDblClick((INT)dwParam);
                    break;
                }
            }
            // for dispatch connection, call Invoke
            else if ( m_iConnPtType == eConnectionPointDispatch ) {
                if ( NULL != m_Connection.pIDispatch ) {

                    DISPPARAMS  dp;
                    VARIANT     vaRet;
                    VARIANTARG  varg;

                    VariantInit(&vaRet);

                    if ( uEventType == eEventOnSampleCollected ) {
                        SETNOPARAMS(dp)
                    } else { 
                        VariantInit(&varg);
                        V_VT(&varg) = VT_I4;
                        V_I4(&varg) = (INT)dwParam;
                    
                        SETDISPPARAMS(dp, 1, &varg, 0, NULL)
                    }

                    m_Connection.pIDispatch->Invoke(uEventType, IID_NULL
                    , LOCALE_USER_DEFAULT, DISPATCH_METHOD, &dp
                    , &vaRet, NULL, NULL);
                }
            }
        }
    }

    ExitSendEvent();
    
    return;
}

/*
 * CImpIConnectionPoint::Unadvise
 *
 * Purpose:
 *  Terminates the connection to the notification sink identified
 *  with dwCookie (that was returned from Advise).  The connection
 *  point has to Release any held pointers for that sink.
 *
 * Parameters:
 *  dwCookie        DWORD connection key from Advise.
 */

STDMETHODIMP 
CImpIConnectionPoint::Unadvise ( 
    IN  DWORD dwCookie )
{
    if (eAdviseKey != dwCookie)
        return ResultFromScode(CONNECT_E_NOCONNECTION);

    EnterUnadvise();

    m_Connection.pIDirect = NULL;

    ExitUnadvise();

    return NOERROR;
}



/*
 * CImpIConnectionPoint::EnumConnections
 *
 * Purpose:
 *  Not implemented because only one conection is allowed
 */

STDMETHODIMP 
CImpIConnectionPoint::EnumConnections ( 
    OUT LPENUMCONNECTIONS *ppEnum
    )
{
    if (ppEnum == NULL)
        return E_POINTER;

    *ppEnum = NULL;

    return ResultFromScode(E_NOTIMPL);
}

/*
 *  Locks for the event sink.
 */

DWORD 
CImpIConnectionPoint::InitEventSinkLock ( void )
{
    DWORD dwStat = 0;
    
    m_lUnadviseRefCount = 0;
    m_lSendEventRefCount = 0;

    if ( NULL == ( m_hEventEventSink = CreateEvent ( NULL, TRUE, TRUE, NULL ) ) )
        dwStat = GetLastError();

    return dwStat;
}

void 
CImpIConnectionPoint::DeinitEventSinkLock ( void )
{
    // Release the event sink lock
    if ( NULL != m_hEventEventSink ) {
        CloseHandle ( m_hEventEventSink );
        m_hEventEventSink = NULL;
    }
    m_lSendEventRefCount = 0;
    m_lUnadviseRefCount = 0;

}

BOOL
CImpIConnectionPoint::EnterSendEvent ( void )
{
    // Return value indicates whether lock is granted.
    // If lock is not granted, must still call ExitSendEvent.

    // Increment the SendEvent reference count when SendEvent is active.
    InterlockedIncrement( &m_lSendEventRefCount );

    // Grant the lock unless the event sink pointer is being modified in Unadvise. 
    return ( 0 == m_lUnadviseRefCount );
}

void
CImpIConnectionPoint::ExitSendEvent ( void )
{
    LONG lTemp;

    // Decrement the SendEvent reference count. 
    lTemp = InterlockedDecrement( &m_lSendEventRefCount );

    // Signal the event sink if SendEvent count decremented to 0.
    // lTemp is the value previous to decrement.
    if ( 0 == lTemp )
        SetEvent( m_hEventEventSink );
}


void
CImpIConnectionPoint::EnterUnadvise ( void )
{
    BOOL bStatus;

    bStatus = ResetEvent( m_hEventEventSink );

    // Increment the Unadvise reference count whenever Unadvise is active.
    // Whenever this is > 0, events are not fired.
    InterlockedIncrement( &m_lUnadviseRefCount );

    // Wait until SendEvent is no longer active.
    while ( m_lSendEventRefCount > 0 ) {
        WaitForSingleObject( m_hEventEventSink, eEventSinkWaitInterval );
        bStatus = ResetEvent( m_hEventEventSink );
    }
}

void
CImpIConnectionPoint::ExitUnadvise ( void )
{
    // Decrement the Unadvise reference count. 
    InterlockedDecrement( &m_lUnadviseRefCount );
}


CImpIEnumConnPt::CImpIEnumConnPt (
    IN  CImpIConnPtCont  *pConnPtCont,
    IN  const IID **ppIID,
    IN  ULONG cItems
    )
{
    m_pConnPtCont = pConnPtCont;
    m_apIID = ppIID;
    m_cItems = cItems;

    m_uCurrent = 0;
    m_cRef = 0;
}


STDMETHODIMP
CImpIEnumConnPt::QueryInterface (
    IN  REFIID riid, 
    OUT PVOID *ppv
    )
{
    if ((riid == IID_IUnknown) || (riid == IID_IEnumConnectionPoints)) {
        *ppv = this;
        AddRef();
        return NOERROR;
    }

    *ppv = NULL;
    return E_NOINTERFACE;
}


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


STDMETHODIMP_(ULONG)
CImpIEnumConnPt::Release(
    VOID
    )
{
    if (--m_cRef == 0) {
        delete this;
        return 0;
    }

    return m_cRef;
}


STDMETHODIMP
CImpIEnumConnPt::Next(
    IN  ULONG cItems,
    OUT IConnectionPoint **apConnPt,
    OUT ULONG *pcReturned)
{
    ULONG i;
    ULONG cRet;
    HRESULT hr;

    hr = NOERROR;

    // Clear the return values
    for (i = 0; i < cItems; i++)
        apConnPt[i] = NULL;

    // Try to fill the caller's array
    for (cRet = 0; cRet < cItems; cRet++) {

        // No more, return success with false
        if (m_uCurrent == m_cItems) {
            hr = S_FALSE;
            break;
        }

        // Ask connection point container for next connection point
        hr = m_pConnPtCont->FindConnectionPoint(*m_apIID[m_uCurrent], &apConnPt[cRet]);

        if (FAILED(hr))
            break;

        m_uCurrent++;
    }

    // If failed, free the accumulated interfaces
    if (FAILED(hr)) {
        for (i = 0; i < cRet; i++)
            ReleaseInterface(apConnPt[i]);
        cRet = 0;
    }

    // If desired, return number of items fetched
    if (pcReturned != NULL)
      *pcReturned = cRet;

    return hr;
}


/***
*HRESULT CImpIEnumConnPt::Skip(unsigned long)
*Purpose:
*  Attempt to skip over the next 'celt' elements in the enumeration
*  sequence.
*
*Entry:
*  celt = the count of elements to skip
*
*Exit:
*  return value = HRESULT
*    S_OK
*    S_FALSE -  the end of the sequence was reached
*
***********************************************************************/
STDMETHODIMP
CImpIEnumConnPt::Skip(
    IN  ULONG   cItems
    )
{
    m_uCurrent += cItems;

    if (m_uCurrent > m_cItems)
        m_uCurrent = m_cItems;

    return (m_uCurrent == m_cItems) ? S_FALSE : S_OK;
}


/***
*HRESULT CImpIEnumConnPt::Reset(void)
*Purpose:
*  Reset the enumeration sequence back to the beginning.
*
*Entry:
*  None
*
*Exit:
*  return value = SHRESULT CODE
*    S_OK
*
***********************************************************************/
STDMETHODIMP
CImpIEnumConnPt::Reset(
    VOID
    )
{
    m_uCurrent = 0;

    return S_OK; 
}


/***
*HRESULT CImpIEnumConnPt::Clone(IEnumVARIANT**)
*Purpose:
*  Retrun a CPoint enumerator with exactly the same state as the
*  current one.
*
*Entry:
*  None
*
*Exit:
*  return value = HRESULT
*    S_OK
*    E_OUTOFMEMORY
*
***********************************************************************/
STDMETHODIMP
CImpIEnumConnPt::Clone (
    OUT IEnumConnectionPoints **ppEnum
    )
{
    CImpIEnumConnPt *pNewEnum;

    *ppEnum = NULL;

    // Create new enumerator
    pNewEnum = new CImpIEnumConnPt(m_pConnPtCont, m_apIID, m_cItems);
    if (pNewEnum == NULL)
        return E_OUTOFMEMORY;

    // Copy current position
    pNewEnum->m_uCurrent = m_uCurrent;

    *ppEnum = pNewEnum;

    return NOERROR;
}