/*
 *	MSRLE32 Driver
 *
 *	Copyright 2001 TAKESHIMA Hidenori <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
 *
 *	FIXME - encoding
 *	FIXME - RLE4
 *
 */

#include "config.h"

#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winuser.h"
#include "mmsystem.h"
#include "vfw.h"

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




typedef struct CodecImpl
{
	BOOL	bInCompress;
	BOOL	bInDecompress;
	BOOL	bInDecompressEx;
} CodecImpl;

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

static LONG MSRLE32_DecompressRLE4(
	BYTE* pDst, LONG pitch,
	const BYTE* pSrc,
	LONG width, LONG height )
{
	FIXME( "RLE4 - not implemented yet\n" );
	return ICERR_UNSUPPORTED;
}

static LONG MSRLE32_DecompressRLE8(
	BYTE* pDst, LONG pitch,
	const BYTE* pSrc,
	LONG width, LONG height )
{
	LONG	x,y;
	LONG	delta_x,delta_y;
	int		len;
	UINT	data;

	x = 0; y = 0;
	while ( y < height )
	{
		len = *pSrc++; data = *pSrc++;
		if ( len > 0 )
		{
			/* run length encoding */
			while ( len-- > 0 && x < width )
				pDst[x++] = (BYTE)data;
		}
		else
		{
			switch ( data )
			{
			case 0: /* EOL */
				x = 0; y++; pDst += pitch;
				break;
			case 1: /* END */
				return ICERR_OK;
			case 2: /* DELTA */
				delta_x = (LONG)*pSrc++;
				delta_y = (LONG)*pSrc++;
				x += delta_x;
				y += delta_y;
				pDst += delta_y * pitch + delta_x;
				break;
			default: /* RAW */
				len = data;
				if ( (len+x) > width )
					len = width-x;
				memcpy( &pDst[x], pSrc, len );
				x += len;
				pSrc += (data+1)&(~1);
				break;
			}
		}
	}

	return ICERR_OK;
}

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

static BOOL MSRLE32_IsValidInfoHeader( const BITMAPINFO* pbiIn )
{
	if ( pbiIn->bmiHeader.biSize < sizeof(BITMAPINFOHEADER) ||
		 pbiIn->bmiHeader.biWidth <= 0 ||
		 pbiIn->bmiHeader.biHeight == 0 ||
		 pbiIn->bmiHeader.biPlanes != 1 )
		return FALSE;

	return TRUE;
}

static DWORD MSRLE32_GetClrUsed( const BITMAPINFO* pbiIn )
{
	if ( pbiIn->bmiHeader.biBitCount > 8 )
		return 0;
	return (pbiIn->bmiHeader.biClrUsed == 0) ? (1<<pbiIn->bmiHeader.biBitCount) : pbiIn->bmiHeader.biClrUsed;
}

static DWORD MSRLE32_GetUncompressedPitch( const BITMAPINFOHEADER* pbiIn )
{
	return ((((pbiIn->biWidth*pbiIn->biBitCount)+7)>>3)+3)&(~3);
}

static DWORD MSRLE32_GetUncompressedSize( const BITMAPINFOHEADER* pbiIn )
{
	return MSRLE32_GetUncompressedPitch( pbiIn ) * abs(pbiIn->biHeight);
}


static BOOL MSRLE32_IsValidRGB( const BITMAPINFO* pbiIn )
{
	if ( !MSRLE32_IsValidInfoHeader( pbiIn ) )
		return FALSE;

	if ( pbiIn->bmiHeader.biSizeImage != 0 &&
		 pbiIn->bmiHeader.biSizeImage < MSRLE32_GetUncompressedSize( &pbiIn->bmiHeader ) )
		return FALSE;


	switch ( pbiIn->bmiHeader.biCompression )
	{
	case 0:
	case mmioFOURCC('R','G','B',' '):
		if ( pbiIn->bmiHeader.biBitCount == 1 ||
			 pbiIn->bmiHeader.biBitCount == 4 ||
			 pbiIn->bmiHeader.biBitCount == 8 ||
			 pbiIn->bmiHeader.biBitCount == 16 ||
			 pbiIn->bmiHeader.biBitCount == 24 ||
			 pbiIn->bmiHeader.biBitCount == 32 )
			return TRUE;
		break;
	default:
		break;
	}

	return FALSE;
}

static BOOL MSRLE32_IsValidRLE( const BITMAPINFO* pbiIn )
{
	if ( !MSRLE32_IsValidInfoHeader( pbiIn ) )
		return FALSE;

	switch ( pbiIn->bmiHeader.biCompression )
	{
	case 1:
	case 2:
	case mmioFOURCC('R','L','E',' '):
	case mmioFOURCC('R','L','E','8'):
	case mmioFOURCC('R','L','E','4'):
	case mmioFOURCC('M','R','L','E'):
		if ( pbiIn->bmiHeader.biBitCount == 8 ||
			 pbiIn->bmiHeader.biBitCount == 4 )
			return TRUE;
		break;
	default:
		break;
	}

	return FALSE;
}

static BOOL MSRLE32_CompareInfoHeader( const BITMAPINFO* pbiIn, const BITMAPINFO* pbiOut )
{
	if ( !MSRLE32_IsValidInfoHeader( pbiIn ) ||
		 !MSRLE32_IsValidInfoHeader( pbiOut ) )
		return FALSE;

	if ( pbiIn->bmiHeader.biWidth != pbiOut->bmiHeader.biWidth ||
		 pbiIn->bmiHeader.biHeight != pbiOut->bmiHeader.biHeight ||
		 pbiIn->bmiHeader.biPlanes != pbiOut->bmiHeader.biPlanes ||
		 pbiIn->bmiHeader.biBitCount != pbiOut->bmiHeader.biBitCount )
		return FALSE;

	return TRUE;
}


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

static LONG Codec_DrvQueryConfigure( CodecImpl* This )
{
	return ICERR_UNSUPPORTED;
}

static LONG Codec_DrvConfigure( CodecImpl* This, HWND hwnd, DRVCONFIGINFO* pinfo )
{
	return ICERR_UNSUPPORTED;
}

static LONG Codec_QueryAbout( void )
{
	return ICERR_UNSUPPORTED;
}

static LONG Codec_About( HWND hwnd )
{
	return ICERR_UNSUPPORTED;
}

static LONG Codec_CompressQuery( CodecImpl* This, BITMAPINFO* pbiIn, BITMAPINFO* pbiOut )
{
	FIXME( "compression is not implemented!\n" );
	return ICERR_UNSUPPORTED;
}

static LONG Codec_CompressBegin( CodecImpl* This, BITMAPINFO* pbiIn, BITMAPINFO* pbiOut )
{
	return ICERR_UNSUPPORTED;
}

static LONG Codec_Compress( CodecImpl* This, ICCOMPRESS* picc, DWORD dwSize )
{
	FIXME( "compression is not implemented!\n" );
	return ICERR_UNSUPPORTED;
}

static LONG Codec_CompressEnd( CodecImpl* This )
{
	This->bInCompress = FALSE;
	return ICERR_UNSUPPORTED;
}

static LONG Codec_CompressFramesInfo( CodecImpl* This, ICCOMPRESSFRAMES* piccf, DWORD dwSize )
{
	return ICERR_UNSUPPORTED;
}

static LONG Codec_CompressGetFormat( CodecImpl* This, BITMAPINFO* pbiIn, BITMAPINFO* pbiOut )
{
	return ICERR_UNSUPPORTED;
}

static LONG Codec_CompressGetSize( CodecImpl* This, BITMAPINFO* pbiIn, BITMAPINFO* pbiOut )
{
	return ICERR_UNSUPPORTED;
}

static LONG Codec_ICQueryConfigure( CodecImpl* This )
{
	return ICERR_OK;
}
static LONG Codec_ICConfigure( CodecImpl* This, HWND hwnd )
{
	MessageBoxA( hwnd, "Wine RLE Driver", "MSRLE32", MB_OK );
	return ICERR_OK;
}

static LONG Codec_DecompressQuery( CodecImpl* This, BITMAPINFO* pbiIn, BITMAPINFO* pbiOut )
{
	if ( pbiIn == NULL )
		return ICERR_BADPARAM;
	if ( !MSRLE32_IsValidRLE( pbiIn ) )
		return ICERR_UNSUPPORTED;

	if ( pbiOut != NULL )
	{
		if ( !MSRLE32_IsValidRGB( pbiOut ) )
			return ICERR_UNSUPPORTED;
		if ( !MSRLE32_CompareInfoHeader( pbiIn, pbiOut ) )
			return ICERR_UNSUPPORTED;
	}

	return ICERR_OK;
}

static LONG Codec_DecompressBegin( CodecImpl* This, BITMAPINFO* pbiIn, BITMAPINFO* pbiOut )
{
	LONG	lr;

	if ( pbiIn == NULL || pbiOut == NULL )
		return ICERR_BADPARAM;
	lr = Codec_DecompressQuery( This, pbiIn, pbiOut );
	if ( lr != ICERR_OK )
		return lr;

	This->bInDecompress = TRUE;

	return ICERR_OK;
}

static LONG Codec_Decompress( CodecImpl* This, ICDECOMPRESS* picd, DWORD dwSize )
{
	LONG	lr;

	if ( !This->bInDecompress )
		return ICERR_BADHANDLE; /* FIXME? */

	if ( ( picd->dwFlags & ICDECOMPRESS_NOTKEYFRAME ) &&
		 ( picd->dwFlags & ICDECOMPRESS_UPDATE ) )
		return ICERR_CANTUPDATE; /* FIXME? */

	if ( picd->lpbiInput == NULL ||
		 picd->lpInput == NULL ||
		 picd->lpbiOutput == NULL ||
		 picd->lpOutput == NULL )
		return ICERR_BADPARAM;

	switch ( picd->lpbiInput->biBitCount )
	{
	case 4: /* RLE4 */
		lr = MSRLE32_DecompressRLE4(
			(BYTE*)picd->lpOutput,
			MSRLE32_GetUncompressedPitch( picd->lpbiInput ),
			(const BYTE*)picd->lpInput,
			picd->lpbiInput->biWidth,
			picd->lpbiInput->biHeight );
		break;
	case 8: /* RLE8 */
		lr = MSRLE32_DecompressRLE8(
			(BYTE*)picd->lpOutput,
			MSRLE32_GetUncompressedPitch( picd->lpbiInput ),
			(const BYTE*)picd->lpInput,
			picd->lpbiInput->biWidth,
			picd->lpbiInput->biHeight );
		break;
	default:
		lr = ICERR_BADBITDEPTH;
		break;
	}

	return lr;
}

static LONG Codec_DecompressEnd( CodecImpl* This )
{
	This->bInDecompress = FALSE;
	return ICERR_OK;
}

static LONG Codec_DecompressGetFormat( CodecImpl* This, BITMAPINFO* pbiIn, BITMAPINFO* pbiOut )
{
	DWORD	biClrUsed;
	LONG	lr;

	if ( pbiIn == NULL )
		return ICERR_BADPARAM;

	lr = Codec_DecompressQuery( This, pbiIn, NULL );
	if ( lr != ICERR_OK )
		return ( pbiOut == NULL ) ? 0 : lr;

	biClrUsed = MSRLE32_GetClrUsed(pbiIn);
	if ( pbiOut == NULL )
		return sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * biClrUsed;

	ZeroMemory( pbiOut, sizeof(BITMAPINFOHEADER) );
	memcpy( &pbiOut->bmiHeader, &pbiIn->bmiHeader, sizeof(BITMAPINFOHEADER) );
	memcpy( &pbiOut->bmiColors, &pbiIn->bmiColors, sizeof(RGBQUAD) * biClrUsed );
	pbiOut->bmiHeader.biCompression = 0;
	pbiOut->bmiHeader.biSizeImage = MSRLE32_GetUncompressedSize( &pbiOut->bmiHeader );

	return ICERR_OK;
}

static LONG Codec_DecompressGetPalette( CodecImpl* This, BITMAPINFO* pbiIn, BITMAPINFO* pbiOut )
{
	if ( pbiIn == NULL )
		return ICERR_BADPARAM;

	return (pbiOut == NULL) ? 0 : ICERR_UNSUPPORTED;
}

static LONG Codec_DecompressSetPalette( CodecImpl* This, BITMAPINFO* pbiIn )
{
	return ICERR_UNSUPPORTED;
}

static LONG Codec_DecompressExQuery( CodecImpl* This, ICDECOMPRESSEX* picdex, DWORD dwSize )
{
	FIXME( "DecompressEx is not implemented!\n" );
	return ICERR_UNSUPPORTED;
}

static LONG Codec_DecompressExBegin( CodecImpl* This, ICDECOMPRESSEX* picdex, DWORD dwSize )
{
	FIXME( "DecompressEx is not implemented!\n" );
	return ICERR_UNSUPPORTED;
}

static LONG Codec_DecompressEx( CodecImpl* This, ICDECOMPRESSEX* picdex, DWORD dwSize )
{
	return ICERR_UNSUPPORTED;
}

static LONG Codec_DecompressExEnd( CodecImpl* This )
{
	This->bInDecompressEx = FALSE;
	return ICERR_UNSUPPORTED;
}

static LONG Codec_GetInfo( CodecImpl* This, ICINFO* pici, DWORD dwSize )
{
	return ICERR_UNSUPPORTED;
}

static LONG Codec_GetQuality( CodecImpl* This, DWORD* pdwQuality )
{
	return ICERR_UNSUPPORTED;
}

static LONG Codec_GetState( CodecImpl* This, LPVOID pvState, DWORD dwSize )
{
	if ( pvState == NULL )
		return 0;

	/* no driver-specific state */

	return 0;
}

static LONG Codec_SetQuality( CodecImpl* This, DWORD* pdwQuality )
{
	return ICERR_UNSUPPORTED;
}

static LONG Codec_SetStatusProc(CodecImpl* This, ICSETSTATUSPROC* picssp, DWORD dwSize )
{
	if ( picssp == NULL )
		return 0;

	/* no driver-specific state */

	return 0;
}

static LONG Codec_SetState( CodecImpl* This, LPVOID pvState, DWORD dwSize )
{
	return ICERR_UNSUPPORTED;
}

static CodecImpl* Codec_AllocDriver( void )
{
	CodecImpl*	This;

	This = HeapAlloc( GetProcessHeap(), 0, sizeof(CodecImpl) );
	if ( This == NULL )
		return NULL;
	ZeroMemory( This, sizeof(CodecImpl) );
	This->bInCompress = FALSE;
	This->bInDecompress = FALSE;
	This->bInDecompressEx = FALSE;

	return This;
}

static void Codec_Close( CodecImpl* This )
{
	if ( This->bInCompress )
		Codec_CompressEnd(This);
	if ( This->bInDecompress )
		Codec_DecompressEnd(This);
	if ( This->bInDecompressEx )
		Codec_DecompressExEnd(This);

	HeapFree( GetProcessHeap(), 0, This );
}






LONG WINAPI MSRLE32_DriverProc(
	DWORD dwDriverId, HDRVR hdrvr, UINT msg, LONG lParam1, LONG lParam2 )
{
	TRACE( "DriverProc(%08lx,%08x,%08x,%08lx,%08lx)\n",
			 dwDriverId, hdrvr, msg, lParam1, lParam2 );

	switch ( msg )
	{
	case DRV_LOAD:
		TRACE("DRV_LOAD\n");
		return TRUE;
	case DRV_FREE:
		TRACE("DRV_FREE\n");
		return TRUE;
	case DRV_OPEN:
		TRACE("DRV_OPEN\n");
		return (LONG)Codec_AllocDriver();
	case DRV_CLOSE:
		TRACE("DRV_CLOSE\n");
		Codec_Close( (CodecImpl*)dwDriverId );
		return TRUE;
	case DRV_ENABLE:
		TRACE("DRV_ENABLE\n");
		return TRUE;
	case DRV_DISABLE:
		TRACE("DRV_DISABLE\n");
		return TRUE;
	case DRV_QUERYCONFIGURE:
		TRACE("DRV_QUERYCONFIGURE\n");
		return Codec_DrvQueryConfigure( (CodecImpl*)dwDriverId );
	case DRV_CONFIGURE:
		TRACE("DRV_CONFIGURE\n");
		return Codec_DrvConfigure( (CodecImpl*)dwDriverId,
					(HWND)lParam1, (DRVCONFIGINFO*)lParam2 );
	case DRV_INSTALL:
		TRACE("DRV_INSTALL\n");
		return DRVCNF_OK;
	case DRV_REMOVE:
		TRACE("DRV_REMOVE\n");
		return 0;
	case DRV_POWER:
		TRACE("DRV_POWER\n");
		return TRUE;

	case ICM_ABOUT:
		TRACE("ICM_ABOUT\n");
		return (lParam1 == -1) ? Codec_QueryAbout() : Codec_About( (HWND)lParam1 );
	case ICM_COMPRESS:
		TRACE("ICM_COMPRESS\n");
		return Codec_Compress((CodecImpl*)dwDriverId,(ICCOMPRESS*)lParam1,(DWORD)lParam2);
	case ICM_COMPRESS_BEGIN:
		TRACE("ICM_COMPRESS_BEGIN\n");
		return Codec_CompressBegin((CodecImpl*)dwDriverId,(BITMAPINFO*)lParam1,(BITMAPINFO*)lParam2);
	case ICM_COMPRESS_END:
		TRACE("ICM_COMPRESS_END\n");
		return Codec_CompressEnd((CodecImpl*)dwDriverId);
	case ICM_COMPRESS_FRAMES_INFO:
		TRACE("ICM_COMPRESS_FRAMES_INFO\n");
		return Codec_CompressFramesInfo((CodecImpl*)dwDriverId,(ICCOMPRESSFRAMES*)lParam1,(DWORD)lParam2);
	case ICM_COMPRESS_GET_FORMAT:
		TRACE("ICM_COMPRESS_GET_FORMAT\n");
		return Codec_CompressGetFormat((CodecImpl*)dwDriverId,(BITMAPINFO*)lParam1,(BITMAPINFO*)lParam2);
	case ICM_COMPRESS_GET_SIZE:
		TRACE("ICM_COMPRESS_GET_SIZE\n");
		return Codec_CompressGetSize((CodecImpl*)dwDriverId,(BITMAPINFO*)lParam1,(BITMAPINFO*)lParam2);
	case ICM_COMPRESS_QUERY:
		TRACE("ICM_COMPRESS_GET_SIZE\n");
		return Codec_CompressQuery((CodecImpl*)dwDriverId,(BITMAPINFO*)lParam1,(BITMAPINFO*)lParam2);

	case ICM_CONFIGURE:
		TRACE("ICM_CONFIGURE\n");
		return ( lParam1 == -1 ) ? Codec_ICQueryConfigure( (CodecImpl*)dwDriverId ) : Codec_ICConfigure( (CodecImpl*)dwDriverId, (HWND)lParam1 );

	case ICM_DECOMPRESS:
		TRACE( "ICM_DECOMPRESS\n" );
		return Codec_Decompress((CodecImpl*)dwDriverId,(ICDECOMPRESS*)lParam1,(DWORD)lParam2);
	case ICM_DECOMPRESS_BEGIN:
		TRACE( "ICM_DECOMPRESS_BEGIN\n" );
		return Codec_DecompressBegin((CodecImpl*)dwDriverId,(BITMAPINFO*)lParam1,(BITMAPINFO*)lParam2);
	case ICM_DECOMPRESS_END:
		TRACE( "ICM_DECOMPRESS_END\n" );
		return Codec_DecompressEnd((CodecImpl*)dwDriverId);
	case ICM_DECOMPRESS_GET_FORMAT:
		TRACE( "ICM_DECOMPRESS_GET_FORMAT\n" );
		return Codec_DecompressGetFormat((CodecImpl*)dwDriverId,(BITMAPINFO*)lParam1,(BITMAPINFO*)lParam2);
	case ICM_DECOMPRESS_GET_PALETTE:
		TRACE( "ICM_DECOMPRESS_GET_PALETTE\n" );
		return Codec_DecompressGetPalette((CodecImpl*)dwDriverId,(BITMAPINFO*)lParam1,(BITMAPINFO*)lParam2);
	case ICM_DECOMPRESS_QUERY:
		TRACE( "ICM_DECOMPRESS_QUERY\n" );
		return Codec_DecompressQuery((CodecImpl*)dwDriverId,(BITMAPINFO*)lParam1,(BITMAPINFO*)lParam2);
	case ICM_DECOMPRESS_SET_PALETTE:
		TRACE( "ICM_DECOMPRESS_SET_PALETTE\n" );
		return Codec_DecompressSetPalette((CodecImpl*)dwDriverId,(BITMAPINFO*)lParam1);

	case ICM_DECOMPRESSEX_BEGIN:
		TRACE( "ICM_DECOMPRESSEX_BEGIN\n" );
		return Codec_DecompressExBegin((CodecImpl*)dwDriverId,(ICDECOMPRESSEX*)lParam1,(DWORD)lParam2);
	case ICM_DECOMPRESSEX:
		TRACE( "ICM_DECOMPRESSEX\n" );
		return Codec_DecompressEx((CodecImpl*)dwDriverId,(ICDECOMPRESSEX*)lParam1,(DWORD)lParam2);
	case ICM_DECOMPRESSEX_END:
		TRACE( "ICM_DECOMPRESSEX_END\n" );
		return Codec_DecompressExEnd((CodecImpl*)dwDriverId);
	case ICM_DECOMPRESSEX_QUERY:
		TRACE( "ICM_DECOMPRESSEX_QUERY\n" );
		return Codec_DecompressExQuery((CodecImpl*)dwDriverId,(ICDECOMPRESSEX*)lParam1,(DWORD)lParam2);

	case ICM_GETINFO:
		TRACE( "ICM_GETINFO\n" );
		return Codec_GetInfo((CodecImpl*)dwDriverId,(ICINFO*)lParam1,(DWORD)lParam2);
	case ICM_GETQUALITY:
		TRACE( "ICM_SETQUALITY\n");
		return Codec_GetQuality((CodecImpl*)dwDriverId,(DWORD*)lParam1);
	case ICM_GETSTATE:
		TRACE( "ICM_GETSTATE\n" );
		return Codec_GetState((CodecImpl*)dwDriverId,(LPVOID)lParam1,(DWORD)lParam2);
	case ICM_SETQUALITY:
		TRACE( "ICM_SETQUALITY\n");
		return Codec_SetQuality((CodecImpl*)dwDriverId,(DWORD*)lParam1);
	case ICM_SET_STATUS_PROC:
		TRACE( "ICM_SET_STATUS_PROC\n" );
		return Codec_SetStatusProc((CodecImpl*)dwDriverId,(ICSETSTATUSPROC*)lParam1,(DWORD)lParam2);
	case ICM_SETSTATE:
		TRACE( "ICM_SETSTATE\n" );
		return Codec_SetState((CodecImpl*)dwDriverId,(LPVOID)lParam1,(DWORD)lParam2);

	}

	return DefDriverProc( dwDriverId, hdrvr, msg, lParam1, lParam2 );
}

BOOL WINAPI MSRLE32_DllMain( HINSTANCE hInst, DWORD dwReason, LPVOID lpvReserved )
{
	TRACE( "(%08x,%08lx,%p)\n",hInst,dwReason,lpvReserved );

	return TRUE;
}
