/*		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;
    BOOL control;
} TempoState;

/*****************************************************************************
 * IDirectMusicTempoTrack implementation structure
 */
typedef struct IDirectMusicTempoTrackImpl IDirectMusicTempoTrackImpl;
struct IDirectMusicTempoTrackImpl
{
    /* IUnknown fields */
    ICOM_VFIELD(IDirectMusicTrack8);
    ICOM_VTABLE(IPersistStream) *lpPSVtbl;
    DWORD ref;
    /* IDirectMusicTempoTrack fields */
    BOOL disabled;
    DWORD count;
    DMUS_IO_TEMPO_ITEM *data;
};

static HRESULT WINAPI IDirectMusicTempoTrackImpl_ParseStream(
	IDirectMusicTempoTrackImpl* This,
	IStream* pStream);

static HRESULT WINAPI IDirectMusicTempoTrackImpl_QueryInterface(
	IDirectMusicTrack8* iface,REFIID riid,LPVOID *ppobj
) {
	ICOM_THIS(IDirectMusicTempoTrackImpl,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 IDirectMusicTempoTrackImpl_AddRef(IDirectMusicTrack8* iface) {
	ICOM_THIS(IDirectMusicTempoTrackImpl,iface);
	return ++(This->ref);
}

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

static HRESULT WINAPI IDirectMusicTempoTrackImpl_Init(
	IDirectMusicTrack8* iface,
	IDirectMusicSegment* pSegment)
{
	ICOM_THIS(IDirectMusicTempoTrackImpl,iface);
	TRACE("(%p)->(%p)\n", This, pSegment);
	/* tempo tracks don't play anything, nothing to do */
	return S_OK;
}

static HRESULT WINAPI IDirectMusicTempoTrackImpl_InitPlay(
	IDirectMusicTrack8* iface,
	IDirectMusicSegmentState* pSegmentState,
	IDirectMusicPerformance* pPerformance,
	void** ppStateData,
	DWORD dwVirtualTrackID,
	DWORD dwFlags)
{
	ICOM_THIS(IDirectMusicTempoTrackImpl,iface);
	IDirectMusicSegment* pSegment;
	TempoState* 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(TempoState));
	state->control = (dwFlags & DMUS_SEGF_CONTROL) || !(dwFlags & DMUS_SEGF_SECONDARY);
	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;
	return S_OK;
}

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

static HRESULT WINAPI IDirectMusicTempoTrackImpl_Play(
	IDirectMusicTrack8* iface,
	void* pStateData,
	MUSIC_TIME mtStart,
	MUSIC_TIME mtEnd,
	MUSIC_TIME mtOffset,
	DWORD dwFlags,
	IDirectMusicPerformance* pPerf,
	IDirectMusicSegmentState* pSegSt,
	DWORD dwVirtualID)
{
	ICOM_THIS(IDirectMusicTempoTrackImpl,iface);
	TempoState* state;
	HRESULT hr;
	DWORD c;

	TRACE("(%p)->(%p)\n", This, pStateData);
	state = pStateData;
	if (!state->control) return DMUS_S_END;
	if (This->disabled) return S_OK;

	for (c=0; c<This->count; c++) {
		DMUS_TEMPO_PMSG *pMsg;
		if (This->data[c].lTime < mtStart ||
		    This->data[c].lTime >= mtEnd)
		    continue;
		/* tempo change found */
		hr = IDirectMusicPerformance_AllocPMsg(pPerf, sizeof(*pMsg), (DMUS_PMSG**)&pMsg);
		if (FAILED(hr)) continue;
		pMsg->dwType = DMUS_PMSGT_TEMPO;
		pMsg->mtTime = This->data[c].lTime + mtOffset;
		pMsg->dwVirtualTrackID = dwVirtualID;
		pMsg->dwFlags = DMUS_PMSGF_MUSICTIME | DMUS_PMSGF_TOOL_QUEUE;
		pMsg->dwGroupID = state->dwGroup;
		pMsg->dblTempo = This->data[c].dblTempo;
		hr = IDirectMusicPerformance_SendPMsg(pPerf, (DMUS_PMSG*)pMsg);
		if (FAILED(hr)) {
			IDirectMusicPerformance_FreePMsg(pPerf, (DMUS_PMSG*)pMsg);
		}
	}
	
	return S_OK;
}

static HRESULT WINAPI IDirectMusicTempoTrackImpl_GetParam(
	IDirectMusicTrack8* iface,
	REFGUID rguidType,
	MUSIC_TIME mtTime,
	MUSIC_TIME* pmtNext,
	void* pParam)
{
	ICOM_THIS(IDirectMusicTempoTrackImpl,iface);
	DMUS_TEMPO_PARAM* param;
	BOOL have_time = FALSE, have_next = FALSE;
	DWORD c;

	TRACE("(%p)->(%s,%ld,%p,%p)\n", This, debugstr_guid(rguidType), mtTime, pmtNext, pParam);
	if (!IsEqualGUID(rguidType, &GUID_TempoParam)) return DMUS_E_GET_UNSUPPORTED;
	if (This->disabled) return DMUS_E_TYPE_DISABLED;

	param = pParam;
	param->mtTime = 0;
	if (pmtNext) *pmtNext = 0;

	for (c=0; c<This->count; c++) {
		if (This->data[c].lTime <= mtTime) {
			MUSIC_TIME ofs = This->data[c].lTime - mtTime;
			if (!have_time || ofs > param->mtTime) {
				/* param->mtTime should be 0 or less */
				param->mtTime = ofs;
				param->dblTempo = This->data[c].dblTempo;
				have_time = TRUE;
			}
		}
		if (pmtNext && This->data[c].lTime > mtTime) {
			if (!have_next || This->data[c].lTime < *pmtNext) {
				*pmtNext = This->data[c].lTime;
				have_next = TRUE;
			}
		}
	}

	if (!have_time) {
		ERR("tempo for music time %ld not found\n", mtTime);
		return DMUS_E_NOT_FOUND;
	}

	return S_OK;
}

static HRESULT WINAPI IDirectMusicTempoTrackImpl_IsParamSupported(
	IDirectMusicTrack8* iface,
	REFGUID rguidType)
{
	ICOM_THIS(IDirectMusicTempoTrackImpl,iface);
	TRACE("(%p)->(%s)\n", This, debugstr_guid(rguidType));
	if (IsEqualGUID(rguidType, &GUID_EnableTempo))
		return This->disabled ? S_OK : DMUS_E_TYPE_DISABLED;
	if (IsEqualGUID(rguidType, &GUID_DisableTempo))
		return This->disabled ? DMUS_E_TYPE_DISABLED : S_OK;
	if (IsEqualGUID(rguidType, &GUID_TempoParam))
		return This->disabled ? DMUS_E_TYPE_DISABLED : S_OK;
	return DMUS_E_TYPE_UNSUPPORTED;
}

static ICOM_VTABLE(IDirectMusicTrack8) dmttvt = 
{
	ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
	IDirectMusicTempoTrackImpl_QueryInterface,
	IDirectMusicTempoTrackImpl_AddRef,
	IDirectMusicTempoTrackImpl_Release,
	IDirectMusicTempoTrackImpl_Init,
	IDirectMusicTempoTrackImpl_InitPlay,
	IDirectMusicTempoTrackImpl_EndPlay,
	IDirectMusicTempoTrackImpl_Play,
	IDirectMusicTempoTrackImpl_GetParam,
	(void*)0xdeaf500a, /* SetParam */
	IDirectMusicTempoTrackImpl_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 IDirectMusicTempoTrackStream_QueryInterface(
	IPersistStream* iface,REFIID riid,LPVOID *ppobj
) {
	ICOM_THIS_MULTI(IDirectMusicTempoTrackImpl,lpPSVtbl,iface);
	return IDirectMusicTrack8_QueryInterface((IDirectMusicTrack8*)This, riid, ppobj);
}

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

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

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

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

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

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

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

static ICOM_VTABLE(IPersistStream) dmpsvt =
{
	ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
	IDirectMusicTempoTrackStream_QueryInterface,
	IDirectMusicTempoTrackStream_AddRef,
	IDirectMusicTempoTrackStream_Release,
	IDirectMusicTempoTrackStream_GetClassID,
	IDirectMusicTempoTrackStream_IsDirty,
	IDirectMusicTempoTrackStream_Load,
	IDirectMusicTempoTrackStream_Save,
	IDirectMusicTempoTrackStream_GetSizeMax
};

static HRESULT WINAPI IDirectMusicTempoTrackImpl_ParseChunk(
	IDirectMusicTempoTrackImpl* 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_TEMPO_ITEM) */
	if (itemsize != sizeof(DMUS_IO_TEMPO_ITEM)) {
		ERR("can't handle itemsize=%ld\n", itemsize);
		return E_FAIL;
	}

	if (This->data) HeapFree(GetProcessHeap(), 0, This->data);
	This->data = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, count*sizeof(DMUS_IO_TEMPO_ITEM));
	This->count = count;
	for (u=0; u<count; u++) {
		FIELD_READ(This->data[u], lTime);
		FIELD_READ(This->data[u], _padding_);
		FIELD_READ(This->data[u], dblTempo);
		TRACE("at %ld, tempo is %f\n", This->data[u].lTime, This->data[u].dblTempo);
	}

	return S_OK;
}

static HRESULT WINAPI IDirectMusicTempoTrackImpl_ParseStream(
	IDirectMusicTempoTrackImpl* 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_TEMPO_TRACK) {
		TRACE("Tempo Track chunk found\n");
		hr = IDirectMusicTempoTrackImpl_ParseChunk(This, hmmio, &ck);
	} else {
		ERR("Tempo Track chunk not found (found %08lx)\n", ck.ckid);
		hr = E_FAIL;
	}
	mmioAscend(hmmio, &ck, 0);
	DMIME_mmioClose(hmmio);
	return hr;
}


/***********************************************************************
 *		DirectMusicTempoTrackCreate 
 */
HRESULT DirectMusicTempoTrackCreate(REFGUID lpGUID, IUnknown **ppObj, IUnknown *pUnkOuter)
{
	IDirectMusicTempoTrackImpl** ippDM=(IDirectMusicTempoTrackImpl**)ppObj;

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

	if (ippDM == NULL)
		return E_INVALIDARG;

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

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

	return DS_OK;
}
