/*
 * Implements IPin and IMemInputPin. (internal)
 *
 * Copyright (C) Hidenori TAKESHIMA <hidenori@a2.ctktv.ne.jp>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "config.h"

#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winuser.h"
#include "winerror.h"
#include "strmif.h"
#include "vfwmsgs.h"

#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(quartz);

#include "quartz_private.h"
#include "basefilt.h"
#include "memalloc.h"


/***************************************************************************
 *
 *	CPinBaseImpl
 *
 */

static HRESULT WINAPI
CPinBaseImpl_fnQueryInterface(IPin* iface,REFIID riid,void** ppobj)
{
	ICOM_THIS(CPinBaseImpl,iface);

	TRACE("(%p)->()\n",This);

	return IUnknown_QueryInterface(This->punkControl,riid,ppobj);
}

static ULONG WINAPI
CPinBaseImpl_fnAddRef(IPin* iface)
{
	ICOM_THIS(CPinBaseImpl,iface);

	TRACE("(%p)->()\n",This);

	return IUnknown_AddRef(This->punkControl);
}

static ULONG WINAPI
CPinBaseImpl_fnRelease(IPin* iface)
{
	ICOM_THIS(CPinBaseImpl,iface);

	TRACE("(%p)->()\n",This);

	return IUnknown_Release(This->punkControl);
}

static HRESULT WINAPI
CPinBaseImpl_fnConnect(IPin* iface,IPin* pPin,const AM_MEDIA_TYPE* pmt)
{
	ICOM_THIS(CPinBaseImpl,iface);
	HRESULT	hr = E_NOTIMPL;
	ULONG	i;
	FILTER_STATE	fs;

	TRACE("(%p)->(%p,%p)\n",This,pPin,pmt);

	if ( !This->bOutput )
	{
		TRACE("Connect() should not be sent to input pins\n");
		return E_UNEXPECTED;
	}
	if ( pPin == NULL )
		return E_POINTER;

	TRACE("try to connect to %p\n",pPin);

	EnterCriticalSection( This->pcsPin );

	if ( This->pPinConnectedTo != NULL )
	{
		hr = VFW_E_ALREADY_CONNECTED;
		goto err;
	}

	/* return fail if running */
	hr = IBaseFilter_GetState((IBaseFilter*)(This->pFilter),0,&fs);
	if ( hr != S_OK || fs != State_Stopped )
	{
		TRACE("not stopped\n");
		hr = VFW_E_NOT_STOPPED;
		goto err;
	}

	if ( This->pHandlers->pOnPreConnect != NULL )
	{
		hr = This->pHandlers->pOnPreConnect(This,pPin);
		if ( FAILED(hr) )
		{
			TRACE("OnPreconnect() failed hr = %08lx\n",hr);
			goto err;
		}
	}

	if ( pmt != NULL )
	{
		hr = IPin_QueryAccept(iface,pmt);
		if ( FAILED(hr) )
			goto err;
		This->pPinConnectedTo = pPin;
		hr = IPin_ReceiveConnection(pPin,iface,pmt);
		This->pPinConnectedTo = NULL;
		if ( FAILED(hr) )
			goto err;
	}
	else
	{
		for ( i = 0; i < This->cAcceptTypes; i++ )
		{
			pmt = &This->pmtAcceptTypes[i];
			hr = IPin_QueryAccept(iface,pmt);
			if ( SUCCEEDED(hr) )
			{
				This->pPinConnectedTo = pPin;
				hr = IPin_ReceiveConnection(pPin,iface,pmt);
				This->pPinConnectedTo = NULL;

				TRACE("ReceiveConnection - %08lx\n",hr);
				if ( SUCCEEDED(hr) )
				{
					goto connected;
				}
			}
		}

		hr = VFW_E_TYPE_NOT_ACCEPTED;
		goto err;
	}

connected:;
	This->pmtConn = QUARTZ_MediaType_Duplicate( pmt );
	if ( This->pmtConn == NULL )
	{
		hr = E_OUTOFMEMORY;
		IPin_Disconnect(pPin);
		goto err;
	}

	This->pPinConnectedTo = pPin; IPin_AddRef(pPin);
	hr = IPin_QueryInterface(pPin,&IID_IMemInputPin,(void**)&This->pMemInputPinConnectedTo);
	if ( FAILED(hr) )
	{
		TRACE("no IMemInputPin\n");
		IPin_Disconnect(pPin);
		goto err;
	}

	if ( This->pHandlers->pOnPostConnect != NULL )
	{
		hr = This->pHandlers->pOnPostConnect(This,pPin);
		if ( FAILED(hr) )
		{
			TRACE("OnPostConnect() failed hr = %08lx\n",hr);
			IPin_Disconnect(pPin);
			goto err;
		}
	}

	hr = S_OK;

err:
	if ( FAILED(hr) )
	{
		IPin_Disconnect(iface);
	}
	LeaveCriticalSection( This->pcsPin );

	TRACE("return %08lx\n",hr);

	return hr;
}

static HRESULT WINAPI
CPinBaseImpl_fnReceiveConnection(IPin* iface,IPin* pPin,const AM_MEDIA_TYPE* pmt)
{
	ICOM_THIS(CPinBaseImpl,iface);
	HRESULT	hr = E_NOTIMPL;
	FILTER_STATE fs;

	TRACE("(%p)->(%p,%p)\n",This,pPin,pmt);

	if ( This->bOutput )
	{
		TRACE("ReceiveConnection() should not be sent to output pins\n");
		return E_UNEXPECTED;
	}
	if ( pPin == NULL || pmt == NULL )
		return E_POINTER;

	EnterCriticalSection( This->pcsPin );

	if ( This->pPinConnectedTo != NULL )
	{
		hr = VFW_E_ALREADY_CONNECTED;
		goto err;
	}

	/* return fail if running */
	hr = IBaseFilter_GetState((IBaseFilter*)(This->pFilter),0,&fs);
	if ( hr != S_OK || fs != State_Stopped )
	{
		TRACE("not stopped\n");
		hr = VFW_E_NOT_STOPPED;
		goto err;
	}

	if ( This->pHandlers->pOnPreConnect != NULL )
	{
		hr = This->pHandlers->pOnPreConnect(This,pPin);
		if ( FAILED(hr) )
		{
			TRACE("OnPreConnect() failed hr = %08lx\n",hr);
			goto err;
		}
	}

	hr = IPin_QueryAccept(iface,pmt);
	if ( FAILED(hr) )
		goto err;

	This->pmtConn = QUARTZ_MediaType_Duplicate( pmt );
	if ( This->pmtConn == NULL )
	{
		hr = E_OUTOFMEMORY;
		goto err;
	}

	if ( This->pHandlers->pOnPostConnect != NULL )
	{
		hr = This->pHandlers->pOnPostConnect(This,pPin);
		if ( FAILED(hr) )
		{
			TRACE("OnPostConnect() failed hr = %08lx\n",hr);
			goto err;
		}
	}

	hr = S_OK;
	This->pPinConnectedTo = pPin; IPin_AddRef(pPin);

err:
	if ( FAILED(hr) )
		IPin_Disconnect(iface);
	LeaveCriticalSection( This->pcsPin );

	return hr;
}

static HRESULT WINAPI
CPinBaseImpl_fnDisconnect(IPin* iface)
{
	ICOM_THIS(CPinBaseImpl,iface);
	HRESULT hr = NOERROR;
	FILTER_STATE fs;

	TRACE("(%p)->()\n",This);

	EnterCriticalSection( This->pcsPin );

	/* return fail if running */
	hr = IBaseFilter_GetState((IBaseFilter*)(This->pFilter),0,&fs);
	if ( hr != S_OK || fs != State_Stopped )
	{
		TRACE("not stopped\n");
		hr = VFW_E_NOT_STOPPED;
		goto err;
	}

	if ( This->pHandlers->pOnDisconnect != NULL )
		hr = This->pHandlers->pOnDisconnect(This);

	if ( This->pmtConn != NULL )
	{
		QUARTZ_MediaType_Destroy( This->pmtConn );
		This->pmtConn = NULL;
	}
	if ( This->pMemInputPinConnectedTo != NULL )
	{
		IMemInputPin_Release(This->pMemInputPinConnectedTo);
		This->pMemInputPinConnectedTo = NULL;
	}
	if ( This->pPinConnectedTo != NULL )
	{
		/* FIXME - cleanup */

		IPin_Release(This->pPinConnectedTo);
		This->pPinConnectedTo = NULL;
		hr = NOERROR;
	}
	else
	{
		hr = S_FALSE; /* FIXME - is this correct??? */
	}

err:
	LeaveCriticalSection( This->pcsPin );

	return hr;
}

static HRESULT WINAPI
CPinBaseImpl_fnConnectedTo(IPin* iface,IPin** ppPin)
{
	ICOM_THIS(CPinBaseImpl,iface);
	HRESULT	hr = VFW_E_NOT_CONNECTED;

	TRACE("(%p)->(%p)\n",This,ppPin);

	if ( ppPin == NULL )
		return E_POINTER;

	EnterCriticalSection( This->pcsPin );

	*ppPin = This->pPinConnectedTo;
	if ( This->pPinConnectedTo != NULL )
	{
		IPin_AddRef(This->pPinConnectedTo);
		hr = NOERROR;
	}

	LeaveCriticalSection( This->pcsPin );

	return hr;
}

static HRESULT WINAPI
CPinBaseImpl_fnConnectionMediaType(IPin* iface,AM_MEDIA_TYPE* pmt)
{
	ICOM_THIS(CPinBaseImpl,iface);
	HRESULT hr = E_FAIL;

	TRACE("(%p)->(%p)\n",This,pmt);

	if ( pmt == NULL )
		return E_POINTER;

	EnterCriticalSection( This->pcsPin );

	if ( This->pmtConn != NULL )
	{
		hr = QUARTZ_MediaType_Copy( pmt, This->pmtConn );
	}
	else
	{
		ZeroMemory( pmt, sizeof(AM_MEDIA_TYPE) );
		pmt->bFixedSizeSamples = TRUE;
		pmt->lSampleSize = 1;
		hr = VFW_E_NOT_CONNECTED;
	}

	LeaveCriticalSection( This->pcsPin );

	return hr;
}

static HRESULT WINAPI
CPinBaseImpl_fnQueryPinInfo(IPin* iface,PIN_INFO* pinfo)
{
	ICOM_THIS(CPinBaseImpl,iface);

	TRACE("(%p)->(%p)\n",This,pinfo);

	if ( pinfo == NULL )
		return E_POINTER;

	EnterCriticalSection( This->pcsPin );

	ZeroMemory( pinfo, sizeof(PIN_INFO) );
	pinfo->pFilter = (IBaseFilter*)(This->pFilter);
	if ( pinfo->pFilter != NULL )
		IBaseFilter_AddRef( pinfo->pFilter );
	pinfo->dir = This->bOutput ? PINDIR_OUTPUT : PINDIR_INPUT;
	if ( This->cbIdLen <= sizeof(pinfo->achName) )
		memcpy( pinfo->achName, This->pwszId, This->cbIdLen );
	else
	{
		memcpy( pinfo->achName, This->pwszId, sizeof(pinfo->achName) );
		pinfo->achName[sizeof(pinfo->achName)/sizeof(pinfo->achName[0])-1] = 0;
	}

	LeaveCriticalSection( This->pcsPin );

	return NOERROR;
}

static HRESULT WINAPI
CPinBaseImpl_fnQueryDirection(IPin* iface,PIN_DIRECTION* pdir)
{
	ICOM_THIS(CPinBaseImpl,iface);

	TRACE("(%p)->(%p)\n",This,pdir);

	if ( pdir == NULL )
		return E_POINTER;

	*pdir = This->bOutput ? PINDIR_OUTPUT : PINDIR_INPUT;

	return NOERROR;
}

static HRESULT WINAPI
CPinBaseImpl_fnQueryId(IPin* iface,LPWSTR* lpwszId)
{
	ICOM_THIS(CPinBaseImpl,iface);

	TRACE("(%p)->(%p)\n",This,lpwszId);

	if ( lpwszId == NULL )
		return E_POINTER;

	*lpwszId = (WCHAR*)CoTaskMemAlloc( This->cbIdLen );
	if ( *lpwszId == NULL )
		return E_OUTOFMEMORY;
	memcpy( *lpwszId, This->pwszId, This->cbIdLen );

	return NOERROR;
}

static HRESULT WINAPI
CPinBaseImpl_fnQueryAccept(IPin* iface,const AM_MEDIA_TYPE* pmt)
{
	ICOM_THIS(CPinBaseImpl,iface);
	HRESULT hr;

	TRACE("(%p)->(%p)\n",This,pmt);

	if ( pmt == NULL )
		return E_POINTER;

	hr = NOERROR;
	EnterCriticalSection( This->pcsPin );
	if ( This->pHandlers->pCheckMediaType != NULL )
		hr = This->pHandlers->pCheckMediaType(This,pmt);
	LeaveCriticalSection( This->pcsPin );

	return hr;
}

static HRESULT WINAPI
CPinBaseImpl_fnEnumMediaTypes(IPin* iface,IEnumMediaTypes** ppenum)
{
	ICOM_THIS(CPinBaseImpl,iface);
	HRESULT hr;

	TRACE("(%p)->(%p)\n",This,ppenum);

	if ( ppenum == NULL )
		return E_POINTER;

	hr = E_NOTIMPL;

	EnterCriticalSection( This->pcsPin );
	if ( This->cAcceptTypes > 0 )
		hr = QUARTZ_CreateEnumMediaTypes(
			ppenum, This->pmtAcceptTypes, This->cAcceptTypes );
	LeaveCriticalSection( This->pcsPin );

	return hr;
}

static HRESULT WINAPI
CPinBaseImpl_fnQueryInternalConnections(IPin* iface,IPin** ppPin,ULONG* pul)
{
	ICOM_THIS(CPinBaseImpl,iface);

	TRACE("(%p)->(%p,%p)\n",This,ppPin,pul);

	/* E_NOTIMPL means 'no internal connections'. */
	return E_NOTIMPL;
}

static HRESULT WINAPI
CPinBaseImpl_fnEndOfStream(IPin* iface)
{
	ICOM_THIS(CPinBaseImpl,iface);
	HRESULT hr = E_NOTIMPL;

	TRACE("(%p)->()\n",This);

	if ( This->bOutput )
		return E_UNEXPECTED;

	EnterCriticalSection( This->pcsPinReceive );
	if ( This->pHandlers->pEndOfStream != NULL )
		hr = This->pHandlers->pEndOfStream(This);
	LeaveCriticalSection( This->pcsPinReceive );

	return hr;
}

static HRESULT WINAPI
CPinBaseImpl_fnBeginFlush(IPin* iface)
{
	ICOM_THIS(CPinBaseImpl,iface);
	HRESULT hr = E_NOTIMPL;

	TRACE("(%p)->()\n",This);

	if ( This->bOutput )
		return E_UNEXPECTED;

	EnterCriticalSection( This->pcsPin );
	if ( This->pHandlers->pBeginFlush != NULL )
		hr = This->pHandlers->pBeginFlush(This);
	LeaveCriticalSection( This->pcsPin );

	EnterCriticalSection( This->pcsPinReceive );
	LeaveCriticalSection( This->pcsPinReceive );

	return hr;
}

static HRESULT WINAPI
CPinBaseImpl_fnEndFlush(IPin* iface)
{
	ICOM_THIS(CPinBaseImpl,iface);
	HRESULT hr = E_NOTIMPL;

	TRACE("(%p)->()\n",This);

	if ( This->bOutput )
		return E_UNEXPECTED;

	EnterCriticalSection( This->pcsPin );
	if ( This->pHandlers->pEndFlush != NULL )
		hr = This->pHandlers->pEndFlush(This);
	LeaveCriticalSection( This->pcsPin );

	return hr;
}

static HRESULT WINAPI
CPinBaseImpl_fnNewSegment(IPin* iface,REFERENCE_TIME rtStart,REFERENCE_TIME rtStop,double rate)
{
	ICOM_THIS(CPinBaseImpl,iface);
	HRESULT hr = E_NOTIMPL;

	TRACE("(%p)->()\n",This);

	if ( This->bOutput )
		return E_UNEXPECTED;

	EnterCriticalSection( This->pcsPin );
	if ( This->pHandlers->pNewSegment != NULL )
		hr = This->pHandlers->pNewSegment(This,rtStart,rtStop,rate);
	LeaveCriticalSection( This->pcsPin );

	return hr;
}




static ICOM_VTABLE(IPin) ipin =
{
	ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
	/* IUnknown fields */
	CPinBaseImpl_fnQueryInterface,
	CPinBaseImpl_fnAddRef,
	CPinBaseImpl_fnRelease,
	/* IPin fields */
	CPinBaseImpl_fnConnect,
	CPinBaseImpl_fnReceiveConnection,
	CPinBaseImpl_fnDisconnect,
	CPinBaseImpl_fnConnectedTo,
	CPinBaseImpl_fnConnectionMediaType,
	CPinBaseImpl_fnQueryPinInfo,
	CPinBaseImpl_fnQueryDirection,
	CPinBaseImpl_fnQueryId,
	CPinBaseImpl_fnQueryAccept,
	CPinBaseImpl_fnEnumMediaTypes,
	CPinBaseImpl_fnQueryInternalConnections,
	CPinBaseImpl_fnEndOfStream,
	CPinBaseImpl_fnBeginFlush,
	CPinBaseImpl_fnEndFlush,
	CPinBaseImpl_fnNewSegment,
};


HRESULT CPinBaseImpl_InitIPin(
	CPinBaseImpl* This, IUnknown* punkControl,
	CRITICAL_SECTION* pcsPin,
	CRITICAL_SECTION* pcsPinReceive,
	CBaseFilterImpl* pFilter, LPCWSTR pwszId,
	BOOL bOutput,
	const CBasePinHandlers*	pHandlers )
{
	HRESULT	hr = NOERROR;

	TRACE("(%p,%p,%p)\n",This,punkControl,pFilter);

	if ( punkControl == NULL )
	{
		ERR( "punkControl must not be NULL\n" );
		return E_INVALIDARG;
	}

	ICOM_VTBL(This) = &ipin;
	This->punkControl = punkControl;
	This->pHandlers = pHandlers;
	This->cbIdLen = sizeof(WCHAR)*(lstrlenW(pwszId)+1);
	This->pwszId = NULL;
	This->bOutput = bOutput;
	This->pmtAcceptTypes = NULL;
	This->cAcceptTypes = 0;
	This->pcsPin = pcsPin;
	This->pcsPinReceive = pcsPinReceive;
	This->pFilter = pFilter;
	This->pPinConnectedTo = NULL;
	This->pMemInputPinConnectedTo = NULL;
	This->pmtConn = NULL;
	This->pAsyncOut = NULL;

	This->pwszId = (WCHAR*)QUARTZ_AllocMem( This->cbIdLen );
	if ( This->pwszId == NULL )
	{
		hr = E_OUTOFMEMORY;
		goto err;
	}
	memcpy( This->pwszId, pwszId, This->cbIdLen );

	return NOERROR;

err:;
	CPinBaseImpl_UninitIPin( This );
	return hr;
}

void CPinBaseImpl_UninitIPin( CPinBaseImpl* This )
{
	TRACE("(%p)\n",This);

	IPin_Disconnect( (IPin*)(This) );

	if ( This->pwszId != NULL )
	{
		QUARTZ_FreeMem( This->pwszId );
		This->pwszId = NULL;
	}
}


/***************************************************************************
 *
 *	CMemInputPinBaseImpl
 *
 */


static HRESULT WINAPI
CMemInputPinBaseImpl_fnQueryInterface(IMemInputPin* iface,REFIID riid,void** ppobj)
{
	ICOM_THIS(CMemInputPinBaseImpl,iface);

	TRACE("(%p)->()\n",This);

	return IUnknown_QueryInterface(This->punkControl,riid,ppobj);
}

static ULONG WINAPI
CMemInputPinBaseImpl_fnAddRef(IMemInputPin* iface)
{
	ICOM_THIS(CMemInputPinBaseImpl,iface);

	TRACE("(%p)->()\n",This);

	return IUnknown_AddRef(This->punkControl);
}

static ULONG WINAPI
CMemInputPinBaseImpl_fnRelease(IMemInputPin* iface)
{
	ICOM_THIS(CMemInputPinBaseImpl,iface);

	TRACE("(%p)->()\n",This);

	return IUnknown_Release(This->punkControl);
}


static HRESULT WINAPI
CMemInputPinBaseImpl_fnGetAllocator(IMemInputPin* iface,IMemAllocator** ppAllocator)
{
	ICOM_THIS(CMemInputPinBaseImpl,iface);
	HRESULT hr = NOERROR;
	IUnknown* punk;

	TRACE("(%p)->()\n",This);

	if ( ppAllocator == NULL )
		return E_POINTER;

	EnterCriticalSection( This->pPin->pcsPin );

	if ( This->pAllocator == NULL )
	{
		hr = QUARTZ_CreateMemoryAllocator(NULL,(void**)&punk);
		if ( hr == NOERROR )
		{
			hr = IUnknown_QueryInterface(punk,
				&IID_IMemAllocator,(void**)&This->pAllocator);
			IUnknown_Release(punk);
		}
	}

	if ( hr == NOERROR )
	{
		*ppAllocator = This->pAllocator;
		IMemAllocator_AddRef(This->pAllocator);
	}

	LeaveCriticalSection( This->pPin->pcsPin );

	return hr;
}

static HRESULT WINAPI
CMemInputPinBaseImpl_fnNotifyAllocator(IMemInputPin* iface,IMemAllocator* pAllocator,BOOL bReadonly)
{
	ICOM_THIS(CMemInputPinBaseImpl,iface);

	TRACE("(%p)->()\n",This);

	if ( pAllocator == NULL )
		return E_POINTER;

	EnterCriticalSection( This->pPin->pcsPin );

	if ( This->pAllocator != NULL )
	{
		IMemAllocator_Release(This->pAllocator);
		This->pAllocator = NULL;
	}
	This->pAllocator = pAllocator;
	IMemAllocator_AddRef(This->pAllocator);

	This->bReadonly = bReadonly;

	LeaveCriticalSection( This->pPin->pcsPin );

	return NOERROR;
}

static HRESULT WINAPI
CMemInputPinBaseImpl_fnGetAllocatorRequirements(IMemInputPin* iface,ALLOCATOR_PROPERTIES* pProp)
{
	ICOM_THIS(CMemInputPinBaseImpl,iface);

	TRACE("(%p)->(%p)\n",This,pProp);

	if ( pProp == NULL )
		return E_POINTER;

	/* E_MOTIMPL means 'no requirements' */
	return E_NOTIMPL;
}

static HRESULT WINAPI
CMemInputPinBaseImpl_fnReceive(IMemInputPin* iface,IMediaSample* pSample)
{
	ICOM_THIS(CMemInputPinBaseImpl,iface);
	HRESULT hr = E_NOTIMPL;

	TRACE("(%p)->(%p)\n",This,pSample);

	EnterCriticalSection( This->pPin->pcsPinReceive );
	if ( This->pPin->pHandlers->pReceive != NULL )
		hr = This->pPin->pHandlers->pReceive(This->pPin,pSample);
	LeaveCriticalSection( This->pPin->pcsPinReceive );

	return hr;
}

static HRESULT WINAPI
CMemInputPinBaseImpl_fnReceiveMultiple(IMemInputPin* iface,IMediaSample** ppSample,long nSample,long* pnSampleProcessed)
{
	ICOM_THIS(CMemInputPinBaseImpl,iface);
	long	n;
	HRESULT hr;

	TRACE("(%p)->()\n",This);

	if ( ppSample == NULL || pnSampleProcessed == NULL )
		return E_POINTER;

	hr = NOERROR;
	for ( n = 0; n < nSample; n++ )
	{
		hr = IMemInputPin_Receive(iface,ppSample[n]);
		if ( FAILED(hr) )
			break;
	}

	*pnSampleProcessed = n;
	return hr;
}

static HRESULT WINAPI
CMemInputPinBaseImpl_fnReceiveCanBlock(IMemInputPin* iface)
{
	ICOM_THIS(CMemInputPinBaseImpl,iface);
	HRESULT hr = E_NOTIMPL;

	TRACE("(%p)->()\n",This);

	EnterCriticalSection( This->pPin->pcsPin );
	if ( This->pPin->pHandlers->pReceiveCanBlock != NULL )
		hr = This->pPin->pHandlers->pReceiveCanBlock(This->pPin);
	LeaveCriticalSection( This->pPin->pcsPin );

	return hr;
}


static ICOM_VTABLE(IMemInputPin) imeminputpin =
{
	ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
	/* IUnknown fields */
	CMemInputPinBaseImpl_fnQueryInterface,
	CMemInputPinBaseImpl_fnAddRef,
	CMemInputPinBaseImpl_fnRelease,
	/* IMemInputPin fields */
	CMemInputPinBaseImpl_fnGetAllocator,
	CMemInputPinBaseImpl_fnNotifyAllocator,
	CMemInputPinBaseImpl_fnGetAllocatorRequirements,
	CMemInputPinBaseImpl_fnReceive,
	CMemInputPinBaseImpl_fnReceiveMultiple,
	CMemInputPinBaseImpl_fnReceiveCanBlock,
};

HRESULT CMemInputPinBaseImpl_InitIMemInputPin(
	CMemInputPinBaseImpl* This, IUnknown* punkControl,
	CPinBaseImpl* pPin )
{
	TRACE("(%p,%p)\n",This,punkControl);

	if ( punkControl == NULL )
	{
		ERR( "punkControl must not be NULL\n" );
		return E_INVALIDARG;
	}

	ICOM_VTBL(This) = &imeminputpin;
	This->punkControl = punkControl;
	This->pPin = pPin;
	This->pAllocator = NULL;
	This->bReadonly = FALSE;

	return NOERROR;
}

void CMemInputPinBaseImpl_UninitIMemInputPin(
	CMemInputPinBaseImpl* This )
{
	TRACE("(%p)\n",This);

	if ( This->pAllocator != NULL )
	{
		IMemAllocator_Release(This->pAllocator);
		This->pAllocator = NULL;
	}
}

/***************************************************************************
 *
 *	CQualityControlPassThruImpl
 *
 */

static HRESULT WINAPI
CQualityControlPassThruImpl_fnQueryInterface(IQualityControl* iface,REFIID riid,void** ppobj)
{
	ICOM_THIS(CQualityControlPassThruImpl,iface);

	TRACE("(%p)->()\n",This);

	return IUnknown_QueryInterface(This->punkControl,riid,ppobj);
}

static ULONG WINAPI
CQualityControlPassThruImpl_fnAddRef(IQualityControl* iface)
{
	ICOM_THIS(CQualityControlPassThruImpl,iface);

	TRACE("(%p)->()\n",This);

	return IUnknown_AddRef(This->punkControl);
}

static ULONG WINAPI
CQualityControlPassThruImpl_fnRelease(IQualityControl* iface)
{
	ICOM_THIS(CQualityControlPassThruImpl,iface);

	TRACE("(%p)->()\n",This);

	return IUnknown_Release(This->punkControl);
}


static HRESULT WINAPI
CQualityControlPassThruImpl_fnNotify(IQualityControl* iface,IBaseFilter* pFilter,Quality q)
{
	ICOM_THIS(CQualityControlPassThruImpl,iface);
	HRESULT hr = S_FALSE;

	TRACE("(%p)->()\n",This);

	if ( This->pControl != NULL )
		return IQualityControl_Notify( This->pControl, pFilter, q );

	EnterCriticalSection( This->pPin->pcsPin );
	if ( This->pPin->pHandlers->pQualityNotify != NULL )
		hr = This->pPin->pHandlers->pQualityNotify(This->pPin,pFilter,q);
	LeaveCriticalSection( This->pPin->pcsPin );

	return hr;
}

static HRESULT WINAPI
CQualityControlPassThruImpl_fnSetSink(IQualityControl* iface,IQualityControl* pControl)
{
	ICOM_THIS(CQualityControlPassThruImpl,iface);

	TRACE("(%p)->()\n",This);

	This->pControl = pControl; /* AddRef() must not be called */

	return NOERROR;
}

static ICOM_VTABLE(IQualityControl) iqualitycontrol =
{
	ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
	/* IUnknown fields */
	CQualityControlPassThruImpl_fnQueryInterface,
	CQualityControlPassThruImpl_fnAddRef,
	CQualityControlPassThruImpl_fnRelease,
	/* IQualityControl fields */
	CQualityControlPassThruImpl_fnNotify,
	CQualityControlPassThruImpl_fnSetSink,
};

HRESULT CQualityControlPassThruImpl_InitIQualityControl(
	CQualityControlPassThruImpl* This, IUnknown* punkControl,
	CPinBaseImpl* pPin )
{
	TRACE("(%p,%p)\n",This,punkControl);

	if ( punkControl == NULL )
	{
		ERR( "punkControl must not be NULL\n" );
		return E_INVALIDARG;
	}

	ICOM_VTBL(This) = &iqualitycontrol;
	This->punkControl = punkControl;
	This->pPin = pPin;

	return NOERROR;
}

void CQualityControlPassThruImpl_UninitIQualityControl(
	CQualityControlPassThruImpl* This )
{
}

/***************************************************************************
 *
 *	helper methods for output pins.
 *
 */

HRESULT CPinBaseImpl_SendSample( CPinBaseImpl* This, IMediaSample* pSample )
{
	if ( This->pHandlers->pReceive == NULL )
		return E_NOTIMPL;

	TRACE("pReceive==%p\n", This->pHandlers->pReceive);

	return This->pHandlers->pReceive( This, pSample );
}

HRESULT CPinBaseImpl_SendReceiveCanBlock( CPinBaseImpl* This )
{
	if ( This->pHandlers->pReceiveCanBlock == NULL )
		return E_NOTIMPL;

	return This->pHandlers->pReceiveCanBlock( This );
}

HRESULT CPinBaseImpl_SendEndOfStream( CPinBaseImpl* This )
{
	if ( This->pHandlers->pEndOfStream == NULL )
		return E_NOTIMPL;

	return This->pHandlers->pEndOfStream( This );
}

HRESULT CPinBaseImpl_SendBeginFlush( CPinBaseImpl* This )
{
	if ( This->pHandlers->pBeginFlush == NULL )
		return E_NOTIMPL;

	return This->pHandlers->pBeginFlush( This );
}

HRESULT CPinBaseImpl_SendEndFlush( CPinBaseImpl* This )
{
	if ( This->pHandlers->pEndFlush == NULL )
		return E_NOTIMPL;

	return This->pHandlers->pEndFlush( This );
}

HRESULT CPinBaseImpl_SendNewSegment( CPinBaseImpl* This, REFERENCE_TIME rtStart, REFERENCE_TIME rtStop, double rate )
{
	if ( This->pHandlers->pNewSegment == NULL )
		return E_NOTIMPL;

	return This->pHandlers->pNewSegment( This, rtStart, rtStop, rate );
}



/***************************************************************************
 *
 *	handlers for output pins.
 *
 */

HRESULT OutputPinSync_Receive( CPinBaseImpl* pImpl, IMediaSample* pSample )
{
	if ( pImpl->pMemInputPinConnectedTo == NULL )
		return NOERROR;

	return IMemInputPin_Receive(pImpl->pMemInputPinConnectedTo,pSample);
}

HRESULT OutputPinSync_ReceiveCanBlock( CPinBaseImpl* pImpl )
{
	if ( pImpl->pMemInputPinConnectedTo == NULL )
		return S_FALSE;

	return IMemInputPin_ReceiveCanBlock(pImpl->pMemInputPinConnectedTo);
}

HRESULT OutputPinSync_EndOfStream( CPinBaseImpl* pImpl )
{
	if ( pImpl->pPinConnectedTo == NULL )
		return NOERROR;

	return IPin_EndOfStream(pImpl->pPinConnectedTo);
}

HRESULT OutputPinSync_BeginFlush( CPinBaseImpl* pImpl )
{
	if ( pImpl->pPinConnectedTo == NULL )
		return NOERROR;

	return IPin_BeginFlush(pImpl->pPinConnectedTo);
}

HRESULT OutputPinSync_EndFlush( CPinBaseImpl* pImpl )
{
	if ( pImpl->pPinConnectedTo == NULL )
		return NOERROR;

	return IPin_EndFlush(pImpl->pPinConnectedTo);
}

HRESULT OutputPinSync_NewSegment( CPinBaseImpl* pImpl, REFERENCE_TIME rtStart, REFERENCE_TIME rtStop, double rate )
{
	if ( pImpl->pPinConnectedTo == NULL )
		return NOERROR;

	return IPin_NewSegment(pImpl->pPinConnectedTo,rtStart,rtStop,rate);
}

/***************************************************************************
 *
 *	handlers for output pins (async).
 *
 */

typedef struct OutputPinTask OutputPinTask;

enum OutputPinTaskType
{
	OutTask_ExitThread,
	OutTask_Receive,
	OutTask_EndOfStream,
	OutTask_BeginFlush,
	OutTask_EndFlush,
	OutTask_NewSegment,
};

struct OutputPinTask
{
	OutputPinTask* pNext;
	enum OutputPinTaskType tasktype;
	IMediaSample* pSample;
	REFERENCE_TIME rtStart;
	REFERENCE_TIME rtStop;
	double rate;
};

struct OutputPinAsyncImpl
{
	HANDLE m_hTaskThread;
	HANDLE m_hTaskEvent;
	IPin* m_pPin; /* connected pin */
	IMemInputPin* m_pMemInputPin; /* connected pin */
	CRITICAL_SECTION m_csTasks;
	OutputPinTask* m_pFirst;
	OutputPinTask* m_pLast;
	OutputPinTask* m_pTaskExitThread;
};

static OutputPinTask* OutputPinAsync_AllocTask( enum OutputPinTaskType tasktype )
{
	OutputPinTask* pTask;

	pTask = (OutputPinTask*)QUARTZ_AllocMem( sizeof(OutputPinTask) );
	pTask->pNext = NULL;
	pTask->tasktype = tasktype;
	pTask->pSample = NULL;

	return pTask;
}

static void OutputPinAsync_FreeTask( OutputPinTask* pTask )
{
	if ( pTask->pSample != NULL )
		IMediaSample_Release( pTask->pSample );
	QUARTZ_FreeMem( pTask );
}

static void OutputPinAsync_AddTask( OutputPinAsyncImpl* This, OutputPinTask* pTask, BOOL bFirst )
{
	EnterCriticalSection( &This->m_csTasks );

	if ( bFirst )
	{
		pTask->pNext = This->m_pFirst;
		This->m_pFirst = pTask;
		if ( This->m_pLast == NULL )
			This->m_pLast = pTask;
	}
	else
	{
		if ( This->m_pLast != NULL )
			This->m_pLast->pNext = pTask;
		else
			This->m_pFirst = pTask;
		This->m_pLast = pTask;
	}

	LeaveCriticalSection( &This->m_csTasks );

	SetEvent( This->m_hTaskEvent );
}

static OutputPinTask* OutputPinAsync_GetNextTask( OutputPinAsyncImpl* This )
{
	OutputPinTask* pTask;

	EnterCriticalSection( &This->m_csTasks );
	pTask = This->m_pFirst;
	if ( pTask != NULL )
	{
		This->m_pFirst = pTask->pNext;
		if ( This->m_pFirst == NULL )
			This->m_pLast = NULL;
		else
			SetEvent( This->m_hTaskEvent );
	}

	LeaveCriticalSection( &This->m_csTasks );

	return pTask;
}

static DWORD WINAPI OutputPinAsync_ThreadEntry( LPVOID pv )
{
	OutputPinAsyncImpl* This = ((CPinBaseImpl*)pv)->pAsyncOut;
	OutputPinTask* pTask;
	BOOL bLoop = TRUE;
	BOOL bInFlush = FALSE;
	HRESULT hr;

	while ( bLoop )
	{
		WaitForSingleObject( This->m_hTaskEvent, INFINITE );
		ResetEvent( This->m_hTaskEvent );

		pTask = OutputPinAsync_GetNextTask( This );
		if ( pTask == NULL )
			continue;

		hr = S_OK;
		switch ( pTask->tasktype )
		{
		case OutTask_ExitThread:
			bLoop = FALSE;
			break;
		case OutTask_Receive:
			if ( !bInFlush )
				hr = IMemInputPin_Receive( This->m_pMemInputPin, pTask->pSample );
			break;
		case OutTask_EndOfStream:
			hr = IPin_EndOfStream( This->m_pPin );
			break;
		case OutTask_BeginFlush:
			bInFlush = TRUE;
			hr = IPin_BeginFlush( This->m_pPin );
			break;
		case OutTask_EndFlush:
			bInFlush = FALSE;
			hr = IPin_EndFlush( This->m_pPin );
			break;
		case OutTask_NewSegment:
			hr = IPin_NewSegment( This->m_pPin, pTask->rtStart, pTask->rtStop, pTask->rate );
			break;
		default:
			ERR( "unexpected task type %d.\n", pTask->tasktype );
			bLoop = FALSE;
			break;
		}

		OutputPinAsync_FreeTask( pTask );

		if ( FAILED(hr) )
		{
			ERR( "hresult %08lx\n", hr );
			bLoop = FALSE;
		}
	}

	return 0;
}

HRESULT OutputPinAsync_OnActive( CPinBaseImpl* pImpl )
{
	HRESULT hr;
	DWORD dwThreadId;

	FIXME("(%p)\n",pImpl);

	if ( pImpl->pMemInputPinConnectedTo == NULL )
		return NOERROR;

	pImpl->pAsyncOut = (OutputPinAsyncImpl*)
		QUARTZ_AllocMem( sizeof( OutputPinAsyncImpl ) );
	if ( pImpl->pAsyncOut == NULL )
		return E_OUTOFMEMORY;

	InitializeCriticalSection( &pImpl->pAsyncOut->m_csTasks );
	pImpl->pAsyncOut->m_hTaskThread = (HANDLE)NULL;
	pImpl->pAsyncOut->m_hTaskEvent = (HANDLE)NULL;
	pImpl->pAsyncOut->m_pFirst = NULL;
	pImpl->pAsyncOut->m_pLast = NULL;
	pImpl->pAsyncOut->m_pTaskExitThread = NULL;
	pImpl->pAsyncOut->m_pPin = pImpl->pPinConnectedTo;
	pImpl->pAsyncOut->m_pMemInputPin = pImpl->pMemInputPinConnectedTo;

	pImpl->pAsyncOut->m_hTaskEvent =
		CreateEventA( NULL, TRUE, FALSE, NULL );
	if ( pImpl->pAsyncOut->m_hTaskEvent == (HANDLE)NULL )
	{
		hr = E_FAIL;
		goto err;
	}

	pImpl->pAsyncOut->m_pTaskExitThread =
		OutputPinAsync_AllocTask( OutTask_ExitThread );
	if ( pImpl->pAsyncOut->m_pTaskExitThread == NULL )
	{
		hr = E_OUTOFMEMORY;
		goto err;
	}

	pImpl->pAsyncOut->m_hTaskThread = CreateThread(
		NULL, 0, OutputPinAsync_ThreadEntry, pImpl,
		0, &dwThreadId );
	if ( pImpl->pAsyncOut->m_hTaskThread == (HANDLE)NULL )
	{
		hr = E_FAIL;
		goto err;
	}

	return NOERROR;
err:
	OutputPinAsync_OnInactive( pImpl );
	return hr;
}

HRESULT OutputPinAsync_OnInactive( CPinBaseImpl* pImpl )
{
	OutputPinTask* pTask;

	FIXME("(%p)\n",pImpl);

	if ( pImpl->pAsyncOut == NULL )
		return NOERROR;

	if ( pImpl->pAsyncOut->m_pTaskExitThread != NULL )
	{
		OutputPinAsync_AddTask( pImpl->pAsyncOut, pImpl->pAsyncOut->m_pTaskExitThread, TRUE );
		pImpl->pAsyncOut->m_pTaskExitThread = NULL;
	}

	if ( pImpl->pAsyncOut->m_hTaskThread != (HANDLE)NULL )
	{
		WaitForSingleObject( pImpl->pAsyncOut->m_hTaskThread, INFINITE );
		CloseHandle( pImpl->pAsyncOut->m_hTaskThread );
	}
	if ( pImpl->pAsyncOut->m_hTaskEvent != (HANDLE)NULL )
		CloseHandle( pImpl->pAsyncOut->m_hTaskEvent );

	/* release all tasks. */
	while ( 1 )
	{
		pTask = OutputPinAsync_GetNextTask( pImpl->pAsyncOut );
		if ( pTask == NULL )
			break;
		OutputPinAsync_FreeTask( pTask );
	}

	DeleteCriticalSection( &pImpl->pAsyncOut->m_csTasks );

	QUARTZ_FreeMem( pImpl->pAsyncOut );
	pImpl->pAsyncOut = NULL;

	return NOERROR;
}

HRESULT OutputPinAsync_Receive( CPinBaseImpl* pImpl, IMediaSample* pSample )
{
	OutputPinAsyncImpl* This = pImpl->pAsyncOut;
	OutputPinTask* pTask;

	TRACE("(%p,%p)\n",pImpl,pSample);

	if ( This == NULL )
		return NOERROR;

	pTask = OutputPinAsync_AllocTask( OutTask_Receive );
	if ( pTask == NULL )
		return E_OUTOFMEMORY;
	pTask->pSample = pSample; IMediaSample_AddRef( pSample );
	OutputPinAsync_AddTask( pImpl->pAsyncOut, pTask, FALSE );

	return NOERROR;
}

HRESULT OutputPinAsync_ReceiveCanBlock( CPinBaseImpl* pImpl )
{
	return S_FALSE;
}

HRESULT OutputPinAsync_EndOfStream( CPinBaseImpl* pImpl )
{
	OutputPinAsyncImpl* This = pImpl->pAsyncOut;
	OutputPinTask* pTask;

	TRACE("(%p)\n",pImpl);

	if ( This == NULL )
		return NOERROR;

	pTask = OutputPinAsync_AllocTask( OutTask_EndOfStream );
	if ( pTask == NULL )
		return E_OUTOFMEMORY;
	OutputPinAsync_AddTask( pImpl->pAsyncOut, pTask, FALSE );

	return NOERROR;
}

HRESULT OutputPinAsync_BeginFlush( CPinBaseImpl* pImpl )
{
	OutputPinAsyncImpl* This = pImpl->pAsyncOut;
	OutputPinTask* pTask;

	TRACE("(%p)\n",pImpl);

	if ( This == NULL )
		return NOERROR;

	pTask = OutputPinAsync_AllocTask( OutTask_BeginFlush );
	if ( pTask == NULL )
		return E_OUTOFMEMORY;
	OutputPinAsync_AddTask( pImpl->pAsyncOut, pTask, TRUE );

	return NOERROR;
}

HRESULT OutputPinAsync_EndFlush( CPinBaseImpl* pImpl )
{
	OutputPinAsyncImpl* This = pImpl->pAsyncOut;
	OutputPinTask* pTask;

	TRACE("(%p)\n",pImpl);

	if ( This == NULL )
		return NOERROR;

	pTask = OutputPinAsync_AllocTask( OutTask_EndFlush );
	if ( pTask == NULL )
		return E_OUTOFMEMORY;
	OutputPinAsync_AddTask( pImpl->pAsyncOut, pTask, FALSE );

	return NOERROR;
}

HRESULT OutputPinAsync_NewSegment( CPinBaseImpl* pImpl, REFERENCE_TIME rtStart, REFERENCE_TIME rtStop, double rate )
{
	OutputPinAsyncImpl* This = pImpl->pAsyncOut;
	OutputPinTask* pTask;

	TRACE("(%p)\n",pImpl);

	if ( This == NULL )
		return NOERROR;

	pTask = OutputPinAsync_AllocTask( OutTask_NewSegment );
	if ( pTask == NULL )
		return E_OUTOFMEMORY;
	pTask->rtStart = rtStart;
	pTask->rtStop = rtStop;
	pTask->rate = rate;
	OutputPinAsync_AddTask( pImpl->pAsyncOut, pTask, FALSE );

	return NOERROR;
}


