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

#ifdef	CONFIG_AUDIO_MP3
#include <../support/mpglib/mpg123.h>
#include <../support/mpglib/mpglib.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 */

/*
 *	Microsoft WAV format description
 */
#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 */

/*
**	This object represent 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 */
	u32_t	num_samples;		/* total number of samples (not bytes) */
	u32_t	num_bytes;			/* total data size in bytes */
	vhandle_t df;			/* file handle */
	u32_t	transferred;		/* number of transferred bytes */
	vboolean writemode;			/* == true for res of wavOpenWrite() */
	vboolean loop;				/* seek to start after last read() */
	u32_t	data_offset;		/* offset to wave data in self->df */
}	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(file_t in,chunk_head_t *head_ret)
{
	u32_t pos,rd;
	pos=fileTell(in);
	fileRead(in,head_ret,4);
	head_ret->len=fileGetLittleLong(in);
	rd=fileTell(in)-pos;
	if(rd!=8)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,
			"riff_read_head(): unable to read chunk header.\n");
#endif	/* CHECKED_BUILD */
		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 first byte
**	after chunk header).
*/
V_LOCAL void_p
riff_read_chunk_data(file_t in,chunk_head_t *chead)
{
	void *cdata;
	cdata=memAlloc(chead->len);
	if((u32_t)fileRead(in,cdata,chead->len)!=chead->len)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"riff_read_chunk_data(): "
			"unexpected end of file in chunk '%c%c%c%c'.\n",
			chead->id[0],chead->id[1],chead->id[2],chead->id[3]);
#endif	/* CHECKED_BUILD */
		memFree(cdata);
		return NULL;
	}
	return cdata;
}

/*
**	Reads wave format chunk.
*/
V_LOCAL	vboolean
riff_read_fmt(file_t in,chunk_head_t *chead,wave_data_t *fmt)
{
	wave_format_t *cdata;
	vboolean ret=false;
	for(;;)
	{
		cdata=(wave_format_t*)riff_read_chunk_data(in,chead);
		if(!cdata)
			break;
#if	0
		if(cdata->format_tag!=LITTLE_SHORT(WAVE_FORMAT_PCM))
		{
			conlPrintf(LOG_TRACE,
				"riff_read_fmt(): format tag (%X)!=WAVE_FORMAT_PCM.\n",
				cdata->format_tag);
		}
#endif		/* 0 */
		fmt->num_channels=LITTLE_SHORT(cdata->num_channels);
		fmt->bits_per_sample=LITTLE_SHORT(cdata->bits_per_sample);
		fmt->sampling_frequency=LITTLE_LONG(cdata->sampling_frequency);
		if(!fmt->num_channels)
			fmt->num_channels=2;
		if(!fmt->bits_per_sample)
			fmt->bits_per_sample = 16;
		if(!fmt->sampling_frequency)
			fmt->sampling_frequency = 44100;
		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;
}

/*
**	Member functions for wave I/O port.
*/

/*
**	port::read() - reads next block of data from file
*/
V_LOCAL	u32_t	V_CALL
read_wav_file(vhandle_t handle,void *dest,u32_t len)
{
	u32_t rlen;
	wave_data_t *self;
	self=(wave_data_t*)handle;
	if(self->loop)
	{
		u32_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;
}

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

/*
**	port::ioctl() - handles requests for parameters
*/
V_LOCAL int V_CALL
ioctl_wav_file(vhandle_t handle,int code,void *data)
{
	u32_t pt;
	void *wavedata_base;
	wave_data_t *self;
	self=(wave_data_t*)handle;
	if(data == NULL)
		return -1;
	switch(code)
	{
		case pctl_get_type:
			fileControl(self->df,pctl_get_type,&pt);
			*((u32_t*)data) = pt;
			break;
		case pctl_wave_get_num_channels:
			*((u32_t*)data)=self->num_channels;
			break;
		case pctl_wave_get_sampling_frequency:
			*((u32_t*)data)=self->sampling_frequency;
			break;
		case pctl_wave_get_bits_per_sample:
			*((u32_t*)data)=self->bits_per_sample;
			break;
		case pctl_wave_get_num_samples:
			*((u32_t*)data)=self->num_samples;
			break;
		case pctl_wave_loop:
			self->loop = (data) ? true : false;
			break;
		case pctl_map_ispace:
			fileControl(self->df,pctl_map_ispace,&wavedata_base);
			*((void**)data) = (u8_t*)wavedata_base + self->data_offset;
			break;
		default:
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_ERROR,
				"ioctl_wav_file(%u,%0X): unknown ioctl code '%u'.\n",
				code,data,code);
#endif		/* CHECKED_BUILD */
			return -1;
	}
	return 0;
}

/*
**	Closes wave file, frees all resources.
*/
V_LOCAL u32_t V_CALL
close_wav_file(file_t handle)
{
	wave_data_t *self;
	self=(wave_data_t*)handle;
	if(--self->port.base.ref_count)
		return self->port.base.ref_count;
	if(self->df != NULL)
	{
		if(self->writemode)
		{
			/*
			 * File was opened for output: now we must write
			 * some 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)
{
	char type_id[4];
	int chunk_count;
	wave_data_p self;
	chunk_head_t head;
	self=(wave_data_p)ioAllocPort(sizeof(wave_data_t));
	self->port.base.destroy=close_wav_file;
	self->port.read=read_wav_file;
	self->port.ioctl=ioctl_wav_file;
	self->df=fileOpenRead(name);
	self->writemode=false;
	if(self->df == NULL)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"waveOpenRead('%s'): unable to open file.\n",
			dbs(name));
#endif	/* CHECKED_BUILD */
		__libvCloseHandle(self);
		return NULL;
	}
	/* Read main header */
	if(!riff_read_head(self->df,&head))
	{
		fileClose(self->df);
		__libvCloseHandle(self);
		return NULL;
	}
	/* Is it a valid RIFF file ? */
	if(memcmp(head.id,"RIFF",4))
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"waveOpenRead('%s'): expected RIFF fourcc, found '%c%c%c%c'.\n",
			name,head.id[0],head.id[1],head.id[2],head.id[3]);
#endif	/* CHECKED_BUILD */
		fileClose(self->df);
		__libvCloseHandle(self);
		return NULL;
	}
	/* Read type_id */
	fileRead(self->df,type_id,4);
	/* Check for type of data */
	if(memcmp(type_id,"WAVE",4))
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"waveOpenRead('%s'): expected WAVE data, found '%s'.\n",
			name,type_id);
#endif	/* CHECKED_BUILD */
		fileClose(self->df);
		__libvCloseHandle(self);
		return NULL;
	}
	/* Find 'fmt ' and 'data' chunks */
	for(chunk_count=0;chunk_count<2;)
	{
		if(!riff_read_head(self->df,&head))
		{
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_ERROR,"waveOpenRead('%s'): unexpected EOF.\n",
				name);
#endif	/* CHECKED_BUILD */
			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;
			/* 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,
				"waveOpenRead('%s'): unknown chunk '%c%c%c%c'; ignored.\n",
				name,head.id[0],head.id[1],head.id[2],head.id[3]);
#endif		/* CHECKED_BUILD */
			fileSeek(self->df,head.len,SEEK_CUR);
		}
	}
	if(chunk_count<2)
	{
		/* we are failed */
		fileClose(self->df);
		__libvCloseHandle(self);
		return NULL;
	}
	/* adjust number of samples and data size */
	self->num_samples=self->num_bytes / self->block_align;
	/* Seek to start of data */
	fileSeek(self->df,self->data_offset,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)
{
	wave_data_p self;
	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=fileOpenWrite(name);
	if(self->df == NULL)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"waveOpenWrite('%s'): can't create file.\n",
			dbs(name));
#endif	/* CHECKED_BUILD */
		__libvCloseHandle(self);
		return NULL;
	}
	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;
}

/*
**	Handy wrappers
*/
u32_t	V_GLOBAL
wavNumChannels(file_t handle)
{
	u32_t num_channels=0;
	fileControl(handle,pctl_wave_get_num_channels,&num_channels);
	return num_channels;
}

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

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

u32_t	V_GLOBAL
wavNumSamples(file_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;
	u32_t nsamples,freq,nchannels,bps,len;
	vhandle_t wf;
	wf=wavOpenRead(name);
	if(wf == NULL)
		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_IN_BUF_SIZE		16384
#define	MP3_OUT_BUF_SIZE	16384

typedef struct mp3_decoder_s
{
	ioport_base_t	vtbl;
	u32_t			num_channels, sampling_frequency, bits_per_sample;
	file_t			in_stream;
	struct			mpstr mp;
	u8_fifo_t		decompressed;
	char			in_buf[MP3_IN_BUF_SIZE];
	char			out_buf[MP3_OUT_BUF_SIZE];
}	mp3_decoder_t, *mp3_decoder_p;

int c=0;

V_LOCAL	u32_t V_CALL
mp3_read(file_t handle,void *buf,u32_t len)
{
	int size, ret;
	u32_t read_in;
	mp3_decoder_t *dec=(mp3_decoder_t *)handle;
	/* while not enought data in 'decompressed', decompress */
	while(u8fCount(&dec->decompressed)<len)
	{
		read_in=fileRead(dec->in_stream,dec->in_buf,MP3_IN_BUF_SIZE);
		if(!read_in)	/* EOF */
			break;
		/* decompress input buffer */
		ret = decodeMP3(&dec->mp,dec->in_buf,read_in,dec->out_buf,
			MP3_OUT_BUF_SIZE,&size);
		while(ret == MP3_OK)
		{
			u8fPushBuffer(&dec->decompressed,(u8_t*)dec->out_buf,(u32_t)size);
			ret = decodeMP3(&dec->mp,NULL,0,dec->out_buf,MP3_OUT_BUF_SIZE,&size);
		}
	}
	if(len>u8fCount(&dec->decompressed))
		len=u8fCount(&dec->decompressed);
	if(len)
		u8fPopBuffer(&dec->decompressed,(u8_t*)buf,len);
	return len;
}

V_LOCAL u32_t V_CALL
mp3_close(file_t handle)
{
	mp3_decoder_p dec=(mp3_decoder_p)handle;
	if(--dec->vtbl.base.ref_count)
		return dec->vtbl.base.ref_count;
	ExitMP3(&dec->mp);
	u8fClear(&dec->decompressed);
	fileClose(dec->in_stream);
	memFreeObject(handle);
	return 0;
}

V_LOCAL int V_CALL
mp3_ioctl(vhandle_t handle,int code,void *data)
{
	mp3_decoder_p self=(mp3_decoder_p)handle;
	if(self == NULL)
		return -1;
	switch(code)
	{
		case pctl_wave_get_num_channels:
			*((u32_t*)data)=self->num_channels;
			break;
		case pctl_wave_get_sampling_frequency:
			*((u32_t*)data)=self->sampling_frequency;
			break;
		case pctl_wave_get_bits_per_sample:
			*((u32_t*)data)=self->bits_per_sample;
			break;
		case pctl_wave_loop:
			fileControl(self->in_stream,pctl_wave_loop,data);
			break;
		default:
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_ERROR,
				"mp3_ioctl(%u,%0X): unknown ioctl code '%u'.\n",
				code,data,code);
#endif		/* CHECKED_BUILD */
			return -1;
	}
	return 0;
}

file_t V_GLOBAL
mp3Open(char *name)
{
	file_t in;
	mp3_decoder_p dec;
	unsigned char id[4];
	u32_t num_channels, sampling_frequency, bits_per_sample;
	in=fileOpenRead(name);
	if(!in)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"mp3Open(%s): unable to open.\n",name);
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	fileRead(in,id,4);
	if(!memcmp(id,"RIFF",4))
	{
		fileClose(in);
		in=wavOpenRead(name);
		fileControl(in,pctl_wave_get_num_channels,&num_channels);
		fileControl(in,pctl_wave_get_sampling_frequency,&sampling_frequency);
		fileControl(in,pctl_wave_get_bits_per_sample,&bits_per_sample);
	}
	else
	{
		fileSeek(in,0,SEEK_SET);
		/*
		 * FIXME: this is temporary HACK, implement real solution
		 */
		num_channels=2;
		sampling_frequency=44100;
		bits_per_sample=16;
	}
	dec=(mp3_decoder_p)ioAllocPort(sizeof(mp3_decoder_t));
	dec->vtbl.base.destroy=mp3_close;
	dec->vtbl.read=mp3_read;
	dec->vtbl.ioctl=mp3_ioctl;
	InitMP3(&dec->mp);
	u8fInit(&dec->decompressed);
	dec->in_stream=in;
	dec->num_channels=num_channels;
	dec->sampling_frequency=sampling_frequency;
	dec->bits_per_sample=bits_per_sample;
	return (file_t)dec;
}

#endif	/* CONFIG_AUDIO_MP3 */
