/////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////
////																					 ////
////																					 ////
////								  - HSndNotify.cpp -								 ////
////																					 ////
////																					 ////
////				Implémentation des fonctions SCOL de la librairie sonore			 ////
////									 Version  1.0									 ////
////																					 ////
////								  Hilaire Verschuere								 ////
////																					 ////
////																					 ////
/////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////


#include "../Basic/ZooScene.h"
DEFINE_HGUID(IID_IDirectSoundNotify, 0xb0210783, 0x89cd, 0x11d0, 0xaf, 0x8, 0x0, 0xa0, 0xc9, 0x25, 0xcd, 0x16);
extern int SND_TIME ;
extern HWND hwnd ;


//-----------------------------------------------------------------------------
// Name: HSndNotify::HSndNotify()
// Constructor
//-----------------------------------------------------------------------------
HSndNotify::HSndNotify( HSound* pHSound )
		   :HThread()	
{
	this->pHSound = pHSound ;
	Evts.resize(0) ;
	handler = 0 ;

	if( pHSound->m_streaming )
	{
		// Mise en place des événements servant à notifier le streaming
		for( DWORD i=0; i<NB_PLAY_NOTIFICATIONS; i++ )
		{
			EvtStruct evtStruct ;
			evtStruct.posInBuffer = pHSound->m_dwNotifySize * (i+1) - 1 ;
			evtStruct.event = CreateEvent( NULL, FALSE, FALSE, NULL ) ;
			evtStruct.pos.resize(0) ;
			evtStruct.endPosition = -1 ;
			Evts.push_back( evtStruct ) ; 
		}
	}
}



//-----------------------------------------------------------------------------
// Name: HSndNotify::~HSndNotify()
// Destructor
//-----------------------------------------------------------------------------
HSndNotify::~HSndNotify()
{
	Stop() ;

    for( EVTVECT::iterator it=Evts.begin() ; it!=Evts.end() ; it++ )
    {
        SAFE_CLOSE_HANDLE( it->event ) ;                                             
    }
}


//-----------------------------------------------------------------------------
// Name: HSndNotify::~HSndNotify)
// Thread routine
//-----------------------------------------------------------------------------
DWORD HSndNotify::ThreadProc()
{
	MSG msg ;
    DWORD	dwResult ;
	int nbEvt = Evts.size() ;
	int nbNotifications = pHSound->m_streaming ? NB_PLAY_NOTIFICATIONS : 0 ;
	HANDLE* evts = new HANDLE[nbEvt] ;
	for( int i=0 ; i<Evts.size() ; i++ ) evts[i] = Evts[i].event ;

    while( ThreadIsRunning ) 
    {
		dwResult = MsgWaitForMultipleObjects( nbEvt, evts, FALSE, INFINITE, QS_ALLEVENTS );

		// Evénement correspondant à une notification de streaming
		if( (WAIT_OBJECT_0 <= dwResult) && (dwResult < (WAIT_OBJECT_0 + nbNotifications)) )
		{
			if( FAILED( pHSound->HandleStreamNotification() ) )
			{
			
				MMechostr(1, "Corrupt file %s.\n", pHSound->m_fileName) ;
				pHSound->StopStreaming() ;
			}

			// événement dont la position dans le buffer est identique à la position d'un point de notification, on traîte ici la demande
			if( Evts[dwResult].pos.size() ) EvtHappened( dwResult ) ;
		}
		else if( (dwResult >= (WAIT_OBJECT_0 + nbNotifications)) && (dwResult < (WAIT_OBJECT_0 + nbEvt)) )
		{
			// événement autre qu'une notification de streaming
			EvtHappened( dwResult ) ;
		}
		else if( dwResult == (WAIT_OBJECT_0 + nbEvt )  )
		{
			// Terminate the thread process
			while( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) 
            { 
                if( msg.message == WM_QUIT ) ThreadIsRunning = false ;
            }			
		}
    }

	SAFE_DELETE_ARRAY( evts ) ;

    return 0;
}






//-----------------------------------------------------------------------------
// Name: HSndNotify::AddSoundEvt()
// Add a struct EvtStruct to the vector of Events
// endStreaming permet de dire que cette événement demande la fin du streaming 
// audio, c'est un cas particulier d'événement qui n'existe qu'une seule fois
// dans le vecteur d'événements
//-----------------------------------------------------------------------------
void HSndNotify::AddEvt( unsigned long position, bool endStreaming )
{
	long posInBuffer = position % pHSound->m_dwDSBufferSize ;

	EVTVECT::iterator it = Evts.begin() ;
	// on ne peut pas placer 2 notifications dans le buffer à la même position
	// donc si il y en à plusieurs nécessaires, on le vérifie et on mémorise dans une liste 
	// de positions qu'il y a plusieurs événements pour un seul événement provenant de DirectSound
	for( it=Evts.begin(); it!=Evts.end(); it++ )
	{
		if( posInBuffer == it->posInBuffer )
		{
			it->pos.push_back( position ) ;
			if( endStreaming ) it->endPosition = it->pos.size() - 1 ;
			break ;
		}
	}

	// Si il nexiste pas déjà un événement pour le même position dans le buffer on en crée un nouveau
	if( it==Evts.end() )
	{
		EvtStruct evtStruct ;
		evtStruct.posInBuffer = posInBuffer ;
		evtStruct.pos.push_back( position ) ;
		evtStruct.event = CreateEvent( NULL, FALSE, FALSE, NULL ) ;
		evtStruct.endPosition =  endStreaming ? 0 : -1 ;
		Evts.push_back( evtStruct ) ; 
	}
}




//-----------------------------------------------------------------------------
// Name: HSndNotify::AddSoundEvt()
// Add an event to the end
//-----------------------------------------------------------------------------
void HSndNotify::AddEndEvt()
{
	if( pHSound->m_streaming ) pHSound->m_handlerEvtEnd = handler ; // Optimisation uniquement dédié à l'événement de fin de lecture des données
	else AddEvt( pHSound->m_dwSize - 1, false ) ;
}




//-----------------------------------------------------------------------------
// Name: HSndNotify::AddSoundEvt()
// Apply events to the Sound Buffer
//-----------------------------------------------------------------------------
HRESULT HSndNotify::SetNotify()
{
	if( !pHSound->m_pBuffer )  return ERRMSG("SetSoundEvt") ;

	LPDIRECTSOUNDNOTIFY pDSEvt ;
	DSBPOSITIONNOTIFY* aPosEvt ; 
	int nbEvt = Evts.size() ;

	if( FAILED( pHSound->m_pBuffer->QueryInterface( IID_IDirectSoundNotify, (VOID**)&pDSEvt ) ) ) return ERRMSG( "QueryInterface" ) ;
	
	aPosEvt = new DSBPOSITIONNOTIFY[ nbEvt ];
	if( aPosEvt == NULL ) return S_FALSE ;
	
	for( int i=0; i<nbEvt; i++ )
	{
		aPosEvt[i].dwOffset     = Evts[i].posInBuffer ;
		aPosEvt[i].hEventNotify = Evts[i].event ;
	}

	if( FAILED( pDSEvt->SetNotificationPositions( nbEvt, aPosEvt ) ) )
	{
		SAFE_DELETE( aPosEvt );
		SAFE_RELEASE( pDSEvt ) ;
		return ERRMSG( "SetNotificationPositions" );
	}

	SAFE_DELETE( aPosEvt );
	SAFE_RELEASE( pDSEvt ) ;

	return S_OK ;
}




//-----------------------------------------------------------------------------
// Name: HSndNotify::RemoveEvt()
// Remove sound reflex in the Direct Sound buffer
//-----------------------------------------------------------------------------
void HSndNotify::RemoveEvt()
{
	if( !pHSound->m_pBuffer )  { ERRMSG("SetSoundEvt") ; return ; }

	EVTVECT::iterator it ;

	if( pHSound->m_streaming )
	{
		int EndEvtOutOfPlayNotification = 1 ;
		for( it=Evts.begin(); it!=(Evts.begin()+NB_PLAY_NOTIFICATIONS); it++ )
		{
			if( it->endPosition == -1 ) it->pos.clear();
			else 
			{
				EndEvtOutOfPlayNotification = 0 ;
				it->pos.resize(1) ;
			}
		}

		it += EndEvtOutOfPlayNotification ;

		for( ; it!=Evts.end(); it++ ) SAFE_CLOSE_HANDLE( it->event ) ;
			
		Evts.resize( NB_PLAY_NOTIFICATIONS + EndEvtOutOfPlayNotification ) ;

		pHSound->m_handlerEvtEnd = 0 ;
	}
	else
	{
		for( it=Evts.begin(); it!=Evts.end(); it++ ) SAFE_CLOSE_HANDLE( it->event ) ;
		Evts.clear() ;
	}
}



//-----------------------------------------------------------------------------
// Name: HSndNotify::EvtSoundHappened()
// Desc: Filter events from the buffer
//-----------------------------------------------------------------------------
void HSndNotify::EvtHappened( int i )
{
	list<unsigned long>::iterator it ;
	
	if( pHSound->m_streaming )
	{
		unsigned long quarterBuff = pHSound->m_dwDSBufferSize/4 ;
		int progress = pHSound->GetPlayProgress() ;
		int down, up ;

		for( it = Evts[i].pos.begin() ; it != Evts[i].pos.end() ; it++ )
		{
			down = progress < quarterBuff ? 0 : progress - quarterBuff ;
			up   = (progress + quarterBuff) > pHSound->m_dwSize ? pHSound->m_dwSize : progress + quarterBuff ;	
			
			if( down <= *it && *it <= up ) 
			{
				if( distance( Evts[i].pos.begin(), it ) == Evts[i].endPosition ) pHSound->StopStreaming() ;
				else PostMessage( hwnd, SND_TIME, handler, NULL ) ;	
			}
		}
	}
	else for( it = Evts[i].pos.begin() ; it != Evts[i].pos.end() ; it++ ) 
	{
		PostMessage( hwnd, SND_TIME, handler, NULL ) ;
	}
}





//-----------------------------------------------------------------------------
// Name: HSndNotify::SetEndStreaming()
// Desc: Change the position of the end event to stop streaming 
//		 ( when data are added )
//-----------------------------------------------------------------------------
void HSndNotify::SetEndStreaming( unsigned long position )
{
	list<unsigned long>::iterator itPos ;
	EVTVECT::iterator it ;

	for( it=Evts.begin() ; it!=Evts.end() ; it++ )
	{
		if( it->endPosition != -1 )
		{
			itPos = it->pos.begin() ;
			advance( itPos, it->endPosition );
			*itPos = position ;
			break ;
		}
	}
}