/*
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

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

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

#include "avifile_private.h"

#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(avifile);

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

static HRESULT WINAPI ACMStream_fnQueryInterface(IAVIStream*iface,REFIID refiid,LPVOID *obj);
static ULONG   WINAPI ACMStream_fnAddRef(IAVIStream*iface);
static ULONG   WINAPI ACMStream_fnRelease(IAVIStream* iface);
static HRESULT WINAPI ACMStream_fnCreate(IAVIStream*iface,LPARAM lParam1,LPARAM lParam2);
static HRESULT WINAPI ACMStream_fnInfo(IAVIStream*iface,AVISTREAMINFOW *psi,LONG size);
static LONG    WINAPI ACMStream_fnFindSample(IAVIStream*iface,LONG pos,LONG flags);
static HRESULT WINAPI ACMStream_fnReadFormat(IAVIStream*iface,LONG pos,LPVOID format,LONG *formatsize);
static HRESULT WINAPI ACMStream_fnSetFormat(IAVIStream*iface,LONG pos,LPVOID format,LONG formatsize);
static HRESULT WINAPI ACMStream_fnRead(IAVIStream*iface,LONG start,LONG samples,LPVOID buffer,LONG buffersize,LONG *bytesread,LONG *samplesread);
static HRESULT WINAPI ACMStream_fnWrite(IAVIStream*iface,LONG start,LONG samples,LPVOID buffer,LONG buffersize,DWORD flags,LONG *sampwritten,LONG *byteswritten);
static HRESULT WINAPI ACMStream_fnDelete(IAVIStream*iface,LONG start,LONG samples);
static HRESULT WINAPI ACMStream_fnReadData(IAVIStream*iface,DWORD fcc,LPVOID lp,LONG *lpread);
static HRESULT WINAPI ACMStream_fnWriteData(IAVIStream*iface,DWORD fcc,LPVOID lp,LONG size);
static HRESULT WINAPI ACMStream_fnSetInfo(IAVIStream*iface,AVISTREAMINFOW*info,LONG infolen);

struct IAVIStreamVtbl iacmst = {
  ACMStream_fnQueryInterface,
  ACMStream_fnAddRef,
  ACMStream_fnRelease,
  ACMStream_fnCreate,
  ACMStream_fnInfo,
  ACMStream_fnFindSample,
  ACMStream_fnReadFormat,
  ACMStream_fnSetFormat,
  ACMStream_fnRead,
  ACMStream_fnWrite,
  ACMStream_fnDelete,
  ACMStream_fnReadData,
  ACMStream_fnWriteData,
  ACMStream_fnSetInfo
};

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

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

  HACMSTREAM      has;

  LPWAVEFORMATEX  lpInFormat;
  LONG            cbInFormat;

  LPWAVEFORMATEX  lpOutFormat;
  LONG            cbOutFormat;

  ACMSTREAMHEADER acmStreamHdr;
} IAVIStreamImpl;

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

#define CONVERT_STREAM_to_THIS(a) { \
           acmStreamSize(This->has,(a)*This->lpInFormat->nBlockAlign,\
                         &(a), ACM_STREAMSIZEF_SOURCE); \
           (a) /= This->lpOutFormat->nBlockAlign; }
#define CONVERT_THIS_to_STREAM(a) { \
           acmStreamSize(This->has,(a)*This->lpOutFormat->nBlockAlign,\
                         &(a), ACM_STREAMSIZEF_DESTINATION); \
           (a) /= This->lpInFormat->nBlockAlign; }

static HRESULT AVIFILE_OpenCompressor(IAVIStreamImpl *This);

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

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

  *ppv = NULL;

  pstream = (IAVIStreamImpl*)LocalAlloc(LPTR, sizeof(IAVIStreamImpl));
  if (pstream == NULL)
    return AVIERR_MEMORY;

  pstream->lpVtbl = &iacmst;

  hr = IAVIStream_QueryInterface((IAVIStream*)pstream, riid, ppv);
  if (FAILED(hr))
    LocalFree((HLOCAL)pstream);

  return hr;
}

static HRESULT WINAPI ACMStream_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 ACMStream_fnAddRef(IAVIStream *iface)
{
  IAVIStreamImpl *This = (IAVIStreamImpl *)iface;

  TRACE("(%p) -> %ld\n", iface, This->ref + 1);

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

  return ++(This->ref);
}

static ULONG WINAPI ACMStream_fnRelease(IAVIStream* iface)
{
  IAVIStreamImpl *This = (IAVIStreamImpl *)iface;

  TRACE("(%p) -> %ld\n", iface, This->ref - 1);

  if (This->ref == 0) {
    /* destruct */
    if (This->has != NULL) {
      if (This->acmStreamHdr.fdwStatus & ACMSTREAMHEADER_STATUSF_PREPARED)
	acmStreamUnprepareHeader(This->has, &This->acmStreamHdr, 0);
      acmStreamClose(This->has, 0);
      This->has = NULL;
    }
    if (This->acmStreamHdr.pbSrc != NULL) {
      GlobalFreePtr(This->acmStreamHdr.pbSrc);
      This->acmStreamHdr.pbSrc = NULL;
    }
    if (This->acmStreamHdr.pbDst != NULL) {
      GlobalFreePtr(This->acmStreamHdr.pbDst);
      This->acmStreamHdr.pbDst = NULL;
    }
    if (This->lpInFormat != NULL) {
      GlobalFreePtr(This->lpInFormat);
      This->lpInFormat = NULL;
      This->cbInFormat = 0;
    }
    if (This->lpOutFormat != NULL) {
      GlobalFreePtr(This->lpOutFormat);
      This->lpOutFormat = NULL;
      This->cbOutFormat = 0;
    }
    if (This->pStream != NULL) {
      IAVIStream_Release(This->pStream);
      This->pStream = NULL;
    }
    LocalFree((HLOCAL)This);

    return 0;
  }

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

  return --This->ref;
}

/* lParam1: PAVISTREAM
 * lParam2: LPAVICOMPRESSOPTIONS -- even if doc's say LPWAVEFORMAT
 */
static HRESULT WINAPI ACMStream_fnCreate(IAVIStream *iface, LPARAM lParam1,
					  LPARAM lParam2)
{
  IAVIStreamImpl *This = (IAVIStreamImpl *)iface;

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

  /* check for swapped parameters */
  if ((LPVOID)lParam1 != NULL &&
      ((LPAVICOMPRESSOPTIONS)lParam1)->fccType == streamtypeAUDIO) {
    register LPARAM tmp = lParam1;

    lParam1 = lParam2;
    lParam2 = tmp;
  }

  if ((LPVOID)lParam1 == NULL)
    return AVIERR_BADPARAM;

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

  This->sInfo.fccHandler = 0; /* be paranoid */

  /* FIXME: check ACM version? Which version does we need? */

  if ((LPVOID)lParam2 != NULL) {
    /* We only need the format from the compress-options */
    if (((LPAVICOMPRESSOPTIONS)lParam2)->fccType == streamtypeAUDIO)
      lParam2 = (LPARAM)((LPAVICOMPRESSOPTIONS)lParam2)->lpFormat;

    if (((LPWAVEFORMATEX)lParam2)->wFormatTag != WAVE_FORMAT_PCM)
      This->cbOutFormat = sizeof(WAVEFORMATEX) + ((LPWAVEFORMATEX)lParam2)->cbSize;
    else
      This->cbOutFormat = sizeof(PCMWAVEFORMAT);

    This->lpOutFormat = (LPWAVEFORMATEX)GlobalAllocPtr(GHND, This->cbOutFormat);
    if (This->lpOutFormat == NULL)
      return AVIERR_MEMORY;

    memcpy(This->lpOutFormat, (LPVOID)lParam2, This->cbOutFormat);
  } else {
    This->lpOutFormat = NULL;
    This->cbOutFormat = 0;
  }

  This->pStream = (PAVISTREAM)lParam1;
  IAVIStream_AddRef(This->pStream);

  return AVIERR_OK;
}

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

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

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

  /* Need codec to correct some values in structure */
  if (This->has == NULL) {
    HRESULT hr = AVIFILE_OpenCompressor(This);

    if (FAILED(hr))
      return hr;
  }

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

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

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

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

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

  /* convert pos from our 'space' to This->pStream's one */
  CONVERT_THIS_to_STREAM(pos);

  /* ask stream */
  pos = IAVIStream_FindSample(This->pStream, pos, flags);

  if (pos != -1) {
    /* convert pos back to our 'space' if it's no size or physical pos */
    if ((flags & FIND_RET) == 0)
      CONVERT_STREAM_to_THIS(pos);
  }

  return pos;
}

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

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

  if (formatsize == NULL)
    return AVIERR_BADPARAM;

  if (This->has == NULL) {
    HRESULT hr = AVIFILE_OpenCompressor(This);

    if (FAILED(hr))
      return hr;
  }

  /* only interested in needed buffersize? */
  if (format == NULL || *formatsize <= 0) {
    *formatsize = This->cbOutFormat;

    return AVIERR_OK;
  }

  /* copy initial format (only as much as will fit) */
  memcpy(format, This->lpOutFormat, min(*formatsize, This->cbOutFormat));
  if (*formatsize < This->cbOutFormat) {
    *formatsize = This->cbOutFormat;
    return AVIERR_BUFFERTOOSMALL;
  }

  *formatsize = This->cbOutFormat;
  return AVIERR_OK;
}

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

  HRESULT hr;

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

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

  /* Input format already known?
   * Changing is unsupported, but be quiet if it's the same */
  if (This->lpInFormat != NULL) {
    if (This->cbInFormat != formatsize ||
	memcmp(format, This->lpInFormat, formatsize) != 0)
      return AVIERR_UNSUPPORTED;

    return AVIERR_OK;
  }

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

  This->lpInFormat = (LPWAVEFORMATEX)GlobalAllocPtr(GMEM_MOVEABLE, formatsize);
  if (This->lpInFormat == NULL)
    return AVIERR_MEMORY;
  This->cbInFormat = formatsize;
  memcpy(This->lpInFormat, format, formatsize);

  /* initialize formats and get compressor */
  hr = AVIFILE_OpenCompressor(This);
  if (FAILED(hr))
    return hr;

  CONVERT_THIS_to_STREAM(pos);

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

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

  HRESULT hr;
  DWORD   size;

  TRACE("(%p,%ld,%ld,%p,%ld,%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;

  /* Do we have our compressor? */
  if (This->has == NULL) {
    hr = AVIFILE_OpenCompressor(This);

    if (FAILED(hr))
      return hr;
  }

  /* only need to pass through? */
  if (This->cbInFormat == This->cbOutFormat &&
      memcmp(This->lpInFormat, This->lpOutFormat, This->cbInFormat) == 0) {
    return IAVIStream_Read(This->pStream, start, samples, buffer, buffersize,
			   bytesread, samplesread);
  }

  /* read as much as fit? */
  if (samples == -1)
    samples = buffersize / This->lpOutFormat->nBlockAlign;
  /* limit to buffersize */
  if (samples * This->lpOutFormat->nBlockAlign > buffersize)
    samples = buffersize / This->lpOutFormat->nBlockAlign;

  /* only return needed size? */
  if (buffer == NULL || buffersize <= 0 || samples == 0) {
    if (bytesread == NULL && samplesread == NULL)
      return AVIERR_BADPARAM;

    if (bytesread != NULL)
      *bytesread = samples * This->lpOutFormat->nBlockAlign;
    if (samplesread != NULL)
      *samplesread = samples;

    return AVIERR_OK;
  }

  /* map our positions to pStream positions */
  CONVERT_THIS_to_STREAM(start);

  /* our needed internal buffersize */
  size = samples * This->lpInFormat->nBlockAlign;

  /* Need to free destination buffer used for writing? */
  if (This->acmStreamHdr.pbDst != NULL) {
    GlobalFreePtr(This->acmStreamHdr.pbDst);
    This->acmStreamHdr.pbDst     = NULL;
    This->acmStreamHdr.dwDstUser = 0;
  }

  /* need bigger source buffer? */
  if (This->acmStreamHdr.pbSrc == NULL ||
      This->acmStreamHdr.dwSrcUser < size) {
    if (This->acmStreamHdr.pbSrc == NULL)
      This->acmStreamHdr.pbSrc = GlobalAllocPtr(GMEM_MOVEABLE, size);
    else
      This->acmStreamHdr.pbSrc = GlobalReAllocPtr(This->acmStreamHdr.pbSrc,
						  size, GMEM_MOVEABLE);
    if (This->acmStreamHdr.pbSrc == NULL)
      return AVIERR_MEMORY;
    This->acmStreamHdr.dwSrcUser = size;
  }

  This->acmStreamHdr.cbStruct = sizeof(This->acmStreamHdr);
  This->acmStreamHdr.cbSrcLengthUsed = 0;
  This->acmStreamHdr.cbDstLengthUsed = 0;
  This->acmStreamHdr.cbSrcLength     = size;

  /* read source data */
  hr = IAVIStream_Read(This->pStream, start, -1, This->acmStreamHdr.pbSrc,
		       This->acmStreamHdr.cbSrcLength,
		       &This->acmStreamHdr.cbSrcLength, NULL);
  if (FAILED(hr) || This->acmStreamHdr.cbSrcLength == 0)
    return hr;

  /* need to prepare stream? */
  This->acmStreamHdr.pbDst       = buffer;
  This->acmStreamHdr.cbDstLength = buffersize;
  if ((This->acmStreamHdr.fdwStatus & ACMSTREAMHEADER_STATUSF_PREPARED) == 0) {
    if (acmStreamPrepareHeader(This->has, &This->acmStreamHdr, 0) != S_OK) {
      This->acmStreamHdr.pbDst       = NULL;
      This->acmStreamHdr.cbDstLength = 0;
      return AVIERR_COMPRESSOR;
    }
  }

  /* now do the conversion */
  /* FIXME: use ACM_CONVERTF_* flags */
  if (acmStreamConvert(This->has, &This->acmStreamHdr, 0) != S_OK)
    hr = AVIERR_COMPRESSOR;

  This->acmStreamHdr.pbDst       = NULL;
  This->acmStreamHdr.cbDstLength = 0;

  /* fill out return parameters if given */
  if (bytesread != NULL)
    *bytesread = This->acmStreamHdr.cbDstLengthUsed;
  if (samplesread != NULL)
    *samplesread =
      This->acmStreamHdr.cbDstLengthUsed / This->lpOutFormat->nBlockAlign;

  return hr;
}

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

  HRESULT hr;
  ULONG   size;

  TRACE("(%p,%ld,%ld,%p,%ld,0x%08lX,%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;

  /* Have we write capability? */
  if ((This->sInfo.dwCaps & AVIFILECAPS_CANWRITE) == 0)
    return AVIERR_READONLY;

  /* also need a compressor */
  if (This->has == NULL)
    return AVIERR_NOCOMPRESSOR;

  /* map our sizes to pStream sizes */
  size = buffersize;
  CONVERT_THIS_to_STREAM(size);
  CONVERT_THIS_to_STREAM(start);

  /* no bytes to write? -- short circuit */
  if (size == 0) {
    return IAVIStream_Write(This->pStream, -1, samples, buffer, size,
			    flags, sampwritten, byteswritten);
  }

  /* Need to free source buffer used for reading? */
  if (This->acmStreamHdr.pbSrc != NULL) {
    GlobalFreePtr(This->acmStreamHdr.pbSrc);
    This->acmStreamHdr.pbSrc     = NULL;
    This->acmStreamHdr.dwSrcUser = 0;
  }

  /* Need bigger destination buffer? */
  if (This->acmStreamHdr.pbDst == NULL ||
      This->acmStreamHdr.dwDstUser < size) {
    if (This->acmStreamHdr.pbDst == NULL)
      This->acmStreamHdr.pbDst = GlobalAllocPtr(GMEM_MOVEABLE, size);
    else
      This->acmStreamHdr.pbDst = GlobalReAllocPtr(This->acmStreamHdr.pbDst,
						  size, GMEM_MOVEABLE);
    if (This->acmStreamHdr.pbDst == NULL)
      return AVIERR_MEMORY;
    This->acmStreamHdr.dwDstUser = size;
  }
  This->acmStreamHdr.cbStruct        = sizeof(This->acmStreamHdr);
  This->acmStreamHdr.cbSrcLengthUsed = 0;
  This->acmStreamHdr.cbDstLengthUsed = 0;
  This->acmStreamHdr.cbDstLength     = This->acmStreamHdr.dwDstUser;

  /* need to prepare stream? */
  This->acmStreamHdr.pbSrc       = buffer;
  This->acmStreamHdr.cbSrcLength = buffersize;
  if ((This->acmStreamHdr.fdwStatus & ACMSTREAMHEADER_STATUSF_PREPARED) == 0) {
    if (acmStreamPrepareHeader(This->has, &This->acmStreamHdr, 0) != S_OK) {
      This->acmStreamHdr.pbSrc       = NULL;
      This->acmStreamHdr.cbSrcLength = 0;
      return AVIERR_COMPRESSOR;
    }
  }

  /* now do the conversion */
  /* FIXME: use ACM_CONVERTF_* flags */
  if (acmStreamConvert(This->has, &This->acmStreamHdr, 0) != S_OK)
    hr = AVIERR_COMPRESSOR;
  else
    hr = AVIERR_OK;

  This->acmStreamHdr.pbSrc       = NULL;
  This->acmStreamHdr.cbSrcLength = 0;

  if (FAILED(hr))
    return hr;

  return IAVIStream_Write(This->pStream,-1,This->acmStreamHdr.cbDstLengthUsed /
			  This->lpOutFormat->nBlockAlign,This->acmStreamHdr.pbDst,
			  This->acmStreamHdr.cbDstLengthUsed,flags,sampwritten,
			  byteswritten);
}

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

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

  /* check parameters */
  if (start < 0 || samples < 0)
    return AVIERR_BADPARAM;

  /* Delete before start of stream? */
  if ((DWORD)(start + samples) < This->sInfo.dwStart)
    return AVIERR_OK;

  /* Delete after end of stream? */
  if ((DWORD)start > This->sInfo.dwLength)
    return AVIERR_OK;

  /* For the rest we need write capability */
  if ((This->sInfo.dwCaps & AVIFILECAPS_CANWRITE) == 0)
    return AVIERR_READONLY;

  /* A compressor is also necessary */
  if (This->has == NULL)
    return AVIERR_NOCOMPRESSOR;

  /* map our positions to pStream positions */
  CONVERT_THIS_to_STREAM(start);
  CONVERT_THIS_to_STREAM(samples);

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

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

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

  assert(This->pStream != NULL);

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

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

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

  assert(This->pStream != NULL);

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

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

  return E_FAIL;
}

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

static HRESULT AVIFILE_OpenCompressor(IAVIStreamImpl *This)
{
  HRESULT hr;

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

  if (This->has != NULL)
    return AVIERR_OK;

  if (This->lpInFormat == NULL) {
    /* decode or encode the data from pStream */
    hr = AVIStreamFormatSize(This->pStream, This->sInfo.dwStart, &This->cbInFormat);
    if (FAILED(hr))
      return hr;
    This->lpInFormat = (LPWAVEFORMATEX)GlobalAllocPtr(GMEM_MOVEABLE, This->cbInFormat);
    if (This->lpInFormat == NULL)
      return AVIERR_MEMORY;

    hr = IAVIStream_ReadFormat(This->pStream, This->sInfo.dwStart,
			       This->lpInFormat, &This->cbInFormat);
    if (FAILED(hr))
      return hr;

    if (This->lpOutFormat == NULL) {
      /* we must decode to default format */
      This->cbOutFormat = sizeof(PCMWAVEFORMAT);
      This->lpOutFormat = (LPWAVEFORMATEX)GlobalAllocPtr(GHND, This->cbOutFormat);
      if (This->lpOutFormat == NULL)
	return AVIERR_MEMORY;

      This->lpOutFormat->wFormatTag = WAVE_FORMAT_PCM;
      if (acmFormatSuggest(NULL, This->lpInFormat, This->lpOutFormat,
			   This->cbOutFormat, ACM_FORMATSUGGESTF_WFORMATTAG) != S_OK)
	return AVIERR_NOCOMPRESSOR;
    }
  } else if (This->lpOutFormat == NULL)
    return AVIERR_ERROR; /* To what should I encode? */

  if (acmStreamOpen(&This->has, NULL, This->lpInFormat, This->lpOutFormat,
		    NULL, 0, 0, ACM_STREAMOPENF_NONREALTIME) != S_OK)
    return AVIERR_NOCOMPRESSOR;

  /* update AVISTREAMINFO structure */
  This->sInfo.dwSampleSize = This->lpOutFormat->nBlockAlign;
  This->sInfo.dwScale      = This->lpOutFormat->nBlockAlign;
  This->sInfo.dwRate       = This->lpOutFormat->nAvgBytesPerSec;
  This->sInfo.dwQuality    = (DWORD)ICQUALITY_DEFAULT;
  SetRectEmpty(&This->sInfo.rcFrame);

  /* convert positions ansd sizes to output format */
  CONVERT_STREAM_to_THIS(This->sInfo.dwStart);
  CONVERT_STREAM_to_THIS(This->sInfo.dwLength);
  CONVERT_STREAM_to_THIS(This->sInfo.dwSuggestedBufferSize);

  return AVIERR_OK;
}
