/*
	wav_id.c
	14.07.99 tn

	machine-independent (work both on big and little endian)
	code to check if we have valid wavheader that is configured
	for cd-quality (16 bit, stereo, 44.1kHz)

	01.04.04 steve wahl

	Do correct RIFF parsing, don't assume a static header style.
	Code borrowed from cdrecord.
*/

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include "largefile.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <glib.h>
#include "xcdroast.h"

typedef struct {
	guchar	ckid[4];
	guchar	cksize[4];
} chunk_t;

typedef struct {
	guchar	wave[4];
} riff_chunk;

typedef struct {
	guchar	fmt_tag[2];
	guchar	channels[2];
	guchar	sample_rate[4];
	guchar	av_byte_rate[4];
	guchar	block_size[2];
	guchar	bits_per_sample[2];
} fmt_chunk;

#define	WAV_RIFF_MAGIC		"RIFF"		/* Magic for file format     */
#define	WAV_WAVE_MAGIC		"WAVE"		/* Magic for Waveform Audio  */
#define	WAV_FMT_MAGIC		"fmt "		/* Start of Waveform format  */
#define	WAV_DATA_MAGIC		"data"		/* Start of data chunk	     */
#define	WAV_FORMAT_PCM		0x0001		/* Linear PCM format	     */
#define	WAV_FORMAT_ULAW		0x0101		/* American ISDN Telephonie  */
#define	WAV_FORMAT_ALAW		0x0102		/* International ISDN Tel.   */
#define	WAV_FORMAT_ADPCM	0x0103		/* ADPCM format		     */

#define	le_a_to_u_short(a)	((unsigned short) \
				((((unsigned char*) a)[0]       & 0xFF) | \
				 (((unsigned char*) a)[1] << 8  & 0xFF00)))

#ifdef	__STDC__
#define	le_a_to_u_long(a)	((unsigned long) \
				((((unsigned char*) a)[0]       & 0xFF) | \
				 (((unsigned char*) a)[1] << 8  & 0xFF00) | \
				 (((unsigned char*) a)[2] << 16 & 0xFF0000) | \
				 (((unsigned char*) a)[3] << 24 & 0xFF000000UL)))
#else
#define	le_a_to_u_long(a)	((unsigned long) \
				((((unsigned char*) a)[0]       & 0xFF) | \
				 (((unsigned char*) a)[1] << 8  & 0xFF00) | \
				 (((unsigned char*) a)[2] << 16 & 0xFF0000) | \
				 (((unsigned char*) a)[3] << 24 & 0xFF000000)))
#endif

/* check if valid wav-header */
/* endian independent version */
/* return number of bytes if valid, 0 if not */
/* if offset is non NULL, place offset in file to base of wave data there */
/* leaves file pointer at begining of wave data if valid */

off_t is_std_wav_file(gint f, off_t *offset)
{
	chunk_t		chunk;
	riff_chunk	riff;
	fmt_chunk	fmt;
	struct stat	sb;
	off_t		cursor;
	gint		gotFormat;
	mode_t		mode;
	off_t		size;

	/*
	 * First check if a bad guy tries to call wavsize()
	 * with an unappropriate file descriptor.
	 * return 0 in this case.
	 */

	if (isatty(f))
		return (0);
	if (fstat(f, &sb) < 0)
		return (0);
	mode = sb.st_mode & S_IFMT;
	if (!S_ISREG(mode) && !S_ISBLK(mode) && !S_ISCHR(mode))
		return (0);

	cursor = (off_t)0;
	lseek(f, cursor, SEEK_SET);
	gotFormat = FALSE;

	for (;;) {
		if (read(f, &chunk, sizeof (chunk)) != sizeof (chunk))
			goto err;
		size = (off_t)le_a_to_u_long(chunk.cksize);

		if (strncmp((char *)chunk.ckid, WAV_RIFF_MAGIC, 4) == 0) {
			/*
			 * We found (first) RIFF header. Check if a WAVE
			 * magic follows. Set up size to be able to skip
			 * past this header.
			 */
			if (read(f, &riff, sizeof (riff)) != sizeof (riff))
				goto err;
			if (strncmp((char *)riff.wave, WAV_WAVE_MAGIC, 4) != 0)
				goto err;
			size = (off_t)sizeof (riff);

		} else if (strncmp((char *)chunk.ckid, WAV_FMT_MAGIC, 4) == 0) {
			/*
			 * We found WAVE "fmt " header. Check size (if it is
			 * valid for a WAVE file) and coding whether it is
			 * useable for a CD. 
			 */
			if (size < (off_t)sizeof (fmt)) goto err;
			if (sizeof (fmt) != read(f, &fmt, sizeof (fmt))) goto err;
			if (le_a_to_u_short(fmt.channels) != 2 ||
			    le_a_to_u_long(fmt.sample_rate) != 44100 ||
			    le_a_to_u_short(fmt.bits_per_sample) != 16) {
				goto err;
			}
			gotFormat = TRUE;

		} else if (strncmp((char *)chunk.ckid, WAV_DATA_MAGIC, 4) == 0) {
			/*
			 * We found WAVE "data" header. This contains the
			 * size value of the audio part.
			 */
			if (!gotFormat) {
				goto err;
			}
			if ((cursor + size + sizeof (chunk)) > sb.st_size)
				size = sb.st_size - (cursor  + sizeof (chunk));
			if (offset)
				*offset = cursor + sizeof (chunk) ;
			return (size);
		}
		cursor += size + sizeof (chunk);
		lseek(f, cursor, SEEK_SET);	/* Skip over current chunk */
	}
err:
	lseek(f, (off_t)0L, SEEK_SET);
	return (0);
}




