#include <windows.h>
#include <mmsystem.h>
#include <stdlib.h>
//#include <crtdbg.h>

#include "SNDinc.H"
#include "sndrec.h"
#include "sndlst2.h"
#include "sndxd.h"

#if defined(_DLL_COMPILATION_MODE) || defined(USE_DBG_MODULE)
#include "snddll.h"
#else
#include "snddbg.h"
#endif

typedef struct RECORD_BUFFER_ *pRECORD_BUFFER;
typedef struct RECORD_VOICE_ *pRECORD_VOICE;

SNDLST2_M_DynamicUseListOf(pRECORD_BUFFER);

typedef struct RECORD_BUFFER_{
	void*			ptrData;
	unsigned long	ulNbSamples;
	WAVEHDR			WaveHdr;
	SndBool			bSentToDriver;
	pRECORD_VOICE	pVoice;
	SNDLST2_M_DynamicElementDeclaration(pRECORD_BUFFER)
} RECORD_BUFFER;

typedef struct RECORD_VOICE_{
	long			lIdBufferClient;
	long			lSampleToBytesShift;
	SndBool			bUsed;
	SND_tdstFormat	stFormat;
	td_pfn_vBufferClientPoped CallbackStack;
	SNDLST2_M_DynamicAnchorDeclaration(pRECORD_BUFFER) pBufferList;
} RECORD_VOICE;

static RECORD_VOICE RecordVoice;

static HWAVEIN hWaveIn;
static SndBool bDriverInRecording=FALSE;

static UINT snd_msg_StartRecord,snd_msg_StopRecord,snd_msg_PushBuffer;
static HANDLE hMutexRecord;
static DWORD id_snd_thread=0;
static SndBool bDriverOutReleased=TRUE;

static long cumul_vumeter=0;
static long nb_element_vumeter=0;

void refresh_vumeter(void* pData,long nb_data)
{
	signed char *ptrByte;
	signed short *ptrWord;
	long cumul=0;
	int i;

	cumul_vumeter=0;
	nb_element_vumeter=0;

	switch (RecordVoice.stFormat.uFormat.stPCM.uwResolution)
	{//nota: pas de différence entre mono et stereo (vumeter mono)
	case 8:
		for (i=0,ptrByte=pData;i<nb_data;i++,ptrByte++)
		{
			cumul_vumeter+=abs(*ptrByte);
		}
		nb_element_vumeter+=nb_data;
		break;
	case 16:
		for (i=0,ptrWord=pData;i<nb_data/2;i++,ptrWord++)
		{
			cumul_vumeter+=abs(*ptrWord)>>8;
		}
		nb_element_vumeter+=nb_data/2;
		break;
	default:
		snd_assert(FALSE);
		break;
	}
}

unsigned char get_vumeter_value()
{
	if (nb_element_vumeter)
	{
		return (unsigned char)(cumul_vumeter/nb_element_vumeter);
	}
	else
		return 0;
}

//push data buffer
//can only be called by sndThread
SndBool PushBuffersLocal()
{
	MMRESULT res;
    char texte[256];
	pRECORD_BUFFER Buffer;
	int i;

	WaitForSingleObject(hMutexRecord,INFINITE);

	SNDLST2_M_DynamicForEachElementOf(&RecordVoice.pBufferList,Buffer,i)
	{
		if (!Buffer->bSentToDriver)
		{
			memset(&Buffer->WaveHdr,0,sizeof(Buffer->WaveHdr));

			Buffer->WaveHdr.lpData=Buffer->ptrData; 
			Buffer->WaveHdr.dwBufferLength=Buffer->ulNbSamples; 
			Buffer->WaveHdr.dwUser=(DWORD)Buffer; 

			if ((res=waveInPrepareHeader(hWaveIn,&Buffer->WaveHdr,sizeof(Buffer->WaveHdr)))!=MMSYSERR_NOERROR)
			{
				waveInGetErrorText(res,texte,sizeof(texte));
				SND_fn_vDisplayError(E_uwSndSystemError,"Error on waveInPrepareHeader");
			}

			if ((res=waveInAddBuffer(hWaveIn,&Buffer->WaveHdr,sizeof(Buffer->WaveHdr)))!=MMSYSERR_NOERROR)
			{
				waveInGetErrorText(res,texte,sizeof(texte));
				SND_fn_vDisplayError(E_uwSndSystemError,"Error on waveInAddBuffer");
			}

			//_RPTF1(_CRT_WARN,"Sent %d\n",&Buffer->WaveHdr);

			Buffer->bSentToDriver=TRUE;
		}
	}

	ReleaseMutex(hMutexRecord);

	return TRUE;
}

//start recording
//can only be called by sndThread
SndBool StartRecordLocal()
{
	MMRESULT res;
    char texte[256];
	WAVEFORMATEX WaveFormatEx;

	WaitForSingleObject(hMutexRecord,INFINITE);

	memset(&WaveFormatEx,0,sizeof(WaveFormatEx));
	WaveFormatEx.wFormatTag=WAVE_FORMAT_PCM;
	WaveFormatEx.nChannels=RecordVoice.stFormat.uFormat.stPCM.uwNbChannels; 
    WaveFormatEx.wBitsPerSample=RecordVoice.stFormat.uFormat.stPCM.uwResolution; 
    WaveFormatEx.nSamplesPerSec=RecordVoice.stFormat.uFormat.stPCM.ulFreq; 
    WaveFormatEx.nBlockAlign=(WORD)(WaveFormatEx.nChannels*WaveFormatEx.wBitsPerSample/8); 
    WaveFormatEx.nAvgBytesPerSec=WaveFormatEx.nSamplesPerSec*WaveFormatEx.nBlockAlign; 
    WaveFormatEx.cbSize=0;

	if ((res=waveInOpen(&hWaveIn,WAVE_MAPPER,&WaveFormatEx,GetCurrentThreadId(),0,CALLBACK_THREAD))!=MMSYSERR_NOERROR)
	{
		waveInGetErrorText(res,texte,sizeof(texte));

		//Pb insoluble -> seule hypothèse recevable:non FullDuplex
		SND_fn_vForceReleaseDriverSxd();

		if ((res=waveInOpen(&hWaveIn,WAVE_MAPPER,&WaveFormatEx,GetCurrentThreadId(),0,CALLBACK_THREAD))!=MMSYSERR_NOERROR)
		{//pb doublement insoluble
			waveInGetErrorText(res,texte,sizeof(texte));

			SND_fn_vForceRestoreDriverSxd();

			ReleaseMutex(hMutexRecord);
			return FALSE;
		}

		bDriverOutReleased=TRUE;
	}

	bDriverInRecording=TRUE;

	PushBuffersLocal();

	if ((res=waveInStart(hWaveIn))!=MMSYSERR_NOERROR)
	{
		waveInGetErrorText(res,texte,sizeof(texte));
		SND_fn_vDisplayError(E_uwSndSystemError,"Error on waveInAddBuffer");
	}

	ReleaseMutex(hMutexRecord);

	return TRUE;
}

//stop recording
//can only be called by sndThread
SndBool StopRecordLocal()
{
	MMRESULT res;
	int i;
	pRECORD_BUFFER pstPushedBuffer,pstPushedBufferNext;
	char texte[128];

	WaitForSingleObject(hMutexRecord,INFINITE);

	res=waveInReset(hWaveIn);
	res=waveInClose(hWaveIn);

	//purge databuffer list
	SNDLST2_M_DynamicForEachMovingElementOf(&RecordVoice.pBufferList,pstPushedBuffer,pstPushedBufferNext,i)
	{
		//_RPTF1(_CRT_WARN,"free %d\n",pstPushedBuffer);

		SNDLST2_M_DynamicIsolate(pstPushedBuffer);

		if ((res=waveInUnprepareHeader(hWaveIn,&pstPushedBuffer->WaveHdr,sizeof(WAVEHDR)))!=MMSYSERR_NOERROR)
			waveInGetErrorText(res,texte,sizeof(texte));

		SND_fn_vFreeSndEx(E_ucSndBlockMain,pstPushedBuffer);			
	}

	if (bDriverOutReleased)
		SND_fn_vForceRestoreDriverSxd();

	bDriverInRecording=FALSE;

	ReleaseMutex(hMutexRecord);

	return TRUE;
}

DWORD WINAPI callback_thread(long* ptr_param)
{
	(*(td_pfn_vBufferClientPoped)ptr_param[0])(ptr_param[1],ptr_param[2]);

	GlobalFree(ptr_param);

	return 0;
}

void start_callback(pRECORD_BUFFER buffer)
{
	/*
	DWORD id_thread;
	long *ptr_param=GlobalAlloc(GMEM_FIXED,3*sizeof(long));

	if (buffer->pVoice->CallbackStack)
	{
		ptr_param=GlobalAlloc(GMEM_FIXED,3*sizeof(long));

		ptr_param[0]=(long)buffer->pVoice->CallbackStack;
		ptr_param[1]=(long)buffer->pVoice;
		ptr_param[2]=(long)buffer;

		CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)callback_thread,(LPVOID)ptr_param,0,&id_thread);
	}
	*/
	(*buffer->pVoice->CallbackStack)((long)buffer->pVoice,(long)buffer);
}


//-------------------------------------------------------------
//	SND_fn_vInitRecord: init SNDREC
//-------------------------------------------------------------
int SND_CALL SND_fn_iInitRecord(SND_tdstInitStruct *pInitStruct)
{
#if defined(_DLL_COMPILATION_MODE)
    SND_DllInitEntryPoints_StaticFunctions((HMODULE)pInitStruct->hProcessInstance );
    SND_DllInitEntryPoints_WavFunctions(SND_hWavModule);
#endif

    snd_msg_StartRecord=RegisterWindowMessage("MSG_StartRecord");
    snd_msg_StopRecord=RegisterWindowMessage("MSG_StopRecord");
	snd_msg_PushBuffer=RegisterWindowMessage("MSG_PushBuffer");

	hMutexRecord=CreateMutex(NULL,FALSE,NULL);

	memset(&RecordVoice,0,sizeof(RecordVoice));

	return C_INIT_OK;
}

//-------------------------------------------------------------
//	SND_fn_vDesInitRecord: desinit SNDREC
//-------------------------------------------------------------
void SND_CALL SND_fn_vDesInitRecord(void)
{
	if (bDriverInRecording)
		SND_fn_vStopRecord((long)&RecordVoice);
}

//---------------------------------------------------------------
//	SND_fn_bTestFormatRecord:test is a fromat is valid for recording
//	Parameters:
//		pformat: format to be tested; some fields may be filled depending of flags value
//			to return a effective valid format
//		flags: boolean combinaison of following
//			FORMATREC_RESOLUTION: test Resolution field value; will fill pFormat with a valid value
//			FORMATREC_FREQUENCY: test Frequency field value; will fill pFormat with a valid value
//			FORMATREC_NBCHANNEL: test NbChannlel field value; will fill pFormat with a valid value
//			FORMATREC_ZIP: test eZip field value; will fill pFormat with a valid value
//	Return:
//		TRUE when a calid format was found; some filed of pFormat could be changed
//		FALSE when no format is availlable regardless to flags value
//---------------------------------------------------------------
SndBool SND_CALL SND_fn_bTestFormatRecord(SND_tdstFormat* pformat,int flags)
{
	MMRESULT ret;
	WAVEFORMATEX WaveFormatEx;
	WAVEINCAPS WaveInCaps;

#define MASK_8BITS		(WAVE_FORMAT_1M08 | WAVE_FORMAT_1S08 | WAVE_FORMAT_2M08 | WAVE_FORMAT_2S08 | WAVE_FORMAT_4M08 | WAVE_FORMAT_4S08)
#define MASK_16BITS		(WAVE_FORMAT_1M16 | WAVE_FORMAT_1S16 | WAVE_FORMAT_2M16 | WAVE_FORMAT_2S16 | WAVE_FORMAT_4M16 | WAVE_FORMAT_4S16)
#define MASK_MONO		(WAVE_FORMAT_1M08 | WAVE_FORMAT_2M08 | WAVE_FORMAT_4M08 | WAVE_FORMAT_1M16 | WAVE_FORMAT_2M16 | WAVE_FORMAT_4M16)
#define MASK_STEREO		(WAVE_FORMAT_1S08 | WAVE_FORMAT_2S08 | WAVE_FORMAT_4S08 | WAVE_FORMAT_1S16 | WAVE_FORMAT_2S16 | WAVE_FORMAT_4S16)

	memset(&WaveInCaps,0,sizeof(WaveInCaps));
	if ((ret=waveInGetDevCaps(WAVE_MAPPER,&WaveInCaps,sizeof(WaveInCaps)))!=MMSYSERR_NOERROR)
	{
		return FALSE;
	}

	//check eZip
	if (flags & FORMATREC_ZIP)
	{//eZip is forced to a value
		if (pformat->eZip!=SAMPLE_PCM)
			//PCM is currently the only format
			return FALSE;
	}
	else
		//return the best eZip value
		pformat->eZip=SAMPLE_PCM;

	//check resolution
	if (flags & FORMATREC_RESOLUTION)
	{//eResolution is forced to a value
		if (pformat->uFormat.stPCM.uwResolution==8)
			WaveInCaps.dwFormats^=MASK_8BITS;
		if (pformat->uFormat.stPCM.uwResolution==16)
			WaveInCaps.dwFormats^=MASK_16BITS;

		//no more compatible format
		if (!WaveInCaps.dwFormats)
			return FALSE;
	}
	else
	{
		if (WaveInCaps.dwFormats & MASK_16BITS)
			WaveInCaps.dwFormats&=MASK_16BITS;

		pformat->uFormat.stPCM.uwResolution=(WaveInCaps.dwFormats & MASK_8BITS) ? 8 : 16;
	}

	//check channel
	if (flags & FORMATREC_NBCHANNEL)
	{//nbchannel is forced to a value
		if (pformat->uFormat.stPCM.uwNbChannels==1)
			WaveInCaps.dwFormats^=MASK_MONO;
		if (pformat->uFormat.stPCM.uwNbChannels==2)
			WaveInCaps.dwFormats^=MASK_STEREO;

		//no more compatible format
		if (!WaveInCaps.dwFormats)
			return FALSE;
	}
	else
	{
		if (WaveInCaps.dwFormats & MASK_STEREO)
			WaveInCaps.dwFormats&=MASK_STEREO;
	
		pformat->uFormat.stPCM.uwNbChannels=(WaveInCaps.dwFormats & MASK_MONO) ? 1 : 2; 
	}

	if (!(flags & FORMATREC_FREQUENCY))
	{
		if (WaveInCaps.dwFormats>=WAVE_FORMAT_4M08)
			pformat->uFormat.stPCM.ulFreq=44100;
		else
			if (WaveInCaps.dwFormats>=WAVE_FORMAT_2M08)
				pformat->uFormat.stPCM.ulFreq=22050;
			else
				pformat->uFormat.stPCM.ulFreq=11025;
	}

	//first try with exact value for frequency
	memset(&WaveFormatEx,0,sizeof(WaveFormatEx));
	WaveFormatEx.wFormatTag=WAVE_FORMAT_PCM;
	WaveFormatEx.nChannels=pformat->uFormat.stPCM.uwNbChannels; 
    WaveFormatEx.wBitsPerSample=pformat->uFormat.stPCM.uwResolution; 
    WaveFormatEx.nSamplesPerSec=pformat->uFormat.stPCM.ulFreq; 
    WaveFormatEx.nBlockAlign=(WORD)(WaveFormatEx.nChannels*WaveFormatEx.wBitsPerSample/8); 
    WaveFormatEx.nAvgBytesPerSec=WaveFormatEx.nSamplesPerSec*WaveFormatEx.nBlockAlign; 
    WaveFormatEx.cbSize=0;

	if ((ret=waveInOpen(NULL,WAVE_MAPPER,&WaveFormatEx,0,0,WAVE_FORMAT_QUERY))==MMSYSERR_NOERROR)
		return TRUE;
	else
		return FALSE;
}

//-----------------------------------------------------------
//	SND_fn_lCreateBufferRecord: create a recording buffer
//	Parameters:
//		SND_tdstFormat: format souhaité
//		pCallback; type de callback
//	Return:
//		C_PLAY_FAILED if failed; else, Buffer is ready to record
//-----------------------------------------------------------
long SND_CALL SND_fn_lCreateBufferRecord(SND_tdstFormat* pformat,SND_tdstCallback* pCallback)
{
	long ret;

	snd_assert(pCallback->eType==BUFFER_STACK);

	WaitForSingleObject(hMutexRecord,INFINITE);

	if (!SND_fn_bTestFormatRecord(pformat,FORMATREC_RESOLUTION|FORMATREC_FREQUENCY|FORMATREC_NBCHANNEL|FORMATREC_ZIP)
		|| RecordVoice.bUsed)
		ret=C_PLAY_FAILED;
	else
	{
		SNDLST2_M_DynamicInitAnchor(&RecordVoice.pBufferList);

		memcpy(&RecordVoice.stFormat,pformat,sizeof(RecordVoice.stFormat));
		RecordVoice.CallbackStack=pCallback->uCallback.CallbackStack;

		ret=(long)&RecordVoice;
	}

	ReleaseMutex(hMutexRecord);

	return ret;
}

void PushBuffers()
{
	HANDLE hEvt;

	if (GetCurrentThreadId()==id_snd_thread)
	{
		PushBuffersLocal();
	}
	else
	{
		if (!PostThreadMessage(id_snd_thread,snd_msg_PushBuffer,(WPARAM)(hEvt=CreateEvent(NULL,FALSE,FALSE,NULL))
				,0))
			SND_fn_vDisplayError(E_uwSndSystemError,"Error on PostThreadMessage");

		WaitForSingleObject(hEvt,INFINITE);

		CloseHandle(hEvt);
	}
}

//---------------------------------------------------------------
//	SND_fn_ucGetInstantVuMeterRecordSound: get current value for VuMeter
//	Return:
//		volume (0=silence; 127= maximum power)
//-----------------------------------------------------------------
unsigned char SND_CALL SND_fn_ucGetInstantVuMeterRecord(void)
{
	unsigned char tmp;

	WaitForSingleObject(hMutexRecord,INFINITE);

	tmp=get_vumeter_value();

	ReleaseMutex(hMutexRecord);

	return tmp;
}

//---------------------------------------------------------------
//	SND_fn_lPushBufferToRecord: add a new buffer to record data into
//	Parameters:
//		id_buffer: id of RecordBuffer
//		ptr: pointer where copy recorded data
//		size_ptr: nb bytes to copy
//	Return:
//		id of data buffer
//-----------------------------------------------------------------
long SND_CALL SND_fn_lPushBufferToRecord(long id_buffer,void* ptr,int size_ptr)
{
	pRECORD_BUFFER pstPushedBuffer;

	WaitForSingleObject(hMutexRecord,INFINITE);

	pstPushedBuffer=SND_fn_pvMallocSndEx(E_ucSndBlockMain,sizeof(RECORD_BUFFER));
    SNDLST2_M_DynamicInitElement(pstPushedBuffer);

	//_RPTF1(_CRT_WARN,"push pour %d\n",pstPushedBuffer);

	pstPushedBuffer->ptrData=ptr;
	pstPushedBuffer->ulNbSamples=size_ptr;
	pstPushedBuffer->bSentToDriver=FALSE;
	snd_assert(id_buffer==(long)&RecordVoice);
	pstPushedBuffer->pVoice=&RecordVoice;

    SNDLST2_M_DynamicAddTail(&RecordVoice.pBufferList,pstPushedBuffer);

	ReleaseMutex(hMutexRecord);

	if (bDriverInRecording)
	{
		PushBuffers();
	}

	return (long)pstPushedBuffer;
}

SndBool SND_CALL SND_fn_bStartRecord(long id_buffer)
{
	HANDLE hEvt;

	if (!PostThreadMessage(id_snd_thread,snd_msg_StartRecord,(WPARAM)(hEvt=CreateEvent(NULL,FALSE,FALSE,NULL))
			,0))
		SND_fn_vDisplayError(E_uwSndSystemError,"Error on PostThreadMessage");

	WaitForSingleObject(hEvt,INFINITE);

	CloseHandle(hEvt);

	return FALSE;
}

void SND_CALL SND_fn_vStopRecord(long id_buffer)
{
	HANDLE hEvt;

	if (!PostThreadMessage(id_snd_thread,snd_msg_StopRecord,(WPARAM)(hEvt=CreateEvent(NULL,FALSE,FALSE,NULL))
			,0))
		SND_fn_vDisplayError(E_uwSndSystemError,"Error on PostThreadMessage");

	WaitForSingleObject(hEvt,INFINITE);

	CloseHandle(hEvt);
}

SndBool SND_CALL SND_fn_bIsBufferFullDuplexRecord(long id_buffer)
{
	return FALSE;
}

SndBool SND_CALL SND_fn_bIsWorkingRecord(long id_buffer)
{
	return bDriverInRecording;
}

//---------------------------------------------------------
//	SND_fn_bManageMsgForRecord: traitement des messages envoyés à la Thread Wave
//	Paramètres:
//		msg: message to be processed
//	Return:
//		TRUE if message was processed  for WaveIn
//		FALSE if message must be processed by the thread
//---------------------------------------------------------
SndBool SND_CALL SND_fn_bManageMsgForRecord(long p_msg)
{
	MSG* msg=(MSG*)p_msg;
    HANDLE hEvt=(HANDLE)msg->wParam;
	static SndBool init_done=FALSE;
	pRECORD_BUFFER buffer;
	LPWAVEHDR pWaveHdr;
	MMRESULT res;
    char texte[256];

	if (!init_done)
	{//MUST be first test (buring init, usermsg may have value 0)
		init_done=TRUE;

		id_snd_thread=GetCurrentThreadId();
	}
	else
	if (msg->message==MM_WIM_DATA)
	{
		pWaveHdr=(LPWAVEHDR)msg->lParam;
		buffer=(pRECORD_BUFFER)pWaveHdr->dwUser;

		if (bDriverInRecording)
		{
			WaitForSingleObject(hMutexRecord,INFINITE);

			snd_assert(&buffer->WaveHdr==pWaveHdr);

			//_RPTF1(_CRT_WARN,"MM_WIM pour %d->free",buffer);

			SNDLST2_M_DynamicIsolate(buffer);

			refresh_vumeter(pWaveHdr->lpData,pWaveHdr->dwBytesRecorded);

			ReleaseMutex(hMutexRecord);

			if (pWaveHdr->dwBytesRecorded > 0)
				start_callback(buffer);
			//else
			//	_RPTF1(_CRT_WARN,"Buffer %x NOT DONE !!!!!!!!!!!!!!!!!\n",msg->lParam);

			if ((res=waveInUnprepareHeader(hWaveIn,&buffer->WaveHdr,sizeof(WAVEHDR)))!=MMSYSERR_NOERROR)
					waveInGetErrorText(res,texte,sizeof(texte));

			SND_fn_vFreeSndEx(E_ucSndBlockMain,buffer);
		}
		//else
			//_RPTF1(_CRT_WARN,"MM_WIM pour %d->rien",buffer);

		return TRUE;
	}
	else
	if (msg->message==snd_msg_StartRecord)
	{
		StartRecordLocal();
               
		SetEvent(hEvt);

		return TRUE;
	}
	else
	if (msg->message==snd_msg_StopRecord)
	{
		StopRecordLocal();
               
		SetEvent(hEvt);

		return TRUE;
	}
	else
	if (msg->message==snd_msg_PushBuffer)
	{
		PushBuffersLocal();
               
		SetEvent(hEvt);

		return TRUE;
	}

	return FALSE;
}



