/*		DirectMusic
 *
 * Copyright 2002 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);

/*******************************************************************************
 * Utility routines
 */

static LRESULT CALLBACK DMIME_MMIOProc(LPSTR lpmmioinfo, UINT uMessage,
				       LPARAM lParam1, LPARAM lParam2)
{
	LPMMIOINFO info = (LPMMIOINFO)lpmmioinfo;
	IStream* pStream = (IStream*)info->adwInfo[1];
	HRESULT hr;

	switch (uMessage) {
	case MMIOM_OPEN: {
		LARGE_INTEGER ofs;
		ULARGE_INTEGER pos;
		pStream = (IStream*)info->adwInfo[0];
		/* need this crud until we fix our mmio implementation */
		info->adwInfo[1] = info->adwInfo[0];
		info->adwInfo[0] = 0;
		ofs.QuadPart = 0;
		/* we'll pretend that the current position is the beginning of the file */
		hr = IStream_Seek(pStream, ofs, STREAM_SEEK_CUR, &pos);
		if (FAILED(hr)) {
			ERR("stream get position failed, hr=%08lx\n", hr);
			return -1;
		}
		TRACE("initial position=%Ld\n", pos.QuadPart);
		info->adwInfo[2] = pos.s.LowPart;
		info->adwInfo[3] = pos.s.HighPart;
		return 0;
	}
	case MMIOM_CLOSE: {
		/* should we do any seeking here? */
		return 0;
	}
	case MMIOM_READ: {
		ULONG ret = 0;
		hr = IStream_Read(pStream, (LPVOID)lParam1, lParam2, &ret);
		if (FAILED(hr)) {
			ERR("stream read (%ld bytes) failed, hr=%08lx\n", lParam2, hr);
			return -1;
		}
		/* TRACE("read %ld bytes (of %ld)\n", ret, lParam2); */
		info->lDiskOffset += ret;
		return ret;
	}
	case MMIOM_WRITE:
	case MMIOM_WRITEFLUSH: {
		ULONG ret = 0;
		hr = IStream_Write(pStream, (LPVOID)lParam1, lParam2, &ret);
		if (FAILED(hr)) {
			ERR("stream write (%ld bytes) failed, hr=%08lx\n", lParam2, hr);
			return -1;
		}
		/* TRACE("wrote %ld bytes (of %ld)\n", ret, lParam2); */
		info->lDiskOffset += ret;
		return ret;
	}
	case MMIOM_SEEK: {
		LARGE_INTEGER ofs;
		ULARGE_INTEGER nofs, org;
		org.s.LowPart = info->adwInfo[2];
		org.s.HighPart = info->adwInfo[3];
		ofs.s.LowPart = lParam1;
		ofs.s.HighPart = 0;
		if (lParam2 == STREAM_SEEK_SET) ofs.QuadPart += org.QuadPart;
		hr = IStream_Seek(pStream, ofs, lParam2, &nofs);
		if (FAILED(hr)) {
			ERR("stream seek failed, hr=%08lx\n", hr);
			return -1;
		}
		nofs.QuadPart -= org.QuadPart;
		/* TRACE("seeked to %ld\n", nofs.s.LowPart); */
		info->lDiskOffset = nofs.s.LowPart;
		return nofs.s.LowPart;
	}
	default:
		FIXME("unexpected message %d\n", uMessage);
		return 0;
	}
}

HMMIO DMIME_mmioOpen(IStream* pStream)
{
	MMIOINFO info;
	HMMIO hmmio;
	memset(&info, 0, sizeof(info));
	info.pIOProc = DMIME_MMIOProc;
	info.adwInfo[0] = (DWORD)pStream;
	hmmio = mmioOpenW(NULL, &info, MMIO_READ);
	if (!hmmio) ERR("couldn't open MMIO stream\n");
	return hmmio;
}

MMRESULT DMIME_mmioClose(HMMIO hmmio)
{
	return mmioClose(hmmio, 0); /* MMIO_FHOPEN? */
}

HRESULT DMIME_CloneStream(HMMIO hmmio, IStream** ppStream)
{
	MMIOINFO info;
	MMRESULT mmres;
	IStream* pStream;
	LARGE_INTEGER ofs;
	HRESULT hr;

	mmres = mmioGetInfo(hmmio, &info, 0);
	if (mmres) {
		ERR("couldn't get MMIO info, err=%d\n", mmres);
		return E_FAIL;
	}
	pStream = (IStream*)info.adwInfo[1];
	hr = IStream_Clone(pStream, ppStream);
	if (FAILED(hr)) return hr;
	/* seek cloned stream to the logical mmio position */
	ofs.s.LowPart = info.adwInfo[2];
	ofs.s.HighPart = info.adwInfo[3];
	if (info.pchBuffer) ofs.QuadPart += info.lBufOffset + info.pchNext - info.pchBuffer;
	else ofs.QuadPart += info.lDiskOffset;
	IStream_Seek(*ppStream, ofs, STREAM_SEEK_SET, NULL);
	return hr;
}

HRESULT DMIME_CommonChunk(HMMIO hmmio, LPMMCKINFO ck, LPDMUS_OBJECTDESC pDesc)
{
	switch (ck->ckid) {
	case DMUS_FOURCC_GUID_CHUNK:
		FIELD_READ(*pDesc, guidObject);
		TRACE("GUID: %s\n", debugstr_guid(&pDesc->guidObject));
		pDesc->dwValidData |= DMUS_OBJ_OBJECT;
		break;
	case DMUS_FOURCC_VERSION_CHUNK:
		FIXME("version chunk\n");
		break;
	case FOURCC_LIST:
		switch (ck->fccType) {
		case DMUS_FOURCC_INFO_LIST:
			FIXME("INFO list\n");
			break;
		case DMUS_FOURCC_UNFO_LIST:
			FIXME("UNFO list\n");
			break;
		default:
			return S_FALSE;
		}
		break;
	default:
		return S_FALSE;
	}
	return S_OK;
}

HRESULT DMIME_LoadReference(HMMIO hmmio, LPMMCKINFO pck, REFIID riid, void** ppObj)
{
	MMIOINFO info;
	MMCKINFO ck;
	MMRESULT mmres;
	IStream* pStream;
	IDirectMusicGetLoader* pGetLoader;
	IDirectMusicLoader* pLoader;
	HRESULT hr;
	DMUS_IO_REFERENCE hdr;
	DMUS_OBJECTDESC desc;

	mmres = mmioGetInfo(hmmio, &info, 0);
	if (mmres) {
		ERR("couldn't get MMIO info, err=%d\n", mmres);
		return E_FAIL;
	}
	pStream = (IStream*)info.adwInfo[1];

	/* Any stream used to load DirectMusic objects must have a
	 * IDirectMusicGetLoader interface to get the loader to use
	 * to load referenced objects with... */
	hr = IStream_QueryInterface(pStream, &IID_IDirectMusicGetLoader, (void**)&pGetLoader);
	if (FAILED(hr)) {
		ERR("couldn't get IDirectMusicGetLoader interface\n");
		return hr;
	}
	/* Get the loader from it */
	hr = IDirectMusicGetLoader_GetLoader(pGetLoader, &pLoader);
	IDirectMusicGetLoader_Release(pGetLoader);
	if (FAILED(hr)) {
		ERR("couldn't get IDirectMusicLoader object\n");
		return hr;
	}

	/* We now have a loader, now read in the object data it needs */

	memset(&desc, 0, sizeof(desc));
	desc.dwSize = sizeof(desc);

	mmres = mmioDescend(hmmio, &ck, pck, 0);
	if (mmres) {
		ERR("mmio error %d\n", mmres);
		return E_FAIL;
	}
	/* first chunk should be reference header */
	if (ck.ckid != DMUS_FOURCC_REF_CHUNK) {
		ERR("Reference chunk not found\n");
		return E_FAIL;
	}
	STRUCT_READ_START(hdr)
	STRUCT_READ(hdr, guidClassID)
	STRUCT_READ(hdr, dwValidData)
	STRUCT_READ_END(hdr)
	mmioAscend(hmmio, &ck, 0);
	TRACE("Reference class: %s\n", debugstr_guid(&hdr.guidClassID));
	memcpy(&desc.guidClass, &hdr.guidClassID, sizeof(desc.guidClass));
	desc.dwValidData |= DMUS_OBJ_CLASS;
	/* read remaining chunks */
	while (!(mmres = mmioDescend(hmmio, &ck, pck, 0))) {
		HRESULT hr = DMIME_CommonChunk(hmmio, &ck, &desc);
		if (hr == S_OK) {
			mmioAscend(hmmio, &ck, 0);
			continue;
		}
		switch (ck.ckid) {
		case DMUS_FOURCC_DATE_CHUNK:
			FIELD_READ(desc, ftDate);
			desc.dwValidData |= DMUS_OBJ_DATE;
			break;
		case DMUS_FOURCC_NAME_CHUNK:
			STRING_READ(desc, wszName);
			TRACE("Name: %s\n", debugstr_w(desc.wszName));
			desc.dwValidData |= DMUS_OBJ_DATE;
			break;
		case DMUS_FOURCC_FILE_CHUNK:
			STRING_READ(desc, wszFileName);
			TRACE("Filename: %s\n", debugstr_w(desc.wszFileName));
			desc.dwValidData |= DMUS_OBJ_FILENAME;
			break;
		case DMUS_FOURCC_CATEGORY_CHUNK:
			STRING_READ(desc, wszCategory);
			TRACE("Category: %s\n", debugstr_w(desc.wszCategory));
			desc.dwValidData |= DMUS_OBJ_CATEGORY;
			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;
	}

	/* object information gathered, try to load it */
	hr = IDirectMusicLoader_GetObject(pLoader, &desc, riid, ppObj);

	IDirectMusicLoader_Release(pLoader);
	return hr;
}

/***********************************************************************
 *		DllCanUnloadNow (DMIME.@)
 */
HRESULT WINAPI DMIME_DllCanUnloadNow(void)
{
    FIXME("(void): stub\n");

    return S_FALSE;
}


/*******************************************************************************
 * DirectMusic ClassFactory
 */
typedef struct
{
    /* IUnknown fields */
    ICOM_VFIELD(IClassFactory);
    DWORD                       ref;
    /* IClassFactory fields */
    REFCLSID			rclsid;
    DMIME_ClassCreator		creator;
} IClassFactoryImpl;

static HRESULT WINAPI 
DMCF_QueryInterface(LPCLASSFACTORY iface,REFIID riid,LPVOID *ppobj) {
	ICOM_THIS(IClassFactoryImpl,iface);

	FIXME("(%p)->(%s,%p),stub!\n",This,debugstr_guid(riid),ppobj);
	return E_NOINTERFACE;
}

static ULONG WINAPI
DMCF_AddRef(LPCLASSFACTORY iface) {
	ICOM_THIS(IClassFactoryImpl,iface);
	return ++(This->ref);
}

static ULONG WINAPI DMCF_Release(LPCLASSFACTORY iface) {
	ICOM_THIS(IClassFactoryImpl,iface);
	/* static class, won't be  freed */
	return --(This->ref);
}

static HRESULT WINAPI DMCF_CreateInstance(
	LPCLASSFACTORY iface,LPUNKNOWN pOuter,REFIID riid,LPVOID *ppobj
) {
	ICOM_THIS(IClassFactoryImpl,iface);
	IUnknown *obj = NULL;
	HRESULT hres;

	TRACE("(%p)->(%p,%s,%p)\n",This,pOuter,debugstr_guid(riid),ppobj);
	hres = This->creator(riid, &obj, pOuter);
	if (FAILED(hres)) return hres;
	hres = IUnknown_QueryInterface(obj, riid, ppobj);
	IUnknown_Release(obj);
	return hres;
}

static HRESULT WINAPI DMCF_LockServer(LPCLASSFACTORY iface,BOOL dolock) {
	ICOM_THIS(IClassFactoryImpl,iface);
	FIXME("(%p)->(%d),stub!\n",This,dolock);
	return S_OK;
}

static ICOM_VTABLE(IClassFactory) DMCF_Vtbl = {
	ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
	DMCF_QueryInterface,
	DMCF_AddRef,
	DMCF_Release,
	DMCF_CreateInstance,
	DMCF_LockServer
};
static IClassFactoryImpl DMIME_Classes[] = {
    {&DMCF_Vtbl, 1, &CLSID_DirectMusicPerformance,   DirectMusicPerformanceCreate},
    {&DMCF_Vtbl, 1, &CLSID_DirectMusicSegment,       DirectMusicSegmentCreate},
    {&DMCF_Vtbl, 1, &CLSID_DirectMusicTempoTrack,    DirectMusicTempoTrackCreate},
    {&DMCF_Vtbl, 1, &CLSID_DirectMusicTimeSigTrack,  DirectMusicTimeSigTrackCreate},
    {&DMCF_Vtbl, 1, &CLSID_DirectMusicWaveTrack,     DirectMusicWaveTrackCreate},
    {&DMCF_Vtbl, 1, &CLSID_DirectMusicSeqTrack,      DirectMusicSeqTrackCreate}
};

/*******************************************************************************
 * DllGetClassObject [DMIME.@]
 * Retrieves class object from a DLL object
 *
 * PARAMS
 *    rclsid [I] CLSID for the class object
 *    riid   [I] Reference to identifier of interface for class object
 *    ppv    [O] Address of variable to receive interface pointer for riid
 *
 * RETURNS
 *    Success: S_OK
 *    Failure: CLASS_E_CLASSNOTAVAILABLE, E_OUTOFMEMORY, E_INVALIDARG,
 *             E_UNEXPECTED
 */
DWORD WINAPI DMIME_DllGetClassObject(REFCLSID rclsid,REFIID riid,LPVOID *ppv)
{
    TRACE("(%p,%p,%p)\n", debugstr_guid(rclsid), debugstr_guid(riid), ppv);
    if ( IsEqualIID( &IID_IClassFactory, riid ) ) {
        unsigned u;
        for (u=0; u<(sizeof(DMIME_Classes)/sizeof(IClassFactoryImpl)); u++)
            if ( IsEqualCLSID( DMIME_Classes[u].rclsid, rclsid ) ) {
                *ppv = (LPVOID)&DMIME_Classes[u];
                IClassFactory_AddRef((IClassFactory*)*ppv);
                return S_OK;
            }
        return CLASS_E_CLASSNOTAVAILABLE;
    }

    FIXME("(%p,%p,%p): no interface found.\n", debugstr_guid(rclsid), debugstr_guid(riid), ppv);
    return CLASS_E_CLASSNOTAVAILABLE;
}



/***********************************************************************
 *		DllRegisterServer (DMIME.@)
 */
HRESULT WINAPI DMIME_DllRegisterServer(void)
{
    FIXME("(void): stub\n");

    return S_OK;
}

/***********************************************************************
 *		DllUnregisterServer (DMIME.@)
 */
HRESULT WINAPI DMIME_DllUnregisterServer(void)
{
    FIXME("(void): stub\n");

    return S_OK;
}
