/*--------------------------------------------------------------------------*\

	FILE........: WAVE.CPP
	TYPE........: C++ Module
	AUTHOR......: David Rowe
	DATE CREATED: 21/1/97

	Functions for reading and writing RIFF Wave files.

        64 bit support 2006, Ron Lee.

	NOTE: dont add any mprintfs here as mes_init() may not be called
        by people who use the vpb_wave_xxx functions without calling
        vpb_open() first.


         Voicetronix Voice Processing Board (VPB) Software
         Copyright (C) 1999-2008 Voicetronix www.voicetronix.com.au

         This library is free software; you can redistribute it and/or
         modify it under the terms of the GNU Lesser General Public
         License as published by the Free Software Foundation; either
         version 2.1 of the License, or (at your option) any later version.

         This library is distributed in the hope that it will be useful,
         but WITHOUT ANY WARRANTY; without even the implied warranty of
         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
         Lesser General Public License for more details.

         You should have received a copy of the GNU Lesser General Public
         License along with this library; if not, write to the Free Software
         Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
         MA  02110-1301  USA

\*---------------------------------------------------------------------------*/

#include "apifunc.h"

#include <cstdio>
#include <cstring>
#include <cmath>

using std::string;


#define	FS		8000	// sample rate		

// wave file format tags

#define	WAVE_LINEAR	1
#define	WAVE_ALAW	6
#define	WAVE_MULAW	7
#define WAVE_OKIADPCM	16


#pragma pack(1)			// force member alignment to one byte

// RIFF-WAVE chunk

typedef struct {
	char        riff[4];
	uint32_t    lwave;		// length of wave segment in bytes
	char        wavefmt[8];
} WAVE;

// format chunk

typedef struct {
	uint32_t    lfmt;		// length of format segment		
	uint16_t      fmt;
	uint16_t      channels;
	int32_t     srate;
	int32_t     bytesec;
	short       blockalign;
	short       bits_sample;
	short       cbsize;
} FMT;

// fact chunk (only required for non-WAVE_LINEAR files)

typedef struct {
	char        fact[4];
	uint32_t    length;		// length of fact chunk after this long
	uint32_t    ldata;		// length of data segment in bytes	
} FACT;

// data chunk

typedef struct {
	char        data[4];
	uint32_t    ldata;		// length of data segment in bytes	
} DATA;

// vlog chunk, custom to Voicetronix, preserved by .wav standard
// Note all strings are length byte + string + null terminate and
// are an even number of words in length.
typedef struct {
	char	    vlog[4];	   // chunk name
	uint32_t    length;	   // chunk length
	short	    channel;	   // Channel number 
	char	    inout[4];	   // Call Direction
	char	    statime[16];   // Record Start Time yyyymmddHHMMSS
	char	    endtime[16];   // Record End Time yyyymmddHHMMSS
	char	    filename[254]; // Original Filename
	char	    cidn[128];	   // CID number ascii
	char	    cidt[128];	   // CID TEXT ascii
	char	    term[64];	   // Reason for call termination text
} VLOG;

// AU file header format...

#define	AU_FILE_MAGIC ((uint32_t)0x2e736e64)
#define AU_ENCODING_MULAW	1
#define	AU_ENCODING_ALAW	27
#define AU_ENCODING_LINEAR	3
#define	AU_ENCODING_ADPCM	23

#pragma pack(1)

typedef	struct {
	uint32_t   magic;    // ".snd"
	uint32_t   hdrsize;  // at least 24 bytes
	uint32_t   datasize; // size of audio sample
	uint32_t   encoding; // data format
	uint32_t   sampling; // sample rate
	uint32_t   channels; // 1 or 2...	
} AU;

#pragma pack()

// state data required for wave files

enum WFILEType { WAVE_FILE, AU_FILE };

struct WFILE {
	WFILEType   file;
	AU          au;
	WAVE	    wave;
	FMT	    fmt;
	FACT	    fact;
	VLOG	    logchunk;
	DATA	    data;
	FILE	   *f;
	uint16_t    bytes_sam;
	uint32_t    bytes_left; // track bytes read in this wave data chunk
};

void au_swap(AU *au);


void WINAPI vpb_wave_open_write(WFILE **wv, const string &filename, int mode)
{ //{{{
	WFILE   *w = new WFILE;
	*wv = w;

	w->file = WAVE_FILE;
	w->f = fopen(filename.c_str(),"wb");	// write, binary
	if (w->f == NULL) {
		delete w;
		throw VpbException("vpb_wave_open_write: failed to open '%s': %m",
				   filename.c_str());
	}

	// validate mode ------------------------------------------
	// filter out flag bits (14, 13 & 12)
	switch(mode & 0x8fff) {
		case VPB_LINEAR:
			w->fmt.fmt = WAVE_LINEAR;
			w->bytes_sam = 2;
			w->fmt.bits_sample = 16;
		break;
		case VPB_ALAW:
			w->fmt.fmt = WAVE_ALAW;
			w->bytes_sam = 1;
			w->fmt.bits_sample = 8;
		break;
		case VPB_MULAW:
			w->fmt.fmt = WAVE_MULAW;
			w->bytes_sam = 1;
			w->fmt.bits_sample = 8;
		break;
	/*	case VPB_OKIADPCM:
			h->fmt = WAVE_OKIADPCM;
			w->bytes_sam = 1;
			h->bits_sample = 4;
		break;*/
		default:
			throw VpbException("vpb_wave_open_write('%s'): unsupported mode 0x%x",
					   filename.c_str(), mode & 0x8ff);
		break;
	}
//printf("mode = %d\n",mode);
	// set up riff wave chunk ---------------------------------

	memcpy(w->wave.riff,"RIFF",4);
	int sizeof_header = sizeof(WAVE) + sizeof(FMT) + sizeof(DATA);
	if (w->fmt.fmt != WAVE_LINEAR)
		sizeof_header += sizeof(FACT);
	if ((mode & VPB_VLOG_CHUNK)!= 0)
		sizeof_header += sizeof(VLOG);
	w->wave.lwave = sizeof_header-8;	// length of wave segment	
	memcpy(w->wave.wavefmt,"WAVEfmt ",8);

	// set up format chunk ------------------------------------

	w->fmt.lfmt		= 0x12;			// format header length	
	w->fmt.channels		= 1;			// mono					
	w->fmt.srate		= FS;			// Fs = 8 kHz			
	w->fmt.bytesec		= FS*w->bytes_sam;	// bytes/second			
	w->fmt.blockalign	= w->bytes_sam;
	w->fmt.cbsize		= 0;

	// set up  fact chunk -------------------------------------

	memcpy(w->fact.fact,"fact",4);
	w->fact.length = 4;
	w->fact.ldata = 0;

	// set up vlog chunk (for logging files only) -------------
	if((mode & VPB_VLOG_CHUNK) != 0)
	{
//		printf("seting up vlog data\n");
		memcpy(w->logchunk.vlog,"vlog",4);
		w->logchunk.length = sizeof(VLOG)-8;
		// make all fields empty & define the size.
		w->logchunk.channel=0;		// Channel number
		w->logchunk.inout[0]=4;		// Call Direction empty
		w->logchunk.inout[1]=0;
		w->logchunk.statime[0]=16;	// Record Start Time yyyymmddHHMMSS
		w->logchunk.statime[1]=0;
		w->logchunk.endtime[0]=16;	// Record End Time yyyymmddHHMMSS
		w->logchunk.endtime[1]=0;
		w->logchunk.filename[0]=254;	// Original Filename
		w->logchunk.filename[1]=0;
		w->logchunk.cidn[0]=128;	// CID number ascii
		w->logchunk.cidn[1]=0;
		w->logchunk.cidt[0]=128;	// CID text ascii
		w->logchunk.cidt[1]=0;
		w->logchunk.term[0]=64;		// Termination text ascii
		w->logchunk.term[1]=0;
	}
	else
		w->logchunk.vlog[0]=0;
//	printf("letters= %c %c %c %c\n",w->logchunk.vlog[0]
//		,w->logchunk.vlog[1],w->logchunk.vlog[2],w->logchunk.vlog[3]);
	// set up data chunk --------------------------------------

	memcpy(w->data.data,"data",4);
	w->data.ldata = 0;                              // length of data segmt	
	// write header to disk

	fwrite(&w->wave, sizeof(WAVE), 1, w->f);
	fwrite(&w->fmt, sizeof(FMT), 1, w->f);
	if ((mode & 0x8fff) != VPB_LINEAR)
		fwrite(&w->fact, sizeof(FACT), 1, w->f);
	// if logging data is required
	if(w->logchunk.vlog[0] =='v')
		fwrite(&w->logchunk, sizeof(VLOG), 1, w->f);
	fwrite(&w->data, sizeof(DATA), 1, w->f);
} //}}}

void WINAPI vpb_wave_open_read(WFILE **wv, const string &filename)
{ //{{{
	WFILE   *w = new WFILE;
	*wv = w;

	w->file = WAVE_FILE;
	w->f = fopen(filename.c_str(),"rb");
	if (w->f == NULL) {
		delete w;
		throw VpbException("vpb_wave_open_read: failed to open '%s': %m",
				   filename.c_str());
	}

	// read wave file header -------------------------------------------

	if(fread(&w->au, sizeof(AU), 1, w->f) ==0)
	{
		fclose(w->f);
		delete w;
		throw VpbException("vpb_wave_open_read: failed to read '%s': %m",
				   filename.c_str());
	}
	au_swap(&w->au);

	if(w->au.magic == AU_FILE_MAGIC)
	{
		w->file = AU_FILE;
		w->fmt.fmt = 0xff;
		w->fmt.srate = w->au.sampling;
		w->fmt.channels = (unsigned short)(w->au.channels);

		// read and discard comment
		// note: I dont think this can buffer overflow, as hdrsize is 
		// unsigned and number of reads is limited by sizeof(AU)
		char tmp;
		unsigned int  i;
		for(i=0; i<w->au.hdrsize-sizeof(AU); i++) {
			fread(&tmp, 1, 1, w->f);
		}

		switch(w->au.encoding)
		{
		case AU_ENCODING_LINEAR:
			w->fmt.fmt = WAVE_LINEAR;
			w->fmt.bytesec = w->au.sampling * 2;
			break;
		case AU_ENCODING_MULAW:
			w->fmt.fmt = WAVE_MULAW;
			w->fmt.bytesec = w->au.sampling;
			break;
		case AU_ENCODING_ALAW:
			w->fmt.fmt = WAVE_ALAW;
			w->fmt.bytesec = w->au.sampling;
			break;
		case AU_ENCODING_ADPCM:
			w->fmt.fmt = WAVE_OKIADPCM;
			w->fmt.bytesec = w->au.sampling / 2;
			break;
		}
	}
	else
	{
		w->file = WAVE_FILE;

		// read in wave file header

		fseek(w->f, 0l, SEEK_SET);
		fread(&w->wave, sizeof(WAVE), 1, w->f);

		// read in length param of wave header
		fread(&w->fmt, sizeof(uint32_t), 1, w->f);

		// check for possible buffer overflow
		if (w->fmt.lfmt > sizeof(FMT)) {
			throw VpbException("vpb_wave_open_read: unsupported format '%s'",
					   filename.c_str());
		}

		// now read rest of header
		fread(&w->fmt.fmt, sizeof(char), w->fmt.lfmt, w->f);

		// now look for data chunk, bypassing other chunks

		char        chunk_id[4];
		int         found_data = 0;
		uint32_t   fact_length;
		int         ret;

		do {
			fread(&chunk_id, sizeof(char), 4, w->f);
			if (strncmp(chunk_id, "data", 4) == 0) {
				found_data = 1;
			}
			else if(strncmp(chunk_id, "vlog", 4) == 0)
			{	// printf("has VLOG chunk\n");
				memcpy(w->logchunk.vlog,"vlog",4);
				fread(&w->logchunk.length, sizeof(uint32_t), 1, w->f);
				fread(&w->logchunk.channel, sizeof(VLOG)-8, 1, w->f);
			}
			else {
				// move past and ignore this chunk
				fread(&fact_length, sizeof(uint32_t), 1, w->f);
				ret = fseek(w->f, fact_length, SEEK_CUR);

				// if this fires means error in
				// wave routine or bad wave file
				if (ret != 0) {
					throw VpbException("vpb_wave_open_read: "
							   "corrupt file '%s'",
							   filename.c_str());
				}
			}
		} while (!found_data);

		// set up w->data 

		ret = fread(&w->data.ldata, sizeof(uint32_t), 1, w->f);
		w->bytes_left = w->data.ldata;
	}

	w->bytes_sam = (uint16_t)ceil((float)(w->fmt.bytesec/w->fmt.srate));
	if(w->fmt.channels > 1) {
		delete w;
		throw VpbException("vpb_wave_open_read: can't mix %d channels from '%s'",
				   w->fmt.channels, filename.c_str());
	}
} //}}}

void WINAPI vpb_wave_close_write(WFILE *w)
{
	fseek(w->f, 0l, SEEK_SET);

	fwrite(&w->wave, sizeof(WAVE), 1, w->f);
	fwrite(&w->fmt, sizeof(FMT), 1, w->f);
	if (w->fmt.fmt != WAVE_LINEAR)
		fwrite(&w->fact, sizeof(FACT), 1, w->f);
	if(w->logchunk.vlog[0] =='v')
		fwrite(&w->logchunk, sizeof(VLOG), 1, w->f);
	fwrite(&w->data, sizeof(DATA), 1, w->f);

	fclose(w->f);
	delete w;
}

void WINAPI vpb_wave_close_read(WFILE *w)
{
	fclose(w->f);
	delete w;
}

int WINAPI vpb_wave_write(WFILE *w, const char *buf, long n)
{
	int write = fwrite(buf, sizeof(char), n, w->f);
	w->wave.lwave += n;
	w->fact.ldata += n;
	w->data.ldata += n;

	return write;
}

int WINAPI vpb_wave_read(WFILE *w, char *buf, unsigned long n)
{
	int     ret;
	size_t  bytes_to_read;

	if(w->file == WAVE_FILE) {

		// only read to end of wave chunk, as some wave files have
		// non-audio chuncks after audio data

		if(w->bytes_left > n) {
			bytes_to_read = n;
		}
		else {
			bytes_to_read = w->bytes_left;
		}
		ret = fread(buf, sizeof(char), bytes_to_read, w->f);
		w->bytes_left -= bytes_to_read;
	}
	else {
		// au files

		ret = fread(buf, sizeof(char), n, w->f);
	}

	/* DR 5/2/03 used for debugging wave file bugs
	int i;
	for(i=0; i<ret; i++) {
		if (buf[i] != (char)0xff) {
			printf("[%d] %02x %c\n", i, buf[i], buf[i]);
		}
	}
	*/

	return ret;
}

int WINAPI vpb_wave_seek(WFILE *w, long offset)
{
	long	sizeof_header = 0;

	switch(w->file)
	{
	    case WAVE_FILE:
		sizeof_header = sizeof(WAVE) + sizeof(FMT) + sizeof(DATA);
		if (w->fmt.fmt != WAVE_LINEAR)
			sizeof_header += sizeof(FACT);
		if (w->logchunk.vlog[0] == 'v')
			sizeof_header += sizeof(VLOG);
		break;

	    case AU_FILE:
		sizeof_header = w->au.hdrsize;
	}
	return fseek(w->f, sizeof_header + offset, SEEK_SET);
}

void WINAPI vpb_wave_set_sample_rate(WFILE *w, unsigned short rate)
{ //{{{
	w->au.sampling  = rate;
	w->fmt.srate    = rate;			// Fs			 
	w->fmt.bytesec  = rate * w->bytes_sam;	// bytes/second		
} //}}}

AudioCompress WINAPI vpb_wave_get_mode(WFILE *w)
{ //{{{
	switch(w->file)
	{
	    case WAVE_FILE:
		switch(w->fmt.fmt)
		{
		    case WAVE_LINEAR:   return VPB_LINEAR;
		    case WAVE_ALAW:     return VPB_ALAW;
		    case WAVE_MULAW:    return VPB_MULAW;
		//  case WAVE_OKIADPCM: return VPB_OKIADPCM;
		}
	    case AU_FILE:
		switch(w->au.encoding)
		{
		    case AU_ENCODING_LINEAR: return VPB_LINEAR;
		    case AU_ENCODING_ALAW:   return VPB_ALAW;
		    case AU_ENCODING_MULAW:  return VPB_MULAW;
		}
	}
	throw VpbException("vpb_wave_get_mode: invalid type %d", w->file);
} //}}}

size_t vpb_wave_get_size(WFILE *w)
{ //{{{
	switch(w->file)
	{
	    case WAVE_FILE: return w->data.ldata;
	    case AU_FILE:   return w->au.datasize;
	}
	throw VpbException("vpb_wave_get_size: invalid type %d", w->file);
} //}}}

static void swap32(uint32_t *l) {
	unsigned char b0,b1,b2,b3;

	b0 = (*l>>24) & 0xff;
	b1 = (*l>>16) & 0xff;
	b2 = (*l>>8)  & 0xff;
	b3 =  *l      & 0xff;

	*l = (b3<<24) + (b2<<16) + (b1<<8) + b0;
}

void au_swap(AU *au) {
	swap32(&au->magic);
	swap32(&au->hdrsize);
	swap32(&au->datasize);
	swap32(&au->encoding);
	swap32(&au->sampling);
	swap32(&au->channels);
}

int WINAPI vpb_wave_set_vlog(WFILE *w, VPB_VLOG *buf)
{
	int n;
//        printf("letters= %c %c %c %c\n",w->logchunk.vlog[0]
//	  ,w->logchunk.vlog[1],w->logchunk.vlog[2],w->logchunk.vlog[3]);

	if(w->logchunk.vlog[0] != 'v')
		return 1;				// No log ckunk in destination file
	w->logchunk.channel = buf->channel;
	n=strlen(buf->inout);
	if( n < w->logchunk.inout[0])
	{
		//printf("buf->inout = %s\n",buf->inout);
		//strcpy(w->logchunk.inout +1, buf->inout);
		memcpy(w->logchunk.inout +1, buf->inout,3);
	}
	else
		return 2;				// Size Error

	if((n=strlen(buf->statime))< w->logchunk.statime[0]-1)
	{
		//printf("buf->statime = %s\n",buf->statime);
		strcpy(w->logchunk.statime +1, buf->statime);
	}
	else
		return 3;				// Size Error

	if((n=strlen(buf->endtime))< w->logchunk.endtime[0]-1)
	{
		strcpy(w->logchunk.endtime +1, buf->endtime);
	}
	else
		return 4;				// Size Error

	if((n=strlen(buf->filename))< ((unsigned char)w->logchunk.filename[0]-1))
	{
		strcpy(w->logchunk.filename +1, buf->filename);
	}
	else
	{	//printf("len = %d\n",n);
		return 5;				// Size Error
	}

	if((n=strlen(buf->cidn))< ((unsigned char)w->logchunk.cidn[0])-1)
	{
		strcpy(w->logchunk.cidn +1, buf->cidn);
	}
	else
		return 6;				// Size Error

	if((n=strlen(buf->cidt))< ((unsigned char)w->logchunk.cidt[0])-1)
	{
		strcpy(w->logchunk.cidt +1, buf->cidt);
	}
	else
		return 7 ;				// Size Error
	if((n=strlen(buf->term))< ((unsigned char)w->logchunk.term[0])-1)
	{
		strcpy(w->logchunk.term +1, buf->term);
	}
	else
		return 8;				// Size Error
	return  0;					// OK
}

int WINAPI vpb_wave_get_vlog(WFILE *w, VPB_VLOG *buf)
{
	//printf("letters= %c %c %c %c\n",w->logchunk.vlog[0]
	//	 ,w->logchunk.vlog[1],w->logchunk.vlog[2],w->logchunk.vlog[3]);

	if(w->logchunk.vlog[0] == 'v')
	{
		buf->channel = w->logchunk.channel;
		memcpy( buf->inout, w->logchunk.inout +1,3);
		buf->inout[3] = 0;
		strcpy( buf->statime, w->logchunk.statime+1);
		strcpy( buf->endtime, w->logchunk.endtime+1);
		strcpy( buf->filename, w->logchunk.filename+1);
		strcpy( buf->cidn, w->logchunk.cidn+1);
		strcpy( buf->cidt, w->logchunk.cidt+1);
		strcpy( buf->term, w->logchunk.term+1);
		return 0;  // OK
	}
	return 1;  // No Chunk
}

