/*++

Copyright (c) 1997-1999  Microsoft Corporation

Module Name:

    Connect.cpp
    
Abstract:

    Handles all outgoing interfaces

Author:

    mquinton - 5/7/97

Notes:

    optional-notes

Revision History:

--*/

#include "stdafx.h"
#include "uuids.h"

extern IGlobalInterfaceTable * gpGIT;
extern CRITICAL_SECTION        gcsGlobalInterfaceTable;


extern ULONG_PTR GenerateHandleAndAddToHashTable( ULONG_PTR Element);
extern void RemoveHandleFromHashTable(ULONG_PTR dwHandle);
extern CHashTable * gpHandleHashTable;


//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
// CTAPIConnectionPoint - implementation of IConnectionPoint
// for TAPI object (ITTAPIEventNotification outgoing interface).
//
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


HRESULT
CTAPIConnectionPoint::Initialize(
                                 IConnectionPointContainer * pCPC,
                                 IID iid
                                )
{
    LOG((TL_TRACE, "Initialize enter"));

    #if DBG
    {
        WCHAR guidName[100];

        StringFromGUID2(iid, (LPOLESTR)&guidName, 100);
        LOG((TL_INFO, "Initialize - IID : %S", guidName));
    }
    #endif

    //
    // Create the unadvise event
    //

    m_hUnadviseEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
    
    if (m_hUnadviseEvent == NULL)
    {
        LOG((TL_TRACE, "Initialize - out of memory"));

        return E_OUTOFMEMORY;
    }

    //
    // Addref the connection point container
    //

    pCPC->AddRef();

    //
    // Addref ourselves
    //

    this->AddRef(); 

    //
    // Save stuff
    //

    m_pCPC = pCPC;
    
    m_iid = iid;
    
    m_pConnectData = NULL;
        
    EnterCriticalSection( &gcsGlobalInterfaceTable );

    m_cThreadsInGet = 0;
    m_fMarkedForDelete = FALSE;

    LeaveCriticalSection( &gcsGlobalInterfaceTable );

    m_bInitialized = TRUE;

    LOG((TL_TRACE, "Initialize exit"));
    return S_OK;
}


// IConnectionPoint methods
HRESULT
STDMETHODCALLTYPE
CTAPIConnectionPoint::GetConnectionInterface(
                                             IID * pIID
                                            )
{
    if ( TAPIIsBadWritePtr( pIID, sizeof (IID) ) )
    {
        LOG((TL_ERROR, "GetConnectionInterface - bad pointer"));

        return E_POINTER;
    }

    Lock();
    
    *pIID = m_iid;

    Unlock();
    
    return S_OK;
}

HRESULT
STDMETHODCALLTYPE
CTAPIConnectionPoint::GetConnectionPointContainer(
    IConnectionPointContainer ** ppCPC
    )
{
    if ( TAPIIsBadWritePtr( ppCPC, sizeof( IConnectionPointContainer *) ) )
    {
        LOG((TL_ERROR, "GetCPC - bad pointer"));

        return E_POINTER;
    }

    Lock();

    *ppCPC = m_pCPC;
    (*ppCPC)->AddRef();

    Unlock();

    return S_OK;
}


//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Advise
//
//      the application calls this function when it wants to register an
//      outgoing interface
//
//      this interface is used to register the ITTAPIEventNotification
//      interface which is used to get all TAPI call control events
//
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

HRESULT
STDMETHODCALLTYPE
CTAPIConnectionPoint::Advise(
                             IUnknown * pUnk,
                             DWORD * pdwCookie
                            )
{
    HRESULT                   hr = S_OK;
    CONNECTDATA             * pCD;
    IID                       iid;

    LOG((TL_TRACE, "Advise[%p] called", this));

    if ( TAPIIsBadWritePtr( pdwCookie, sizeof (DWORD) ) )
    {
        LOG((TL_ERROR, "Advise - bad pointer"));

        return E_POINTER;
    }

    if ( IsBadReadPtr( pUnk, sizeof(IUnknown *) ) )
    {
        LOG((TL_ERROR, "Advise - bad IUnknown"));

        return E_POINTER;
    }
    
    Lock();

    if ( m_bInitialized == FALSE )
    {
        LOG((TL_ERROR, "Advise - not initialized"));

        Unlock();

        return TAPI_E_NOT_INITIALIZED;
    }

    //
    // We only allow one callback per connection point
    //

    if ( NULL != m_pConnectData )
    {
        LOG((TL_ERROR, "Advise - advise already called"));

        Unlock();
        
        return CONNECT_E_ADVISELIMIT;
    }

    //
    // Create a new connectdata struct
    //

    m_pConnectData = (CONNECTDATA *) ClientAlloc( sizeof CONNECTDATA );
    
    if (NULL == m_pConnectData)
    {
        LOG((TL_ERROR, "Advise failed - pCD == NULL"));

        Unlock();
        
        return E_OUTOFMEMORY;
    }
    
    //
    // Keep a reference to the callback
    //

    try
    {
        pUnk->AddRef();
    }
    catch(...)
    {
        LOG((TL_ERROR, "Advise - IUnknown bad"));

        ClientFree( m_pConnectData );

        m_pConnectData = NULL;

        Unlock();
        
        return E_POINTER;
    }

    //
    // Save the interface
    //

    m_pConnectData->pUnk = pUnk;
    
    ITTAPIEventNotification *pEventNotification;
    hr = pUnk->QueryInterface(IID_ITTAPIEventNotification,
                              (void**)(&pEventNotification)
                             );
    if (SUCCEEDED(hr) )
    {
        iid  = IID_ITTAPIEventNotification; 
        pEventNotification->Release();
    }
    else
    {
        iid  = DIID_ITTAPIDispatchEventNotification;    
    }

    m_iid = iid;
    
    m_pConnectData->dwCookie = CreateHandleTableEntry((ULONG_PTR)m_pConnectData);
 
    //
    // Return the cookie
    //

    *pdwCookie = m_pConnectData->dwCookie;

    //set it to FALSE if not already set.
    m_fMarkedForDelete = FALSE;

    Unlock();

    LOG((TL_TRACE, "Advise generated cookie [%lx]", *pdwCookie));


    //
    // Put the callback in the globalinterfacetable
    // so it can be accessed across threads
    //

    EnterCriticalSection( &gcsGlobalInterfaceTable );  

    if ( NULL != gpGIT )
    {
        hr = gpGIT->RegisterInterfaceInGlobal(
                                              pUnk,
                                              iid,
                                              &m_dwCallbackCookie
                                             );
    }
    else
    {
        hr = E_FAIL;
    }
    
    LeaveCriticalSection( &gcsGlobalInterfaceTable );
    
    if ( FAILED(hr) )
    {
        Lock();

        LOG((TL_ERROR, "Advise - RegisterInterfaceInGlobal failed - %lx", hr));

        DeleteHandleTableEntry(m_pConnectData->dwCookie);
        
        *pdwCookie = 0;

        ClientFree( m_pConnectData );

        m_pConnectData = NULL;

        m_fMarkedForDelete = TRUE;

        pUnk->Release();

        Unlock();
    }

    LOG((TL_TRACE, "Advise - exit"));
    
    return hr;           
}

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
// Unadvise
//
// Used to unregister an interface
//
// dwCookie - Cookie used to identify the interface registration, returned in
//            advise
//
// returns
//      S_OK
//      CONNECT_E_NOCONNECTION
//          dwCookie is not a valid connection
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

HRESULT
STDMETHODCALLTYPE
CTAPIConnectionPoint::Unadvise(
                               DWORD dwCookie
                              )
{
    HRESULT     hr =  S_OK;
    
    LOG((TL_TRACE, "Unadvise[%p] - enter. Cookie: [%lx]", this, dwCookie));
    
    Lock();

    //
    // Check connection point
    //

    if ( NULL != m_pConnectData )
    {
        //
        // Check cookie
        //

        if (m_pConnectData->dwCookie == dwCookie)
        {
            LOG((TL_INFO, "Unadvise - immediate "));
            
            //
            // Remove entry for this cookie from the handle table 
            //

            DeleteHandleTableEntry(m_pConnectData->dwCookie);
            
            //
            // Free the connect data
            //

            m_pConnectData->dwCookie = 0;

            m_pConnectData->pUnk->Release();

            ClientFree( m_pConnectData );

            m_pConnectData = NULL;
           
            Unlock();

            EnterCriticalSection( &gcsGlobalInterfaceTable ); 

            //
            // Mark for delete
            //

            m_fMarkedForDelete = TRUE;

            if ( NULL != gpGIT )
            {
                //
                // If there are threads in get we must wait for them to complete so
                // we can call revoke
                //

                while ( m_cThreadsInGet != 0 )
                {
                    LOG((TL_INFO, "Unadvise - %ld threads in get", m_cThreadsInGet));

                    LeaveCriticalSection( &gcsGlobalInterfaceTable );                 

                    DWORD dwSignalled;
                  
                    CoWaitForMultipleHandles (
                              0,
                              INFINITE,
                              1,
                              &m_hUnadviseEvent,
                              &dwSignalled
                             );

                    EnterCriticalSection( &gcsGlobalInterfaceTable ); 
                }
                    
                //
                // We have guaranteed that no threads are in get. Do the revoke.
                //
                
                hr = gpGIT->RevokeInterfaceFromGlobal( m_dwCallbackCookie );

                if ( FAILED(hr) )
                {
                    LOG((TL_ERROR, "Unadvise - RevokeInterfaceFromGlobal failed - hr = %lx", hr));
                }

                m_dwCallbackCookie = 0;
            }
            else
            {
                LOG((TL_ERROR, "Unadvise - no global interface table"));
            }

            LeaveCriticalSection( &gcsGlobalInterfaceTable );
        }
        else
        {
            Unlock();
            LOG((TL_ERROR, "Unadvise - cp does not match "));
            hr = CONNECT_E_NOCONNECTION;
        }
    }
    else
    {
        Unlock();
        LOG((TL_ERROR, "Unadvise - cp not registered "));
        hr = CONNECT_E_NOCONNECTION;
    }
        
    LOG((TL_TRACE, hr, "Unadvise - exit"));
    return hr;
}


//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
// EnumConnections
//
// Used to enumerate connections already made on this connection point
//
// ppEnum
//      return enumerator in here
//
// returns
//      S_OK
//      E_POINTER
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

HRESULT
STDMETHODCALLTYPE
CTAPIConnectionPoint::EnumConnections(
                                      IEnumConnections ** ppEnum
                                     )
{
    HRESULT         hr = S_OK;

    if ( TAPIIsBadWritePtr( ppEnum, sizeof( IEnumConnections *) ) )
    {
        LOG((TL_ERROR, "EnumConnections - bad pointer"));
        return E_POINTER;
    }
    
    //
    // Create enumerate object
    //
    CComObject< CTapiTypeEnum <IEnumConnections,
                                CONNECTDATA,
                                _Copy<CONNECTDATA>,
                                &IID_IEnumConnections> > * p;

    hr = CComObject< CTapiTypeEnum <IEnumConnections,
                                    CONNECTDATA,
                                    _Copy<CONNECTDATA>,
                                    &IID_IEnumConnections> >::CreateInstance( &p );

    if (S_OK != hr)
    {
        return hr;
    }

    //
    // Initialize it
    //

    ConnectDataArray     newarray;

    Lock();
    
    if ( NULL != m_pConnectData )
    {
        newarray.Add(*m_pConnectData);
    }

    Unlock();
    
    hr = p->Initialize( newarray );   

    newarray.Shutdown();

    if (S_OK != hr)
    {
        return hr;
    }

    *ppEnum = p;
    
    return S_OK;
}


//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
// FinalRelease
//      release all CONNECTDATA structs
//
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

void
CTAPIConnectionPoint::FinalRelease()
{
    LOG((TL_TRACE, "FinalRelease - enter"));

    if (NULL != m_pConnectData)
    {
        //
        // The app didn't call unadvise. Let's do it now.
        //

        LOG((TL_INFO, "FinalRelease - calling unadvise"));

        Unadvise(m_pConnectData->dwCookie) ;        
    }

    //
    // Release the connection point container
    //

    if (m_pCPC)
    {
        m_pCPC->Release();
        m_pCPC = NULL;
    }

    //
    // Close the unadvise event
    //

    if (m_hUnadviseEvent)
    {
        CloseHandle(m_hUnadviseEvent);
        m_hUnadviseEvent = NULL;
    }

    LOG((TL_TRACE, "FinalRelease - exit"));
}



//++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
// the object calls this to get a marshaled event
// pointer in the correct thread
//
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ULONG_PTR
CTAPIConnectionPoint::GrabEventCallback()
{
    IUnknown      * pReturn = NULL;
    HRESULT         hr = E_FAIL;
    DWORD           dwCallbackCookie;
    IID             iid;

    Lock();   
    
    //
    // If we're already released, don't try to send any events.
    //

    if ( NULL != m_pConnectData )
    {
        //
        // Copy member data
        //

        iid = m_iid;

        Unlock();
             
        EnterCriticalSection( &gcsGlobalInterfaceTable );                

        if (m_fMarkedForDelete == FALSE)
        {            
            //
            // Add to the count of threads in get.
            //

            m_cThreadsInGet++;

            //
            // Copy member data
            //

            dwCallbackCookie = m_dwCallbackCookie;

            if (gpGIT != NULL)
            {
                gpGIT->AddRef();

                //
                // Don't hold a critical section while getting
                //

                LeaveCriticalSection( &gcsGlobalInterfaceTable );
                
                hr = gpGIT->GetInterfaceFromGlobal(
                                                   dwCallbackCookie,
                                                   iid,
                                                   (void **)&pReturn
                                                  );
                if ( SUCCEEDED(hr) )
                {
                    LOG((TL_INFO, "GrabEventCallback - GetInterfaceFromGlobal suceeded [%p]", pReturn));
                }
                else
                {
                    LOG((TL_ERROR, "GrabEventCallback - GetInterfaceFromGlobal failed - hr = %lx", hr));
                    pReturn =  NULL;
                }

                EnterCriticalSection( &gcsGlobalInterfaceTable );
                gpGIT->Release();
            }

            //
            // Done. Decrement the count of threads in get.
            //

            m_cThreadsInGet--;
        }
        else
        {
            LOG((TL_INFO, "GrabEventCallback - already marked for delete"));
        }

        LeaveCriticalSection( &gcsGlobalInterfaceTable );

        if ( m_fMarkedForDelete == TRUE )
        {
            //
            // Someone called unadvise while we were using the cookie.
            // Signal so they can do the revoke now.
            //

            if ( m_hUnadviseEvent )
            {
                SetEvent(m_hUnadviseEvent);
            }
            else
            {
                LOG((TL_ERROR, "GrabEventCallback - no event"));

                _ASSERTE(FALSE);
            }

            //
            // If we got a callback, no need to return it because
            // unadvise has been called.
            //

            if ( pReturn != NULL )
            {
                pReturn->Release();
                pReturn = NULL;
            }
        } 
        
    }
    else
    {
        LOG((TL_ERROR, "GrabEventCallback - already released"));

        Unlock();
    }
    
    LOG((TL_TRACE, hr, "GrabEventCallback - exit"));

    return (ULONG_PTR)pReturn;
}