/*
 * Implementation of IMediaFilter for FilterGraph.
 *
 * FIXME - stub.
 *
 * 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 "control.h"
#include "uuids.h"
#include "vfwmsgs.h"
#include "evcode.h"

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

#include "quartz_private.h"
#include "fgraph.h"


#define	WINE_QUARTZ_POLL_INTERVAL	10

/*****************************************************************************/

static
HRESULT CFilterGraph_PollGraphState(
	CFilterGraph* This,
	FILTER_STATE* pState)
{
	HRESULT	hr;
	QUARTZ_CompListItem*	pItem;
	IBaseFilter*	pFilter;

	hr = S_OK;
	*pState = State_Stopped;

	EnterCriticalSection( &This->m_csGraphState );
	QUARTZ_CompList_Lock( This->m_pFilterList );

	pItem = QUARTZ_CompList_GetFirst( This->m_pFilterList );

	while ( pItem != NULL )
	{
		pFilter = (IBaseFilter*)QUARTZ_CompList_GetItemPtr( pItem );
		hr = IBaseFilter_GetState( pFilter, (DWORD)0, pState );
		if ( hr != S_OK )
			break;

		pItem = QUARTZ_CompList_GetNext( This->m_pFilterList, pItem );
	}

	QUARTZ_CompList_Unlock( This->m_pFilterList );
	LeaveCriticalSection( &This->m_csGraphState );

	TRACE( "returns %08lx, state %d\n",
		hr, *pState );

	return hr;
}

/*****************************************************************************/

static HRESULT WINAPI
IMediaFilter_fnQueryInterface(IMediaFilter* iface,REFIID riid,void** ppobj)
{
	CFilterGraph_THIS(iface,mediafilter);

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

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

static ULONG WINAPI
IMediaFilter_fnAddRef(IMediaFilter* iface)
{
	CFilterGraph_THIS(iface,mediafilter);

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

	return IUnknown_AddRef(This->unk.punkControl);
}

static ULONG WINAPI
IMediaFilter_fnRelease(IMediaFilter* iface)
{
	CFilterGraph_THIS(iface,mediafilter);

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

	return IUnknown_Release(This->unk.punkControl);
}


static HRESULT WINAPI
IMediaFilter_fnGetClassID(IMediaFilter* iface,CLSID* pclsid)
{
	CFilterGraph_THIS(iface,mediafilter);

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

	return IPersist_GetClassID(
		CFilterGraph_IPersist(This),pclsid);
}

static HRESULT WINAPI
IMediaFilter_fnStop(IMediaFilter* iface)
{
	CFilterGraph_THIS(iface,mediafilter);
	HRESULT	hr;
	HRESULT	hrFilter;
	QUARTZ_CompListItem*	pItem;
	IBaseFilter*	pFilter;

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

	hr = S_OK;

	EnterCriticalSection( &This->m_csGraphState );

	if ( This->m_stateGraph != State_Stopped )
	{
		/* IDistributorNotify_Stop() */

		QUARTZ_CompList_Lock( This->m_pFilterList );

		pItem = QUARTZ_CompList_GetFirst( This->m_pFilterList );

		while ( pItem != NULL )
		{
			pFilter = (IBaseFilter*)QUARTZ_CompList_GetItemPtr( pItem );
			hrFilter = IBaseFilter_Stop( pFilter );
			if ( hrFilter != S_OK )
			{
				if ( SUCCEEDED(hr) )
					hr = hrFilter;
			}

			pItem = QUARTZ_CompList_GetNext( This->m_pFilterList, pItem );
		}

		QUARTZ_CompList_Unlock( This->m_pFilterList );

		This->m_stateGraph = State_Stopped;
	}

	LeaveCriticalSection( &This->m_csGraphState );

	return hr;
}

static HRESULT WINAPI
IMediaFilter_fnPause(IMediaFilter* iface)
{
	CFilterGraph_THIS(iface,mediafilter);
	HRESULT	hr;
	HRESULT	hrFilter;
	QUARTZ_CompListItem*	pItem;
	IBaseFilter*	pFilter;

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

	hr = S_OK;

	EnterCriticalSection( &This->m_csGraphState );

	if ( This->m_stateGraph != State_Paused )
	{
		/* IDistributorNotify_Pause() */

		QUARTZ_CompList_Lock( This->m_pFilterList );

		pItem = QUARTZ_CompList_GetFirst( This->m_pFilterList );

		while ( pItem != NULL )
		{
			pFilter = (IBaseFilter*)QUARTZ_CompList_GetItemPtr( pItem );
			hrFilter = IBaseFilter_Pause( pFilter );
			if ( hrFilter != S_OK )
			{
				if ( SUCCEEDED(hr) )
					hr = hrFilter;
			}

			pItem = QUARTZ_CompList_GetNext( This->m_pFilterList, pItem );
		}

		QUARTZ_CompList_Unlock( This->m_pFilterList );

		This->m_stateGraph = State_Paused;
	}

	LeaveCriticalSection( &This->m_csGraphState );

	return hr;
}

static HRESULT WINAPI
IMediaFilter_fnRun(IMediaFilter* iface,REFERENCE_TIME rtStart)
{
	CFilterGraph_THIS(iface,mediafilter);
	HRESULT	hr;
	HRESULT	hrFilter;
	QUARTZ_CompListItem*	pItem;
	IBaseFilter*	pFilter;
	IReferenceClock*	pClock;

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

	EnterCriticalSection( &This->m_csGraphState );

	if ( This->m_stateGraph == State_Stopped )
	{
		hr = IMediaFilter_Pause(iface);
		if ( FAILED(hr) )
			goto end;
	}

        /* handle the special time. */
        if ( rtStart == (REFERENCE_TIME)0 )
        {
                hr = IMediaFilter_GetSyncSource(iface,&pClock);
                if ( hr == S_OK && pClock != NULL )
                {
                        IReferenceClock_GetTime(pClock,&rtStart);
                        IReferenceClock_Release(pClock);
                }
        }

	hr = NOERROR;

	if ( This->m_stateGraph != State_Running )
	{
		/* IDistributorNotify_Run() */

		QUARTZ_CompList_Lock( This->m_pFilterList );

		pItem = QUARTZ_CompList_GetFirst( This->m_pFilterList );

		while ( pItem != NULL )
		{
			pFilter = (IBaseFilter*)QUARTZ_CompList_GetItemPtr( pItem );
			hrFilter = IBaseFilter_Run( pFilter, rtStart );
			if ( hrFilter != S_OK )
			{
				if ( SUCCEEDED(hr) )
					hr = hrFilter;
			}

			pItem = QUARTZ_CompList_GetNext( This->m_pFilterList, pItem );
		}

		QUARTZ_CompList_Unlock( This->m_pFilterList );

		This->m_stateGraph = State_Running;
	}

end:
	LeaveCriticalSection( &This->m_csGraphState );

	return hr;
}

static HRESULT WINAPI
IMediaFilter_fnGetState(IMediaFilter* iface,DWORD dwTimeOut,FILTER_STATE* pState)
{
	CFilterGraph_THIS(iface,mediafilter);
	HRESULT	hr;
	DWORD	dwTickStart;
	DWORD	dwTickUsed;

	TRACE("(%p)->(%p)\n",This,pState);
	if ( pState == NULL )
		return E_POINTER;

	dwTickStart = GetTickCount();

	while ( 1 )
	{
		hr = CFilterGraph_PollGraphState( This, pState );
		if ( hr != VFW_S_STATE_INTERMEDIATE )
			break;
		if ( dwTimeOut == 0 )
			break;

		Sleep( (dwTimeOut >= WINE_QUARTZ_POLL_INTERVAL) ?
			WINE_QUARTZ_POLL_INTERVAL : dwTimeOut );

		dwTickUsed = GetTickCount() - dwTickStart;

		dwTickStart += dwTickUsed;
		if ( dwTimeOut <= dwTickUsed )
			dwTimeOut = 0;
		else
			dwTimeOut -= dwTickUsed;
	}

	EnterCriticalSection( &This->m_csGraphState );
	*pState = This->m_stateGraph;
	LeaveCriticalSection( &This->m_csGraphState );

	return hr;
}

static HRESULT WINAPI
IMediaFilter_fnSetSyncSource(IMediaFilter* iface,IReferenceClock* pobjClock)
{
	CFilterGraph_THIS(iface,mediafilter);
	QUARTZ_CompListItem*	pItem;
	IBaseFilter*	pFilter;
	HRESULT hr = NOERROR;
	HRESULT hrCur;

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

	/* IDistributorNotify_SetSyncSource() */

	EnterCriticalSection( &This->m_csClock );
	QUARTZ_CompList_Lock( This->m_pFilterList );

	if ( This->m_pClock != NULL )
	{
		IReferenceClock_Release(This->m_pClock);
		This->m_pClock = NULL;
	}

	This->m_pClock = pobjClock;
	if ( pobjClock != NULL )
		IReferenceClock_AddRef( pobjClock );

	pItem = QUARTZ_CompList_GetFirst( This->m_pFilterList );
	while ( pItem != NULL )
	{
		pFilter = (IBaseFilter*)QUARTZ_CompList_GetItemPtr( pItem );
		hrCur = IBaseFilter_SetSyncSource(pFilter,pobjClock);
		if ( FAILED(hrCur) )
			hr = hrCur;
		pItem = QUARTZ_CompList_GetNext( This->m_pFilterList, pItem );
	}

	QUARTZ_CompList_Unlock( This->m_pFilterList );

	IMediaEventSink_Notify(CFilterGraph_IMediaEventSink(This),
		EC_CLOCK_CHANGED, 0, 0);

	LeaveCriticalSection( &This->m_csClock );

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

	return hr;
}

static HRESULT WINAPI
IMediaFilter_fnGetSyncSource(IMediaFilter* iface,IReferenceClock** ppobjClock)
{
	CFilterGraph_THIS(iface,mediafilter);
	HRESULT hr = VFW_E_NO_CLOCK;

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

	if ( ppobjClock == NULL )
		return E_POINTER;

	EnterCriticalSection( &This->m_csClock );
	*ppobjClock = This->m_pClock;
	if ( This->m_pClock != NULL )
	{
		hr = NOERROR;
		IReferenceClock_AddRef( This->m_pClock );
	}
	LeaveCriticalSection( &This->m_csClock );

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

	return hr;
}



static ICOM_VTABLE(IMediaFilter) imediafilter =
{
	ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
	/* IUnknown fields */
	IMediaFilter_fnQueryInterface,
	IMediaFilter_fnAddRef,
	IMediaFilter_fnRelease,
	/* IPersist fields */
	IMediaFilter_fnGetClassID,
	/* IMediaFilter fields */
	IMediaFilter_fnStop,
	IMediaFilter_fnPause,
	IMediaFilter_fnRun,
	IMediaFilter_fnGetState,
	IMediaFilter_fnSetSyncSource,
	IMediaFilter_fnGetSyncSource,
};

HRESULT CFilterGraph_InitIMediaFilter( CFilterGraph* pfg )
{
	TRACE("(%p)\n",pfg);

	ICOM_VTBL(&pfg->mediafilter) = &imediafilter;

	InitializeCriticalSection( &pfg->m_csGraphState );
	InitializeCriticalSection( &pfg->m_csClock );
	pfg->m_stateGraph = State_Stopped;
	pfg->m_pClock = NULL;

	return NOERROR;
}

void CFilterGraph_UninitIMediaFilter( CFilterGraph* pfg )
{
	TRACE("(%p)\n",pfg);

	if ( pfg->m_pClock != NULL )
	{
		IReferenceClock_Release( pfg->m_pClock );
		pfg->m_pClock = NULL;
	}

	DeleteCriticalSection( &pfg->m_csGraphState );
	DeleteCriticalSection( &pfg->m_csClock );
}
