/*
 * CLSID_FilterGraph event handling.
 *
 * 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 "evcode.h"
#include "uuids.h"
#include "vfwmsgs.h"

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

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

#define EVENTQUEUE_BLOCKSIZE	2
#define EVENTQUEUE_MAX			1024

struct FilterGraph_MEDIAEVENT
{
	long		lEventCode;
	LONG_PTR	lParam1;
	LONG_PTR	lParam2;
};


static HRESULT FGEVENT_KeepEvent(
	CFilterGraph* This,
	BOOL bKeep,
	long lEventCode, LONG_PTR lParam1, LONG_PTR lParam2 )
{
	switch ( lEventCode )
	{
	case EC_COMPLETE:
		if ( ++This->m_cCompletes < This->m_cRenders )
			return E_FAIL; /* don't tell app */
		break;
	case EC_USERABORT:
		break;
	case EC_ERRORABORT:
		break;
	case EC_TIME:
		break;
	/*case EC_REPAINT:*/
	case EC_STREAM_ERROR_STOPPED:
		break;
	case EC_STREAM_ERROR_STILLPLAYING:
		break;
	case EC_ERROR_STILLPLAYING:
		break;
	case EC_PALETTE_CHANGED:
		break;
	case EC_VIDEO_SIZE_CHANGED:
		break;
	case EC_QUALITY_CHANGE:
		break;
	/*case EC_SHUTTING_DOWN:*/
	case EC_CLOCK_CHANGED:
		break;

	case EC_OPENING_FILE:
		break;
	case EC_BUFFERING_DATA:
		break;
	case EC_FULLSCREEN_LOST:
		if ( bKeep )
		{
			if ( ((IBaseFilter*)lParam2) != NULL )
				IBaseFilter_AddRef( (IBaseFilter*)lParam2 );
		}
		else
		{
			if ( ((IBaseFilter*)lParam2) != NULL )
				IBaseFilter_Release( (IBaseFilter*)lParam2 );
		}
		break;
	/*case EC_ACTIVATE:*/
	/*case EC_NEED_RESTART:*/
	/*case EC_WINDOW_DESTROYED:*/
	/*case EC_DISPLAY_CHANGED:*/
	/*case EC_STARVATION:*/
	/*case EC_OLE_EVENT:*/
	/*case EC_NOTIFY_WINDOW:*/
	/*case EC_STREAM_CONTROL_STOPPED:*/
	/*case EC_STREAM_CONTROL_STARTED:*/
	/*case EC_END_OF_SEGMENT:*/
	/*case EC_SEGMENT_STARTED:*/
	case EC_LENGTH_CHANGED:
		break;

	default:
		if ( lEventCode < EC_USER )
		{
			FIXME( "unknown system event %08lx\n", lEventCode );
			return E_INVALIDARG;
		}
		TRACE( "user event %08lx\n", lEventCode );
		break;
	}

	return NOERROR;
}

/***************************************************************************
 *
 *	CLSID_FilterGraph::IMediaEvent[Ex]
 *
 */

static HRESULT WINAPI
IMediaEventEx_fnQueryInterface(IMediaEventEx* iface,REFIID riid,void** ppobj)
{
	CFilterGraph_THIS(iface,mediaevent);

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

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

static ULONG WINAPI
IMediaEventEx_fnAddRef(IMediaEventEx* iface)
{
	CFilterGraph_THIS(iface,mediaevent);

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

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

static ULONG WINAPI
IMediaEventEx_fnRelease(IMediaEventEx* iface)
{
	CFilterGraph_THIS(iface,mediaevent);

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

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

static HRESULT WINAPI
IMediaEventEx_fnGetTypeInfoCount(IMediaEventEx* iface,UINT* pcTypeInfo)
{
	CFilterGraph_THIS(iface,mediaevent);

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

	return E_NOTIMPL;
}

static HRESULT WINAPI
IMediaEventEx_fnGetTypeInfo(IMediaEventEx* iface,UINT iTypeInfo, LCID lcid, ITypeInfo** ppobj)
{
	CFilterGraph_THIS(iface,mediaevent);

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

	return E_NOTIMPL;
}

static HRESULT WINAPI
IMediaEventEx_fnGetIDsOfNames(IMediaEventEx* iface,REFIID riid, LPOLESTR* ppwszName, UINT cNames, LCID lcid, DISPID* pDispId)
{
	CFilterGraph_THIS(iface,mediaevent);

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

	return E_NOTIMPL;
}

static HRESULT WINAPI
IMediaEventEx_fnInvoke(IMediaEventEx* iface,DISPID DispId, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarRes, EXCEPINFO* pExcepInfo, UINT* puArgErr)
{
	CFilterGraph_THIS(iface,mediaevent);

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

	return E_NOTIMPL;
}


static HRESULT WINAPI
IMediaEventEx_fnGetEventHandle(IMediaEventEx* iface,OAEVENT* hEvent)
{
	CFilterGraph_THIS(iface,mediaevent);

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

	*hEvent = (OAEVENT)This->m_hMediaEvent;

	return NOERROR;
}

static HRESULT WINAPI
IMediaEventEx_fnGetEvent(IMediaEventEx* iface,long* plEventCode,LONG_PTR* plParam1,LONG_PTR* plParam2,long lTimeOut)
{
	CFilterGraph_THIS(iface,mediaevent);
	ULONG cQueued;
	DWORD dw;
	DWORD dwStart;
	HRESULT	hr;
	FilterGraph_MEDIAEVENT*	pEvent;

	TRACE("(%p)->(%p,%p,%p,%ld)\n",This,plEventCode,
		plParam1,plParam2,lTimeOut);

	if ( plEventCode == NULL || plParam1 == NULL || plParam2 == NULL )
		return E_POINTER;

	while ( 1 )
	{
		dwStart = GetTickCount();
		dw = WaitForSingleObject( This->m_hMediaEvent, lTimeOut );
		if ( dw == WAIT_TIMEOUT )
			return VFW_E_TIMEOUT;
		if ( dw != WAIT_OBJECT_0 )
			return E_FAIL;

		EnterCriticalSection( &This->m_csMediaEvents );
		hr = S_FALSE;
		if ( This->m_cbMediaEventsMax > 0 )
		{
			cQueued =
				(This->m_cbMediaEventsMax +
				 This->m_cbMediaEventsPut - This->m_cbMediaEventsGet) %
					This->m_cbMediaEventsMax;
			if ( cQueued > 0 )
			{
				pEvent = &This->m_pMediaEvents[This->m_cbMediaEventsGet];
				*plEventCode = pEvent->lEventCode;
				*plParam1 = pEvent->lParam1;
				*plParam2 = pEvent->lParam2;
				This->m_cbMediaEventsGet = (This->m_cbMediaEventsGet + 1) %
						This->m_cbMediaEventsMax;

				hr = NOERROR;
				if ( This->m_cbMediaEventsPut == This->m_cbMediaEventsGet )
					ResetEvent( This->m_hMediaEvent );
			}
		}
		LeaveCriticalSection( &This->m_csMediaEvents );

		if ( hr != S_FALSE )
			return hr;
		if ( lTimeOut != INFINITE )
		{
			lTimeOut -= GetTickCount() - dwStart;
			if ( lTimeOut < 0 )
				return VFW_E_TIMEOUT;
		}
	}
}

static HRESULT WINAPI
IMediaEventEx_fnWaitForCompletion(IMediaEventEx* iface,long lTimeOut,long* plEventCode)
{
	CFilterGraph_THIS(iface,mediaevent);
	HRESULT hr;
	long lEventCode;
	LONG_PTR lParam1;
	LONG_PTR lParam2;
	DWORD dwTimePrev;
	DWORD dwTimeCur;

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

	if ( plEventCode == NULL )
		return E_POINTER;
	*plEventCode = 0;

	dwTimePrev = GetTickCount();

	while ( 1 )
	{
		hr = IMediaEventEx_GetEvent(
				CFilterGraph_IMediaEventEx(This),
				&lEventCode,&lParam1,&lParam2,lTimeOut);
		if ( hr == VFW_E_TIMEOUT )
			hr = E_ABORT;
		if ( hr != NOERROR )
			return hr;
		IMediaEventEx_FreeEventParams(
				CFilterGraph_IMediaEventEx(This),
				lEventCode,lParam1,lParam2);

		if ( lEventCode == EC_COMPLETE ||
			 lEventCode == EC_ERRORABORT ||
			 lEventCode == EC_USERABORT )
		{
			*plEventCode = lEventCode;
			return NOERROR;
		}

		if ( lTimeOut != INFINITE )
		{
			dwTimeCur = GetTickCount();
			lTimeOut -= dwTimeCur - dwTimePrev;
			dwTimePrev = dwTimeCur;
			if ( lTimeOut < 0 )
				return E_ABORT;
		}
	}
}

static HRESULT WINAPI
IMediaEventEx_fnCancelDefaultHandling(IMediaEventEx* iface,long lEventCode)
{
	CFilterGraph_THIS(iface,mediaevent);

	FIXME("(%p)->() stub!\n",This);

	return E_NOTIMPL;
}

static HRESULT WINAPI
IMediaEventEx_fnRestoreDefaultHandling(IMediaEventEx* iface,long lEventCode)
{
	CFilterGraph_THIS(iface,mediaevent);

	FIXME("(%p)->() stub!\n",This);

	return E_NOTIMPL;
}

static HRESULT WINAPI
IMediaEventEx_fnFreeEventParams(IMediaEventEx* iface,long lEventCode,LONG_PTR lParam1,LONG_PTR lParam2)
{
	CFilterGraph_THIS(iface,mediaevent);

	TRACE("(%p)->(%08lx,%08x,%08x)\n",This,lEventCode,lParam1,lParam2);

	return FGEVENT_KeepEvent( This, FALSE, lEventCode, lParam1, lParam2 );
}

static HRESULT WINAPI
IMediaEventEx_fnSetNotifyWindow(IMediaEventEx* iface,OAHWND hwnd,long message,LONG_PTR lParam)
{
	CFilterGraph_THIS(iface,mediaevent);

	TRACE("(%p)->(%08x,%08lx,%08x)\n",This,hwnd,message,lParam);

	EnterCriticalSection( &This->m_csMediaEvents );
	This->m_hwndEventNotify = (HWND)hwnd;
	This->m_lEventNotifyMsg = message;
	This->m_lEventNotifyParam = lParam;
	LeaveCriticalSection( &This->m_csMediaEvents );

	return NOERROR;
}

static HRESULT WINAPI
IMediaEventEx_fnSetNotifyFlags(IMediaEventEx* iface,long lNotifyFlags)
{
	CFilterGraph_THIS(iface,mediaevent);

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

	if ( lNotifyFlags != 0 && lNotifyFlags != 1 )
		return E_INVALIDARG;

	EnterCriticalSection( &This->m_csMediaEvents );
	This->m_lEventNotifyFlags = lNotifyFlags;
	LeaveCriticalSection( &This->m_csMediaEvents );

	return NOERROR;
}

static HRESULT WINAPI
IMediaEventEx_fnGetNotifyFlags(IMediaEventEx* iface,long* plNotifyFlags)
{
	CFilterGraph_THIS(iface,mediaevent);

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

	if ( plNotifyFlags == NULL )
		return E_POINTER;

	EnterCriticalSection( &This->m_csMediaEvents );
	*plNotifyFlags = This->m_lEventNotifyFlags;
	LeaveCriticalSection( &This->m_csMediaEvents );

	return NOERROR;
}



static ICOM_VTABLE(IMediaEventEx) imediaevent =
{
	ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
	/* IUnknown fields */
	IMediaEventEx_fnQueryInterface,
	IMediaEventEx_fnAddRef,
	IMediaEventEx_fnRelease,
	/* IDispatch fields */
	IMediaEventEx_fnGetTypeInfoCount,
	IMediaEventEx_fnGetTypeInfo,
	IMediaEventEx_fnGetIDsOfNames,
	IMediaEventEx_fnInvoke,
	/* IMediaEvent fields */
	IMediaEventEx_fnGetEventHandle,
	IMediaEventEx_fnGetEvent,
	IMediaEventEx_fnWaitForCompletion,
	IMediaEventEx_fnCancelDefaultHandling,
	IMediaEventEx_fnRestoreDefaultHandling,
	IMediaEventEx_fnFreeEventParams,
	/* IMediaEventEx fields */
	IMediaEventEx_fnSetNotifyWindow,
	IMediaEventEx_fnSetNotifyFlags,
	IMediaEventEx_fnGetNotifyFlags,
};


HRESULT CFilterGraph_InitIMediaEventEx( CFilterGraph* pfg )
{
	TRACE("(%p)\n",pfg);
	ICOM_VTBL(&pfg->mediaevent) = &imediaevent;

	pfg->m_hMediaEvent = CreateEventA( NULL, TRUE, FALSE, NULL );
	if ( pfg->m_hMediaEvent == (HANDLE)NULL )
		return E_OUTOFMEMORY;

	InitializeCriticalSection( &pfg->m_csMediaEvents );
	pfg->m_pMediaEvents = NULL;
	pfg->m_cbMediaEventsPut = 0;
	pfg->m_cbMediaEventsGet = 0;
	pfg->m_cbMediaEventsMax = 0;
	pfg->m_hwndEventNotify = (HWND)NULL;
	pfg->m_lEventNotifyMsg = 0;
	pfg->m_lEventNotifyParam = 0;
	pfg->m_lEventNotifyFlags = 0;

	return NOERROR;
}

void CFilterGraph_UninitIMediaEventEx( CFilterGraph* pfg )
{
	HRESULT hr;
	long lEventCode;
	LONG_PTR lParam1;
	LONG_PTR lParam2;

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

	while ( 1 )
	{
		hr = IMediaEventEx_GetEvent(
				CFilterGraph_IMediaEventEx(pfg),
				&lEventCode,&lParam1,&lParam2,0);
		if ( hr != NOERROR )
			break;
		IMediaEventEx_FreeEventParams(
				CFilterGraph_IMediaEventEx(pfg),
				lEventCode,lParam1,lParam2);
	}

	if ( pfg->m_pMediaEvents != NULL )
	{
		QUARTZ_FreeMem( pfg->m_pMediaEvents );
		pfg->m_pMediaEvents = NULL;
	}

	DeleteCriticalSection( &pfg->m_csMediaEvents );
	CloseHandle( pfg->m_hMediaEvent );
}

/***************************************************************************
 *
 *	CLSID_FilterGraph::IMediaEventSink
 *
 */

static HRESULT WINAPI
IMediaEventSink_fnQueryInterface(IMediaEventSink* iface,REFIID riid,void** ppobj)
{
	CFilterGraph_THIS(iface,mediaeventsink);

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

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

static ULONG WINAPI
IMediaEventSink_fnAddRef(IMediaEventSink* iface)
{
	CFilterGraph_THIS(iface,mediaeventsink);

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

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

static ULONG WINAPI
IMediaEventSink_fnRelease(IMediaEventSink* iface)
{
	CFilterGraph_THIS(iface,mediaeventsink);

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

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

static HRESULT WINAPI
IMediaEventSink_fnNotify(IMediaEventSink* iface,long lEventCode,LONG_PTR lParam1,LONG_PTR lParam2)
{
	CFilterGraph_THIS(iface,mediaeventsink);
	HRESULT hr = NOERROR;
	ULONG cQueued;
	ULONG cTemp;
	FilterGraph_MEDIAEVENT*	pEvent;

	TRACE("(%p)->(%08lx,%08x,%08x) stub!\n",This,lEventCode,lParam1,lParam2);

	EnterCriticalSection( &This->m_csMediaEvents );

	/* allocate a new entry. */
	if ( This->m_cbMediaEventsMax == 0 )
		cQueued = 0;
	else
		cQueued =
			(This->m_cbMediaEventsMax +
			 This->m_cbMediaEventsPut - This->m_cbMediaEventsGet) %
				This->m_cbMediaEventsMax;

	if ( (cQueued + 1) >= This->m_cbMediaEventsMax )
	{
		if ( This->m_cbMediaEventsMax >= EVENTQUEUE_MAX )
		{
			hr = E_FAIL;
			goto end;
		}
		pEvent = (FilterGraph_MEDIAEVENT*)
			QUARTZ_AllocMem( sizeof(FilterGraph_MEDIAEVENT) *
				(This->m_cbMediaEventsMax+EVENTQUEUE_BLOCKSIZE) );
		if ( pEvent == NULL )
		{
			hr = E_OUTOFMEMORY;
			goto end;
		}
		if ( cQueued > 0 )
		{
			if ( (This->m_cbMediaEventsGet + cQueued) >=
				This->m_cbMediaEventsMax )
			{
				cTemp = This->m_cbMediaEventsMax - This->m_cbMediaEventsGet;
				memcpy(
					pEvent,
					&This->m_pMediaEvents[This->m_cbMediaEventsGet],
					sizeof(FilterGraph_MEDIAEVENT) * cTemp );
				memcpy(
					pEvent + cTemp,
					&This->m_pMediaEvents[0],
					sizeof(FilterGraph_MEDIAEVENT) * (cQueued - cTemp) );
			}
			else
			{
				memcpy(
					pEvent,
					&This->m_pMediaEvents[This->m_cbMediaEventsGet],
					sizeof(FilterGraph_MEDIAEVENT) * cQueued );
			}
			QUARTZ_FreeMem( This->m_pMediaEvents );
		}
		This->m_pMediaEvents = pEvent;
		This->m_cbMediaEventsMax += EVENTQUEUE_BLOCKSIZE;
		This->m_cbMediaEventsPut = cQueued;
		This->m_cbMediaEventsGet = 0;
	}

	/* duplicate params if necessary. */
	hr = FGEVENT_KeepEvent( This, TRUE, lEventCode, lParam1, lParam2 );
	if ( FAILED(hr) )
		goto end;

	if ( lEventCode == EC_COMPLETE ) lParam2 = 0;

	/* add to the queue. */
	pEvent = &This->m_pMediaEvents[This->m_cbMediaEventsPut];
	pEvent->lEventCode = lEventCode;
	pEvent->lParam1 = lParam1;
	pEvent->lParam2 = lParam2;
	This->m_cbMediaEventsPut =
		(This->m_cbMediaEventsPut + 1) % This->m_cbMediaEventsMax;

	SetEvent( This->m_hMediaEvent );
	if ( This->m_hwndEventNotify != (HWND)NULL &&
		 This->m_lEventNotifyFlags == 0 )
	{
		PostMessageA(
			This->m_hwndEventNotify,
			This->m_lEventNotifyMsg,
			(WPARAM)0,
			(LPARAM)This->m_lEventNotifyParam );
	}

	hr = NOERROR;
end:
	LeaveCriticalSection( &This->m_csMediaEvents );

	return hr;
}


static ICOM_VTABLE(IMediaEventSink) imediaeventsink =
{
	ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
	/* IUnknown fields */
	IMediaEventSink_fnQueryInterface,
	IMediaEventSink_fnAddRef,
	IMediaEventSink_fnRelease,
	/* IMediaEventSink fields */
	IMediaEventSink_fnNotify,
};



HRESULT CFilterGraph_InitIMediaEventSink( CFilterGraph* pfg )
{
	TRACE("(%p)\n",pfg);
	ICOM_VTBL(&pfg->mediaeventsink) = &imediaeventsink;

	return NOERROR;
}

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

