/*
 * Implements AVI Decompressor(CLSID_AVIDec).
 *
 * 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 "vfw.h"
#include "strmif.h"
#include "control.h"
#include "amvideo.h"
#include "vfwmsgs.h"
#include "uuids.h"

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

#include "quartz_private.h"
#include "xform.h"


static const WCHAR AVIDec_FilterName[] =
{'A','V','I',' ','D','e','c','o','m','p','r','e','s','s','o','r',0};

static const WCHAR AVIDraw_FilterName[] =
{'A','V','I',' ','D','r','a','w',0};

typedef struct CAVIDecImpl
{
	HIC hicCached;
	HIC hicTrans;
	AM_MEDIA_TYPE m_mtOut;
	BITMAPINFO* m_pbiIn;
	BITMAPINFO* m_pbiOut;
	BYTE* m_pOutBuf;
} CAVIDecImpl;

/***************************************************************************
 *
 *	CAVIDecImpl methods
 *
 */

static void AVIDec_ReleaseDIBBuffers(CAVIDecImpl* This)
{
	TRACE("(%p)\n",This);

	if ( This->m_pbiIn != NULL )
	{
		QUARTZ_FreeMem(This->m_pbiIn); This->m_pbiIn = NULL;
	}
	if ( This->m_pbiOut != NULL )
	{
		QUARTZ_FreeMem(This->m_pbiOut); This->m_pbiOut = NULL;
	}
	if ( This->m_pOutBuf != NULL )
	{
		QUARTZ_FreeMem(This->m_pOutBuf); This->m_pOutBuf = NULL;
	}
}

static BITMAPINFO* AVIDec_DuplicateBitmapInfo(const BITMAPINFO* pbi)
{
	DWORD dwSize;
	BITMAPINFO*	pbiRet;

	dwSize = pbi->bmiHeader.biSize;
	if ( dwSize < sizeof(BITMAPINFOHEADER) )
		return NULL;
	if ( pbi->bmiHeader.biBitCount <= 8 )
	{
		if ( pbi->bmiHeader.biClrUsed == 0 )
			dwSize += sizeof(RGBQUAD)*(1<<pbi->bmiHeader.biBitCount);
		else
			dwSize += sizeof(RGBQUAD)*pbi->bmiHeader.biClrUsed;
	}
	if ( pbi->bmiHeader.biCompression == 3 &&
		 dwSize == sizeof(BITMAPINFOHEADER) )
		dwSize += sizeof(DWORD)*3;

	pbiRet = (BITMAPINFO*)QUARTZ_AllocMem(dwSize);
	if ( pbiRet != NULL )
		memcpy( pbiRet, pbi, dwSize );

	return pbiRet;
}

static HRESULT AVIDec_Init( CTransformBaseImpl* pImpl )
{
	CAVIDecImpl*	This = pImpl->m_pUserData;

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

	if ( This != NULL )
		return NOERROR;

	This = (CAVIDecImpl*)QUARTZ_AllocMem( sizeof(CAVIDecImpl) );
	if ( This == NULL )
		return E_OUTOFMEMORY;
	ZeroMemory( This, sizeof(CAVIDecImpl) );
	pImpl->m_pUserData = This;
	/* construct */
	This->hicCached = (HIC)NULL;
	This->hicTrans = (HIC)NULL;
	ZeroMemory( &This->m_mtOut, sizeof(AM_MEDIA_TYPE) );
	This->m_pbiIn = NULL;
	This->m_pbiOut = NULL;
	This->m_pOutBuf = NULL;

	return NOERROR;
}

static HRESULT AVIDec_Cleanup( CTransformBaseImpl* pImpl )
{
	CAVIDecImpl*	This = pImpl->m_pUserData;

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

	if ( This == NULL )
		return NOERROR;

	/* destruct */
	QUARTZ_MediaType_Free( &This->m_mtOut );

	AVIDec_ReleaseDIBBuffers(This);

	if ( This->hicCached != (HIC)NULL )
		ICClose(This->hicCached);
	if ( This->hicTrans != (HIC)NULL )
		ICClose(This->hicTrans);

	QUARTZ_FreeMem( This );
	pImpl->m_pUserData = NULL;

	return NOERROR;
}

static HRESULT AVIDec_CheckMediaType( CTransformBaseImpl* pImpl, const AM_MEDIA_TYPE* pmtIn, const AM_MEDIA_TYPE* pmtOut )
{
	CAVIDecImpl*	This = pImpl->m_pUserData;
	BITMAPINFO*	pbiIn = NULL;
	BITMAPINFO*	pbiOut = NULL;
	HIC	hic;

	TRACE("(%p)\n",This);
	if ( This == NULL )
		return E_UNEXPECTED;

	if ( !IsEqualGUID( &pmtIn->majortype, &MEDIATYPE_Video ) )
		return E_FAIL;
	if ( !IsEqualGUID( &pmtIn->formattype, &FORMAT_VideoInfo ) )
		return E_FAIL;
	pbiIn = (BITMAPINFO*)(&((VIDEOINFOHEADER*)pmtIn->pbFormat)->bmiHeader);
	if ( pmtOut != NULL )
	{
		if ( !IsEqualGUID( &pmtOut->majortype, &MEDIATYPE_Video ) )
			return E_FAIL;
		if ( !IsEqualGUID( &pmtOut->formattype, &FORMAT_VideoInfo ) )
			return E_FAIL;
		pbiOut = (BITMAPINFO*)(&((VIDEOINFOHEADER*)pmtOut->pbFormat)->bmiHeader);
	}

	if ( This->hicCached != (HIC)NULL &&
		 ICDecompressQuery( This->hicCached, pbiIn, pbiOut ) == ICERR_OK )
	{
		TRACE("supported format\n");
		return NOERROR;
	}

	TRACE("try to find a decoder...\n");
	hic = ICLocate(
		mmioFOURCC('V','I','D','C'), 0,
		&pbiIn->bmiHeader, &pbiOut->bmiHeader, ICMODE_DECOMPRESS );
	if ( hic == (HIC)NULL )
	{
		WARN("no decoder for %c%c%c%c\n",
			(int)(( pbiIn->bmiHeader.biCompression >>  0 ) & 0xff),
			(int)(( pbiIn->bmiHeader.biCompression >>  8 ) & 0xff),
			(int)(( pbiIn->bmiHeader.biCompression >> 16 ) & 0xff),
			(int)(( pbiIn->bmiHeader.biCompression >> 24 ) & 0xff) );
		return E_FAIL;
	}
	TRACE("found\n");

	if ( This->hicCached != (HIC)NULL )
		ICClose(This->hicCached);
	This->hicCached = hic;

	return NOERROR;
}

static HRESULT AVIDec_GetOutputTypes( CTransformBaseImpl* pImpl, const AM_MEDIA_TYPE* pmtIn, const AM_MEDIA_TYPE** ppmtAcceptTypes, ULONG* pcAcceptTypes )
{
	CAVIDecImpl*	This = pImpl->m_pUserData;
	HRESULT hr;
	LONG cbFmt;
	BITMAPINFO*	pbiIn = NULL;
	BITMAPINFO*	pbiOut = NULL;

	TRACE("(%p)\n",This);
	hr = AVIDec_CheckMediaType( pImpl, pmtIn, NULL );
	if ( FAILED(hr) )
		return hr;

	TRACE("(%p) - get size of format\n",This);
	pbiIn = (BITMAPINFO*)(&((VIDEOINFOHEADER*)pmtIn->pbFormat)->bmiHeader);
	cbFmt = (LONG)ICDecompressGetFormatSize( This->hicCached, pbiIn );
	if ( cbFmt < sizeof(BITMAPINFOHEADER) )
		return E_FAIL;

	QUARTZ_MediaType_Free( &This->m_mtOut );
	ZeroMemory( &This->m_mtOut, sizeof(AM_MEDIA_TYPE) );

	memcpy( &This->m_mtOut.majortype, &MEDIATYPE_Video, sizeof(GUID) );
	memcpy( &This->m_mtOut.formattype, &FORMAT_VideoInfo, sizeof(GUID) );
	This->m_mtOut.cbFormat = sizeof(VIDEOINFOHEADER) + cbFmt + sizeof(RGBQUAD)*256;
	This->m_mtOut.pbFormat = (BYTE*)CoTaskMemAlloc(This->m_mtOut.cbFormat);
	if ( This->m_mtOut.pbFormat == NULL )
		return E_OUTOFMEMORY;
	ZeroMemory( This->m_mtOut.pbFormat, This->m_mtOut.cbFormat );

	pbiOut = (BITMAPINFO*)(&((VIDEOINFOHEADER*)This->m_mtOut.pbFormat)->bmiHeader);

	TRACE("(%p) - get format\n",This);
	if ( ICDecompressGetFormat( This->hicCached, pbiIn, pbiOut ) != ICERR_OK )
		return E_FAIL;

	hr = QUARTZ_MediaSubType_FromBitmap( &This->m_mtOut.subtype, &pbiOut->bmiHeader );
	if ( FAILED(hr) )
		return hr;
	if ( hr != S_OK )
		QUARTZ_MediaSubType_FromFourCC( &This->m_mtOut.subtype, pbiOut->bmiHeader.biCompression );

	This->m_mtOut.bFixedSizeSamples = (pbiOut->bmiHeader.biCompression == 0) ? 1 : 0;
	This->m_mtOut.lSampleSize = (pbiOut->bmiHeader.biCompression == 0) ? DIBSIZE(pbiOut->bmiHeader) : pbiOut->bmiHeader.biSizeImage;

	/* get palette */
	if ( pbiOut->bmiHeader.biBitCount <= 8 )
	{
		TRACE("(%p) - get palette\n",This);
		if ( ICDecompressGetPalette( This->hicCached, pbiIn, pbiOut ) != ICERR_OK )
		{
			TRACE("(%p) - use the input palette\n",This);
			if ( pbiIn->bmiHeader.biBitCount != pbiOut->bmiHeader.biBitCount )
			{
				FIXME( "no palette...FIXME?\n" );
				return E_FAIL;
			}
			if ( pbiOut->bmiHeader.biClrUsed == 0 )
				pbiOut->bmiHeader.biClrUsed = 1<<pbiOut->bmiHeader.biBitCount;
			if ( pbiOut->bmiHeader.biClrUsed > (1<<pbiOut->bmiHeader.biBitCount) )
			{
				ERR( "biClrUsed=%ld\n", pbiOut->bmiHeader.biClrUsed );
				return E_FAIL;
			}

			memcpy( pbiOut->bmiColors, pbiIn->bmiColors,
				sizeof(RGBQUAD) * pbiOut->bmiHeader.biClrUsed );
		}
	}

	TRACE("(%p) - return format\n",This);
	*ppmtAcceptTypes = &This->m_mtOut;
	*pcAcceptTypes = 1;

	return NOERROR;
}

static HRESULT AVIDec_GetAllocProp( CTransformBaseImpl* pImpl, const AM_MEDIA_TYPE* pmtIn, const AM_MEDIA_TYPE* pmtOut, ALLOCATOR_PROPERTIES* pProp, BOOL* pbTransInPlace, BOOL* pbTryToReuseSample )
{
	CAVIDecImpl*	This = pImpl->m_pUserData;
	BITMAPINFO*	pbiOut = NULL;
	HRESULT hr;

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

	if ( This == NULL )
		return E_UNEXPECTED;

	hr = AVIDec_CheckMediaType( pImpl, pmtIn, pmtOut );
	if ( FAILED(hr) )
		return hr;

	pbiOut = (BITMAPINFO*)(&((VIDEOINFOHEADER*)pmtOut->pbFormat)->bmiHeader);

	pProp->cBuffers = 1;
	if ( pbiOut->bmiHeader.biCompression == 0 )
		pProp->cbBuffer = DIBSIZE(pbiOut->bmiHeader);
	else
		pProp->cbBuffer = pbiOut->bmiHeader.biSizeImage;

	*pbTransInPlace = FALSE;
	*pbTryToReuseSample = TRUE;

	return NOERROR;
}

static HRESULT AVIDec_BeginTransform( CTransformBaseImpl* pImpl, const AM_MEDIA_TYPE* pmtIn, const AM_MEDIA_TYPE* pmtOut, BOOL bReuseSample )
{
	CAVIDecImpl*	This = pImpl->m_pUserData;
	BITMAPINFO*	pbiIn = NULL;
	BITMAPINFO*	pbiOut = NULL;
	HRESULT hr;

	TRACE("(%p,%p,%p,%d)\n",This,pmtIn,pmtOut,bReuseSample);

	if ( This == NULL ||
		 This->hicTrans != (HIC)NULL )
		return E_UNEXPECTED;

	hr = AVIDec_CheckMediaType( pImpl, pmtIn, pmtOut );
	if ( FAILED(hr) )
		return hr;

	AVIDec_ReleaseDIBBuffers(This);

	pbiIn = (BITMAPINFO*)(&((VIDEOINFOHEADER*)pmtIn->pbFormat)->bmiHeader);
	pbiOut = (BITMAPINFO*)(&((VIDEOINFOHEADER*)pmtOut->pbFormat)->bmiHeader);
	This->m_pbiIn = AVIDec_DuplicateBitmapInfo(pbiIn);
	This->m_pbiOut = AVIDec_DuplicateBitmapInfo(pbiOut);
	if ( This->m_pbiIn == NULL || This->m_pbiOut == NULL )
		return E_OUTOFMEMORY;
	if ( This->m_pbiOut->bmiHeader.biCompression == 0 || This->m_pbiOut->bmiHeader.biCompression == 3 )
		This->m_pbiOut->bmiHeader.biSizeImage = DIBSIZE(This->m_pbiOut->bmiHeader);

	if ( !bReuseSample )
	{
		This->m_pOutBuf = QUARTZ_AllocMem(This->m_pbiOut->bmiHeader.biSizeImage);
		if ( This->m_pOutBuf == NULL )
			return E_OUTOFMEMORY;
		ZeroMemory( This->m_pOutBuf, This->m_pbiOut->bmiHeader.biSizeImage );
	}

	if ( ICERR_OK != ICDecompressBegin(
		This->hicCached, This->m_pbiIn, This->m_pbiOut ) )
		return E_FAIL;

	This->hicTrans = This->hicCached;
	This->hicCached = (HIC)NULL;

	return NOERROR;
}

static HRESULT AVIDec_Transform( CTransformBaseImpl* pImpl, IMediaSample* pSampIn, IMediaSample* pSampOut )
{
	CAVIDecImpl*	This = pImpl->m_pUserData;
	DWORD	dwFlags;
	BYTE*	pDataIn = NULL;
	BYTE*	pDataOut = NULL;
	HRESULT hr;

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

	if ( This == NULL || pSampOut == NULL ||
		 This->hicTrans == (HIC)NULL ||
		 This->m_pbiIn == NULL ||
		 This->m_pbiOut == NULL )
		return E_UNEXPECTED;

	hr = IMediaSample_GetPointer( pSampIn, &pDataIn );
	if ( FAILED(hr) )
		return hr;
	hr = IMediaSample_GetPointer( pSampOut, &pDataOut );
	if ( FAILED(hr) )
		return hr;

	dwFlags = 0;
	/*** FIXME!!!
	 *
	 * if ( IMediaSample_IsSyncPoint(pSampIn) != S_OK )
	 *	dwFlags |= ICDECOMPRESS_NOTKEYFRAME;
	 ****/

	if ( IMediaSample_IsPreroll(pSampIn) == S_OK )
		dwFlags |= ICDECOMPRESS_PREROLL;

	if ( ICERR_OK != ICDecompress(
		This->hicTrans,
		dwFlags,
		&This->m_pbiIn->bmiHeader,
		pDataIn,
		&This->m_pbiOut->bmiHeader,
		( This->m_pOutBuf != NULL ) ? This->m_pOutBuf : pDataOut ) )
		return E_FAIL;

	if ( This->m_pOutBuf != NULL )
		memcpy( pDataOut, This->m_pOutBuf,
				This->m_pbiOut->bmiHeader.biSizeImage );

	return NOERROR;
}

static HRESULT AVIDec_EndTransform( CTransformBaseImpl* pImpl )
{
	CAVIDecImpl*	This = pImpl->m_pUserData;

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

	if ( This == NULL )
		return E_UNEXPECTED;
	if ( This->hicTrans == (HIC)NULL )
		return NOERROR;

	ICDecompressEnd(This->hicTrans);

	if ( This->hicCached != (HIC)NULL )
		ICClose(This->hicCached);
	This->hicCached = This->hicTrans;
	This->hicTrans = (HIC)NULL;

	AVIDec_ReleaseDIBBuffers(This);

	return NOERROR;
}


static const TransformBaseHandlers transhandlers =
{
	AVIDec_Init,
	AVIDec_Cleanup,
	AVIDec_CheckMediaType,
	AVIDec_GetOutputTypes,
	AVIDec_GetAllocProp,
	AVIDec_BeginTransform,
	NULL,
	AVIDec_Transform,
	AVIDec_EndTransform,
};


HRESULT QUARTZ_CreateAVIDec(IUnknown* punkOuter,void** ppobj)
{
	return QUARTZ_CreateTransformBase(
		punkOuter,ppobj,
		&CLSID_AVIDec,
		AVIDec_FilterName,
		NULL, NULL,
		&transhandlers );
}


HRESULT QUARTZ_CreateAVIDraw(IUnknown* punkOuter,void** ppobj)
{
	return QUARTZ_CreateTransformBase(
		punkOuter,ppobj,
		&CLSID_AVIDraw,
		AVIDraw_FilterName,
		NULL, NULL,
		&transhandlers );
}

