//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES.
//
#include "common.h"
#include "cobex.h"
#include "conpoint.h"
#include <olectl.h>

CEnumConnectionPoints::CEnumConnectionPoints(IConnectionPoint *pcnpInit) 
{   
	_ulRefs = 1;  
	_ulIndex = 0;	
    _pcnp = pcnpInit;

    if (_pcnp)
        _pcnp->AddRef();
}

CEnumConnectionPoints::~CEnumConnectionPoints()
{
	SVSUTIL_ASSERT(_ulRefs == 0);

    if (_pcnp)
        _pcnp->Release();
}

HRESULT STDMETHODCALLTYPE
CEnumConnectionPoints::Next(ULONG cConnections, LPCONNECTIONPOINT *ppCP, ULONG *pcFetched)
{
	if (!ppCP)
		return E_POINTER;

	*ppCP = NULL;

    if ((cConnections == 0) || ((cConnections > 1) && (pcFetched == NULL)))
        return E_INVALIDARG;

	ULONG cCopied = 0;
	if (pcFetched)
		*pcFetched = 0;

	if (_pcnp != NULL && _ulIndex == 0)
	{
		*ppCP = _pcnp;
		_pcnp->AddRef();
		if (pcFetched)
			*pcFetched = 1;

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

HRESULT STDMETHODCALLTYPE
CEnumConnectionPoints::Skip(ULONG cConnections)
{
    if (cConnections == 0) 
        return E_INVALIDARG;

	if (_ulIndex > 0 || !_pcnp)
		return S_FALSE;

	_ulIndex++;

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

HRESULT STDMETHODCALLTYPE 
CEnumConnectionPoints::Reset(void)
{
	_ulIndex = 0;
	return S_OK;
}

ULONG STDMETHODCALLTYPE 
CEnumConnectionPoints::AddRef()
{
    return InterlockedIncrement((LONG*)&_ulRefs);
}

ULONG STDMETHODCALLTYPE
CEnumConnectionPoints::Release()
{
    ULONG ulRefs;
	
	InterlockedExchange((LONG *)&ulRefs, _ulRefs);
    SVSUTIL_ASSERT(ulRefs != 0xffffffff);
    
	if (InterlockedDecrement((LONG*)&_ulRefs) == 0)
    {
        delete this;
        return 0;
    }
    return ulRefs - 1;
}

HRESULT STDMETHODCALLTYPE
CEnumConnectionPoints::QueryInterface(REFIID riid, void** ppv)
{
    if ((riid == IID_IEnumConnectionPoints) || (riid == IID_IUnknown))
    {
        *ppv = (IEnumConnectionPoints *) this;
    }
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
    AddRef();
    return S_OK;
}

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

CConnectionPoint::CConnectionPoint(IConnectionPointContainer *pCPC)
{
	_ulRefs = 1;
	_ulNextCookie = 1;
	_pSinkList = NULL;
	_ulConns = 0;
	_pCPC = pCPC;
	if (_pCPC)
		_pCPC->AddRef();
}

CConnectionPoint::~CConnectionPoint()
{
	SVSUTIL_ASSERT(_ulRefs == 0);

	if (_pCPC)
		_pCPC->Release();

	PSINKNODE pNode = _pSinkList;
	while(pNode)
	{
		SVSUTIL_ASSERT(pNode->pObSink);
		
		PSINKNODE pNode1 = pNode->pNext;
        PREFAST_ASSERT(pNode);
        PREFAST_ASSERT(pNode->pObSink);
		pNode->pObSink->Release();
		delete pNode;
		pNode = pNode1;
	}
}

void 
CConnectionPoint::ContainerReleased() 
{
	SVSUTIL_ASSERT(gpSynch->IsLocked());
	PREFAST_ASSERT(_pCPC);
	_pCPC->Release();
	_pCPC = NULL;
}


//NOTE: the *ONLY* way for this to fail is if it cant get the lock
HRESULT
CConnectionPoint::Notify(OBEX_EVENT evt, IUnknown *pOBEXDevice, IUnknown *pUnk2)
{
	SVSUTIL_ASSERT(gpSynch->IsLocked() && pOBEXDevice);
	DEBUGMSG(OBEX_COBEX_ZONE,(L"CConnectionPoint::Notify()\n"));
		
	PSINKNODE pSinkNode = _pSinkList;
	PSINKNODE pPrev = NULL;

	while (pSinkNode)
	{
		IObexSink *pSink = pSinkNode->pObSink;
		pSinkNode->ulFlags |= SN_LOCKED;

		PREFAST_ASSERT(pSink);

		DEBUGMSG(OBEX_COBEX_ZONE,(L"CConnectionPoint::Notify- Unlocking, going out to notify(event=%d, pSink=0x%08x)\n", evt, pSink));
		ReleaseLock();

		//run the sinks Notify
		HRESULT hr = pSink->Notify(evt, pOBEXDevice, pUnk2);

		if(!GetLock())
			return E_FAIL;

		//Notify is always called on the EnumThread, and hence is assured that g_pObex will remain initialized
		DEBUGMSG(OBEX_COBEX_ZONE,(L"CConnectionPoint::Notify- Got lock. Notify returned 0x%08x\n", hr));

		pSinkNode->ulFlags &= ~SN_LOCKED;

		//if the sink node was marked for removal when we unlocked and went out to 
		//  notify, remove it now
		if (pSinkNode->ulFlags & SN_REMOVED)
		{
			DEBUGMSG(OBEX_COBEX_ZONE,(L"CConnectionPoint::Notify - Node 0x%08x marked for removal during Notify.Removing it now.\n", pSinkNode));

			if (!pPrev)
				_pSinkList = pSinkNode->pNext;
			else
				pPrev->pNext = pSinkNode->pNext;
			
			PSINKNODE pCurrent = pSinkNode->pNext;
            PREFAST_ASSERT(pSinkNode);
            PREFAST_ASSERT(pSinkNode->pObSink);
			pSinkNode->pObSink->Release();
			delete pSinkNode;
			_ulConns--;

			pSinkNode = pCurrent;
		}
		else
		{
			pPrev = pSinkNode;
			pSinkNode = pSinkNode->pNext;
		}
	}	

	return S_OK;
}



HRESULT STDMETHODCALLTYPE
CConnectionPoint::GetConnectionInterface(IID *pIID)
{
	if (!pIID)
		return E_POINTER;
	*pIID = IID_IObexSink;
	return S_OK;
}

HRESULT STDMETHODCALLTYPE
CConnectionPoint::GetConnectionPointContainer(IConnectionPointContainer **ppCPC)
{
	if (!ppCPC)
		return E_POINTER;
	
	*ppCPC = NULL;

	if (!GetLock())
		return E_FAIL;

	HRESULT hr = E_FAIL;

	*ppCPC = _pCPC;
	
	if (_pCPC)
	{
		_pCPC->AddRef();
		hr = S_OK;
	}

	ReleaseLock();

	return hr;
}

HRESULT STDMETHODCALLTYPE
CConnectionPoint::Advise(IUnknown *pUnkSink, DWORD *pdwCookie)
{
	if ((!pUnkSink) || (!pdwCookie))
		return E_POINTER;

	*pdwCookie = 0;

	//make sure this IUnknown pointer supports IObexSink
	IObexSink *pObSink = NULL;
	HRESULT hr = pUnkSink->QueryInterface(IID_IObexSink, (LPVOID *)&pObSink);
	if ((FAILED(hr)) || (!pObSink) )
	{
		DEBUGMSG(OBEX_COBEX_ZONE,(L"CConnectionPoint::Advise - Interface does not support IObexSink()\n"));
		return CONNECT_E_CANNOTCONNECT;
	}

	hr = E_FAIL;
	SINKNODE *pSink = NULL;

	if (!GetLock())
		return hr;
	
	SVSUTIL_ASSERT(pObSink);

	//chain in the Sink
	pSink = new SINKNODE;
	if (pSink)
	{
		pSink->ulFlags = 0;

		//ref the sink and put it in the linked list
		pObSink->AddRef();
		pSink->pObSink = pObSink;
		pSink->ulCookie = _ulNextCookie++;
		pSink->pNext = _pSinkList;
		
		*pdwCookie = pSink->ulCookie;
		_pSinkList = pSink;
		_ulConns++;
		hr = S_OK;
	
		DEBUGMSG(OBEX_COBEX_ZONE,(L"CConnectionPoint::Advise - pUnkSink=0x%08x, Cookie=%d\n", pUnkSink, _pSinkList->ulCookie));
	}
	else
	{		
		hr = E_OUTOFMEMORY;
		DEBUGMSG(OBEX_COBEX_ZONE, (L"CConnectionPoint::Advise(pUnkSink=0x%08x) fails. E_OUTOFMEMORY\n", pUnkSink));
	}

	if(pObSink)
		pObSink->Release();
		
	ReleaseLock();

	return hr;
}

HRESULT STDMETHODCALLTYPE
CConnectionPoint::Unadvise(DWORD dwCookie)
{
	DEBUGMSG(OBEX_COBEX_ZONE,(L"CConnectionPoint::Unadvise - Cookie=%d\n", dwCookie));

	if (!dwCookie)
		return E_INVALIDARG;
	
	HRESULT hr = E_FAIL;
	
	if (!GetLock())
		return hr;
	
	PSINKNODE pSinkNode = _pSinkList;
	PSINKNODE pPrev = NULL;
	while(pSinkNode)
	{
        if (pSinkNode->ulCookie == dwCookie)
        {
            pSinkNode->ulFlags |= SN_REMOVED;

			if (!(pSinkNode->ulFlags & SN_LOCKED))
			{
				if (!pPrev)
					_pSinkList = pSinkNode->pNext;
				else
					pPrev->pNext = pSinkNode->pNext;
				
				PREFAST_ASSERT(pSinkNode->pObSink);
				pSinkNode->pObSink->Release();

				delete pSinkNode;
				_ulConns--;
			}

			DEBUGMSG(OBEX_COBEX_ZONE,(L"CConnectionPoint::Unadvise : Removed Cookie=%d\n", dwCookie));
			ReleaseLock();
			return S_OK;
        };
		pPrev = pSinkNode;
		pSinkNode = pSinkNode->pNext;
	}

	SVSUTIL_ASSERT(pSinkNode == NULL);

	DEBUGMSG(OBEX_COBEX_ZONE,(L"CConnectionPoint::Unadvise : Missing Cookie=%d\n", dwCookie));
	
	ReleaseLock();

	return CONNECT_E_CANNOTCONNECT;
}

HRESULT STDMETHODCALLTYPE
CConnectionPoint::EnumConnections(IEnumConnections **ppEnum)
{
	if (!ppEnum)
		return E_POINTER;
	
	*ppEnum = NULL;

	HRESULT hr = E_FAIL;

	if (!GetLock())
		return hr;

	CEnumConnections *pEnum = new CEnumConnections();
	if (pEnum)
	{
		hr = pEnum->Init(_pSinkList, _ulConns);
		if (SUCCEEDED(hr))
		{
			*ppEnum = (IEnumConnections *)pEnum;
			(*ppEnum)->AddRef();
			hr = S_OK;
		}
		pEnum->Release();
	}
	else
		hr = E_OUTOFMEMORY;
	
	ReleaseLock();

	return hr;
}


ULONG STDMETHODCALLTYPE 
CConnectionPoint::AddRef()
{
    return InterlockedIncrement((LONG*)&_ulRefs);
}

ULONG STDMETHODCALLTYPE
CConnectionPoint::Release()
{
    ULONG ulRefs;
	InterlockedExchange((LONG *)&ulRefs, _ulRefs);
    SVSUTIL_ASSERT(ulRefs != 0xffffffff);

    if (InterlockedDecrement((LONG*)&_ulRefs) == 0)
    {
        delete this;
        return 0;
    }
    return ulRefs - 1;
}

HRESULT STDMETHODCALLTYPE
CConnectionPoint::QueryInterface(REFIID riid, void** ppv)
{
    if ((riid == IID_IConnectionPoint) || (riid == IID_IUnknown))
    {
        *ppv = (IConnectionPoint *) this;
    }
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
    AddRef();
    return S_OK;
}

////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////
CEnumConnections::CEnumConnections()
{
	_ulRefs = 1;
	_ulIndex = 0;
	_ulConns = 0;
	_pConnData = NULL;
}

CEnumConnections::~CEnumConnections()
{
	SVSUTIL_ASSERT(_ulRefs == 0);

	if (_pConnData)
	{
		for (ULONG i=0; i<_ulConns; i++)
		{
			PREFAST_ASSERT(_pConnData[i].pUnk);
			_pConnData[i].pUnk->Release();
		}
	}
	delete[] _pConnData;
	_pConnData = NULL;
}


HRESULT 
CEnumConnections::Init(SINKNODE *pSinkList, ULONG ulConns)
{
    //
    // 100 is arbituary, it should be safe this big but realistically
    //    never get there
	if (!ulConns || ulConns > 100)
	{
		SVSUTIL_ASSERT(!pSinkList);
		return S_FALSE;
	}

	if (!pSinkList)
		return E_INVALIDARG;

	SVSUTIL_ASSERT(gpSynch->IsLocked());

	_pConnData = new CONNECTDATA[ulConns];
	if (!_pConnData)
		return E_OUTOFMEMORY;

	for (ULONG i=0; (pSinkList) && (i<ulConns); i++)
	{
		_pConnData[i].pUnk = (IUnknown *)pSinkList->pObSink;
		_pConnData[i].pUnk->AddRef();
		_pConnData[i].dwCookie = pSinkList->ulCookie;
		pSinkList = pSinkList->pNext;
	}

	if (i==ulConns && pSinkList == NULL)
	{
		_ulConns = ulConns;
		return S_OK;
	}

	delete[] _pConnData;
	_ulConns = 0;
	return E_FAIL;
}


HRESULT STDMETHODCALLTYPE
CEnumConnections::Next(ULONG cConnections, CONNECTDATA *pCD, ULONG *pcFetched)
{
	if (!pCD)
		return E_POINTER;

    if ((cConnections == 0) || ((cConnections > 1) && (pcFetched == NULL)))
        return E_INVALIDARG;

	ULONG cCopied = 0;
	if (pcFetched)
		*pcFetched = 0;

	if (_pConnData != NULL && _ulIndex < _ulConns)
	{
		while(_ulIndex < _ulConns)
		{	
			pCD[cCopied].pUnk = _pConnData[_ulIndex].pUnk;
			pCD[cCopied].pUnk->AddRef();
			pCD[cCopied].dwCookie = _pConnData[_ulIndex].dwCookie;
			cCopied++;
			_ulIndex++;
		}
		if (pcFetched)
			*pcFetched = cCopied;
	}
	return (cConnections == cCopied) ? S_OK : S_FALSE;
}

HRESULT STDMETHODCALLTYPE
CEnumConnections::Skip(ULONG cConnections)
{
    if (cConnections == 0) 
        return E_INVALIDARG;

	if (_ulIndex + cConnections >= _ulConns)
	{
		_ulIndex = _ulConns;
		return S_FALSE;
	}
	else
	{
		_ulIndex += cConnections;
		return S_OK;
	}
}

HRESULT STDMETHODCALLTYPE
CEnumConnections::Reset(void)
{
	_ulIndex = 0;
	return S_OK;
}

ULONG STDMETHODCALLTYPE 
CEnumConnections::AddRef()
{
    return InterlockedIncrement((LONG*)&_ulRefs);
}

ULONG STDMETHODCALLTYPE
CEnumConnections::Release()
{
    ULONG ulRefs;
	InterlockedExchange((LONG *)&ulRefs, _ulRefs);
    SVSUTIL_ASSERT(ulRefs != 0xffffffff);

    if (InterlockedDecrement((LONG*)&_ulRefs) == 0)
    {
        delete this;
        return 0;
    }
    return ulRefs - 1;
}

HRESULT STDMETHODCALLTYPE
CEnumConnections::QueryInterface(REFIID riid, void** ppv)
{
    if ((riid == IID_IEnumConnections) || (riid == IID_IUnknown))
    {
        *ppv = (IEnumConnections *) this;
    }
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
    AddRef();
    return S_OK;
}
