/*
 * Copyright 2002 Michael Günnewig
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include <assert.h>
#include <stdarg.h>

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

#include "avifile_private.h"

#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(avifile);

#define MAX_FRAMESIZE       (16 * 1024 * 1024)
#define MAX_FRAMESIZE_DIFF  512

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

static HRESULT WINAPI ICMStream_fnQueryInterface(IAVIStream*iface,REFIID refiid,LPVOID *obj);
static ULONG   WINAPI ICMStream_fnAddRef(IAVIStream*iface);
static ULONG   WINAPI ICMStream_fnRelease(IAVIStream* iface);
static HRESULT WINAPI ICMStream_fnCreate(IAVIStream*iface,LPARAM lParam1,LPARAM lParam2);
static HRESULT WINAPI ICMStream_fnInfo(IAVIStream*iface,AVISTREAMINFOW *psi,LONG size);
static LONG    WINAPI ICMStream_fnFindSample(IAVIStream*iface,LONG pos,LONG flags);
static HRESULT WINAPI ICMStream_fnReadFormat(IAVIStream*iface,LONG pos,LPVOID format,LONG *formatsize);
static HRESULT WINAPI ICMStream_fnSetFormat(IAVIStream*iface,LONG pos,LPVOID format,LONG formatsize);
static HRESULT WINAPI ICMStream_fnRead(IAVIStream*iface,LONG start,LONG samples,LPVOID buffer,LONG buffersize,LONG *bytesread,LONG *samplesread);
static HRESULT WINAPI ICMStream_fnWrite(IAVIStream*iface,LONG start,LONG samples,LPVOID buffer,LONG buffersize,DWORD flags,LONG *sampwritten,LONG *byteswritten);
static HRESULT WINAPI ICMStream_fnDelete(IAVIStream*iface,LONG start,LONG samples);
static HRESULT WINAPI ICMStream_fnReadData(IAVIStream*iface,DWORD fcc,LPVOID lp,LONG *lpread);
static HRESULT WINAPI ICMStream_fnWriteData(IAVIStream*iface,DWORD fcc,LPVOID lp,LONG size);
static HRESULT WINAPI ICMStream_fnSetInfo(IAVIStream*iface,AVISTREAMINFOW*info,LONG infolen);

static const struct IAVIStreamVtbl iicmst = {
  ICMStream_fnQueryInterface,
  ICMStream_fnAddRef,
  ICMStream_fnRelease,
  ICMStream_fnCreate,
  ICMStream_fnInfo,
  ICMStream_fnFindSample,
  ICMStream_fnReadFormat,
  ICMStream_fnSetFormat,
  ICMStream_fnRead,
  ICMStream_fnWrite,
  ICMStream_fnDelete,
  ICMStream_fnReadData,
  ICMStream_fnWriteData,
  ICMStream_fnSetInfo
};

typedef struct _IAVIStreamImpl {
  /* IUnknown stuff */
  const IAVIStreamVtbl *lpVtbl;
  LONG		     ref;

  /* IAVIStream stuff */
  PAVISTREAM         pStream;
  AVISTREAMINFOW     sInfo;

  PGETFRAME          pg;
  HIC                hic;
  DWORD              dwICMFlags;

  LONG               lCurrent;
  LONG               lLastKey;
  LONG               lKeyFrameEvery;
  DWORD              dwLastQuality;
  DWORD              dwBytesPerFrame;
  DWORD              dwUnusedBytes;

  LPBITMAPINFOHEADER lpbiCur;  /* current frame */
  LPVOID             lpCur;
  LPBITMAPINFOHEADER lpbiPrev; /* previous frame */
  LPVOID             lpPrev;

  LPBITMAPINFOHEADER lpbiOutput; /* output format of codec */
  LONG               cbOutput;
  LPBITMAPINFOHEADER lpbiInput;  /* input format for codec */
  LONG               cbInput;
} IAVIStreamImpl;

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

static HRESULT AVIFILE_EncodeFrame(IAVIStreamImpl *This,
				   LPBITMAPINFOHEADER lpbi, LPVOID lpBits);
static HRESULT AVIFILE_OpenGetFrame(IAVIStreamImpl *This);

static inline void AVIFILE_Reset(IAVIStreamImpl *This)
{
  This->lCurrent      = -1;
  This->lLastKey      = 0;
  This->dwLastQuality = ICQUALITY_HIGH;
  This->dwUnusedBytes = 0;
}

HRESULT AVIFILE_CreateICMStream(REFIID riid, LPVOID *ppv)
{
  IAVIStreamImpl *pstream;
  HRESULT         hr;

  assert(riid != NULL && ppv != NULL);

  *ppv = NULL;

  pstream = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(IAVIStreamImpl));
  if (pstream == NULL)
    return AVIERR_MEMORY;

  pstream->lpVtbl  = &iicmst;
  AVIFILE_Reset(pstream);

  hr = IAVIStream_QueryInterface((IAVIStream*)pstream, riid, ppv);
  if (FAILED(hr))
    HeapFree(GetProcessHeap(), 0, pstream);

  return hr;
}

static HRESULT WINAPI ICMStream_fnQueryInterface(IAVIStream *iface,
						  REFIID refiid, LPVOID *obj)
{
  IAVIStreamImpl *This = (IAVIStreamImpl *)iface;

  TRACE("(%p,%s,%p)\n", iface, debugstr_guid(refiid), obj);

  if (IsEqualGUID(&IID_IUnknown, refiid) ||
      IsEqualGUID(&IID_IAVIStream, refiid)) {
    *obj = This;
    IAVIStream_AddRef(iface);

    return S_OK;
  }

  return OLE_E_ENUM_NOMORE;
}

static ULONG WINAPI ICMStream_fnAddRef(IAVIStream *iface)
{
  IAVIStreamImpl *This = (IAVIStreamImpl *)iface;
  ULONG ref = InterlockedIncrement(&This->ref);

  TRACE("(%p) -> %d\n", iface, ref);

  /* also add reference to the nested stream */
  if (This->pStream != NULL)
    IAVIStream_AddRef(This->pStream);

  return ref;
}

static ULONG WINAPI ICMStream_fnRelease(IAVIStream* iface)
{
  IAVIStreamImpl *This = (IAVIStreamImpl *)iface;
  ULONG ref = InterlockedDecrement(&This->ref);

  TRACE("(%p) -> %d\n", iface, ref);

  if (ref == 0) {
    /* destruct */
    if (This->pg != NULL) {
      AVIStreamGetFrameClose(This->pg);
      This->pg = NULL;
    }
    if (This->pStream != NULL) {
      IAVIStream_Release(This->pStream);
      This->pStream = NULL;
    }
    if (This->hic != NULL) {
      if (This->lpbiPrev != NULL) {
	ICDecompressEnd(This->hic);
	HeapFree(GetProcessHeap(), 0, This->lpbiPrev);
	This->lpbiPrev = NULL;
	This->lpPrev   = NULL;
      }
      ICCompressEnd(This->hic);
      This->hic = NULL;
    }
    if (This->lpbiCur != NULL) {
      HeapFree(GetProcessHeap(), 0, This->lpbiCur);
      This->lpbiCur = NULL;
      This->lpCur   = NULL;
    }
    if (This->lpbiOutput != NULL) {
      HeapFree(GetProcessHeap(), 0, This->lpbiOutput);
      This->lpbiOutput = NULL;
      This->cbOutput   = 0;
    }
    if (This->lpbiInput != NULL) {
      HeapFree(GetProcessHeap(), 0, This->lpbiInput);
      This->lpbiInput = NULL;
      This->cbInput   = 0;
    }

    HeapFree(GetProcessHeap(), 0, This);

    return 0;
  }

  /* also release reference to the nested stream */
  if (This->pStream != NULL)
    IAVIStream_Release(This->pStream);

  return ref;
}

/* lParam1: PAVISTREAM
 * lParam2: LPAVICOMPRESSOPTIONS
 */
static HRESULT WINAPI ICMStream_fnCreate(IAVIStream *iface, LPARAM lParam1,
					  LPARAM lParam2)
{
  IAVIStreamImpl *This = (IAVIStreamImpl *)iface;

  ICINFO               icinfo;
  ICCOMPRESSFRAMES     icFrames;
  LPAVICOMPRESSOPTIONS pco = (LPAVICOMPRESSOPTIONS)lParam2;

  TRACE("(%p,0x%08lX,0x%08lX)\n", iface, lParam1, lParam2);

  /* check parameter */
  if ((LPVOID)lParam1 == NULL)
    return AVIERR_BADPARAM;

  /* get infos from stream */
  IAVIStream_Info((PAVISTREAM)lParam1, &This->sInfo, sizeof(This->sInfo));
  if (This->sInfo.fccType != streamtypeVIDEO)
    return AVIERR_ERROR; /* error in registry or AVIMakeCompressedStream */

  /* add reference to the stream */
  This->pStream = (PAVISTREAM)lParam1;
  IAVIStream_AddRef(This->pStream);

  AVIFILE_Reset(This);

  if (pco != NULL && pco->fccHandler != comptypeDIB) {
    /* we should compress */
    This->sInfo.fccHandler = pco->fccHandler;

    This->hic = ICOpen(ICTYPE_VIDEO, pco->fccHandler, ICMODE_COMPRESS);
    if (This->hic == NULL)
      return AVIERR_NOCOMPRESSOR;

    /* restore saved state of codec */
    if (pco->cbParms > 0 && pco->lpParms != NULL) {
      ICSetState(This->hic, pco->lpParms, pco->cbParms);
    }

    /* set quality -- resolve default quality */
    This->sInfo.dwQuality = pco->dwQuality;
    if (pco->dwQuality == ICQUALITY_DEFAULT)
      This->sInfo.dwQuality = ICGetDefaultQuality(This->hic);

    /* get capabilities of codec */
    ICGetInfo(This->hic, &icinfo, sizeof(icinfo));
    This->dwICMFlags = icinfo.dwFlags;

    /* use keyframes? */
    if ((pco->dwFlags & AVICOMPRESSF_KEYFRAMES) &&
	(icinfo.dwFlags & (VIDCF_TEMPORAL|VIDCF_FASTTEMPORALC))) {
      This->lKeyFrameEvery = pco->dwKeyFrameEvery;
    } else
      This->lKeyFrameEvery = 1;

    /* use datarate? */
    if ((pco->dwFlags & AVICOMPRESSF_DATARATE)) {
      /* Do we have a chance to reduce size to desired one? */
      if ((icinfo.dwFlags & (VIDCF_CRUNCH|VIDCF_QUALITY)) == 0)
	return AVIERR_NOCOMPRESSOR;

      assert(This->sInfo.dwRate != 0);

      This->dwBytesPerFrame = MulDiv(pco->dwBytesPerSecond,
				     This->sInfo.dwScale, This->sInfo.dwRate);
    } else {
      pco->dwBytesPerSecond = 0;
      This->dwBytesPerFrame = 0;
    }

    if (icinfo.dwFlags & VIDCF_COMPRESSFRAMES) {
      memset(&icFrames, 0, sizeof(icFrames));
      icFrames.lpbiOutput  = This->lpbiOutput;
      icFrames.lpbiInput   = This->lpbiInput;
      icFrames.lFrameCount = This->sInfo.dwLength;
      icFrames.lQuality    = This->sInfo.dwQuality;
      icFrames.lDataRate   = pco->dwBytesPerSecond;
      icFrames.lKeyRate    = This->lKeyFrameEvery;
      icFrames.dwRate      = This->sInfo.dwRate;
      icFrames.dwScale     = This->sInfo.dwScale;
      ICSendMessage(This->hic, ICM_COMPRESS_FRAMES_INFO,
		    (LPARAM)&icFrames, (LPARAM)sizeof(icFrames));
    }
  } else
    This->sInfo.fccHandler = comptypeDIB;

  return AVIERR_OK;
}

static HRESULT WINAPI ICMStream_fnInfo(IAVIStream *iface,LPAVISTREAMINFOW psi,
					LONG size)
{
  IAVIStreamImpl *This = (IAVIStreamImpl *)iface;

  TRACE("(%p,%p,%d)\n", iface, psi, size);

  if (psi == NULL)
    return AVIERR_BADPARAM;
  if (size < 0)
    return AVIERR_BADSIZE;

  memcpy(psi, &This->sInfo, min((DWORD)size, sizeof(This->sInfo)));

  if ((DWORD)size < sizeof(This->sInfo))
    return AVIERR_BUFFERTOOSMALL;
  return AVIERR_OK;
}

static LONG WINAPI ICMStream_fnFindSample(IAVIStream *iface, LONG pos,
					   LONG flags)
{
  IAVIStreamImpl *This = (IAVIStreamImpl *)iface;

  TRACE("(%p,%d,0x%08X)\n",iface,pos,flags);

  if (flags & FIND_FROM_START) {
    pos = This->sInfo.dwStart;
    flags &= ~(FIND_FROM_START|FIND_PREV);
    flags |= FIND_NEXT;
  }

  if (flags & FIND_RET)
    WARN(": FIND_RET flags will be ignored!\n");

  if (flags & FIND_KEY) {
    if (This->hic == NULL)
      return pos; /* we decompress so every frame is a keyframe */

    if (flags & FIND_PREV) {
      /* need to read old or new frames? */
      if (This->lLastKey <= pos || pos < This->lCurrent)
	IAVIStream_Read(iface, pos, 1, NULL, 0, NULL, NULL);

      return This->lLastKey;
    }
  } else if (flags & FIND_ANY) {
    return pos; /* We really don't know, reread is to expensive, so guess. */
  } else if (flags & FIND_FORMAT) {
    if (flags & FIND_PREV)
      return 0;
  }

  return -1;
}

static HRESULT WINAPI ICMStream_fnReadFormat(IAVIStream *iface, LONG pos,
					      LPVOID format, LONG *formatsize)
{
  IAVIStreamImpl *This = (IAVIStreamImpl *)iface;

  LPBITMAPINFOHEADER lpbi;
  HRESULT            hr;

  TRACE("(%p,%d,%p,%p)\n", iface, pos, format, formatsize);

  if (formatsize == NULL)
    return AVIERR_BADPARAM;

  if (This->pg == NULL) {
    hr = AVIFILE_OpenGetFrame(This);

    if (FAILED(hr))
      return hr;
  }

  lpbi = AVIStreamGetFrame(This->pg, pos);
  if (lpbi == NULL)
    return AVIERR_MEMORY;

  if (This->hic == NULL) {
    LONG size = lpbi->biSize + lpbi->biClrUsed * sizeof(RGBQUAD);

    if (size > 0) {
      if (This->sInfo.dwSuggestedBufferSize < lpbi->biSizeImage)
	This->sInfo.dwSuggestedBufferSize = lpbi->biSizeImage;

      This->cbOutput = size;
      if (format != NULL) {
	if (This->lpbiOutput != NULL)
	  memcpy(format, This->lpbiOutput, min(*formatsize, This->cbOutput));
	else
	  memcpy(format, lpbi, min(*formatsize, size));
      }
    }
  } else if (format != NULL)
    memcpy(format, This->lpbiOutput, min(*formatsize, This->cbOutput));

  if (*formatsize < This->cbOutput)
    hr = AVIERR_BUFFERTOOSMALL;
  else
    hr = AVIERR_OK;

  *formatsize = This->cbOutput;
  return hr;
}

static HRESULT WINAPI ICMStream_fnSetFormat(IAVIStream *iface, LONG pos,
					     LPVOID format, LONG formatsize)
{
  IAVIStreamImpl *This = (IAVIStreamImpl *)iface;

  TRACE("(%p,%d,%p,%d)\n", iface, pos, format, formatsize);

  /* check parameters */
  if (format == NULL || formatsize <= 0)
    return AVIERR_BADPARAM;

  /* We can only accept RGB data for writing */
  if (((LPBITMAPINFOHEADER)format)->biCompression != BI_RGB) {
    WARN(": need RGB data as input\n");
    return AVIERR_UNSUPPORTED;
  }

  /* Input format already known?
   * Changing of palette is supported, but be quiet if it's the same */
  if (This->lpbiInput != NULL) {
    if (This->cbInput != formatsize)
      return AVIERR_UNSUPPORTED;

    if (memcmp(format, This->lpbiInput, formatsize) == 0)
      return AVIERR_OK;
  }

  /* Does the nested stream support writing? */
  if ((This->sInfo.dwCaps & AVIFILECAPS_CANWRITE) == 0)
    return AVIERR_READONLY;

  /* check if frame is already written */
  if (This->sInfo.dwLength + This->sInfo.dwStart > pos)
    return AVIERR_UNSUPPORTED;

  /* check if we should compress */
  if (This->sInfo.fccHandler == 0 ||
      This->sInfo.fccHandler == mmioFOURCC('N','O','N','E'))
    This->sInfo.fccHandler = comptypeDIB;

  /* only pass through? */
  if (This->sInfo.fccHandler == comptypeDIB)
    return IAVIStream_SetFormat(This->pStream, pos, format, formatsize);

  /* initial format setting? */
  if (This->lpbiInput == NULL) {
    ULONG size;

    assert(This->hic != NULL);

    /* get memory for input format */
    This->lpbiInput = HeapAlloc(GetProcessHeap(), 0, formatsize);
    if (This->lpbiInput == NULL)
      return AVIERR_MEMORY;
    This->cbInput = formatsize;
    memcpy(This->lpbiInput, format, formatsize);

    /* get output format */
    size = ICCompressGetFormatSize(This->hic, This->lpbiInput);
    if (size < sizeof(BITMAPINFOHEADER))
      return AVIERR_COMPRESSOR;
    This->lpbiOutput = HeapAlloc(GetProcessHeap(), 0, size);
    if (This->lpbiOutput == NULL)
      return AVIERR_MEMORY;
    This->cbOutput = size;
    if (ICCompressGetFormat(This->hic,This->lpbiInput,This->lpbiOutput) < S_OK)
      return AVIERR_COMPRESSOR;

    /* update AVISTREAMINFO structure */
    This->sInfo.rcFrame.right  =
      This->sInfo.rcFrame.left + This->lpbiOutput->biWidth;
    This->sInfo.rcFrame.bottom =
      This->sInfo.rcFrame.top  + This->lpbiOutput->biHeight;

    /* prepare codec for compression */
    if (ICCompressBegin(This->hic, This->lpbiInput, This->lpbiOutput) != S_OK)
      return AVIERR_COMPRESSOR;

    /* allocate memory for compressed frame */
    size = ICCompressGetSize(This->hic, This->lpbiInput, This->lpbiOutput);
    This->lpbiCur = HeapAlloc(GetProcessHeap(), 0, This->cbOutput + size);
    if (This->lpbiCur == NULL)
      return AVIERR_MEMORY;
    memcpy(This->lpbiCur, This->lpbiOutput, This->cbOutput);
    This->lpCur = DIBPTR(This->lpbiCur);

    /* allocate memory for last frame if needed */
    if (This->lKeyFrameEvery != 1 &&
	(This->dwICMFlags & VIDCF_FASTTEMPORALC) == 0) {
      size = ICDecompressGetFormatSize(This->hic, This->lpbiOutput);
      This->lpbiPrev = HeapAlloc(GetProcessHeap(), 0, size);
      if (This->lpbiPrev == NULL)
	return AVIERR_MEMORY;
      if (ICDecompressGetFormat(This->hic, This->lpbiOutput, This->lpbiPrev) < S_OK)
	return AVIERR_COMPRESSOR;

      if (This->lpbiPrev->biSizeImage == 0) {
	This->lpbiPrev->biSizeImage =
	  DIBWIDTHBYTES(*This->lpbiPrev) * This->lpbiPrev->biHeight;
      }

      /* get memory for format and picture */
      size += This->lpbiPrev->biSizeImage;
      This->lpbiPrev = HeapReAlloc(GetProcessHeap(), 0, This->lpbiPrev, size);
      if (This->lpbiPrev == NULL)
	return AVIERR_MEMORY;
      This->lpPrev = DIBPTR(This->lpbiPrev);

      /* prepare codec also for decompression */
      if (ICDecompressBegin(This->hic,This->lpbiOutput,This->lpbiPrev) != S_OK)
	return AVIERR_COMPRESSOR;
    }
  } else {
    /* format change -- check that's only the palette */
    LPBITMAPINFOHEADER lpbi = format;

    if (lpbi->biSize != This->lpbiInput->biSize ||
	lpbi->biWidth != This->lpbiInput->biWidth ||
	lpbi->biHeight != This->lpbiInput->biHeight ||
	lpbi->biBitCount != This->lpbiInput->biBitCount ||
	lpbi->biPlanes != This->lpbiInput->biPlanes ||
	lpbi->biCompression != This->lpbiInput->biCompression ||
	lpbi->biClrUsed != This->lpbiInput->biClrUsed)
      return AVIERR_UNSUPPORTED;

    /* get new output format */
    if (ICCompressGetFormat(This->hic, lpbi, This->lpbiOutput) < S_OK)
      return AVIERR_BADFORMAT;

    /* restart compression */
    ICCompressEnd(This->hic);
    if (ICCompressBegin(This->hic, lpbi, This->lpbiOutput) != S_OK)
      return AVIERR_COMPRESSOR;

    /* check if we need to restart decompression also */
    if (This->lKeyFrameEvery != 1 &&
	(This->dwICMFlags & VIDCF_FASTTEMPORALC) == 0) {
      ICDecompressEnd(This->hic);
      if (ICDecompressGetFormat(This->hic,This->lpbiOutput,This->lpbiPrev) < S_OK)
	return AVIERR_COMPRESSOR;
      if (ICDecompressBegin(This->hic,This->lpbiOutput,This->lpbiPrev) != S_OK)
	return AVIERR_COMPRESSOR;
    }
  }

  /* tell nested stream the new format */
  return IAVIStream_SetFormat(This->pStream, pos,
			      This->lpbiOutput, This->cbOutput);
}

static HRESULT WINAPI ICMStream_fnRead(IAVIStream *iface, LONG start,
					LONG samples, LPVOID buffer,
					LONG buffersize, LPLONG bytesread,
					LPLONG samplesread)
{
  IAVIStreamImpl *This = (IAVIStreamImpl *)iface;

  LPBITMAPINFOHEADER lpbi;

  TRACE("(%p,%d,%d,%p,%d,%p,%p)\n", iface, start, samples, buffer,
 	buffersize, bytesread, samplesread);

  /* clear return parameters if given */
  if (bytesread != NULL)
    *bytesread = 0;
  if (samplesread != NULL)
    *samplesread = 0;

  if (samples == 0)
    return AVIERR_OK;

  /* check parameters */
  if (samples != 1 && (bytesread == NULL && samplesread == NULL))
    return AVIERR_BADPARAM;
  if (samples == -1) /* read as much as we could */
    samples = 1;

  if (This->pg == NULL) {
    HRESULT hr = AVIFILE_OpenGetFrame(This);

    if (FAILED(hr))
      return hr;
  }

  /* compress or decompress? */
  if (This->hic == NULL) {
    /* decompress */
    lpbi = AVIStreamGetFrame(This->pg, start);
    if (lpbi == NULL)
      return AVIERR_MEMORY;

    if (buffer != NULL && buffersize > 0) {
      /* check buffersize */
      if (buffersize < lpbi->biSizeImage)
	return AVIERR_BUFFERTOOSMALL;

      memcpy(buffer, DIBPTR(lpbi), lpbi->biSizeImage);
    }

    /* fill out return parameters if given */
    if (bytesread != NULL)
      *bytesread = lpbi->biSizeImage;
  } else {
    /* compress */
    if (This->lCurrent > start)
      AVIFILE_Reset(This);

    while (start > This->lCurrent) {
      HRESULT hr;

      lpbi = AVIStreamGetFrame(This->pg, ++This->lCurrent);
      if (lpbi == NULL) {
	AVIFILE_Reset(This);
	return AVIERR_MEMORY;
      }

      hr = AVIFILE_EncodeFrame(This, lpbi, DIBPTR(lpbi));
      if (FAILED(hr)) {
	AVIFILE_Reset(This);
	return hr;
      }
    }

    if (buffer != NULL && buffersize > 0) {
      /* check buffersize */
      if (This->lpbiCur->biSizeImage > buffersize)
	return AVIERR_BUFFERTOOSMALL;

      memcpy(buffer, This->lpCur, This->lpbiCur->biSizeImage);
    }

    /* fill out return parameters if given */
    if (bytesread != NULL)
      *bytesread = This->lpbiCur->biSizeImage;
  }

  /* fill out return parameters if given */
  if (samplesread != NULL)
    *samplesread = 1;

  return AVIERR_OK;
}

static HRESULT WINAPI ICMStream_fnWrite(IAVIStream *iface, LONG start,
					 LONG samples, LPVOID buffer,
					 LONG buffersize, DWORD flags,
					 LPLONG sampwritten,
					 LPLONG byteswritten)
{
  IAVIStreamImpl *This = (IAVIStreamImpl *)iface;

  HRESULT hr;

  TRACE("(%p,%d,%d,%p,%d,0x%08X,%p,%p)\n", iface, start, samples,
	buffer, buffersize, flags, sampwritten, byteswritten);

  /* clear return parameters if given */
  if (sampwritten != NULL)
    *sampwritten = 0;
  if (byteswritten != NULL)
    *byteswritten = 0;

  /* check parameters */
  if (buffer == NULL && (buffersize > 0 || samples > 0))
    return AVIERR_BADPARAM;

  if (This->sInfo.fccHandler == comptypeDIB) {
    /* only pass through */
    flags |= AVIIF_KEYFRAME;

    return IAVIStream_Write(This->pStream, start, samples, buffer, buffersize,
			    flags, sampwritten, byteswritten);
  } else {
    /* compress data before writing to pStream */
    if (samples != 1 && (sampwritten == NULL && byteswritten == NULL))
      return AVIERR_UNSUPPORTED;

    This->lCurrent = start;
    hr = AVIFILE_EncodeFrame(This, This->lpbiInput, buffer);
    if (FAILED(hr))
      return hr;

    if (This->lLastKey == start)
      flags |= AVIIF_KEYFRAME;

    return IAVIStream_Write(This->pStream, start, samples, This->lpCur,
			    This->lpbiCur->biSizeImage, flags, byteswritten,
			    sampwritten);
  }
}

static HRESULT WINAPI ICMStream_fnDelete(IAVIStream *iface, LONG start,
					  LONG samples)
{
  IAVIStreamImpl *This = (IAVIStreamImpl *)iface;

  TRACE("(%p,%d,%d)\n", iface, start, samples);

  return IAVIStream_Delete(This->pStream, start, samples);
}

static HRESULT WINAPI ICMStream_fnReadData(IAVIStream *iface, DWORD fcc,
					    LPVOID lp, LPLONG lpread)
{
  IAVIStreamImpl *This = (IAVIStreamImpl *)iface;

  TRACE("(%p,0x%08X,%p,%p)\n", iface, fcc, lp, lpread);

  assert(This->pStream != NULL);

  return IAVIStream_ReadData(This->pStream, fcc, lp, lpread);
}

static HRESULT WINAPI ICMStream_fnWriteData(IAVIStream *iface, DWORD fcc,
					     LPVOID lp, LONG size)
{
  IAVIStreamImpl *This = (IAVIStreamImpl *)iface;

  TRACE("(%p,0x%08x,%p,%d)\n", iface, fcc, lp, size);

  assert(This->pStream != NULL);

  return IAVIStream_WriteData(This->pStream, fcc, lp, size);
}

static HRESULT WINAPI ICMStream_fnSetInfo(IAVIStream *iface,
					   LPAVISTREAMINFOW info, LONG infolen)
{
  FIXME("(%p,%p,%d): stub\n", iface, info, infolen);

  return E_FAIL;
}

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

static HRESULT AVIFILE_EncodeFrame(IAVIStreamImpl *This,
				   LPBITMAPINFOHEADER lpbi, LPVOID lpBits)
{
  DWORD dwMinQual, dwMaxQual, dwCurQual;
  DWORD dwRequest;
  DWORD icmFlags = 0;
  DWORD idxFlags = 0;
  BOOL  bDecreasedQual = FALSE;
  BOOL  doSizeCheck;
  BOOL  noPrev;

  /* make lKeyFrameEvery and at start a keyframe */
  if ((This->lKeyFrameEvery != 0 &&
       (This->lCurrent - This->lLastKey) >= This->lKeyFrameEvery) ||
      This->lCurrent == This->sInfo.dwStart) {
    idxFlags = AVIIF_KEYFRAME;
    icmFlags = ICCOMPRESS_KEYFRAME;
  }

  if (This->lKeyFrameEvery != 0) {
    if (This->lCurrent == This->sInfo.dwStart) {
      if (idxFlags & AVIIF_KEYFRAME) {
	/* for keyframes allow to consume all unused bytes */
	dwRequest = This->dwBytesPerFrame + This->dwUnusedBytes;
	This->dwUnusedBytes = 0;
      } else {
	/* for non-keyframes only allow something of the unused bytes to be consumed */
	DWORD tmp1 = 0;
	DWORD tmp2;

	if (This->dwBytesPerFrame >= This->dwUnusedBytes)
	  tmp1 = This->dwBytesPerFrame / This->lKeyFrameEvery;
	tmp2 = (This->dwUnusedBytes + tmp1) / This->lKeyFrameEvery;

	dwRequest = This->dwBytesPerFrame - tmp1 + tmp2;
	This->dwUnusedBytes -= tmp2;
      }
    } else
      dwRequest = MAX_FRAMESIZE;
  } else {
    /* only one keyframe at start desired */
    if (This->lCurrent == This->sInfo.dwStart) {
      dwRequest = This->dwBytesPerFrame + This->dwUnusedBytes;
      This->dwUnusedBytes = 0;
    } else
      dwRequest = MAX_FRAMESIZE;
  }

  /* must we check for framesize to gain requested
   * datarate or could we trust codec? */
  doSizeCheck = (dwRequest != 0 && ((This->dwICMFlags & (VIDCF_CRUNCH|VIDCF_QUALITY)) == 0));

  dwMaxQual = dwCurQual = This->sInfo.dwQuality;
  dwMinQual = ICQUALITY_LOW;

  noPrev = TRUE;
  if ((icmFlags & ICCOMPRESS_KEYFRAME) == 0 && 
      (This->dwICMFlags & VIDCF_FASTTEMPORALC) == 0)
    noPrev = FALSE;

  do {
    DWORD   idxCkid = 0;
    DWORD   res;

    res = ICCompress(This->hic,icmFlags,This->lpbiCur,This->lpCur,lpbi,lpBits,
		     &idxCkid, &idxFlags, This->lCurrent, dwRequest, dwCurQual,
		     noPrev ? NULL:This->lpbiPrev, noPrev ? NULL:This->lpPrev);
    if (res == ICERR_NEWPALETTE) {
      FIXME(": codec has changed palette -- unhandled!\n");
    } else if (res != ICERR_OK)
      return AVIERR_COMPRESSOR;

    /* need to check for framesize */
    if (! doSizeCheck)
      break;

    if (dwRequest >= This->lpbiCur->biSizeImage) {
      /* frame is smaller -- try to maximize quality */
      if (dwMaxQual - dwCurQual > 10) {
	DWORD tmp = dwRequest / 8;

	if (tmp < MAX_FRAMESIZE_DIFF)
	  tmp = MAX_FRAMESIZE_DIFF;

	if (tmp < dwRequest - This->lpbiCur->biSizeImage && bDecreasedQual) {
	  tmp = dwCurQual;
	  dwCurQual = (dwMinQual + dwMaxQual) / 2;
	  dwMinQual = tmp;
	  continue;
	}
      } else
	break;
    } else if (dwMaxQual - dwMinQual <= 1) {
      break;
    } else {
      dwMaxQual = dwCurQual;

      if (bDecreasedQual || dwCurQual == This->dwLastQuality)
	dwCurQual = (dwMinQual + dwMaxQual) / 2;
      else
	FIXME(": no new quality computed min=%u cur=%u max=%u last=%u\n",
	      dwMinQual, dwCurQual, dwMaxQual, This->dwLastQuality);

      bDecreasedQual = TRUE;
    }
  } while (TRUE);

  /* remember some values */
  This->dwLastQuality = dwCurQual;
  This->dwUnusedBytes = dwRequest - This->lpbiCur->biSizeImage;
  if (icmFlags & ICCOMPRESS_KEYFRAME)
    This->lLastKey = This->lCurrent;

  /* Does we manage previous frame? */
  if (This->lpPrev != NULL && This->lKeyFrameEvery != 1)
    ICDecompress(This->hic, 0, This->lpbiCur, This->lpCur,
		 This->lpbiPrev, This->lpPrev);

  return AVIERR_OK;
}

static HRESULT AVIFILE_OpenGetFrame(IAVIStreamImpl *This)
{
  LPBITMAPINFOHEADER lpbi;
  DWORD              size;

  /* pre-conditions */
  assert(This != NULL);
  assert(This->pStream != NULL);
  assert(This->pg == NULL);

  This->pg = AVIStreamGetFrameOpen(This->pStream, NULL);
  if (This->pg == NULL)
    return AVIERR_ERROR;

  /* When we only decompress this is enough */
  if (This->sInfo.fccHandler == comptypeDIB)
    return AVIERR_OK;

  assert(This->hic != NULL);
  assert(This->lpbiOutput == NULL);

  /* get input format */
  lpbi = AVIStreamGetFrame(This->pg, This->sInfo.dwStart);
  if (lpbi == NULL)
    return AVIERR_MEMORY;

  /* get memory for output format */
  size = ICCompressGetFormatSize(This->hic, lpbi);
  if ((LONG)size < (LONG)sizeof(BITMAPINFOHEADER))
    return AVIERR_COMPRESSOR;
  This->lpbiOutput = HeapAlloc(GetProcessHeap(), 0, size);
  if (This->lpbiOutput == NULL)
    return AVIERR_MEMORY;
  This->cbOutput = size;

  if (ICCompressGetFormat(This->hic, lpbi, This->lpbiOutput) < S_OK)
    return AVIERR_BADFORMAT;

  /* update AVISTREAMINFO structure */
  This->sInfo.rcFrame.right  =
    This->sInfo.rcFrame.left + This->lpbiOutput->biWidth;
  This->sInfo.rcFrame.bottom =
    This->sInfo.rcFrame.top  + This->lpbiOutput->biHeight;
  This->sInfo.dwSuggestedBufferSize =
    ICCompressGetSize(This->hic, lpbi, This->lpbiOutput);

  /* prepare codec for compression */
  if (ICCompressBegin(This->hic, lpbi, This->lpbiOutput) != S_OK)
    return AVIERR_COMPRESSOR;

  /* allocate memory for current frame */
  size += This->sInfo.dwSuggestedBufferSize;
  This->lpbiCur = HeapAlloc(GetProcessHeap(), 0, size);
  if (This->lpbiCur == NULL)
    return AVIERR_MEMORY;
  memcpy(This->lpbiCur, This->lpbiOutput, This->cbOutput);
  This->lpCur = DIBPTR(This->lpbiCur);

  /* allocate memory for last frame if needed */
  if (This->lKeyFrameEvery != 1 &&
      (This->dwICMFlags & VIDCF_FASTTEMPORALC) == 0) {
    size = ICDecompressGetFormatSize(This->hic, This->lpbiOutput);
    This->lpbiPrev = HeapAlloc(GetProcessHeap(), 0, size);
    if (This->lpbiPrev == NULL)
      return AVIERR_MEMORY;
    if (ICDecompressGetFormat(This->hic, This->lpbiOutput, This->lpbiPrev) < S_OK)
      return AVIERR_COMPRESSOR;

    if (This->lpbiPrev->biSizeImage == 0) {
      This->lpbiPrev->biSizeImage =
	DIBWIDTHBYTES(*This->lpbiPrev) * This->lpbiPrev->biHeight;
    }

    /* get memory for format and picture */
    size += This->lpbiPrev->biSizeImage;
    This->lpbiPrev = HeapReAlloc(GetProcessHeap(), 0, This->lpbiPrev, size );
    if (This->lpbiPrev == NULL)
      return AVIERR_MEMORY;
    This->lpPrev = DIBPTR(This->lpbiPrev);

    /* prepare codec also for decompression */
    if (ICDecompressBegin(This->hic,This->lpbiOutput,This->lpbiPrev) != S_OK)
      return AVIERR_COMPRESSOR;
  }

  return AVIERR_OK;
}
