/*		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 "dmime_private.h"

WINE_DEFAULT_DEBUG_CHANNEL(dmusic);

typedef struct TrackItem {
    struct TrackItem* next;
    IDirectMusicTrack* pTrack;
    BOOL playing;
    DWORD dwVirtualID;
    void* pStateData;
} TrackItem;

static TrackItem* AllocTrackItem(void)
{
	return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(TrackItem));
}

static void FreeTrackItem(TrackItem* item)
{
	if (item->pTrack) {
		if (item->playing)
			IDirectMusicTrack_EndPlay(item->pTrack, item->pStateData);
		IDirectMusicTrack_Release(item->pTrack);
	}
	HeapFree(GetProcessHeap(), 0, item);
}

static void FreeTrackItems(TrackItem* item)
{
	while (item) {
		TrackItem* next = item->next;
		FreeTrackItem(item);
		item = next;
	}
}

/*****************************************************************************
 * IDirectMusicSegmentState implementation structure
 */
typedef struct IDirectMusicSegmentStateImpl IDirectMusicSegmentStateImpl;
struct IDirectMusicSegmentStateImpl
{
    /* IUnknown fields */
    ICOM_VFIELD(IDirectMusicSegmentState8);
    DWORD ref;
    /* IDirectMusicSegmentState fields */
    IDirectMusicPerformance8* pPerf;
    IDirectMusicSegment8* pSeg;
    TrackItem* tracks;
    BOOL playing, start_sent, almostend_sent, end_sent;
    MUSIC_TIME mtStart;
    MUSIC_TIME mtEnd;
    MUSIC_TIME mtLast;
};

static HRESULT WINAPI IDirectMusicSegmentStateImpl_QueryInterface(
	IDirectMusicSegmentState8* iface,REFIID riid,LPVOID *ppobj
) {
	ICOM_THIS(IDirectMusicSegmentStateImpl,iface);

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

static ULONG WINAPI IDirectMusicSegmentStateImpl_AddRef(IDirectMusicSegmentState8* iface) {
	ICOM_THIS(IDirectMusicSegmentStateImpl,iface);
	return ++(This->ref);
}

static ULONG WINAPI IDirectMusicSegmentStateImpl_Release(IDirectMusicSegmentState8* iface) {
	ICOM_THIS(IDirectMusicSegmentStateImpl,iface);
	TRACE("(%p), ref was %ld\n",This,This->ref);
	if (!--(This->ref)) {
		FreeTrackItems(This->tracks);
		IDirectMusicSegment8_Release(This->pSeg);
		IDirectMusicPerformance8_Release(This->pPerf);
		HeapFree(GetProcessHeap(),0,This);
		return 0;
	}
	return This->ref;
}

static HRESULT WINAPI IDirectMusicSegmentStateImpl_GetSegment(
	IDirectMusicSegmentState8* iface,
	IDirectMusicSegment** ppSegment)
{
	ICOM_THIS(IDirectMusicSegmentStateImpl,iface);
	TRACE("(%p)->(%p)\n", This, ppSegment);
	IDirectMusicSegment8_AddRef(This->pSeg);
	*ppSegment = (IDirectMusicSegment*)This->pSeg;
	return S_OK;
}

static HRESULT WINAPI IDirectMusicSegmentStateImpl_GetStartTime(
	IDirectMusicSegmentState8* iface,
	MUSIC_TIME* pmtStart)
{
	ICOM_THIS(IDirectMusicSegmentStateImpl,iface);
	TRACE("(%p)->(%p)\n", This, pmtStart);
	if (!This->playing) {
		/* we should probably provide one in DirectMusicCreateSegmentState,
		 * instead of finding it in DirectMusicMarkSegmentStatePlaying */
		FIXME("no segment start time available\n");
		return E_FAIL;
	}
	*pmtStart = This->mtStart;
	return S_OK;
}

static ICOM_VTABLE(IDirectMusicSegmentState8) dmssvt = 
{
	ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
	IDirectMusicSegmentStateImpl_QueryInterface,
	IDirectMusicSegmentStateImpl_AddRef,
	IDirectMusicSegmentStateImpl_Release,
	(void*)0xdeaf7005, /* GetRepeats */
	IDirectMusicSegmentStateImpl_GetSegment,
	IDirectMusicSegmentStateImpl_GetStartTime,
	(void*)0xdeaf7008, /* GetSeek */
	(void*)0xdeaf7009, /* GetStartPoint */
	(void*)0xdeaf700a, /* SetTrackConfig */
	(void*)0xdeaf700b  /* GetObjectInPath */
};


/***********************************************************************
 *		DirectMusicCreateSegmentState
 */
HRESULT DirectMusicCreateSegmentState(IDirectMusicPerformance8 *pPerf,
				      IDirectMusicSegment8 *pSeg,
				      DWORD dwFlags,
				      DWORD dwVirtualID,
				      IDirectMusicSegmentState8 **ppObj)
{
	IDirectMusicSegmentStateImpl** ippDM=(IDirectMusicSegmentStateImpl**)ppObj;
	DWORD dwIndex;
	TrackItem* track;
	HRESULT hr;

	TRACE("(%p,%p,0x%lx,0x%lx,%p)\n",pPerf,pSeg,dwFlags,dwVirtualID,ippDM);

	if (ippDM == NULL)
		return E_INVALIDARG;

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

	ICOM_VTBL(*ippDM)	= &dmssvt;
	(*ippDM)->ref		= 1;
	IDirectMusicPerformance8_AddRef(pPerf);
	(*ippDM)->pPerf		= pPerf;
	IDirectMusicSegment8_AddRef(pSeg);
	(*ippDM)->pSeg		= pSeg;

	/* Enumerate all tracks in segment */
	dwIndex = 0;
	track = NULL;
	while (TRUE) {
		IDirectMusicTrack* pTrack;
		TrackItem* new_track;
		hr = IDirectMusicSegment8_GetTrack(pSeg, &GUID_NULL, (DWORD)-1, dwIndex, &pTrack);
		if (FAILED(hr)) break;
		new_track = AllocTrackItem();
		new_track->pTrack = pTrack;
		if (track) track->next = new_track;
		else (*ippDM)->tracks = new_track;
		track = new_track;
		dwIndex++;
	}

	/* Initialize playback */
	for (track = (*ippDM)->tracks; track; track = track->next) {
		track->dwVirtualID = dwVirtualID++;
		hr = IDirectMusicTrack_InitPlay(track->pTrack,
						(IDirectMusicSegmentState*)*ppObj,
						(IDirectMusicPerformance*)pPerf,
						&track->pStateData,
						track->dwVirtualID,
						dwFlags);
		if (SUCCEEDED(hr)) track->playing = TRUE;
	}

	return DS_OK;
}

HRESULT DirectMusicMarkSegmentStatePlaying(IDirectMusicSegmentState8* iface, BOOL playing, REFERENCE_TIME rtTime, DWORD dwFlags)
{
	ICOM_THIS(IDirectMusicSegmentStateImpl,iface);
        HRESULT hr;

	TRACE("(%p,%d,%Ld,0x%lx)\n", This, playing, rtTime, dwFlags);
	This->playing = playing;
	if (playing)
	{
		MUSIC_TIME mtLen = 0;
		This->mtStart = 0;
		IDirectMusicPerformance8_ReferenceToMusicTime(This->pPerf, rtTime, &This->mtStart);
		IDirectMusicSegment8_GetLength(This->pSeg, &mtLen);
		This->mtEnd = This->mtStart + mtLen;
		This->mtLast = This->mtStart;
	}
	else if (This->start_sent && !This->end_sent) {
		DMUS_NOTIFICATION_PMSG *pMsg;
		TRACE("sending DMUS_NOTIFICATION_SEGABORT\n");
		hr = IDirectMusicPerformance8_AllocPMsg(This->pPerf, sizeof(*pMsg), (DMUS_PMSG**)&pMsg);
		if (FAILED(hr))
			return S_FALSE;
		/* DMUS_PMSG_PART */
		pMsg->dwType = DMUS_PMSGT_NOTIFICATION;
		pMsg->rtTime = rtTime;
		pMsg->mtTime = This->mtStart;
		pMsg->dwPChannel = 0;
		pMsg->dwVirtualTrackID = 0;
		pMsg->dwFlags = DMUS_PMSGF_REFTIME;
		pMsg->pTool = NULL;
		pMsg->pGraph = NULL;
		pMsg->dwVoiceID = 0;
		pMsg->dwGroupID = 0xFFFFFFF;
		IDirectMusicSegmentState8_AddRef(iface);
		pMsg->punkUser = (IUnknown*)iface;

		/* DMUS_NOTIFICATION_PMSG proper..  */
		pMsg->guidNotificationType = GUID_NOTIFICATION_SEGMENT;
		pMsg->dwNotificationOption = DMUS_NOTIFICATION_SEGABORT;
		pMsg->dwField1 = 0;
		pMsg->dwField2 = 0;

		/* Send it */
		hr = IDirectMusicPerformance_SendPMsg(This->pPerf, (DMUS_PMSG*)pMsg);
		if (FAILED(hr)) 
		{
			IDirectMusicPerformance_FreePMsg(This->pPerf, (DMUS_PMSG*)pMsg);
		}

		This->start_sent = FALSE;
		This->almostend_sent = FALSE;
		This->end_sent = FALSE;
	}
	return S_OK;
}

HRESULT DirectMusicIsSegmentStatePlaying(IDirectMusicSegmentState8* iface)
{
	ICOM_THIS(IDirectMusicSegmentStateImpl,iface);
	return This->playing ? S_OK : S_FALSE;
}

HRESULT DirectMusicPlaySegmentState(IDirectMusicSegmentState8* iface, MUSIC_TIME mtEnd)
{
	ICOM_THIS(IDirectMusicSegmentStateImpl,iface);
	MUSIC_TIME mtStart, mtOffset;
	TrackItem* track;
	HRESULT hr;

	if (!This->playing) return S_FALSE;

	TRACE("(%p,%ld)\n", This, mtEnd);

	if (mtEnd < This->mtStart) {
		/* not started yet */
		return S_OK;
	}

	mtStart = This->mtLast;
	This->mtLast = mtEnd;

	if (!This->start_sent) {
		DMUS_NOTIFICATION_PMSG *pMsg;
		TRACE("sending DMUS_NOTIFICATION_SEGSTART\n");
		hr = IDirectMusicPerformance8_AllocPMsg(This->pPerf, sizeof(*pMsg), (DMUS_PMSG**)&pMsg);
		if (FAILED(hr))
			return S_FALSE;
		/* DMUS_PMSG_PART */
		pMsg->dwType = DMUS_PMSGT_NOTIFICATION;
		pMsg->mtTime = This->mtStart;
		pMsg->dwPChannel = 0;
		pMsg->dwVirtualTrackID = 0;
		pMsg->dwFlags = DMUS_PMSGF_MUSICTIME;
		pMsg->pTool = NULL;
		pMsg->pGraph = NULL;
		pMsg->dwVoiceID = 0;
		pMsg->dwGroupID = 0xFFFFFFF;
		IDirectMusicSegmentState8_AddRef(iface);
		pMsg->punkUser = (IUnknown*)iface;

		/* DMUS_NOTIFICATION_PMSG proper..  */
		pMsg->guidNotificationType = GUID_NOTIFICATION_SEGMENT;
		pMsg->dwNotificationOption = DMUS_NOTIFICATION_SEGSTART;
		pMsg->dwField1 = 0;
		pMsg->dwField2 = 0;

		/* Send it */
		hr = IDirectMusicPerformance_SendPMsg(This->pPerf, (DMUS_PMSG*)pMsg);
		if (FAILED(hr)) 
		{
			IDirectMusicPerformance_FreePMsg(This->pPerf, (DMUS_PMSG*)pMsg);
		}
		This->start_sent = TRUE;
	}

	if (mtEnd >= This->mtEnd && !This->almostend_sent) {
		DMUS_NOTIFICATION_PMSG *pMsg;
		TRACE("sending DMUS_NOTIFICATION_SEGALMOSTEND\n");
		hr = IDirectMusicPerformance8_AllocPMsg(This->pPerf, sizeof(*pMsg), (DMUS_PMSG**)&pMsg);
		if (FAILED(hr))
			return S_FALSE;
		/* DMUS_PMSG_PART */
		pMsg->dwType = DMUS_PMSGT_NOTIFICATION;
		pMsg->mtTime = This->mtStart;
		pMsg->dwPChannel = 0;
		pMsg->dwVirtualTrackID = 0;
		pMsg->dwFlags = DMUS_PMSGF_MUSICTIME;
		pMsg->pTool = NULL;
		pMsg->pGraph = NULL;
		pMsg->dwVoiceID = 0;
		pMsg->dwGroupID = 0xFFFFFFF;
		IDirectMusicSegmentState8_AddRef(iface);
		pMsg->punkUser = (IUnknown*)iface;

		/* DMUS_NOTIFICATION_PMSG proper..  */
		pMsg->guidNotificationType = GUID_NOTIFICATION_SEGMENT;
		pMsg->dwNotificationOption = DMUS_NOTIFICATION_SEGALMOSTEND;
		pMsg->dwField1 = 0;
		pMsg->dwField2 = 0;

		/* Send it */
		hr = IDirectMusicPerformance_SendPMsg(This->pPerf, (DMUS_PMSG*)pMsg);
		if (FAILED(hr)) 
		{
			IDirectMusicPerformance_FreePMsg(This->pPerf, (DMUS_PMSG*)pMsg);
		}
		This->almostend_sent = TRUE;
	}

	if (mtStart >= This->mtEnd && !This->end_sent) {
		DMUS_NOTIFICATION_PMSG *pMsg;
		TRACE("sending DMUS_NOTIFICATION_SEGEND\n");
		hr = IDirectMusicPerformance8_AllocPMsg(This->pPerf, sizeof(*pMsg), (DMUS_PMSG**)&pMsg);
		if (FAILED(hr))
			return S_FALSE;
		/* DMUS_PMSG_PART */
		pMsg->dwType = DMUS_PMSGT_NOTIFICATION;
		pMsg->mtTime = This->mtStart;
		pMsg->dwPChannel = 0;
		pMsg->dwVirtualTrackID = 0;
		pMsg->dwFlags = DMUS_PMSGF_MUSICTIME;
		pMsg->pTool = NULL;
		pMsg->pGraph = NULL;
		pMsg->dwVoiceID = 0;
		pMsg->dwGroupID = 0xFFFFFFF;
		IDirectMusicSegmentState8_AddRef(iface);
		pMsg->punkUser = (IUnknown*)iface;

		/* DMUS_NOTIFICATION_PMSG proper..  */
		pMsg->guidNotificationType = GUID_NOTIFICATION_SEGMENT;
		pMsg->dwNotificationOption = DMUS_NOTIFICATION_SEGEND;
		pMsg->dwField1 = 0;
		pMsg->dwField2 = 0;

		/* Send it */
		hr = IDirectMusicPerformance_SendPMsg(This->pPerf, (DMUS_PMSG*)pMsg);
		if (FAILED(hr)) 
		{
			IDirectMusicPerformance_FreePMsg(This->pPerf, (DMUS_PMSG*)pMsg);
		}
		This->end_sent = TRUE;
	}

	if (mtStart >= This->mtEnd) {
		/* nothing more to play */
		return DMUS_S_END;
	}

	/* doesn't handle loops or start points yet */
	mtOffset = This->mtStart;
	if (mtOffset <= mtStart) mtStart -= mtOffset; else mtStart = 0;
	mtEnd -= mtOffset;
	for (track = This->tracks; track; track = track->next) {
		hr = IDirectMusicTrack_Play(track->pTrack,
					    track->pStateData,
					    mtStart,
					    mtEnd,
					    mtOffset,
					    0,
					    (IDirectMusicPerformance*)This->pPerf,
					    (IDirectMusicSegmentState*)This,
					    track->dwVirtualID);
		if (SUCCEEDED(hr)) track->playing = TRUE;
	}

	return S_OK;
}
