#include "SNDinc.h"

#include "sndthrd.h"

#if defined(_DLL_COMPILATION_MODE)
#include "snddll.h"
#endif

#include "sndxd.h"

#include "sndres.h"
#include "sndstrm.h"

typedef struct STREAMING_BUFFER_ *pSTREAMING_BUFFER;
typedef struct STREAMING_VOICE_ *pSTREAMING_VOICE;

SNDLST2_M_DynamicUseListOf(pSTREAMING_BUFFER);
SNDLST2_M_DynamicUseListOf(pSTREAMING_VOICE);

typedef struct STREAMING_BUFFER_{
	void*			ptrData;
	unsigned long	ulNbSamples;
	long			lId;
	SNDLST2_M_DynamicElementDeclaration(pSTREAMING_BUFFER)
} STREAMING_BUFFER;

typedef struct STREAMING_VOICE_{
	long			lIdBufferClient;
	long			lSampleToBytesShift;
	tdhSndFile		hFile;
	long			lLastPushed;			//id du dernier data-buffer
	SndBool			bEnding;		//TRUE quand il n'y a plus de données à pusher
	SndBool			bEnded;			//TRUE quand la lectre est finie
	SndBool			bMustLoop;		//TRUE si res loopante
    unsigned long	ulByteLoop;	//offset du point de rebouclage dans le fichier
	unsigned long	ulByteMax;	//offset de dernier octet dans le fichier
	unsigned long	ulNextPtr;	//offset du prochain octet à lire dans le fichier
	SND_td_pfn_vSoundCallback pfnCallback;
	long			lParCallback;
	SndBool			bWaitingCallback;
	SndBool			bStereo;
	SNDLST2_M_DynamicAnchorDeclaration(pSTREAMING_BUFFER) pList;
	SNDLST2_M_DynamicElementDeclaration(pSTREAMING_VOICE)
} STREAMING_VOICE;

typedef struct LISTE_STREAMING_VOICE_{
	SNDLST2_M_DynamicAnchorDeclaration(pSTREAMING_VOICE) pList;
} LISTE_STREAMING_VOICE;

static LISTE_STREAMING_VOICE ListStreaming;
static SndBool bInitDone=FALSE;

#define SIZE_DATABUFFER 5120
#define NB_BUFFER_AVANCE 4

//------------
void purge_databuffer(pSTREAMING_BUFFER databuffer)
{
	SNDLST2_M_DynamicIsolate(databuffer);
	SND_fn_vFreeSnd(databuffer->ptrData);
	SND_fn_vFreeSndEx(E_ucSndBlockMain,databuffer);
}

void push_databuffer(pSTREAMING_VOICE streaming)
{
	pSTREAMING_BUFFER	databuffer;
	unsigned long		nb_lus,nb_to_read,nb_left;
	SND_tdstStackBuffer stackbuffer;

	if (!streaming->bEnding)
	{
		databuffer=SND_fn_pvMallocSndEx(E_ucSndBlockMain,sizeof(STREAMING_BUFFER));
		SNDLST2_M_DynamicInitElement(databuffer);
		databuffer->ulNbSamples=SIZE_DATABUFFER>>streaming->lSampleToBytesShift;
		databuffer->ptrData=SND_fn_pvMallocSnd(SIZE_DATABUFFER);
        
		nb_to_read=SIZE_DATABUFFER;

		if ((nb_lus=SND_fn_dwReadFileSnd(streaming->hFile
			,min(nb_to_read,streaming->ulByteMax-streaming->ulNextPtr)
			,databuffer->ptrData)) != nb_to_read)
		{//on est arrivé à la fin du fichier
			if (streaming->bMustLoop)
			{
		        streaming->ulNextPtr=streaming->ulByteLoop;
				SND_fn_dwSeekFileSnd(streaming->hFile,streaming->ulNextPtr,SEEKFILESND_BEGIN);

				nb_left=nb_to_read-nb_lus;
				while (nb_left>0)
				{//reboucler tant que le buffer n'est pas plein
					streaming->ulNextPtr=streaming->ulByteLoop;
					nb_lus=SND_fn_dwReadFileSnd(streaming->hFile
						,min(nb_left,streaming->ulByteMax-streaming->ulNextPtr)
						,(char*)databuffer->ptrData+nb_to_read-nb_left);
					nb_left-=nb_lus;

					if (!nb_lus)
						break;
				}
				streaming->ulNextPtr=streaming->ulByteLoop+nb_lus;
			}
			else
			{
				databuffer->ulNbSamples=nb_lus>>streaming->lSampleToBytesShift;
				streaming->lLastPushed=databuffer->lId;
				SND_fn_vCloseFileSnd(streaming->hFile);
				streaming->hFile=OPENFILESND_FAILED;
				streaming->bEnding=TRUE;
			}
		}
		else
		{//on n'est pas encore à la fin du fichier
			streaming->ulNextPtr+=nb_to_read;
		}

					
		SNDLST2_M_DynamicAddTail(&streaming->pList,databuffer);

		stackbuffer.ulNbSamples=databuffer->ulNbSamples;
		stackbuffer.ptrData=databuffer->ptrData;
		databuffer->lId=SND_fn_lPushBufferExSxd(streaming->lIdBufferClient,&stackbuffer);

		if (streaming->bEnding)
			streaming->lLastPushed=databuffer->lId;

		//_RPTF1(_CRT_WARN,"Push %d\n",databuffer->lId);
	}
	else
	{
		if (streaming->bWaitingCallback)
		{
			streaming->bWaitingCallback=FALSE;

			(*streaming->pfnCallback)(streaming->lParCallback);
		}
	}
}

void SND_CALL StreamingCallback(long user_id,long poped_id)
{
	pSTREAMING_VOICE	streaming;
	pSTREAMING_BUFFER	databuffer;
	int					i;
	SndBool				found=FALSE;

	//_RPTF1(_CRT_WARN,"Callback %d\n",poped_id);

	SND_fn_vEnterCriticalSectionThreadSnd();

	streaming=(pSTREAMING_VOICE)user_id;
	databuffer=(pSTREAMING_BUFFER)poped_id;
	SNDLST2_M_DynamicForEachElementOf(&streaming->pList,databuffer,i)
	{
		if (databuffer->lId==poped_id)
		{
			purge_databuffer(databuffer);
			found=TRUE;
			break;
		}
	}

	snd_assert(found);

	if (!streaming->bEnding)
		push_databuffer(streaming);
	else
	{
		if (streaming->lLastPushed==poped_id)
			streaming->bEnded=TRUE;
	}

	SND_fn_vQuitCriticalSectionThreadSnd();
}

void purge_streaming(pSTREAMING_VOICE streaming)
{
	pSTREAMING_BUFFER	pDataBuffer,pDataBufferNext;
	int i;

	snd_assert(streaming->bEnded);

	SND_fn_vDeleteBufferSxd(streaming->lIdBufferClient);
	streaming->lIdBufferClient=C_PLAY_FAILED;

	if (streaming->hFile!=OPENFILESND_FAILED)
		SND_fn_vCloseFileSnd(streaming->hFile);
	streaming->hFile=OPENFILESND_FAILED;

	SNDLST2_M_DynamicForEachMovingElementOf(&streaming->pList,pDataBuffer,pDataBufferNext,i)
	{
		purge_databuffer(pDataBuffer);
	}

	if (streaming->bWaitingCallback)
	{
		streaming->bWaitingCallback=FALSE;

		(*streaming->pfnCallback)(streaming->lParCallback);
	}

	SNDLST2_M_DynamicIsolate(streaming);

	SND_fn_vFreeSndEx(E_ucSndBlockMain,streaming);
}


///
long SND_fn_lPlaySxdStream(tduRefRes res,SoundParam *par,long prio,SND_td_pfn_vSoundCallback fn_callback,long par_callback)
{
	pSTREAMING_VOICE	streaming;
	SND_tdstFormat		format;
	SND_tdstCallback	callback;
	char				filename[256];
	int					i;
	SoundParam			*pstSoundParam;

	if (bInitDone)
	{
		streaming=SND_fn_pvMallocSndEx(E_ucSndBlockMain,sizeof(STREAMING_VOICE));
		SNDLST2_M_DynamicInitElement(streaming);
		SNDLST2_M_DynamicAddTail(&ListStreaming.pList,streaming);

		SNDLST2_M_DynamicInitAnchor(&streaming->pList);

		format.eZip=SAMPLE_PCM;
		format.uFormat.stPCM.ulNbSamples=0;
		format.uFormat.stPCM.uwResolution=res.pstPtr->uRes.stSample.uwResolution;
		format.uFormat.stPCM.uwNbChannels=res.pstPtr->uRes.stSample.uwNbChannels;
		format.uFormat.stPCM.ulFreq=res.pstPtr->uRes.stSample.ulFreq;

		callback.eType=BUFFER_STACK;
		callback.uCallback.CallbackStack=StreamingCallback;
		callback.uInfo.lNbSampleToPush=0;
		
		streaming->bMustLoop=res.pstPtr->uRes.stSample.bLoop;
        streaming->ulByteLoop=res.pstPtr->uRes.stSample.uData.stStream.ulOffsetLoop;
        streaming->ulByteMax=res.pstPtr->uRes.stSample.uData.stStream.ulOffsetLast;
        streaming->ulNextPtr=res.pstPtr->uRes.stSample.uData.stStream.ulOffsetFirst;

        SND_fn_vResolveFileName(res.pstPtr->uRes.stSample.uData.stStream.szFileName,filename);
		streaming->hFile=SND_fn_hOpenFileReadSnd(filename);
		SND_fn_dwSeekFileSnd(streaming->hFile,streaming->ulNextPtr,SEEKFILESND_BEGIN);

		pstSoundParam=SND_fn_pvMallocSnd(SND_M_lGetSizeOfSoundParam(par));
		memcpy(pstSoundParam,par,SND_M_lGetSizeOfSoundParam(par));
		if (format.uFormat.stPCM.uwNbChannels==2)
		{
			pstSoundParam->ucVol=par->ucVol/2;
            pstSoundParam->iFlags|=C_SOUNDPARAM_COEF_VOL;
		}

		if (format.uFormat.stPCM.uwNbChannels==2)
			streaming->bStereo=TRUE;
		else
			streaming->bStereo=FALSE;

		streaming->lLastPushed=C_PLAY_FAILED;
		streaming->bEnded=FALSE;
		streaming->bEnding=FALSE;
		streaming->lSampleToBytesShift=format.uFormat.stPCM.uwResolution/8+format.uFormat.stPCM.uwNbChannels-2;
		streaming->lIdBufferClient=SND_fn_lCreateNewBufferExSxd(&format,&callback,pstSoundParam,(long)streaming);

		SND_fn_vFreeSnd(pstSoundParam);

		if (fn_callback)
		{
			streaming->pfnCallback=fn_callback;
			streaming->lParCallback=par_callback;
			streaming->bWaitingCallback=TRUE;
		}
		else
			streaming->bWaitingCallback=FALSE;

		//lancement des 1° push
		for (i=0;i<NB_BUFFER_AVANCE;i++)
		{
			push_databuffer(streaming);
		}
	
		return (long)streaming;

	/*
        if (buffer_dispo_streaming.bClientActive)
            fn_vKillStream();

        SND_fn_vResolveFileName(res.pstPtr->uRes.stSample.uData.stStream.szFileName,filename);

        buffer_dispo_streaming.hFile=SND_fn_hOpenFileReadSnd(filename);
        buffer_dispo_streaming.bFileOpened=TRUE;
        if (buffer_dispo_streaming.hFile==OPENFILESND_FAILED)
		{
			SND_fn_vDisplayError(E_uwSndCannotOpenFile,filename);
            return C_PLAY_FAILED;
		}
        SND_fn_dwSeekFileSnd(buffer_dispo_streaming.hFile,res.pstPtr->uRes.stSample.uData.stStream.ulOffsetFirst,SEEKFILESND_BEGIN);
		nb_to_read=2*SIZE_FILE_BUFFER;
        nb_lus=SND_fn_dwReadFileSnd(buffer_dispo_streaming.hFile,nb_to_read,hFileBuffer[0]);

        buffer_dispo_streaming.uRes.pstPtr=res.pstPtr;
		buffer_dispo_streaming.bMustLoop=res.pstPtr->uRes.stSample.bLoop;
        buffer_dispo_streaming.ulByteLoop=res.pstPtr->uRes.stSample.uData.stStream.ulOffsetLoop;
        buffer_dispo_streaming.ulByteMax=res.pstPtr->uRes.stSample.uData.stStream.ulOffsetLast;
        buffer_dispo_streaming.bStarting=TRUE;
        buffer_dispo_streaming.bStarted=TRUE;
        buffer_dispo_streaming.bIsEnding=FALSE;
        buffer_dispo_streaming.bIsLooping=FALSE;
        buffer_dispo_streaming.lNextPtr=2*SIZE_FILE_BUFFER;
        buffer_dispo_streaming.iNextFileBuffer=0;

		if (nb_lus!=nb_to_read)
		{//fin du sample
			if (buffer_dispo_streaming.bMustLoop)
				    {//on reboucle
                        nb_to_read-=nb_lus;
						while (nb_to_read!=0)
						{
							SND_fn_dwSeekFileSnd(buffer_dispo_streaming.hFile,buffer_dispo_streaming.ulByteLoop,SEEKFILESND_BEGIN);
							nb_lus=SND_fn_dwReadFileSnd(buffer_dispo_streaming.hFile,nb_to_read,hFileBuffer[buffer_dispo_streaming.iNextFileBuffer]);
							if (nb_lus!=nb_to_read)
								nb_to_read-=nb_lus;
							else
								nb_to_read=0;
						}
                        buffer_dispo_streaming.bIsLooping=TRUE;
                        buffer_dispo_streaming.lNextPtr=buffer_dispo_streaming.ulByteLoop+nb_lus;
                    }
                    else
                    {//début de la fin (prématuré)
//						buffer_dispo_streaming.bStarting=FALSE;
                        SND_fn_vCloseFileSnd(buffer_dispo_streaming.hFile);
                        buffer_dispo_streaming.bFileOpened=FALSE;
                        memset((char*)(hFileBuffer[buffer_dispo_streaming.iNextFileBuffer])+nb_lus,0,nb_to_read-nb_lus);
                        buffer_dispo_streaming.bIsEnding=TRUE;
                        buffer_dispo_streaming.lCmptEnding=2;
						if (nb_to_read-nb_lus>SIZE_FILE_BUFFER)
							//cas juste où le sample est assez petit (à peine partit, on arrêtre)
							buffer_dispo_streaming.lCmptEnding--;

						//_RPT0( _CRT_WARN,"bIsEnding mis à TRUE -");
                    }
		}

		if (fn_callback)
		{
			buffer_dispo_streaming.fn_callback=fn_callback;
			buffer_dispo_streaming.par_callback=par_callback;
			buffer_dispo_streaming.bWaitingCallback=TRUE;
		}

        if (fn_bCreateStream(res.pstPtr->uRes.stSample.uwResolution,res.pstPtr->uRes.stSample.uwNbChannels,res.pstPtr->uRes.stSample.ulFreq))
	        return VOICE_STREAMING;
			*/
	}
	
	return C_PLAY_FAILED;
}

void SND_fn_vRemoveCallbackSxdStream(long voice)
{
}

SndBool SND_fn_bSetParamSxdStream(long voice,SoundParam *par)
{
	pSTREAMING_VOICE	streaming;
	SoundParam			*pstSoundParam;

	if (voice==C_PLAY_FAILED)
		return FALSE;

	streaming=(pSTREAMING_VOICE)voice;
	if (streaming->bEnded) 
	{
		purge_streaming(streaming);
		return FALSE;
	}
	else
	{
		pstSoundParam=SND_fn_pvMallocSnd(SND_M_lGetSizeOfSoundParam(par));
		memcpy(pstSoundParam,par,SND_M_lGetSizeOfSoundParam(par));
		if (streaming->bStereo)
		{
			pstSoundParam->ucVol=par->ucVol/2;
            pstSoundParam->iFlags|=C_SOUNDPARAM_COEF_VOL;
		}

		SND_fn_vSetParamBufferSxd(streaming->lIdBufferClient,pstSoundParam);

		SND_fn_vFreeSnd(pstSoundParam);

		return TRUE;
	}
}

SndBool SND_fn_bTestIsPlayingSxdStream(long voice)
{
	pSTREAMING_VOICE	streaming;

	if (voice==C_PLAY_FAILED)
		return FALSE;

	streaming=(pSTREAMING_VOICE)voice;
	if (streaming->bEnded) 
	{
		purge_streaming(streaming);
		return FALSE;
	}
	else
	{
		return TRUE;
	}
}

void SND_fn_vStopSxdStream(long voice)
{
	pSTREAMING_VOICE	streaming;

	if (voice!=C_PLAY_FAILED)
	{
		streaming=(pSTREAMING_VOICE)voice;
		streaming->bEnded=TRUE;
		
		if (streaming->bWaitingCallback)
		{
			streaming->bWaitingCallback=FALSE;

			(*streaming->pfnCallback)(streaming->lParCallback);
		}

		purge_streaming(streaming);
	}
}

void SND_fn_vPauseSxdStream(long voice)
{
}

void SND_fn_vResumeSxdStream(long voice)
{
}

int SND_fn_iInitSxdStream(SND_tdstInitStruct *pInitStruct)
{
	SNDLST2_M_DynamicInitAnchor(&ListStreaming.pList);

	bInitDone=TRUE;

	return C_INIT_OK;
}

SndBool SND_fn_bTestInitSxdStream(void)
{
	return TRUE;
}

void SND_fn_vDesInitSxdStream(void)
{
	pSTREAMING_VOICE pStreaming,pStreamingNext;
	int i;

	SNDLST2_M_DynamicForEachMovingElementOf(&ListStreaming.pList,pStreaming,pStreamingNext,i)
	{
		purge_streaming(pStreaming);
	}
}

//---------------------------------------------------------
//	GetPos: retourne la position "théorique" de la ressource
//			exprimé en seconde depuis le début
//			différent du temps absolu (car pitch, dérive..)
//	Entrée: voie concernée
//	Sortie: temps en S (SndReal)
//----------------------------------------------------------
SndReal SND_fn_rGetPosSxdStream(long voie)
{
	pSTREAMING_VOICE	streaming;

	if (voie!=C_PLAY_FAILED)
	{
		streaming=(pSTREAMING_VOICE)voie;

		if (!streaming->bEnded)
			return SND_fn_rGetPosBufferSxd(streaming->lIdBufferClient);
		else
			return SND_C_POS_ENDED;
	}
	else
		return SND_C_POS_UNKNOWN;
}

//---------------------------------------------------------
//	GetLength: retourne la durée "théorique" de la ressource
//			exprimé en seconde (constant pour une ressource donnée)
//	Entrée: voie concernée
//	Sortie: durée en S (SndReal)
//----------------------------------------------------------
SndReal SND_fn_rGetLengthSxdStream(long voie)
{
    return SND_C_LENGTH_UNKNOWN;
}

//-----------------------------------------------------------
//      ConvertRsvDiskToMem: convertir in BlockResourceDisk en Mem
//      Entrées:ptrBegin=pointeur sur les données UTILES (jamias d'offset)
//------------------------------------------------------------
void SND_fn_vConvertResDiskToMemSxdStream(tdstBlockResourceDisk *disk
                                                 ,tdstBlockResourceMem *mem
                                                 ,void* ptrBegin)
{       
#ifndef DISABLE_WAVE
        snd_assert(disk->eType==TYPE_SAMPLE);
        snd_assert(disk->uRes.stSample.eZip==SAMPLE_PCM);
        snd_assert(disk->uRes.stSample.bStream);

        mem->Id=disk->Id;
        mem->eType=disk->eType;
        mem->eStorage=disk->eStorage;
        mem->ucVolume=disk->ucVolume;
        mem->bIsLoaded=TRUE;

		//partie spécifique au type de ressource
       
        //
        mem->uRes.stSample.eZip=SAMPLE_PCM;
        mem->uRes.stSample.bPitchable=disk->uRes.stSample.bPitchable;
        mem->uRes.stSample.bVolable=disk->uRes.stSample.bVolable;
        mem->uRes.stSample.bPanable=disk->uRes.stSample.bPanable;
        mem->uRes.stSample.bSpacable=disk->uRes.stSample.bSpacable;
        mem->uRes.stSample.bReverbable=disk->uRes.stSample.bReverbable;
        mem->uRes.stSample.bStream=disk->uRes.stSample.bStream;
        mem->uRes.stSample.bLoop=disk->uRes.stSample.bLoop;
        //mem->uRes.stSample.ulInc0=(disk->uRes.stSample.ulFreq<<OFFSET_BIT_INC)/g_stFormatOutput.nSamplesPerSec;
        mem->uRes.stSample.ulFreq=disk->uRes.stSample.ulFreq;
        mem->uRes.stSample.uwResolution=disk->uRes.stSample.uwResolution;
        mem->uRes.stSample.uwNbChannels=disk->uRes.stSample.uwNbChannels;

        strcpy(mem->uRes.stSample.uData.stStream.szFileName,disk->uRes.stSample.czFileName);
        mem->uRes.stSample.uData.stStream.ulOffsetFirst=disk->ulDataOffset;
		if (!disk->uRes.stSample.bLoop)
		{
			mem->uRes.stSample.uData.stStream.ulOffsetLoop=0;
			mem->uRes.stSample.uData.stStream.ulOffsetLast=disk->ulDataSize+disk->ulDataOffset;
		}
		else
		{
			mem->uRes.stSample.uData.stStream.ulOffsetLoop=disk->ulDataOffset+disk->uRes.stSample.ulStartLoop;
			mem->uRes.stSample.uData.stStream.ulOffsetLast=disk->ulDataOffset+disk->uRes.stSample.ulStartLoop+disk->uRes.stSample.ulLoopLength;
		}
        //le sample est prêt à l'emplois
#endif
}

SndBool SND_fn_bLoadResScriptSxdStream(tdstBlockResourceDisk *disk,tdstBlockResourceMem *mem)
{
#ifndef DISABLE_WAVE
	snd_assert(disk->uRes.stSample.eZip!=SAMPLE_AIFF);
	snd_assert(disk->uRes.stSample.bStream);
	//stream sample cannot be AIIF type

	SND_fn_vConvertResDiskToMemSxdStream(disk,mem,NULL);

	//en script, chaque ressources charge ces propres data (on ne tient pas compte de eStorage)
	mem->eStorage=TYPE_EXTERNAL;

	return TRUE;
#else
	return FALSE;
#endif
}

SndBool SND_fn_bLoadResBinarySxdStream(tdstBlockResourceDisk *_pBRDisk,tdstBlockResourceMem *_pBRMem,char *pDataBloc)
{
#ifndef DISABLE_WAVE
	snd_assert(_pBRDisk->uRes.stSample.eZip!=SAMPLE_AIFF);
	snd_assert(_pBRDisk->uRes.stSample.bStream);
	//binary load is disabled for AIFF sample

	SND_fn_vConvertResDiskToMemSxdStream(_pBRDisk,_pBRMem,NULL);

	return TRUE;
#else
	return FALSE;
#endif
}

void SND_fn_vUnLoadResSxdStream(tdstBlockResourceMem* mem)
{
#ifndef DISABLE_WAVE
	snd_assert(mem->uRes.stSample.bStream);

	mem->uRes.stSample.uData.stMem.pvPtrFirst=NULL;

	mem->eType=TYPE_INVALID;
#endif
}


SndBool SND_fn_bSetResourceStaticVolumeSxdStream(tdstBlockResourceMem* pstRes,unsigned char ucVolume)
{
#ifndef DISABLE_WAVE
	pstRes->ucVolume=ucVolume;

	return TRUE;
#else
	return FALSE;
#endif
}

