#include <../source/TAL/objbase.h>
#include <../source/PAL/dataman.h>
#include <../source/PAL/oshead.h>

/*
 *	LINUX-TODO: determine DSP caps and MAP_IN_BUF, if possible.
 */

/*
 *	TODO: move these into protected section.
 */
vboolean	initialized=false;
vboolean	recording=false;

int         flipflop;
rwl_fifo_t	accum;
u32_t		buf_len;
void 		(V_CALL *OnProgress)(void *arg) = NULL;
void		*OnProgressArg = NULL;

#ifdef	__WIN32__

#define	NUM_BUFFERS	16
V_LOCAL	char		*abuf[NUM_BUFFERS];
V_LOCAL	HWAVEIN		hDev=NULL;
V_LOCAL	WAVEFORMATEX	wfe;
V_LOCAL	WAVEHDR		whd[NUM_BUFFERS];
V_LOCAL	vboolean	input_pending=true;

#ifdef	CHECKED_BUILD

char_p	V_CALL
mmsError(MMRESULT res)
{
	switch(res)
	{
		case MMSYSERR_ALLOCATED:
			return "resource is already allocated";
		case MMSYSERR_BADDEVICEID:
			return "device identifier is out of range";
		case MMSYSERR_NODRIVER:
			return "no device driver is present";
		case MMSYSERR_NOMEM:
			return "unable to allocate or lock memory";
		case WAVERR_BADFORMAT:
			return "unsupported waveform-audio format";
		case WAVERR_SYNC:
			return "device is synchronous";
		default:
			break;
	}
	return "unknown error";
}

#endif	/* CHECKED_BUILD */

/*
 * Callback for waveInOpen()
 */
#include <../source/PAL/argsused.h>
V_LOCAL void CALLBACK
waveInProc(HWAVEIN hwi,UINT uMsg,DWORD dwInstance,
	DWORD dwParam1,DWORD dwParam2)
{
	int i;
	switch(uMsg)
	{
		case WIM_DATA:
			if(whd[flipflop].dwBytesRecorded)
				rwlPushBuffer(accum,whd[flipflop].lpData,
					whd[flipflop].dwBytesRecorded);
			if(input_pending)
				waveInAddBuffer(hDev,&whd[flipflop],sizeof(WAVEHDR));
			flipflop = (flipflop+1) % NUM_BUFFERS;
			if(OnProgress)
				OnProgress(OnProgressArg);
			return;
		case WIM_OPEN:
			return;
		case WIM_CLOSE:
			rwlDestroy(accum);
			for(i=0;i<NUM_BUFFERS;i++)
				memFree(abuf[i]);
			memZero(whd,NUM_BUFFERS * sizeof(WAVEHDR));
			memZero(abuf,NUM_BUFFERS * sizeof(char*));
			accum=NULL;
			hDev=NULL;
			return;
	}
}

/*
 * mmsInitHeader() also used by PAL/waveout.c
 */
void	V_CALL
mmsInitHeader(WAVEFORMATEX *wfe,u32_t num_channels,
	u32_t sampling_frequency,u32_t bits_per_sample)
{
	memZero(wfe,sizeof(WAVEFORMATEX));
	wfe->wFormatTag=WAVE_FORMAT_PCM;
	wfe->nSamplesPerSec=sampling_frequency;
	wfe->wBitsPerSample=(USHORT)bits_per_sample;
	wfe->nChannels=(USHORT)num_channels;
	wfe->nBlockAlign=(USHORT)((wfe->nChannels*wfe->wBitsPerSample) >> 3);
	wfe->nAvgBytesPerSec=wfe->nSamplesPerSec*wfe->nBlockAlign;
	wfe->cbSize=0;
}
 
/*
 * Prepares for capturing wave data.
 */
vboolean	V_GLOBAL
waveCaptureInit(u32_t num_channels,u32_t sampling_frequency,
	u32_t bits_per_sample,void (V_CALL *on_progress)(void *),
	void *on_progress_arg)
{
	int i;
	UINT dev,nd;
	MMRESULT errcode;
#ifdef	CHECKED_BUILD
	WAVEINCAPS wc;
#endif	/* CHECKED_BUILD */
	if(initialized)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"waveCaptureInit(%d,%d,%d): wave capturing "
			"already initialized.\n",num_channels,sampling_frequency,bits_per_sample);
#endif	/* CHECKED_BUILD */
		return false;
	}
	mmsInitHeader(&wfe,num_channels,sampling_frequency,bits_per_sample);
	nd=waveInGetNumDevs();
	if(!nd)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"waveCaptureInit(%d,%d,%d): couldn't find"
			"sound hardware.\n",num_channels,sampling_frequency,
			bits_per_sample);
#endif	/* CHECKED_BUILD */
	}
	/* Select appropriate sound device */
	for(dev=0;dev<nd;dev++)
	{
		errcode=waveInOpen(&hDev,dev,&wfe,(DWORD)waveInProc,0,
			CALLBACK_FUNCTION);
		if(errcode!=MMSYSERR_NOERROR)
		{
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_TRACE,"waveCaptureInit(%d,%d,%d): waveInOpen(%d)"
				" failed: '%s'.\n",num_channels,sampling_frequency,
			bits_per_sample,dev,mmsError(errcode));
#endif		/* CHECKED_BUILD */
			continue;
		}
#ifdef	CHECKED_BUILD
		memZero(&wc,sizeof(WAVEINCAPS));
		waveInGetDevCaps(dev,&wc,sizeof(WAVEINCAPS));
		conlPrintf(LOG_INFO,"waveCaptureInit(): using device '%s'.\n",
			wc.szPname);
#endif	/* CHECKED_BUILD */
		break;
	}
	if(dev==nd)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"waveCaptureInit(%d,%d,%d): "
			"no appropriate hardware found.\n",num_channels,
			sampling_frequency,bits_per_sample);
#endif	/* CHECKED_BUILD */
		return false;
	}
	/* The ONLY way to work with non-standart sampling frequencies */
	buf_len=num_channels * sampling_frequency * (bits_per_sample >> 3);
	/* Avoid using too large buffers */
#ifdef	CONFIG_LARGE_WAVE_INPUT_BUFFER	/* for some built-in VIA codecs */
	switch(sampling_frequency)
	{
		case 44100:
			buf_len >>= 1;
		case 22050:
			buf_len >>= 1;
		default:
			buf_len >>= (num_channels >> 1);
			break;
	}
#else	/* !CONFIG_LARGE_WAVE_INPUT_BUFFER */
	while(buf_len > 0x2000 && !(buf_len & 1))
		buf_len >>= 1;
#endif	/* !CONFIG_LARGE_WAVE_INPUT_BUFFER */
	/* Avoid using too small buffers */
	while(buf_len < 0x1000)
		buf_len <<= 1;
	/* Allocate space for audio data */
	flipflop=0;
	errcode=MMSYSERR_NOERROR;
	for(i=0;i<NUM_BUFFERS;i++)
	{
		abuf[i]=(char*)memAlloc(buf_len);
		memZero(abuf[i],buf_len);
		memZero(&whd[i],sizeof(WAVEHDR));
		whd[i].lpData=abuf[i];
		whd[i].dwBufferLength=buf_len;
		errcode=waveInPrepareHeader(hDev,&whd[i],sizeof(WAVEHDR));
		if(errcode!=MMSYSERR_NOERROR)
			break;
		errcode=waveInAddBuffer(hDev,&whd[i],sizeof(WAVEHDR));
		if(errcode!=MMSYSERR_NOERROR)
			break;
	}
	while(errcode==MMSYSERR_NOERROR)
	{
		accum=rwlCreate();
		if(!accum)
			break;
		/* We are ready to capture audio data */
		initialized=true;
		OnProgress=on_progress;
		OnProgressArg=on_progress_arg;
		return true;
	}
#ifdef	CHECKED_BUILD
	conlPrintf(LOG_ERROR,"waveCaptureInit(%d,%d,%d): "
		"initialization failed: '%s'.\n",num_channels,
		sampling_frequency,bits_per_sample,mmsError(errcode));
#endif	/* CHECKED_BUILD */
	if(accum)
	{
		rwlDestroy(accum);
		accum=NULL;
	}
	for(i=0;i<NUM_BUFFERS;i++)
		memFree(abuf[i]);
	memZero(whd,NUM_BUFFERS * sizeof(WAVEHDR));
	memZero(abuf,NUM_BUFFERS * sizeof(char*));
	waveInClose(hDev);
	return false;
}

vboolean	V_GLOBAL
waveCaptureResume(void)
{
	MMRESULT errcode;
	if(!initialized)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,
			"waveCaptureResume(): capture was not initialized.\n");
#endif	/* CHECKED_BUILD */
		return false;
	}
	input_pending=true;
	errcode = waveInStart(hDev);
	if(errcode==MMSYSERR_NOERROR)
	{
		recording=true;
		return true;
	}
#ifdef CHECKED_BUILD
	conlPrintf(LOG_ERROR,"waveCaptureResume(): waveInStart()"
		" failed: '%s'.\n",mmsError(errcode));
#endif /* CHECKED_BUILD */
	input_pending=false;
	return false;
}

void	V_GLOBAL
waveCapturePause(void)
{
	if(recording)
	{
		waveInStop(hDev);
		recording=false;
	}
#ifdef	CHECKED_BUILD
	else
	{
		conlPuts(LOG_ERROR,
			"waveCapturePause(): capture was not started.\n");
	}
#endif	/* CHECKED_BUILD */
}

void	V_GLOBAL
waveCaptureShutdown(void)
{
	if(initialized)
	{
		input_pending=false;
		waveInReset(hDev);
		waveInClose(hDev);
		recording=false;
		initialized=false;
	}
#ifdef	CHECKED_BUILD
	else
	{
		conlPuts(LOG_ERROR,
			"waveCaptureShutdown(): capture was not initialazed.\n");
	}
#endif	/* CHECKED_BUILD */
}

#elif	defined(__linux)	/* !__WIN32__ */

V_LOCAL		int			wavein_dev = -1;
V_LOCAL		thread_t	wavein_thread=NULL;
V_LOCAL		cond_t		wavein_processing_cond=NULL;
V_LOCAL		char		*wavein_buffer;

V_LOCAL	void V_CALL
waveCaptureThread(void *param)
{
	ssize_t rl=0;
	while(initialized)
	{
		condWait(wavein_processing_cond);
		condReset(wavein_processing_cond);
		while(recording)
		{
			rl=read(wavein_dev,wavein_buffer,buf_len);
			if(rl)
			{
				rwlPushBuffer(accum,wavein_buffer,rl);
				if(OnProgress)
					OnProgress(OnProgressArg);
			}
			else
				timeDelay(10);
		}
	}
	threadExit(0);
}

V_LOCAL	void
waveClean(void)
{
	recording=false;
	initialized=false;
	if(wavein_processing_cond)
	{
		condSet(wavein_processing_cond);
		if(wavein_thread)
		{
			threadJoin(wavein_thread,NULL);
			wavein_thread=NULL;
		}
		condDestroy(wavein_processing_cond);
		wavein_processing_cond=NULL;
	}
	if(accum)
	{
		rwlDestroy(accum);
		accum=NULL;
	}
	if(wavein_dev!=-1)
	{
		close(wavein_dev);
		wavein_dev=-1;
	}
	if(wavein_buffer)
	{
		memFree(wavein_buffer);
		wavein_buffer=NULL;
	}
}

vboolean	V_GLOBAL
waveCaptureInit(u32_t num_channels,u32_t sampling_frequency,
	u32_t bits_per_sample,void (V_CALL *on_progress)(void *),
	void *on_progress_arg)
{
	int inburst,maxlen;
	int dsp_rate, dsp_channels;
#ifdef	USE_SAMPLESIZE
	int dsp_samplesize;
#else	/* !USE_SAMPLESIZE */
	int fmt;
#endif	/* !USE_SAMPLESIZE */
	if(initialized)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,
			"waveCaptureInit(): capture already initialized.\n");
#endif	/* CHECKED_BUILD */
		return false;
	}
	wavein_dev=open("/dev/dsp",O_RDONLY | O_SYNC, S_IRUSR | S_IRGRP);
	if(wavein_dev==-1)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,
			"waveCaptureInit(): no input hardware.\n");
#endif	/* CHECKED_BUILD */
		return false;
	}
	maxlen=(int)(sampling_frequency * bits_per_sample * num_channels);
	inburst=maxlen;
	if(ioctl(wavein_dev,SNDCTL_DSP_GETBLKSIZE,&inburst)<0)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,
			"waveCaptureInit(): can't determine DMA buffer length.\n");
#endif	/* CHECKED_BUILD */
		goto init_failed;
	}
	buf_len=(maxlen < inburst) ? (u32_t)maxlen : (u32_t)inburst;
	wavein_buffer=(char*)memAlloc(buf_len);
	if(!wavein_buffer)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"waveCaptureInit(): can't allocate %u bytes.\n",buf_len);
#endif	/* CHECKED_BUILD */
		goto init_failed;
	}
	dsp_rate=(int)sampling_frequency;
    if(ioctl(wavein_dev,SNDCTL_DSP_SPEED,&dsp_rate)<0)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"waveCaptureInit(%u,%u,%u): can't set rate %u Hz.\n",
			num_channels,sampling_frequency,bits_per_sample,
			sampling_frequency);
#endif	/* CHECKED_BUILD */
		goto init_failed;
	}
	dsp_channels=(int)(num_channels-1);
	if(ioctl(wavein_dev,SNDCTL_DSP_STEREO,&dsp_channels)<0)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"waveCaptureInit(%u,%u,%u): can't setup for %s input.\n",
			num_channels,sampling_frequency,bits_per_sample,
			num_channels == 1 ? "mono" : "stereo");
#endif	/* CHECKED_BUILD */
		goto init_failed;
	}
#ifdef	USE_SAMPLESIZE
	dsp_samplesize=(int)bits_per_sample;
	if(ioctl(wavein_devn,SNDCTL_DSP_SAMPLESIZE,&dsp_samplesize)<0)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"waveCaptureInit(%u,%u,%u): can't set "
			"%u bits per sample.\n",num_channels,
			sampling_frequency,bits_per_sample,bits_per_sample);
#endif	/* CHECKED_BUILD */
		goto init_failed;
	}
#else	/* !USE_SAMPLESIZE */
	switch(bits_per_sample)
	{
		case 16:
			fmt = AFMT_S16_LE;
      		break;
		case 8:
			fmt = AFMT_U8;
			break;
		default:
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_WARNING,"waveCaptureInit(%u,%u,%u): "
				"strange sample size '%u'.\n",num_channels,
				sampling_frequency,bits_per_sample,bits_per_sample);
#endif		/* CHECKED_BUILD */
			fmt = (int)bits_per_sample;
			break;
	}
	if(ioctl(wavein_dev,SNDCTL_DSP_SETFMT,&fmt)<0)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"waveCaptureInit(%u,%u,%u): "
			"can't set %u bits per sample.\n",num_channels,
			sampling_frequency,bits_per_sample,bits_per_sample);
#endif	/* CHECKED_BUILD */
		goto init_failed;
	}
#endif	/* !USE_SAMPLESIZE */
	/* Set up capture thread */
	initialized=true;
	recording=false;
	wavein_processing_cond=condCreate(false);
	condReset(wavein_processing_cond);
	accum=rwlCreate();
	rwlBlockMode(accum,true);
	wavein_thread=threadCreate(waveCaptureThread,NULL);
	OnProgress=on_progress;
	OnProgressArg=on_progress_arg;
	return true;
init_failed:
	waveClean();
	return false;
}

vboolean	V_GLOBAL
waveCaptureResume(void)
{
	if(!initialized)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,
			"waveCaptureResume(): capture was not initialized.\n");
#endif	/* CHECKED_BUILD */
		return false;
	}
	if(recording)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_WARNING,
			"waveCaptureResume(): capture already started.\n");
#endif	/* CHECKED_BUILD */
		return true;
	}
	recording=true;
	condSet(wavein_processing_cond);
	return true;
}

void	V_GLOBAL
waveCapturePause(void)
{
	if(!initialized || !recording)
		return;
	recording=false;
	return;
}

void	V_GLOBAL
waveCaptureShutdown()
{
	if(!initialized)
		return;
	waveClean();
	return;
}

#endif	/* defined(__linux) */

u32_t	V_GLOBAL
waveCaptureLength(void)
{
	if(recording)
		return rwlCount(accum);
	return 0;
}

vboolean	V_GLOBAL
waveCaptureBlockMode(vboolean blocking)
{
	if(initialized && !recording)
	{
		rwlBlockMode(accum,blocking);
		return true;
	}
#ifdef	CHECKED_BUILD
	if(!initialized)
		conlPuts(LOG_ERROR,
			"waveCaptureBlockMode(): capture was not initialized.\n");
	else
		conlPuts(LOG_ERROR,
			"waveCaptureBlockMode(): capture already started.\n");
#endif	/* CHECKED_BUILD */
	return false;
}

u32_t	V_GLOBAL
waveCaptureGet(void *dest_buf,u32_t requested_bytes)
{
	if(!recording)
		return 0;
	return rwlPopBuffer(accum,dest_buf,requested_bytes);
}
