/*		DirectMusic
 *
 * Copyright 2002-2003 TransGaming Technologies Inc. 
 *
 */

#include "config.h"
#include <assert.h>
#include <string.h>

#include "wine/debug.h"
#include "winbase.h"
#include "winerror.h"
#include "windef.h"
#include "wingdi.h"
#include "winuser.h"
#include "winnls.h"
#include "mmsystem.h"
#include "ole2.h"
#include "strmif.h"
#include "control.h"
#include "uuids.h"
#include "errors.h"
#include "dsound.h"
#include "dmusici.h"
#include "dmusicf.h"
#include "dmime_private.h"

WINE_DEFAULT_DEBUG_CHANNEL(dmusic);

typedef struct {
    DWORD dwGroup;
} SeqState;

/*****************************************************************************
 * IDirectMusicSeqTrack implementation structure
 */
typedef struct IDirectMusicSeqTrackImpl IDirectMusicSeqTrackImpl;
struct IDirectMusicSeqTrackImpl
{
    /* IUnknown fields */
    ICOM_VFIELD(IDirectMusicTrack8);
    ICOM_VTABLE(IPersistStream) *lpPSVtbl;
    DWORD ref;
    /* IDirectMusicSeqTrack fields */
    DWORD seqs, curves;
    DMUS_IO_SEQ_ITEM *seq;
    DMUS_IO_CURVE_ITEM *curve;
};

static HRESULT WINAPI IDirectMusicSeqTrackImpl_ParseStream(
	IDirectMusicSeqTrackImpl* This,
	IStream* pStream);

static HRESULT WINAPI IDirectMusicSeqTrackImpl_QueryInterface(
	IDirectMusicTrack8* iface,REFIID riid,LPVOID *ppobj
) {
	ICOM_THIS(IDirectMusicSeqTrackImpl,iface);

	TRACE("(%p,%s,%p)\n",This,debugstr_guid(riid),ppobj);
	if (IsEqualGUID(riid, &IID_IUnknown) ||
	    IsEqualGUID(riid, &IID_IDirectMusicTrack) ||
	    IsEqualGUID(riid, &IID_IDirectMusicTrack8)) {
	    *ppobj = (LPVOID)This;
	    This->ref++;
	    return S_OK;
	}
	if (IsEqualGUID(riid, &IID_IPersist) ||
	    IsEqualGUID(riid, &IID_IPersistStream)) {
	    *ppobj = (LPVOID)&This->lpPSVtbl;
	    This->ref++;
	    return S_OK;
	}
	FIXME("(%p): no interface for %s\n", This, debugstr_guid(riid));
	return E_NOINTERFACE;
}

static ULONG WINAPI IDirectMusicSeqTrackImpl_AddRef(IDirectMusicTrack8* iface) {
	ICOM_THIS(IDirectMusicSeqTrackImpl,iface);
	return ++(This->ref);
}

static ULONG WINAPI IDirectMusicSeqTrackImpl_Release(IDirectMusicTrack8* iface) {
	ICOM_THIS(IDirectMusicSeqTrackImpl,iface);
	TRACE("(%p), ref was %ld\n",This,This->ref);
	if (!--(This->ref)) {
		HeapFree(GetProcessHeap(),0,This);
		return 0;
	}
	return This->ref;
}

static HRESULT WINAPI IDirectMusicSeqTrackImpl_Init(
	IDirectMusicTrack8* iface,
	IDirectMusicSegment* pSegment)
{
	ICOM_THIS(IDirectMusicSeqTrackImpl,iface);
	TRACE("(%p)->(%p)\n", This, pSegment);
	return S_OK;
}

static HRESULT WINAPI IDirectMusicSeqTrackImpl_InitPlay(
	IDirectMusicTrack8* iface,
	IDirectMusicSegmentState* pSegmentState,
	IDirectMusicPerformance* pPerformance,
	void** ppStateData,
	DWORD dwVirtualTrackID,
	DWORD dwFlags)
{
	ICOM_THIS(IDirectMusicSeqTrackImpl,iface);
	IDirectMusicSegment* pSegment;
	SeqState* state;
	HRESULT hr;

	TRACE("(%p)->(%p,%p,%p,%ld,0x%lx)\n", This,
	      pSegmentState, pPerformance, ppStateData, dwVirtualTrackID, dwFlags);
	state = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(SeqState));
	state->dwGroup = (DWORD)-1;
	hr = IDirectMusicSegmentState_GetSegment(pSegmentState, &pSegment);
	if (SUCCEEDED(hr)) {
		hr = IDirectMusicSegment_GetTrackGroup(pSegment, (IDirectMusicTrack*)This,
						       &state->dwGroup);
		IDirectMusicSegment_Release(pSegment);
	}
	*ppStateData = state;

	TRACE("sequencer events: %ld\n", This->seqs);
	TRACE("curve events: %ld\n", This->curves);

	if (This->seqs || This->curves) FIXME("sequencer track can't be played yet!\n");

	return S_OK;
}

static HRESULT WINAPI IDirectMusicSeqTrackImpl_EndPlay(
	IDirectMusicTrack8* iface,
	void* pStateData)
{
	ICOM_THIS(IDirectMusicSeqTrackImpl,iface);
	SeqState* state;
	TRACE("(%p)->(%p)\n", This, pStateData);
	state = pStateData;
	HeapFree(GetProcessHeap(), 0, state);
	return S_OK;
}

static HRESULT WINAPI IDirectMusicSeqTrackImpl_Play(
	IDirectMusicTrack8* iface,
	void* pStateData,
	MUSIC_TIME mtStart,
	MUSIC_TIME mtEnd,
	MUSIC_TIME mtOffset,
	DWORD dwFlags,
	IDirectMusicPerformance* pPerf,
	IDirectMusicSegmentState* pSegSt,
	DWORD dwVirtualID)
{
	ICOM_THIS(IDirectMusicSeqTrackImpl,iface);

	TRACE("(%p)->(%p,%ld,%ld,%ld,0x%lx,%p,%p,%ld)\n", This, pStateData,
	      mtStart, mtEnd, mtOffset, dwFlags, pPerf, pSegSt, dwVirtualID);

	/* FIXME */

	return S_OK;
}

static HRESULT WINAPI IDirectMusicSeqTrackImpl_GetParam(
        IDirectMusicTrack8* iface,
        REFGUID rguidType,
        MUSIC_TIME mtTime,
        MUSIC_TIME* pmtNext,
        void* pParam)
{
        ICOM_THIS(IDirectMusicSeqTrackImpl,iface);
        FIXME("(%p)->(%s,%ld,%p,%p): stub\n", This, debugstr_guid(rguidType), mtTime, pmtNext, pParam);
        return DMUS_E_GET_UNSUPPORTED;
}

static ICOM_VTABLE(IDirectMusicTrack8) dmttvt = 
{
	ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
	IDirectMusicSeqTrackImpl_QueryInterface,
	IDirectMusicSeqTrackImpl_AddRef,
	IDirectMusicSeqTrackImpl_Release,
	IDirectMusicSeqTrackImpl_Init,
	IDirectMusicSeqTrackImpl_InitPlay,
	IDirectMusicSeqTrackImpl_EndPlay,
	IDirectMusicSeqTrackImpl_Play,
	IDirectMusicSeqTrackImpl_GetParam,
	(void*)0xdeaf500a, /* SetParam */
	(void*)0xdeaf500b, /* IsParamSupported */
	(void*)0xdeaf500c, /* AddNotificationType */
	(void*)0xdeaf500d, /* RemoveNotificationType */
	(void*)0xdeaf500e, /* Clone */
	(void*)0xdeaf500f, /* PlayEx */
	(void*)0xdeaf5010, /* GetParamEx */
	(void*)0xdeaf5011, /* SetParamEx */
	(void*)0xdeaf5012, /* Compose */
	(void*)0xdeaf5013  /* Join */
};

static HRESULT WINAPI IDirectMusicSeqTrackStream_QueryInterface(
	IPersistStream* iface,REFIID riid,LPVOID *ppobj
) {
	ICOM_THIS_MULTI(IDirectMusicSeqTrackImpl,lpPSVtbl,iface);
	return IDirectMusicTrack8_QueryInterface((IDirectMusicTrack8*)This, riid, ppobj);
}

static ULONG WINAPI IDirectMusicSeqTrackStream_AddRef(IPersistStream* iface) {
	ICOM_THIS_MULTI(IDirectMusicSeqTrackImpl,lpPSVtbl,iface);
	return IDirectMusicTrack8_AddRef((IDirectMusicTrack8*)This);
}

static ULONG WINAPI IDirectMusicSeqTrackStream_Release(IPersistStream* iface) {
	ICOM_THIS_MULTI(IDirectMusicSeqTrackImpl,lpPSVtbl,iface);
	return IDirectMusicTrack8_Release((IDirectMusicTrack8*)This);
}

static HRESULT WINAPI IDirectMusicSeqTrackStream_GetClassID(
	IPersistStream* iface,
	CLSID* pClassID)
{
	ICOM_THIS_MULTI(IDirectMusicSeqTrackImpl,lpPSVtbl,iface);
	TRACE("(%p)->(%p)\n", This, pClassID);
	memcpy(pClassID, &CLSID_DirectMusicSeqTrack, sizeof(CLSID));
	return S_OK;
}

static HRESULT WINAPI IDirectMusicSeqTrackStream_IsDirty(
	IPersistStream* iface)
{
	ICOM_THIS_MULTI(IDirectMusicSeqTrackImpl,lpPSVtbl,iface);
	TRACE("(%p)->()\n", This);
	return S_FALSE; /* who cares */
}

static HRESULT WINAPI IDirectMusicSeqTrackStream_Load(
	IPersistStream* iface,
	IStream* pStream)
{
	ICOM_THIS_MULTI(IDirectMusicSeqTrackImpl,lpPSVtbl,iface);
	TRACE("(%p)->(%p)\n", This, pStream);
	return IDirectMusicSeqTrackImpl_ParseStream(This, pStream);
}

static HRESULT WINAPI IDirectMusicSeqTrackStream_Save(
	IPersistStream* iface,
	IStream* pStream,
	BOOL fClearDirty)
{
	ICOM_THIS_MULTI(IDirectMusicSeqTrackImpl,lpPSVtbl,iface);
	FIXME("(%p)->(%p,%d): stub\n", This, pStream, fClearDirty);
	return E_NOTIMPL;
}

static HRESULT WINAPI IDirectMusicSeqTrackStream_GetSizeMax(
	IPersistStream* iface,
	ULARGE_INTEGER* pcbSize)
{
	ICOM_THIS_MULTI(IDirectMusicSeqTrackImpl,lpPSVtbl,iface);
	FIXME("(%p)->(%p): stub\n", This, pcbSize);
	pcbSize->QuadPart = 0;
	return E_NOTIMPL;
}

static ICOM_VTABLE(IPersistStream) dmpsvt =

{
	ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
	IDirectMusicSeqTrackStream_QueryInterface,
	IDirectMusicSeqTrackStream_AddRef,
	IDirectMusicSeqTrackStream_Release,
	IDirectMusicSeqTrackStream_GetClassID,
	IDirectMusicSeqTrackStream_IsDirty,
	IDirectMusicSeqTrackStream_Load,
	IDirectMusicSeqTrackStream_Save,
	IDirectMusicSeqTrackStream_GetSizeMax
};

static HRESULT WINAPI IDirectMusicSeqTrackImpl_ParseSeqList(
	IDirectMusicSeqTrackImpl* This,
	HMMIO hmmio,
	LPMMCKINFO pck)
{
	DWORD itemsize = 0;
	DWORD count, u;

	mmioRead(hmmio, (LPVOID)&itemsize, 4);
	TRACE("itemsize=%ld\n", itemsize);
	count = (pck->cksize - 4) / itemsize;
	/* itemsize should be sizeof(DMUS_IO_SEQ_ITEM) */
	if (itemsize != sizeof(DMUS_IO_SEQ_ITEM)) {
		ERR("can't handle itemsize=%ld\n", itemsize);
		return E_FAIL;
	}

	if (This->seq) HeapFree(GetProcessHeap(), 0, This->seq);
	This->seq = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, count*sizeof(DMUS_IO_SEQ_ITEM));
	This->seqs = count;
	for (u=0; u<count; u++) {
		FIELD_READ(This->seq[u], mtTime);
		FIELD_READ(This->seq[u], mtDuration);
		FIELD_READ(This->seq[u], dwPChannel);
		FIELD_READ(This->seq[u], nOffset);
		FIELD_READ(This->seq[u], bStatus);
		FIELD_READ(This->seq[u], bByte1);
		FIELD_READ(This->seq[u], bByte2);
	}

	return S_OK;
}

static HRESULT WINAPI IDirectMusicSeqTrackImpl_ParseCurveList(
	IDirectMusicSeqTrackImpl* This,
	HMMIO hmmio,
	LPMMCKINFO pck)
{
	DWORD itemsize = 0;
	DWORD count, u;

	mmioRead(hmmio, (LPVOID)&itemsize, 4);
	TRACE("itemsize=%ld\n", itemsize);
	count = (pck->cksize - 4) / itemsize;
	/* itemsize should be sizeof(DMUS_IO_CURVE_ITEM) */
	if (itemsize != sizeof(DMUS_IO_CURVE_ITEM)) {
		ERR("can't handle itemsize=%ld\n", itemsize);
		return E_FAIL;
	}

	if (This->curve) HeapFree(GetProcessHeap(), 0, This->curve);
	This->curve = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, count*sizeof(DMUS_IO_CURVE_ITEM));
	This->curves = count;
	for (u=0; u<count; u++) {
		FIELD_READ(This->curve[u], mtStart);
		FIELD_READ(This->curve[u], mtDuration);
		FIELD_READ(This->curve[u], mtResetDuration);
		FIELD_READ(This->curve[u], dwPChannel);
		FIELD_READ(This->curve[u], nOffset);
		FIELD_READ(This->curve[u], nStartValue);
		FIELD_READ(This->curve[u], nEndValue);
		FIELD_READ(This->curve[u], nResetValue);
		FIELD_READ(This->curve[u], bType);
		FIELD_READ(This->curve[u], bCurveShape);
		FIELD_READ(This->curve[u], bCCData);
		FIELD_READ(This->curve[u], bFlags);
		FIELD_READ(This->curve[u], wParamType);
		FIELD_READ(This->curve[u], wMergeIndex);
	}

	return S_OK;
}

static HRESULT WINAPI IDirectMusicSeqTrackImpl_ParseList(
	IDirectMusicSeqTrackImpl* This,
	HMMIO hmmio,
	LPMMCKINFO pck)
{
	MMCKINFO ck;
	MMRESULT mmres;

	/* read chunks */
	while (!(mmres = mmioDescend(hmmio, &ck, pck, 0))) {
		HRESULT hr;
		switch (ck.ckid) {
		case DMUS_FOURCC_SEQ_LIST:
			TRACE("Sequencer list found\n");
			hr = IDirectMusicSeqTrackImpl_ParseSeqList(This, hmmio, &ck);
			if (FAILED(hr)) return hr;
			break;
		case DMUS_FOURCC_CURVE_LIST:
			TRACE("Sequencer list found\n");
			hr = IDirectMusicSeqTrackImpl_ParseCurveList(This, hmmio, &ck);
			if (FAILED(hr)) return hr;
			break;
		default:
			FIXME("unknown chunk %08lx\n", ck.ckid);
			break;
		}
		mmioAscend(hmmio, &ck, 0);
	}
	if (mmres != MMIOERR_CHUNKNOTFOUND) {
		ERR("mmio error %d\n", mmres);
		return E_FAIL;
	}
	return S_OK;
}

static HRESULT WINAPI IDirectMusicSeqTrackImpl_ParseStream(
	IDirectMusicSeqTrackImpl* This,
	IStream* pStream)
{
	HMMIO hmmio;
	MMCKINFO ck;
	MMRESULT mmres;
	HRESULT hr = E_FAIL;

	hmmio = DMIME_mmioOpen(pStream);
	if (!hmmio) return E_FAIL;
	/* descend into data chunk */
	mmres = mmioDescend(hmmio, &ck, NULL, 0);
	if (mmres) {
		ERR("mmio error %d\n", mmres);
		DMIME_mmioClose(hmmio);
		return E_FAIL;
	}
	if (ck.ckid == DMUS_FOURCC_SEQ_TRACK) {
		TRACE("Sequencer Track list found\n");
		hr = IDirectMusicSeqTrackImpl_ParseList(This, hmmio, &ck);
	} else {
		ERR("Sequencer Track not found (found %08lx)\n", ck.ckid);
		hr = E_FAIL;
	}
	mmioAscend(hmmio, &ck, 0);
	DMIME_mmioClose(hmmio);
	return hr;
}


/***********************************************************************
 *		DirectMusicSeqTrackCreate 
 */
HRESULT DirectMusicSeqTrackCreate(REFGUID lpGUID, IUnknown **ppObj, IUnknown *pUnkOuter)
{
	IDirectMusicSeqTrackImpl** ippDM=(IDirectMusicSeqTrackImpl**)ppObj;

	if (lpGUID)
		TRACE("(%p,%p,%p)\n",lpGUID,ippDM,pUnkOuter);
	else
		TRACE("DirectMusicSeqTrackCreate (%p)\n", ippDM);

	if (ippDM == NULL)
		return E_INVALIDARG;

	/* Allocate memory */
	*ippDM = (IDirectMusicSeqTrackImpl*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(IDirectMusicSeqTrackImpl));
	if (*ippDM == NULL)
		return E_OUTOFMEMORY;

	ICOM_VTBL(*ippDM)	= &dmttvt;
	(*ippDM)->lpPSVtbl	= &dmpsvt;
	(*ippDM)->ref		= 1;

	return DS_OK;
}
