#include <../source/TAL/objbase.h>
#include <../source/vfs/iobase.h>

/*
 * Status: good.
 * Last update: 27.aug.2006
 */

#ifdef	CONFIG_AUDIO_MP3
#include <../support/mp3lib/src/mp3.h>
#endif	/* CONFIG_AUDIO_MP3 */

#ifndef	WAVE_FORMAT_PCM
#define	WAVE_FORMAT_PCM		1
#endif	/* WAVE_FORMAT_PCM */

#ifdef	_MSC_VER
#pragma warning(disable:4103)
#endif	/* _MSC_VER */

/*
 * M$ WAV format header.
 */
#include <../source/PAL/pack0.h>
typedef struct	wave_format_s
{
	u16_t	format_tag;			/*	only WAVE_FORMAT_PCM supported */
	u16_t	num_channels;		/*	must be either 1 or 2 */
	u32_t	sampling_frequency;
	u32_t	bytes_per_second;	/*	== samples_per_second * block_align */
	u16_t	block_align;		/*	== num_channels*bits_per_sample / 8 */
	u16_t	bits_per_sample;
}	ATTRIBUTE_PACKED wave_format_t, *wave_format_p;
#include <../source/PAL/ppack.h>

#ifdef	_MSC_VER
#pragma warning(default:4103)
#endif	/* _MSC_VER */

/*
 * Uncompressed PCM wave file.
 */
typedef struct	wave_data_s
{
	ioport_base_t	port;
	u16_t	num_channels;		/*	must be either 1 or 2 */
	u32_t	sampling_frequency;
	u32_t	bytes_per_second;	/*	== samples_per_second * block_align */
	u16_t	block_align;		/*	== num_channels*bits_per_sample / 8 */
	u16_t	bits_per_sample;	/* Bit rate */
	vsize_t	num_samples;		/* total number of samples (not bytes) */
	vsize_t	num_bytes;			/* total data size in bytes */
	vhandle_t df;				/* input file handle */
	vsize_t	transferred;		/* number of transferred bytes */
	vboolean writemode;			/* == true if self was wavOpenWrite()'d */
	vboolean loop;				/* seek to start after last read() */
	vsize_t	data_offset;		/* offset to wave data in self->df */
	vsize_t	tail_garbage_length;
}	wave_data_t, *wave_data_p;

typedef struct chunk_head_s
{
	char	id[4];		/* identifier: "RIFF" for file header */
	u32_t	len;		/* remaining chunk length after header */
}	chunk_head_t, *chunk_head_p;

/*
 * Reads header of the next chunk in RIFF file.
 * Returns false if unexpected EOF reached.
 */
V_LOCAL vboolean
riff_read_head(vhandle_t in,chunk_head_t *head_ret)
{
	CALLER_NAME(riff_read_head)
	u32_t pos,rd;
	pos = fileTell(in);
	fileRead(in,head_ret->id,4);
	head_ret->len = fileGetLittleLong(in);
	rd = fileTell(in) - pos;
	if(rd != 8)
	{
		ERROR_GENERIC("unable to read chunk header");
		return false;
	}
	return true;
}

/*
 * Reads chunk and returns pointer to memAlloc()'ed block (or
 * NULL if unexpected EOF reached).
 * File pointer must be propely positioned (to the first byte
 * after chunk header).
 */
V_LOCAL void_p
riff_read_chunk_data(vhandle_t in,chunk_head_t *chead)
{
	CALLER_NAME(riff_read_chunk_data)
	void *cdata;
	cdata = memAlloc(chead->len);
	if(fileRead(in,cdata,chead->len) != chead->len)
	{
		ERROR_GENERIC("unexpected EOF");
		memFree(cdata);
		return NULL;
	}
	return cdata;
}

/*
 * Reads wave format chunk.
 */
V_LOCAL	vboolean
riff_read_fmt(vhandle_t in,chunk_head_t *chead,wave_data_t *fmt)
{
	CALLER_NAME(riff_read_fmt)
	wave_format_t *cdata;
	vboolean ret = false;
	for(;;)
	{
		cdata = (wave_format_t*)riff_read_chunk_data(in,chead);
		if(!cdata)
			break;
		fmt->num_channels = LE2ME_SHORT(cdata->num_channels);
		fmt->bits_per_sample = LE2ME_SHORT(cdata->bits_per_sample);
		fmt->sampling_frequency = LE2ME_LONG(cdata->sampling_frequency);
#ifdef CONFIG_WORKAROUND_ZERO_BPS
		if(!fmt->bits_per_sample)
		{
			WARNING_GENERIC("zero BPS, assuming 16");
			fmt->bits_per_sample = 16;
		}
#endif /* CONFIG_WORKAROUND_ZERO_BPS */
		if(!fmt->num_channels || !fmt->bits_per_sample || !fmt->sampling_frequency)
		{
			ERROR_GENERIC("at least one of num_channels/bps/freq is zero");
			break;
		}
		fmt->block_align =
			(u16_t)(fmt->num_channels * (fmt->bits_per_sample >> 3));
		fmt->bytes_per_second = fmt->sampling_frequency * fmt->block_align;
		ret = true;
		break;
	}
	memFree(cdata);
	return ret;
}

/*
 * read_wav_file() - reads next block of data from file
 */
V_LOCAL	vsize_t	V_CALL
read_wav_file(vhandle_t handle,void *dest,vsize_t len)
{
	vsize_t rlen;
	wave_data_t *self;
	self=(wave_data_t*)handle;
	if(self->loop)
	{
		vsize_t alen;
		/* length after current position */
		rlen=self->num_bytes - self->transferred;
		if(rlen > len)
			rlen=len;
		alen=len-rlen;
		/* Read block */
		if(rlen)
		{
			rlen=fileRead(self->df,dest,rlen);
			self->transferred += rlen;
			dest = ((u8_t*)dest) + rlen;
		}
		if(alen)
		{
			alen=fileRead(self->df,dest,alen);
			fileSeek(self->df,self->data_offset,SEEK_SET);
			self->transferred = alen;
		}
		return rlen + alen;
	}
	else
	{
		/* Remaining length of data */
		rlen = self->num_bytes - self->transferred;
		if(rlen < len)
			len=rlen;
		/* Read block */
		if(!len)
			return 0;
		rlen = fileRead(self->df,dest,len);
		self->transferred += rlen;
	}
	return rlen;
}

/*
 * write_wav_file() - writes next block of data to file
 */
V_LOCAL vsize_t V_CALL
write_wav_file(vhandle_t handle,void *src,vsize_t len)
{
	vsize_t wlen;
	wave_data_t *self;
	self = (wave_data_t*)handle;
	wlen = fileWrite(self->df,src,len);
	self->transferred += wlen;
	return wlen;
}

/*
 * tell_wav_file() - returns offset (in bytes) from the start of waveform data
 */
V_LOCAL vsize_t V_CALL
tell_wav_file(vhandle_t handle)
{
	wave_data_t *self = (wave_data_t*)handle;
	return fileTell(self->df) - self->data_offset;
}

/*
 * seek_wav_file() - sets offset (in bytes) from the start of waveform data
 */
V_LOCAL vsize_t V_CALL
seek_wav_file(vhandle_t handle,vssize_t offset,int whence)
{
	wave_data_t *self = (wave_data_t*)handle;
	vssize_t block_end = self->data_offset + self->num_bytes;
	switch(whence)
	{
		case SEEK_SET:
			offset += self->data_offset;
			break;
		case SEEK_END:
			offset += block_end;
			break;
		default:
			break;
	}
	if(offset < 0 || (vsize_t)offset < self->data_offset)
		offset = self->data_offset;
	else
	if(offset > block_end)
		offset = block_end;
	offset = fileSeek(self->df,offset,SEEK_SET);
	self->transferred = offset - self->data_offset;
	return self->transferred;
}

/*
 * ioctl_wav_file() - handles IO control requests
 */
V_LOCAL int V_CALL
ioctl_wav_file(vhandle_t handle,unsigned code,void *data)
{
	CALLER_NAME(ioctl_wav_file)
	u32_t pt;
	void *wavedata_base;
	wave_data_t *self = (wave_data_t*)handle;
	switch(code)
	{
		case pctl_get_type:
			if(!data)
				return -1;
			fileControl(self->df,pctl_get_type,&pt);
			*((u32_t*)data) = pt;
			break;
		case pctl_is_eof:
			if(!data)
				return -1;
			if(self->writemode)
				return -1;
			*((vboolean*)data) = self->num_bytes == self->transferred;
			break;
		case pctl_wave_get_num_channels:
			if(!data)
				return -1;
			*((u32_t*)data)=self->num_channels;
			break;
		case pctl_wave_get_sampling_frequency:
			if(!data)
				return -1;
			*((u32_t*)data)=self->sampling_frequency;
			break;
		case pctl_wave_get_bits_per_sample:
			if(!data)
				return -1;
			*((u32_t*)data)=self->bits_per_sample;
			break;
		case pctl_wave_get_num_samples:
			if(!data)
				return -1;
			*((vsize_t*)data)=self->num_samples;
			break;
		case pctl_wave_loop:
			if(!data)
				return -1;
			self->loop = (data) ? true : false;
			break;
		case pctl_map_ispace:
			if(!data)
				return -1;
			fileControl(self->df,pctl_map_ispace,&wavedata_base);
			*((void**)data) = (u8_t*)wavedata_base + self->data_offset;
			break;
		default:
			ERROR_GENERIC("unknown ioctl code");
			return -1;
	}
	return 0;
}

/*
 * Closes wave file, frees all resources.
 */
V_LOCAL u32_t V_CALL
close_wav_file(vhandle_t handle)
{
	wave_data_t *self;
	self=(wave_data_t*)handle;
	UNREF_EXIT(&self->port);
	if(self->df != NULL)
	{
		if(self->writemode)
		{
			/*
			 * File was opened for output: now we must write
			 * all necessary information to the header.
			 */
			fileSeek(self->df,0x04,SEEK_SET);
			/* Write size of main chunk */
			filePutLittleLong(self->df,0x24 + self->transferred);
			fileWrite(self->df,"WAVEfmt ",8);
			/* Write size of 'data' chunk */
			fileSeek(self->df,0x28,SEEK_SET);
			filePutLittleLong(self->df,self->transferred);
		}
		fileClose(self->df);
	}
	memFreeObject(self);
	return 0;
}

/*
 * Public functions: two constructors and several wrappers.
 */

/*
 * Starts reading from wav file. Returned handle than can be
 * passed to fileRead() and fileClose().
 */
vhandle_t	V_GLOBAL
wavOpenRead(char *name)
{
	CALLER_NAME(wavOpenRead)
	char type_id[4];
	int chunk_count;
	wave_data_p self;
	chunk_head_t head;
	vhandle_t infile;
	if(!name || !*name)
	{
		ERROR_GENERIC(error_incorrect_parameters);
		return LIBV_INVALID_HANDLE;
	}
	infile = fileOpenRead(name);
	if(infile == LIBV_INVALID_HANDLE)
	{
		ERROR_GENERIC_2ARG("unable to open: ",name);
		return LIBV_INVALID_HANDLE;
	}
	self = (wave_data_p)ioAllocPort(sizeof(wave_data_t));
	self->port.base.destroy=close_wav_file;
	self->port.read=read_wav_file;
	self->port.seek=seek_wav_file;
	self->port.tell=tell_wav_file;
	self->port.ioctl=ioctl_wav_file;
	self->df = infile;
	self->writemode=false;
	/* Read main header */
	if(!riff_read_head(self->df,&head))
	{
		fileClose(self->df);
		memFreeObject(self);
		return LIBV_INVALID_HANDLE;
	}
	/* Is it a valid RIFF file ? */
	if(memcmp(head.id,"RIFF",4))
	{
		ERROR_GENERIC_2ARG("expected RIFF fourcc in file ",name);
		fileClose(self->df);
		memFreeObject(self);
		return LIBV_INVALID_HANDLE;
	}
	/* Read type_id */
	fileRead(self->df,type_id,4);
	/* Check for type of data */
	if(memcmp(type_id,"WAVE",4))
	{
		ERROR_GENERIC_2ARG("expected WAVE data in file ",name);
		fileClose(self->df);
		memFreeObject(self);
		return LIBV_INVALID_HANDLE;
	}
	/* Find 'fmt ' and 'data' chunks */
	for(chunk_count=0;chunk_count<2;)
	{
		if(!riff_read_head(self->df,&head))
		{
			ERROR_GENERIC_2ARG("unexpected EOF in file ",name);
			break;
		}
		if(!memcmp(head.id,"fmt ",4)) /* read wave format */
		{
			if(!riff_read_fmt(self->df,&head,self))
				break;
			chunk_count++;
			continue;
		} else if(!memcmp(head.id,"data",4))
		{
			/* save parameters of data block */
			self->data_offset = fileTell(self->df);
			self->num_bytes = head.len;
			self->tail_garbage_length = fileSize(self->df) - (self->data_offset+self->num_bytes);
			/* Skip data block; data will be read later */
			fileSeek(self->df,head.len,SEEK_CUR);
			chunk_count++;
			continue;
		} else /* skip other chunks */
		{
#ifdef	CHECKED_BUILD
			conlPrintf(LOG_TRACE,
				"%s(): chunk '%c%c%c%c' ignored in file %s.\n",
				caller_name,head.id[0],head.id[1],head.id[2],head.id[3],name);
#endif	/* CHECKED_BUILD */
			fileSeek(self->df,head.len,SEEK_CUR);
		}
	}
	if(chunk_count < 2)
	{
		/* we are failed to read both needed chunks */
		fileClose(self->df);
		memFreeObject(self);
		return LIBV_INVALID_HANDLE;
	}
	/* adjust number of samples and data size */
	self->num_samples = self->num_bytes / self->block_align;
	/* Seek to start of data */
	fileSeek(self,0,SEEK_SET);
	return self;
}

/*
 * Starts writing to wav file. Returned handle than can be
 * passed to fileWrite() and fileClose().
 */
vhandle_t	V_GLOBAL
wavOpenWrite(char *name,
	u32_t num_channels,
	u32_t sampling_frequency,
	u32_t bits_per_sample)
{
	CALLER_NAME(wavOpenWrite)
	wave_data_p self;
	vhandle_t outfile;
	if(!name || !*name)
	{
		ERROR_GENERIC(error_incorrect_parameters);
		return LIBV_INVALID_HANDLE;
	}
	outfile = fileOpenWrite(name);
	if(outfile == LIBV_INVALID_HANDLE)
	{
		ERROR_GENERIC_2ARG("unable to write: ",name);
		return NULL;
	}
	self = (wave_data_p)ioAllocPort(sizeof(wave_data_t));
	self->port.base.destroy=close_wav_file;
	self->port.write=write_wav_file;
	self->port.ioctl=ioctl_wav_file;
	self->df = outfile;
	self->writemode = true;
	self->num_channels = (u16_t)num_channels;
	self->sampling_frequency = (u32_t)sampling_frequency;
	self->bits_per_sample = (u16_t)bits_per_sample;
	self->block_align=
		(u16_t)(self->num_channels * (self->bits_per_sample >> 3));
	self->bytes_per_second = self->sampling_frequency * self->block_align;
	/* Write main header */
	fileWrite(self->df,"RIFF",4);
	/* Now we cant determine all data in header, so it 
	 * must be fixed later */
	filePutLittleLong(self->df,0x24);
	/* Write type_id and id of 'fmt ' chunk */
	fileWrite(self->df,"WAVEfmt ",8);
	/* Write size of fmt chunk */
	filePutLittleLong(self->df,0x10);
	/* Write wave parameters */
	filePutLittleShort(self->df,WAVE_FORMAT_PCM);
	filePutLittleShort(self->df,self->num_channels);
	filePutLittleLong(self->df,self->sampling_frequency);
	filePutLittleLong(self->df,self->bytes_per_second);
	filePutLittleShort(self->df,self->block_align);
	filePutLittleShort(self->df,self->bits_per_sample);
	/* Write id of 'data' chunk */
	fileWrite(self->df,"data",4);
	/* Write size of 'data' chunk */
	filePutLittleLong(self->df,0);
	return self;
}

/*
 * User-space helper functions
 */
u32_t	V_GLOBAL
wavNumChannels(vhandle_t handle)
{
	u32_t num_channels=0;
	fileControl(handle,pctl_wave_get_num_channels,&num_channels);
	return num_channels;
}

u32_t	V_GLOBAL
wavSamplingFrequency(vhandle_t handle)
{
	u32_t freq=0;
	fileControl(handle,pctl_wave_get_sampling_frequency,&freq);
	return freq;
}

u32_t	V_GLOBAL
wavBitsPerSample(vhandle_t handle)
{
	u32_t bps=0;
	fileControl(handle,pctl_wave_get_bits_per_sample,&bps);
	return bps;
}

u32_t	V_GLOBAL
wavNumSamples(vhandle_t handle)
{
	u32_t num_samples=0;
	fileControl(handle,pctl_wave_get_num_samples,&num_samples);
	return num_samples;
}

/*
 * Reads all wave data into memAlloc()'ed buffer.
 */
void_p	V_GLOBAL
wavLoadFile(char *name,
	u32_t *nsamples_ret,
	u32_t *nchannels_ret,
	u32_t *freq_ret,
	u32_t *bps_ret)
{
	void *buffer;
	vsize_t nsamples, len;
	u32_t freq,nchannels,bps;
	vhandle_t wf;
	wf = wavOpenRead(name);
	if(wf == LIBV_INVALID_HANDLE)
		return NULL;
	/* Get wave parameters */
	if(!fileControl(wf,pctl_wave_get_sampling_frequency,&freq) ||
		!fileControl(wf,pctl_wave_get_bits_per_sample,&bps) ||
		!fileControl(wf,pctl_wave_get_num_samples,&nsamples) ||
		!fileControl(wf,pctl_wave_get_num_channels,&nchannels) )
	{
		fileClose(wf);
		return NULL;
	}
	len = ((freq * nchannels * bps) >> 3) * nsamples;
	/* Now allocate memory and read wave data */
	buffer=(u8_t*)memAlloc(len);
	if(buffer == NULL)
		return NULL;
	fileRead(wf,buffer,len);
	fileClose(wf);
	if(nsamples_ret)
		*nsamples_ret=nsamples;
	if(nchannels_ret)
		*nchannels_ret=nchannels;
	if(freq_ret)
		*freq_ret=freq;
	if(bps_ret)
		*bps_ret=bps;
	return buffer;
}

#ifdef	CONFIG_AUDIO_MP3

#define MP3_BUFFLEN 4608

typedef struct mp3_decoder_s
{
	ioport_base_t	vtbl;
	MP3_context_p	ctx;
	u8_fifo_t		fifo;
	u8_t			buffer[MP3_BUFFLEN];
}	mp3_decoder_t, *mp3_decoder_p;

/*
 * Routine to call from mp3 library
 */
int LibV_audio_read(struct MP3_context_s *ctx,char *buf,int size)
{
	vsize_t len = fileRead(ctx->input_handle,buf,size);
	if(!len)
	{
		if(ctx->mp3_loop)
		{
			fileSeek(ctx->input_handle,0,SEEK_SET);
			len = fileRead(ctx->input_handle,buf,size);
		}
	}
	return (int)len;
}

V_LOCAL	vsize_t V_CALL
mp3_read(vhandle_t handle,void *buf,vsize_t len)
{
	int decoded;
	mp3_decoder_t *dec = (mp3_decoder_t *)handle;
	while(u8fCount(&dec->fifo) < len)
	{
		decoded = MP3_DecodeFrame(dec->ctx,dec->buffer,-1);
		if(decoded <= 0) /* EOF */
			break;
		u8fPushBuffer(&dec->fifo,dec->buffer,(u32_t)decoded);
	}
	if(len > u8fCount(&dec->fifo))
		len = u8fCount(&dec->fifo);
	if(len)
		u8fPopBuffer(&dec->fifo,(u8_t*)buf,len);
	return len;
}

#include <../source/PAL/argsused.h>
V_LOCAL vsize_t V_CALL
mp3_seek(vhandle_t handle,vssize_t offset,int whence)
{
	CALLER_NAME(mp3_seek)
	mp3_decoder_t *dec=(mp3_decoder_t *)handle;
	if(offset || whence != SEEK_SET)
	{
		ERROR_GENERIC("only rewind operation is supported");
		return fileTell(dec->ctx->input_handle);
	}
	return fileSeek(dec->ctx->input_handle,0,SEEK_SET);
}

V_LOCAL u32_t V_CALL
mp3_close(vhandle_t handle)
{
	mp3_decoder_p dec=(mp3_decoder_p)handle;
	UNREF_EXIT(&dec->vtbl);
	fileClose(dec->ctx->input_handle);
	MP3_Close(dec->ctx);
	u8fClear(&dec->fifo);
	memFreeObject(handle);
	return 0;
}

V_LOCAL int V_CALL
mp3_ioctl(vhandle_t handle,unsigned code,void *data)
{
	CALLER_NAME(mp3_ioctl)
	mp3_decoder_p self = (mp3_decoder_p)handle;
	switch(code)
	{
		case pctl_wave_get_num_channels:
			if(!data)
				return -1;
			*((u32_t*)data) = 2; /* FIXME: this is to hide bug (?) in MP3 library */
			break;
		case pctl_wave_get_sampling_frequency:
			if(!data)
				return -1;
			*((u32_t*)data) = self->ctx->MP3_samplerate;
			break;
		case pctl_wave_get_bits_per_sample:
			if(!data)
				return -1;
			*((u32_t*)data) = 16; /* FIXME */
			break;
		case pctl_wave_loop:
			if(!data || !*((u32_p)data))
				self->ctx->mp3_loop = false;
			else
				self->ctx->mp3_loop = true;
			break;
		default:
			ERROR_GENERIC("unknown ioctl code");
			return -1;
	}
	return 0;
}

vhandle_t V_GLOBAL
mp3Open(char *name)
{
	CALLER_NAME(mp3Open)
	vhandle_t in;
	int decoded;
	mp3_decoder_p dec;
	unsigned char id[4];
	MP3_context_p ctx;
	in = fileOpenRead(name);
	if(in == LIBV_INVALID_HANDLE)
	{
		ERROR_GENERIC_2ARG("unable to open: ",name);
		return LIBV_INVALID_HANDLE;
	}
	fileRead(in,id,4);
	if(!memcmp(id,"RIFF",4))
	{
		fileClose(in);
		in = wavOpenRead(name);
		if(in == LIBV_INVALID_HANDLE)
			return LIBV_INVALID_HANDLE;
		WARNING_GENERIC_2ARG("skipped RIFF header: ",name);
	}
	else
		fileSeek(in,0,SEEK_SET);
	ctx = MP3_CreateContext(LibV_audio_read);
	ctx->input_handle = in;
	ctx->MP3_samplerate = 0;
	dec = (mp3_decoder_p)ioAllocPort(sizeof(mp3_decoder_t));
	dec->ctx = ctx;
	dec->vtbl.base.destroy = mp3_close;
	dec->vtbl.read = mp3_read;
	dec->vtbl.seek = mp3_seek;
	dec->vtbl.ioctl = mp3_ioctl;
	u8fInit(&dec->fifo);
	decoded = MP3_DecodeFrame(dec->ctx,dec->buffer,-1);
	if(decoded <= 0)
	{
		ERROR_GENERIC_2ARG("invalid or corrupted MP3 file: ",name);
		fileClose(dec->ctx->input_handle);
		MP3_Close(dec->ctx);
		u8fClear(&dec->fifo);
		memFreeObject(dec);
		return LIBV_INVALID_HANDLE;
	}
	u8fPushBuffer(&dec->fifo,dec->buffer,(u32_t)decoded);
	return (vhandle_t)dec;
}

#endif	/* CONFIG_AUDIO_MP3 */
