/*		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;
} TimeSigState;

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

static HRESULT WINAPI IDirectMusicTimeSigTrackImpl_ParseStream(
	IDirectMusicTimeSigTrackImpl* This,
	IStream* pStream);

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

static ULONG WINAPI IDirectMusicTimeSigTrackImpl_Release(IDirectMusicTrack8* iface) {
	ICOM_THIS(IDirectMusicTimeSigTrackImpl,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 IDirectMusicTimeSigTrackImpl_Init(
	IDirectMusicTrack8* iface,
	IDirectMusicSegment* pSegment)
{
	ICOM_THIS(IDirectMusicTimeSigTrackImpl,iface);
	TRACE("(%p)->(%p)\n", This, pSegment);
	/* time signature tracks don't play anything, nothing to do */
	return S_OK;
}

static HRESULT WINAPI IDirectMusicTimeSigTrackImpl_InitPlay(
	IDirectMusicTrack8* iface,
	IDirectMusicSegmentState* pSegmentState,
	IDirectMusicPerformance* pPerformance,
	void** ppStateData,
	DWORD dwVirtualTrackID,
	DWORD dwFlags)
{
	ICOM_THIS(IDirectMusicTimeSigTrackImpl,iface);
	IDirectMusicSegment* pSegment;
	TimeSigState* 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(TimeSigState));
	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 IDirectMusicTimeSigTrackImpl_EndPlay(
	IDirectMusicTrack8* iface,
	void* pStateData)
{
	ICOM_THIS(IDirectMusicTimeSigTrackImpl,iface);
	TimeSigState* state;
	TRACE("(%p)->(%p)\n", This, pStateData);
	state = pStateData;
	HeapFree(GetProcessHeap(), 0, state);
	return S_OK;
}

static HRESULT WINAPI IDirectMusicTimeSigTrackImpl_Play(
	IDirectMusicTrack8* iface,
	void* pStateData,
	MUSIC_TIME mtStart,
	MUSIC_TIME mtEnd,
	MUSIC_TIME mtOffset,
	DWORD dwFlags,
	IDirectMusicPerformance* pPerf,
	IDirectMusicSegmentState* pSegSt,
	DWORD dwVirtualID)
{
	ICOM_THIS(IDirectMusicTimeSigTrackImpl,iface);
	TimeSigState* 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_TIMESIG_PMSG *pMsg;
		if (This->data[c].lTime < mtStart ||
		    This->data[c].lTime >= mtEnd)
		    continue;
		/* time signature change found */
		hr = IDirectMusicPerformance_AllocPMsg(pPerf, sizeof(*pMsg), (DMUS_PMSG**)&pMsg);
		if (FAILED(hr)) continue;
		pMsg->dwType = DMUS_PMSGT_TIMESIG;
		pMsg->mtTime = This->data[c].lTime + mtOffset;
		pMsg->dwVirtualTrackID = dwVirtualID;
		pMsg->dwFlags = DMUS_PMSGF_MUSICTIME | DMUS_PMSGF_TOOL_QUEUE;
		pMsg->dwGroupID = state->dwGroup;
		pMsg->bBeatsPerMeasure = This->data[c].bBeatsPerMeasure;
		pMsg->bBeat = This->data[c].bBeat;
		pMsg->wGridsPerBeat = This->data[c].wGridsPerBeat;
		hr = IDirectMusicPerformance_SendPMsg(pPerf, (DMUS_PMSG*)pMsg);
		if (FAILED(hr)) {
			IDirectMusicPerformance_FreePMsg(pPerf, (DMUS_PMSG*)pMsg);
		}
	}
	
	return S_OK;
}

static HRESULT WINAPI IDirectMusicTimeSigTrackImpl_GetParam(
	IDirectMusicTrack8* iface,
	REFGUID rguidType,
	MUSIC_TIME mtTime,
	MUSIC_TIME* pmtNext,
	void* pParam)
{
	ICOM_THIS(IDirectMusicTimeSigTrackImpl,iface);
	DMUS_TIMESIGNATURE* 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_TimeSignature)) 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->bBeatsPerMeasure = This->data[c].bBeatsPerMeasure;
				param->bBeat = This->data[c].bBeat;
				param->wGridsPerBeat = This->data[c].wGridsPerBeat;
				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("time signature for music time %ld not found\n", mtTime);
		return DMUS_E_NOT_FOUND;
	}

	return S_OK;
}

static HRESULT WINAPI IDirectMusicTimeSigTrackImpl_IsParamSupported(
	IDirectMusicTrack8* iface,
	REFGUID rguidType)
{
	ICOM_THIS(IDirectMusicTimeSigTrackImpl,iface);
	TRACE("(%p)->(%s)\n", This, debugstr_guid(rguidType));
	if (IsEqualGUID(rguidType, &GUID_EnableTimeSig))
		return This->disabled ? S_OK : DMUS_E_TYPE_DISABLED;
	if (IsEqualGUID(rguidType, &GUID_DisableTimeSig))
		return This->disabled ? DMUS_E_TYPE_DISABLED : S_OK;
	if (IsEqualGUID(rguidType, &GUID_TimeSignature))
		return This->disabled ? DMUS_E_TYPE_DISABLED : S_OK;
	return DMUS_E_TYPE_UNSUPPORTED;
}

static ICOM_VTABLE(IDirectMusicTrack8) dmttvt = 
{
	ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
	IDirectMusicTimeSigTrackImpl_QueryInterface,
	IDirectMusicTimeSigTrackImpl_AddRef,
	IDirectMusicTimeSigTrackImpl_Release,
	IDirectMusicTimeSigTrackImpl_Init,
	IDirectMusicTimeSigTrackImpl_InitPlay,
	IDirectMusicTimeSigTrackImpl_EndPlay,
	IDirectMusicTimeSigTrackImpl_Play,
	IDirectMusicTimeSigTrackImpl_GetParam,
	(void*)0xdeaf510a, /* SetParam */
	IDirectMusicTimeSigTrackImpl_IsParamSupported,
	(void*)0xdeaf510c, /* AddNotificationType */
	(void*)0xdeaf510d, /* RemoveNotificationType */
	(void*)0xdeaf510e, /* Clone */
	(void*)0xdeaf510f, /* PlayEx */
	(void*)0xdeaf5110, /* GetParamEx */
	(void*)0xdeaf5111, /* SetParamEx */
	(void*)0xdeaf5112, /* Compose */
	(void*)0xdeaf5113  /* Join */
};

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

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

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

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

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

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

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

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

static ICOM_VTABLE(IPersistStream) dmpsvt =
{
	ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
	IDirectMusicTimeSigTrackStream_QueryInterface,
	IDirectMusicTimeSigTrackStream_AddRef,
	IDirectMusicTimeSigTrackStream_Release,
	IDirectMusicTimeSigTrackStream_GetClassID,
	IDirectMusicTimeSigTrackStream_IsDirty,
	IDirectMusicTimeSigTrackStream_Load,
	IDirectMusicTimeSigTrackStream_Save,
	IDirectMusicTimeSigTrackStream_GetSizeMax
};

static HRESULT WINAPI IDirectMusicTimeSigTrackImpl_ParseChunk(
	IDirectMusicTimeSigTrackImpl* 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_TIMESIGNATURE_ITEM) */
	if (itemsize != sizeof(DMUS_IO_TIMESIGNATURE_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_TIMESIGNATURE_ITEM));
	This->count = count;
	for (u=0; u<count; u++) {
		FIELD_READ(This->data[u], lTime);
		FIELD_READ(This->data[u], bBeatsPerMeasure);
		FIELD_READ(This->data[u], bBeat);
		FIELD_READ(This->data[u], wGridsPerBeat);
		TRACE("at %ld, time signature is %d,%d,%d\n", This->data[u].lTime,
		      This->data[u].bBeatsPerMeasure, This->data[u].bBeat, This->data[u].wGridsPerBeat);
	}

	return S_OK;
}

static HRESULT WINAPI IDirectMusicTimeSigTrackImpl_ParseList(
	IDirectMusicTimeSigTrackImpl* This,
	HMMIO hmmio,
	LPMMCKINFO pck)
{
	MMCKINFO ck;
	MMRESULT mmres;
	DWORD count = 0;

	while (!(mmres = mmioDescend(hmmio, &ck, pck, 0))) {
		HRESULT hr;
		if (ck.ckid != DMUS_FOURCC_TIMESIG_CHUNK) {
			ERR("Time Signature chunk not found\n");
			return E_FAIL;
		}
		if (count++) {
			ERR("More than one time signature chunk found!\n");
			return E_FAIL;
		}
		hr = IDirectMusicTimeSigTrackImpl_ParseChunk(This, hmmio, &ck);
		if (FAILED(hr)) return hr;
		mmioAscend(hmmio, &ck, 0);
	}
	if (mmres != MMIOERR_CHUNKNOTFOUND) {
		ERR("mmio error %d\n", mmres);
		return E_FAIL;
	}
	return S_OK;
}

static HRESULT WINAPI IDirectMusicTimeSigTrackImpl_ParseStream(
	IDirectMusicTimeSigTrackImpl* 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 == FOURCC_LIST && ck.fccType == DMUS_FOURCC_TIMESIGTRACK_LIST) {
		/* DirectX 8 style track */
		TRACE("Time Signature Track list found\n");
		hr = IDirectMusicTimeSigTrackImpl_ParseList(This, hmmio, &ck);
	}
	else if (ck.ckid == DMUS_FOURCC_TIMESIGNATURE_TRACK) {
		/* DirectX 6/7 style track */
		TRACE("Time Signature Track found\n");
		hr = IDirectMusicTimeSigTrackImpl_ParseChunk(This, hmmio, &ck);
	}
	else {
		ERR("Time Signature Track not found (found %08lx)\n", ck.ckid);
		hr = E_FAIL;
	}
	mmioAscend(hmmio, &ck, 0);
	DMIME_mmioClose(hmmio);
	return hr;
}


/***********************************************************************
 *		DirectMusicTimeSigTrackCreate 
 */
HRESULT DirectMusicTimeSigTrackCreate(REFGUID lpGUID, IUnknown **ppObj, IUnknown *pUnkOuter)
{
	IDirectMusicTimeSigTrackImpl** ippDM=(IDirectMusicTimeSigTrackImpl**)ppObj;

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

	if (ippDM == NULL)
		return E_INVALIDARG;

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

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

	return DS_OK;
}
