/*
 * Implements IEnumMediaTypes and helper functions. (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 "mmsystem.h"
#include "strmif.h"
#include "vfwmsgs.h"
#include "uuids.h"

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

#include "quartz_private.h"
#include "mtype.h"
#include "iunk.h"


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



HRESULT QUARTZ_MediaType_Copy(
	AM_MEDIA_TYPE* pmtDst,
	const AM_MEDIA_TYPE* pmtSrc )
{
	memcpy( &pmtDst->majortype, &pmtSrc->majortype, sizeof(GUID) );
	memcpy( &pmtDst->subtype, &pmtSrc->subtype, sizeof(GUID) );
	pmtDst->bFixedSizeSamples = pmtSrc->bFixedSizeSamples;
	pmtDst->bTemporalCompression = pmtSrc->bTemporalCompression;
	pmtDst->lSampleSize = pmtSrc->lSampleSize;
	memcpy( &pmtDst->formattype, &pmtSrc->formattype, sizeof(GUID) );
	pmtDst->pUnk = NULL;
	pmtDst->cbFormat = pmtSrc->cbFormat;
	pmtDst->pbFormat = NULL;

	if ( pmtSrc->pbFormat != NULL && pmtSrc->cbFormat != 0 )
	{
		pmtDst->pbFormat = (BYTE*)CoTaskMemAlloc( pmtSrc->cbFormat );
		if ( pmtDst->pbFormat == NULL )
		{
			return E_OUTOFMEMORY;
		}
		memcpy( pmtDst->pbFormat, pmtSrc->pbFormat, pmtSrc->cbFormat );
	}

	if ( pmtSrc->pUnk != NULL )
	{
		pmtDst->pUnk = pmtSrc->pUnk;
		IUnknown_AddRef( pmtSrc->pUnk );
	}

	return S_OK;
}

void QUARTZ_MediaType_Free(
	AM_MEDIA_TYPE* pmt )
{
	if ( pmt->pUnk != NULL )
	{
		IUnknown_Release( pmt->pUnk );
		pmt->pUnk = NULL;
	}
	if ( pmt->pbFormat != NULL )
	{
		CoTaskMemFree( pmt->pbFormat );
		pmt->cbFormat = 0;
		pmt->pbFormat = NULL;
	}
}

AM_MEDIA_TYPE* QUARTZ_MediaType_Duplicate(
	const AM_MEDIA_TYPE* pmtSrc )
{
	AM_MEDIA_TYPE*	pmtDup;

	pmtDup = (AM_MEDIA_TYPE*)CoTaskMemAlloc( sizeof(AM_MEDIA_TYPE) );
	if ( pmtDup == NULL )
		return NULL;
	if ( QUARTZ_MediaType_Copy( pmtDup, pmtSrc ) != S_OK )
	{
		CoTaskMemFree( pmtDup );
		return NULL;
	}

	return pmtDup;
}

void QUARTZ_MediaType_Destroy(
	AM_MEDIA_TYPE* pmt )
{
	QUARTZ_MediaType_Free( pmt );
	CoTaskMemFree( pmt );
}

void QUARTZ_MediaSubType_FromFourCC(
	GUID* psubtype, DWORD dwFourCC )
{
	TRACE( "FourCC %c%c%c%c\n",
			(int)(dwFourCC>> 0)&0xff,
			(int)(dwFourCC>> 8)&0xff,
			(int)(dwFourCC>>16)&0xff,
			(int)(dwFourCC>>24)&0xff );
	memcpy( psubtype, &MEDIASUBTYPE_PCM, sizeof(GUID) );
	psubtype->Data1 = dwFourCC;
}

BOOL QUARTZ_MediaSubType_IsFourCC(
	const GUID* psubtype )
{
	GUID guidTemp;

	QUARTZ_MediaSubType_FromFourCC(
		&guidTemp, psubtype->Data1 );
	return IsEqualGUID( psubtype, &guidTemp );
}

HRESULT QUARTZ_MediaSubType_FromBitmap(
	GUID* psubtype, const BITMAPINFOHEADER* pbi )
{
	HRESULT hr;
	DWORD*	pdwBitf;

	if ( (pbi->biCompression & 0xffff0000) != 0 )
		return S_FALSE;

	if ( pbi->biWidth <= 0 || pbi->biHeight == 0 )
		return E_FAIL;

	hr = E_FAIL;
	switch ( pbi->biCompression )
	{
	case 0:
		if ( pbi->biPlanes != 1 )
			break;
		switch ( pbi->biBitCount )
		{
		case  1:
			memcpy( psubtype, &MEDIASUBTYPE_RGB1, sizeof(GUID) );
			hr = S_OK;
			break;
		case  4:
			memcpy( psubtype, &MEDIASUBTYPE_RGB4, sizeof(GUID) );
			hr = S_OK;
			break;
		case  8:
			memcpy( psubtype, &MEDIASUBTYPE_RGB8, sizeof(GUID) );
			hr = S_OK;
			break;
		case 16:
			memcpy( psubtype, &MEDIASUBTYPE_RGB555, sizeof(GUID) );
			hr = S_OK;
			break;
		case 24:
			memcpy( psubtype, &MEDIASUBTYPE_RGB24, sizeof(GUID) );
			hr = S_OK;
			break;
		case 32:
			memcpy( psubtype, &MEDIASUBTYPE_RGB32, sizeof(GUID) );
			hr = S_OK;
			break;
		}
		break;
	case 1:
		if ( pbi->biPlanes == 1 && pbi->biHeight > 0 &&
			 pbi->biBitCount == 8 )
		{
			QUARTZ_MediaSubType_FromFourCC( psubtype, 1 );
			hr = S_OK;
		}
		break;
	case 2:
		if ( pbi->biPlanes == 1 && pbi->biHeight > 0 &&
			 pbi->biBitCount == 4 )
		{
			QUARTZ_MediaSubType_FromFourCC( psubtype, 2 );
			hr = S_OK;
		}
		break;
	case 3:
		if ( pbi->biPlanes != 1 )
			break;
		pdwBitf = (DWORD*)( (BYTE*)pbi + sizeof(BITMAPINFOHEADER) );
		switch ( pbi->biBitCount )
		{
		case 16:
			if ( pdwBitf[0] == 0x7c00 &&
				 pdwBitf[1] == 0x03e0 &&
				 pdwBitf[2] == 0x001f )
			{
				memcpy( psubtype, &MEDIASUBTYPE_RGB555, sizeof(GUID) );
				hr = S_OK;
			}
			if ( pdwBitf[0] == 0xf800 &&
				 pdwBitf[1] == 0x07e0 &&
				 pdwBitf[2] == 0x001f )
			{
				memcpy( psubtype, &MEDIASUBTYPE_RGB565, sizeof(GUID) );
				hr = S_OK;
			}
			break;
		case 32:
			if ( pdwBitf[0] == 0x00ff0000 &&
				 pdwBitf[1] == 0x0000ff00 &&
				 pdwBitf[2] == 0x000000ff )
			{
				memcpy( psubtype, &MEDIASUBTYPE_RGB32, sizeof(GUID) );
				hr = S_OK;
			}
			break;
		}
		break;
	}

	return hr;
}

void QUARTZ_PatchBitmapInfoHeader( BITMAPINFOHEADER* pbi )
{
	switch ( pbi->biCompression )
	{
	case mmioFOURCC('R','G','B',' '):
		pbi->biCompression = 0;
		break;
	case mmioFOURCC('R','L','E',' '):
	case mmioFOURCC('M','R','L','E'):
	case mmioFOURCC('R','L','E','8'):
	case mmioFOURCC('R','L','E','4'):
		if ( pbi->biBitCount == 4 )
			pbi->biCompression = 2;
		else
			pbi->biCompression = 1;
		break;
	}
}

BOOL QUARTZ_BitmapHasFixedSample( const BITMAPINFOHEADER* pbi )
{
	switch ( pbi->biCompression )
	{
	case 0:
	case 3:
	case mmioFOURCC('I','4','2','0'):
	case mmioFOURCC('I','Y','U','V'):
	case mmioFOURCC('Y','U','Y','V'):
	case mmioFOURCC('Y','V','U','9'):
	case mmioFOURCC('Y','4','1','1'):
	case mmioFOURCC('Y','4','1','P'):
	case mmioFOURCC('Y','U','Y','2'):
	case mmioFOURCC('Y','V','Y','U'):
	case mmioFOURCC('U','Y','V','Y'):
	case mmioFOURCC('Y','2','1','1'):
	case mmioFOURCC('Y','V','1','2'):
		return TRUE;
	}

	return FALSE;
}


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

typedef struct IEnumMediaTypesImpl
{
	ICOM_VFIELD(IEnumMediaTypes);
} IEnumMediaTypesImpl;

typedef struct
{
	QUARTZ_IUnkImpl	unk;
	IEnumMediaTypesImpl	enummtype;
	struct QUARTZ_IFEntry	IFEntries[1];
	CRITICAL_SECTION	cs;
	AM_MEDIA_TYPE*	pTypes;
	ULONG	cTypes;
	ULONG	cCur;
} CEnumMediaTypes;

#define	CEnumMediaTypes_THIS(iface,member)		CEnumMediaTypes*	This = ((CEnumMediaTypes*)(((char*)iface)-offsetof(CEnumMediaTypes,member)))



static HRESULT WINAPI
IEnumMediaTypes_fnQueryInterface(IEnumMediaTypes* iface,REFIID riid,void** ppobj)
{
	CEnumMediaTypes_THIS(iface,enummtype);

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

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

static ULONG WINAPI
IEnumMediaTypes_fnAddRef(IEnumMediaTypes* iface)
{
	CEnumMediaTypes_THIS(iface,enummtype);

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

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

static ULONG WINAPI
IEnumMediaTypes_fnRelease(IEnumMediaTypes* iface)
{
	CEnumMediaTypes_THIS(iface,enummtype);

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

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

static HRESULT WINAPI
IEnumMediaTypes_fnNext(IEnumMediaTypes* iface,ULONG cReq,AM_MEDIA_TYPE** ppmtype,ULONG* pcFetched)
{
	CEnumMediaTypes_THIS(iface,enummtype);
	HRESULT	hr;
	ULONG	cFetched;

	TRACE("(%p)->(%lu,%p,%p)\n",This,cReq,ppmtype,pcFetched);

	if ( pcFetched == NULL && cReq > 1 )
		return E_INVALIDARG;
	if ( ppmtype == NULL )
		return E_POINTER;

	EnterCriticalSection( &This->cs );

	hr = NOERROR;
	cFetched = 0;
	while ( cReq > 0 )
	{
		if ( This->cCur >= This->cTypes )
		{
			hr = S_FALSE;
			break;
		}
		ppmtype[ cFetched ] =
			QUARTZ_MediaType_Duplicate( &This->pTypes[ This->cCur ] );
		if ( ppmtype[ cFetched ] == NULL )
		{
			hr = E_OUTOFMEMORY;
			while ( cFetched > 0 )
			{
				cFetched --;
				QUARTZ_MediaType_Destroy( ppmtype[ cFetched ] );
			}
			break;
		}

		cFetched ++;

		This->cCur ++;
		cReq --;
	}

	LeaveCriticalSection( &This->cs );

	if ( pcFetched != NULL )
		*pcFetched = cFetched;

	return hr;
}

static HRESULT WINAPI
IEnumMediaTypes_fnSkip(IEnumMediaTypes* iface,ULONG cSkip)
{
	CEnumMediaTypes_THIS(iface,enummtype);
	HRESULT	hr;

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

	EnterCriticalSection( &This->cs );

	hr = NOERROR;
	while ( cSkip > 0 )
	{
		if ( This->cCur >= This->cTypes )
		{
			hr = S_FALSE;
			break;
		}
		This->cCur ++;
		cSkip --;
	}

	LeaveCriticalSection( &This->cs );

	return hr;
}

static HRESULT WINAPI
IEnumMediaTypes_fnReset(IEnumMediaTypes* iface)
{
	CEnumMediaTypes_THIS(iface,enummtype);

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

	EnterCriticalSection( &This->cs );

	This->cCur = 0;

	LeaveCriticalSection( &This->cs );

	return NOERROR;
}

static HRESULT WINAPI
IEnumMediaTypes_fnClone(IEnumMediaTypes* iface,IEnumMediaTypes** ppobj)
{
	CEnumMediaTypes_THIS(iface,enummtype);
	HRESULT	hr;

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

	if ( ppobj == NULL )
		return E_POINTER;

	EnterCriticalSection( &This->cs );

	hr = QUARTZ_CreateEnumMediaTypes(
		ppobj,
		This->pTypes, This->cTypes );
	if ( SUCCEEDED(hr) )
		IEnumMediaTypes_Skip( *ppobj, This->cCur );

	LeaveCriticalSection( &This->cs );

	return hr;
}


static ICOM_VTABLE(IEnumMediaTypes) ienummtype =
{
	ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
	/* IUnknown fields */
	IEnumMediaTypes_fnQueryInterface,
	IEnumMediaTypes_fnAddRef,
	IEnumMediaTypes_fnRelease,
	/* IEnumMediaTypes fields */
	IEnumMediaTypes_fnNext,
	IEnumMediaTypes_fnSkip,
	IEnumMediaTypes_fnReset,
	IEnumMediaTypes_fnClone,
};


/* can I use offsetof safely? - FIXME? */
static QUARTZ_IFEntry IFEntries[] =
{
  { &IID_IEnumMediaTypes, offsetof(CEnumMediaTypes,enummtype)-offsetof(CEnumMediaTypes,unk) },
};


void QUARTZ_DestroyEnumMediaTypes(IUnknown* punk)
{
	CEnumMediaTypes_THIS(punk,unk);
	ULONG	i;

	if ( This->pTypes != NULL )
	{
		for ( i = 0; i < This->cTypes; i++ )
			QUARTZ_MediaType_Free( &This->pTypes[i] );
		QUARTZ_FreeMem( This->pTypes );
	}

	DeleteCriticalSection( &This->cs );
}

HRESULT QUARTZ_CreateEnumMediaTypes(
	IEnumMediaTypes** ppobj,
	const AM_MEDIA_TYPE* pTypes, ULONG cTypes )
{
	CEnumMediaTypes*	penum;
	AM_MEDIA_TYPE*	pTypesDup = NULL;
	ULONG	i;
	HRESULT	hr;

	TRACE("(%p,%p,%lu)\n",ppobj,pTypes,cTypes);

	if ( cTypes > 0 )
	{
		pTypesDup = (AM_MEDIA_TYPE*)QUARTZ_AllocMem(
			sizeof( AM_MEDIA_TYPE ) * cTypes );
		if ( pTypesDup == NULL )
			return E_OUTOFMEMORY;

		i = 0;
		while ( i < cTypes )
		{
			hr = QUARTZ_MediaType_Copy( &pTypesDup[i], &pTypes[i] );
			if ( FAILED(hr) )
			{
				while ( i > 0 )
				{
					i --;
					QUARTZ_MediaType_Free( &pTypesDup[i] );
				}
				QUARTZ_FreeMem( pTypesDup );
				return hr;
			}

			i ++;
		}
	}

	penum = (CEnumMediaTypes*)QUARTZ_AllocObj( sizeof(CEnumMediaTypes) );
	if ( penum == NULL )
	{
		return E_OUTOFMEMORY;
	}
	penum->pTypes = pTypesDup;
	penum->cTypes = cTypes;
	penum->cCur = 0;

	QUARTZ_IUnkInit( &penum->unk, NULL );
	ICOM_VTBL(&penum->enummtype) = &ienummtype;

	penum->unk.pEntries = IFEntries;
	penum->unk.dwEntries = sizeof(IFEntries)/sizeof(IFEntries[0]);
	penum->unk.pOnFinalRelease = QUARTZ_DestroyEnumMediaTypes;

	InitializeCriticalSection( &penum->cs );

	*ppobj = (IEnumMediaTypes*)(&penum->enummtype);

	return S_OK;
}


