/*
	Audio File Library
	Copyright (C) 1998-2000, Michael Pruett <michael@68k.org>
	Copyright (C) 2000, Silicon Graphics, Inc.

	This library is free software; you can redistribute it and/or
	modify it under the terms of the GNU Library General Public
	License as published by the Free Software Foundation; either
	version 2 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
	Library General Public License for more details.

	You should have received a copy of the GNU Library General Public
	License along with this library; if not, write to the
	Free Software Foundation, Inc., 59 Temple Place - Suite 330,
	Boston, MA  02111-1307  USA.
*/

/*
	util.c

	This file contains general utility routines for the Audio File
	Library.
*/

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

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <assert.h>

#include "audiofile.h"
#include "aupvlist.h"

#include "afinternal.h"
#include "util.h"
#include "units.h"
#include "compression.h"
#include "modules.h"
#include "byteorder.h"
#include "aupvinternal.h"

#include "print.h"

extern _PCMInfo _af_default_signed_integer_pcm_mappings[];
extern _PCMInfo _af_default_unsigned_integer_pcm_mappings[];
extern _PCMInfo _af_default_float_pcm_mapping;
extern _PCMInfo _af_default_double_pcm_mapping;

extern _CompressionUnit _af_compression[];

/*
	_af_filesetup_ok and _af_filehandle_ok are sanity check routines
	which are called at the beginning of every external subroutine.
*/
bool _af_filesetup_ok (AFfilesetup setup)
{
	if (setup == AF_NULL_FILESETUP)
	{
		_af_error(AF_BAD_FILESETUP, "null file setup");
		return AF_FALSE;
	}
	if (setup->valid != _AF_VALID_FILESETUP)
	{
		_af_error(AF_BAD_FILESETUP, "invalid file setup");
		return AF_FALSE;
	}
	return AF_TRUE;
}

bool _af_filehandle_can_read (AFfilehandle file)
{
	if (file->access != _AF_READ_ACCESS)
	{
		_af_error(AF_BAD_NOREADACC, "file not opened for read access");
		return AF_FALSE;
	}

	return AF_TRUE;
}

bool _af_filehandle_can_write (AFfilehandle file)
{
	if (file->access != _AF_WRITE_ACCESS)
	{
		_af_error(AF_BAD_NOWRITEACC, "file not opened for write access");
		return AF_FALSE;
	}

	return AF_TRUE;
}

bool _af_filehandle_ok (AFfilehandle file)
{
	if (file == AF_NULL_FILEHANDLE)
	{
		_af_error(AF_BAD_FILEHANDLE, "null file handle");
		return AF_FALSE;
	}
	if (file->valid != _AF_VALID_FILEHANDLE)
	{
		_af_error(AF_BAD_FILEHANDLE, "invalid file handle");
		return AF_FALSE;
	}
	return AF_TRUE;
}

void _af_printid (u_int32_t id)
{
	printf("%c%c%c%c",
		(id >> 24) & 0xff,
		(id >> 16) & 0xff,
		(id >> 8) & 0xff,
		id & 0xff);
}

void _af_print_pvlist (AUpvlist list)
{
	int i;

	assert(list);

	printf("list.valid: %d\n", list->valid);
	printf("list.count: %d\n", list->count);

	for (i=0; i<list->count; i++)
	{
		printf("item %d valid %d, should be %d\n",
			i, list->items[i].valid, _AU_VALID_PVITEM);

		switch (list->items[i].type)
		{
			case AU_PVTYPE_LONG:
				printf("item #%d, parameter %d, long: %ld\n",
					i, list->items[i].parameter,
					list->items[i].value.l);
				break;
			case AU_PVTYPE_DOUBLE:
				printf("item #%d, parameter %d, double: %f\n",
					i, list->items[i].parameter,
					list->items[i].value.d);
				break;
			case AU_PVTYPE_PTR:
				printf("item #%d, parameter %d, pointer: %p\n",
					i, list->items[i].parameter,
					list->items[i].value.v);
				break;

			default:
				printf("item #%d, invalid type %d\n", i,
					list->items[i].type);
				assert(0);
				break;
		}
	}
}

void _af_print_audioformat (_AudioFormat *fmt)
{
	/* sampleRate, channelCount */
	printf("{ %7.2f Hz %d ch ", fmt->sampleRate, fmt->channelCount);

	/* sampleFormat, sampleWidth */
	switch (fmt->sampleFormat)
	{
		case AF_SAMPFMT_TWOSCOMP:
			printf("%db 2 ", fmt->sampleWidth);
			break;
		case AF_SAMPFMT_UNSIGNED:
			printf("%db u ", fmt->sampleWidth);
			break;
		case AF_SAMPFMT_FLOAT:
			printf("flt ");
			break;
		case AF_SAMPFMT_DOUBLE:
			printf("dbl ");
			break;
		default:
			printf("%dsampfmt? ", fmt->sampleFormat);
	}

	/* pcm */
	printf("(%.30g+-%.30g [%.30g,%.30g]) ",
		fmt->pcm.intercept, fmt->pcm.slope,
		fmt->pcm.minClip, fmt->pcm.maxClip);

	/* byteOrder */
	switch (fmt->byteOrder)
	{
		case AF_BYTEORDER_BIGENDIAN:
			printf("big ");
			break;
		case AF_BYTEORDER_LITTLEENDIAN:
			printf("little ");
			break;
		default:
			printf("%dbyteorder? ", fmt->byteOrder);
			break;
	}

	/* compression */
	{
		int idx = _af_compression_index_from_id(fmt->compressionType);
		if (idx < 0)
		{
			printf("%dcompression?", fmt->compressionType);
		}
		else if (fmt->compressionType == AF_COMPRESSION_NONE)
			printf("pcm");
		else
			printf("%s", _af_compression[idx].label);
	}

	printf(" }");
}

void _af_print_tracks (AFfilehandle filehandle)
{
	int	i;
	for (i=0; i<filehandle->trackCount; i++)
	{
		_Track	*track = &filehandle->tracks[i];
		printf("track %d\n", i);
		printf(" id %d\n", track->id);
		printf(" sample format\n");
		_af_print_audioformat(&track->f);
		printf(" virtual format\n");
		_af_print_audioformat(&track->v);
		printf(" total file frames: %" AF_FRAMECOUNT_PRINT_FMT "\n",
			track->totalfframes);
		printf(" total virtual frames: %" AF_FRAMECOUNT_PRINT_FMT "\n",
			track->totalvframes);
		printf(" next file frame: %" AF_FRAMECOUNT_PRINT_FMT "\n",
			track->nextfframe);
		printf(" next virtual frame: %" AF_FRAMECOUNT_PRINT_FMT "\n",
			track->nextvframe);
		printf(" frames to ignore: %" AF_FRAMECOUNT_PRINT_FMT "\n",
			track->frames2ignore);

		printf(" data_size: %" AF_FILEOFFSET_PRINT_FMT "\n",
			track->data_size);
		printf(" fpos_first_frame: %" AF_FILEOFFSET_PRINT_FMT "\n",
			track->fpos_first_frame);
		printf(" fpos_next_frame: %" AF_FILEOFFSET_PRINT_FMT "\n",
			track->fpos_next_frame);
		printf(" fpos_after_data: %" AF_FILEOFFSET_PRINT_FMT "\n",
			track->fpos_after_data);

		printf(" channel matrix:");
		_af_print_channel_matrix(track->channelMatrix,
			track->f.channelCount, track->v.channelCount);
		printf("\n");

		printf(" marker count: %d\n", track->markerCount);
	}
}

void _af_print_filehandle (AFfilehandle filehandle)
{
	printf("file handle: 0x%p\n", filehandle);

	if (filehandle->valid == _AF_VALID_FILEHANDLE)
		printf("valid\n");
	else
		printf("invalid!\n");

	printf(" access: ");
	if (filehandle->access == _AF_READ_ACCESS)
		putchar('r');
	else
		putchar('w');

	printf(" fileFormat: %d\n", filehandle->fileFormat);

	printf(" instrument count: %d\n", filehandle->instrumentCount);
	printf(" instruments: 0x%p\n", filehandle->instruments);

	printf(" miscellaneous count: %d\n", filehandle->miscellaneousCount);
	printf(" miscellaneous: 0x%p\n", filehandle->miscellaneous);

	printf(" trackCount: %d\n", filehandle->trackCount);
	printf(" tracks: 0x%p\n", filehandle->tracks);
	_af_print_tracks(filehandle);
}

void *_af_malloc (size_t size)
{
	void	*p;

	if (size <= 0)
	{
		_af_error(AF_BAD_MALLOC, "bad memory allocation size request %d", size);
		return NULL;
	}

	p = malloc(size);

#ifdef AF_DEBUG
	if (p)
		memset(p, 0xff, size);
#endif

	if (p == NULL)
	{
		_af_error(AF_BAD_MALLOC, "allocation of %d bytes failed", size);
		return NULL;
	}

	return p;
}

char *_af_strdup (char *s)
{
	char	*p = malloc(strlen(s) + 1);

	if (p)
		strcpy(p, s);

	return p;
}

void *_af_realloc (void *p, size_t size)
{
	if (size <= 0)
	{
		_af_error(AF_BAD_MALLOC, "bad memory allocation size request %d", size);
		return NULL;
	}

	p = realloc(p, size);

	if (p == NULL)
	{
		_af_error(AF_BAD_MALLOC, "allocation of %d bytes failed", size);
		return NULL;
	}

	return p;
}

void *_af_calloc (size_t nmemb, size_t size)
{
	void	*p;

	if (nmemb <= 0 || size <= 0)
	{
		_af_error(AF_BAD_MALLOC, "bad memory allocation size request "
			"%d elements of %d bytes each", nmemb, size);
		return NULL;
	}

	p = calloc(nmemb, size);

	if (p == NULL)
	{
		_af_error(AF_BAD_MALLOC, "allocation of %d bytes failed",
			nmemb*size);
		return NULL;
	}

	return p;
}

AUpvlist _af_pv_long (long val)
{
	AUpvlist	ret = AUpvnew(1);
	AUpvsetparam(ret, 0, 0);
	AUpvsetvaltype(ret, 0, AU_PVTYPE_LONG);
	AUpvsetval(ret, 0, &val);
	return ret;
}

AUpvlist _af_pv_double (double val)
{
	AUpvlist	ret = AUpvnew(1);
	AUpvsetparam(ret, 0, 0);
	AUpvsetvaltype(ret, 0, AU_PVTYPE_DOUBLE);
	AUpvsetval(ret, 0, &val);
	return ret;
}

AUpvlist _af_pv_pointer (void *val)
{
	AUpvlist	ret = AUpvnew(1);
	AUpvsetparam(ret, 0, 0);
	AUpvsetvaltype(ret, 0, AU_PVTYPE_PTR);
	AUpvsetval(ret, 0, &val);
	return ret;
}

bool _af_pv_getlong (AUpvlist pvlist, int param, long *l)
{
	int	i;

	for (i=0; i<AUpvgetmaxitems(pvlist); i++)
	{
		int	p, t;

		AUpvgetparam(pvlist, i, &p);

		if (p != param)
			continue;

		AUpvgetvaltype(pvlist, i, &t);

		/* Ensure that this parameter is of type AU_PVTYPE_LONG. */
		if (t != AU_PVTYPE_LONG)
			return AF_FALSE;

		AUpvgetval(pvlist, i, l);
		return AF_TRUE;
	}

	return AF_FALSE;
}

bool _af_pv_getdouble (AUpvlist pvlist, int param, double *d)
{
	int	i;

	for (i=0; i<AUpvgetmaxitems(pvlist); i++)
	{
		int	p, t;

		AUpvgetparam(pvlist, i, &p);

		if (p != param)
			continue;

		AUpvgetvaltype(pvlist, i, &t);

		/* Ensure that this parameter is of type AU_PVTYPE_DOUBLE. */
		if (t != AU_PVTYPE_DOUBLE)
			return AF_FALSE;

		AUpvgetval(pvlist, i, d);
		return AF_TRUE;
	}

	return AF_FALSE;
}

bool _af_pv_getptr (AUpvlist pvlist, int param, void **v)
{
	int	i;

	for (i=0; i<AUpvgetmaxitems(pvlist); i++)
	{
		int	p, t;

		AUpvgetparam(pvlist, i, &p);

		if (p != param)
			continue;

		AUpvgetvaltype(pvlist, i, &t);

		/* Ensure that this parameter is of type AU_PVTYPE_PTR. */
		if (t != AU_PVTYPE_PTR)
			return AF_FALSE;

		AUpvgetval(pvlist, i, v);
		return AF_TRUE;
	}

	return AF_FALSE;
}

_TrackSetup *_af_filesetup_get_tracksetup (AFfilesetup setup, int trackid)
{
	int	i;
	for (i=0; i<setup->trackCount; i++)
	{
		if (setup->tracks[i].id == trackid)
			return &setup->tracks[i];
	}

	_af_error(AF_BAD_TRACKID, "bad track id %d", trackid);

	return NULL;
}

_Track *_af_filehandle_get_track (AFfilehandle file, int trackid)
{
	int	i;
	for (i=0; i<file->trackCount; i++)
	{
		if (file->tracks[i].id == trackid)
			return &file->tracks[i];
	}

	_af_error(AF_BAD_TRACKID, "bad track id %d", trackid);

	return NULL;
}

int _af_format_sample_size_uncompressed (_AudioFormat *format, bool stretch3to4)
{
	int	size = 0;

	switch (format->sampleFormat)
	{
		case AF_SAMPFMT_FLOAT:
			size = sizeof (float);
			break;
		case AF_SAMPFMT_DOUBLE:
			size = sizeof (double);
			break;
		default:
			size = (int) (format->sampleWidth + 7) / 8;
			if (format->compressionType == AF_COMPRESSION_NONE &&
				size == 3 && stretch3to4)
				size = 4;
			break;
	}

	return size;
}

float _af_format_sample_size (_AudioFormat *fmt, bool stretch3to4)
{
	int	compressionIndex;
	float	squishFactor;

	compressionIndex = _af_compression_index_from_id(fmt->compressionType);
	squishFactor = _af_compression[compressionIndex].squishFactor;

	return _af_format_sample_size_uncompressed(fmt, stretch3to4) /
		squishFactor;
}

int _af_format_frame_size_uncompressed (_AudioFormat *fmt, bool stretch3to4)
{
	return _af_format_sample_size_uncompressed(fmt, stretch3to4) *
		fmt->channelCount;
}

float _af_format_frame_size (_AudioFormat *fmt, bool stretch3to4)
{
	int	compressionIndex;
	float	squishFactor;

	compressionIndex = _af_compression_index_from_id(fmt->compressionType);
	squishFactor = _af_compression[compressionIndex].squishFactor;

	return _af_format_frame_size_uncompressed(fmt, stretch3to4) /
		squishFactor;
}

/*
	Set the sampleFormat and sampleWidth fields in f, and set the
	PCM info to the appropriate default values for the given sample
	format and sample width.
*/
status _af_set_sample_format (_AudioFormat *f, int sampleFormat, int sampleWidth)
{
	switch (sampleFormat)
	{
		case AF_SAMPFMT_UNSIGNED:
		case AF_SAMPFMT_TWOSCOMP:
		if (sampleWidth < 1 || sampleWidth > 32)
		{
			_af_error(AF_BAD_SAMPFMT,
				"illegal sample width %d for integer data",
				sampleWidth);
			return AF_FAIL;
		}
		else
		{
			int bytes;

			f->sampleFormat = sampleFormat;
			f->sampleWidth = sampleWidth;

			bytes = _af_format_sample_size_uncompressed(f, AF_FALSE);

			if (sampleFormat == AF_SAMPFMT_TWOSCOMP)
				f->pcm = _af_default_signed_integer_pcm_mappings[bytes];
			else
				f->pcm = _af_default_unsigned_integer_pcm_mappings[bytes];
		}
		break;

		case AF_SAMPFMT_FLOAT:
			f->sampleFormat = sampleFormat;
			f->sampleWidth = 32;
			f->pcm = _af_default_float_pcm_mapping;
			break;
		case AF_SAMPFMT_DOUBLE:
			f->sampleFormat = sampleFormat;
			f->sampleWidth = 64;      /*for convenience */
			f->pcm = _af_default_double_pcm_mapping;
			break;
		default:
			_af_error(AF_BAD_SAMPFMT, "unknown sample format %d",
				sampleFormat);
			return AF_FAIL;
	}

	return AF_SUCCEED;
}

/*
	Verify the uniqueness of the nids ids given.

	idname is the name of what the ids identify, as in "loop"
	iderr is an error as in AF_BAD_LOOPID
*/

bool _af_unique_ids (int *ids, int nids, char *idname, int iderr)
{
	int i;

	for (i = 0; i < nids; i++)
	{
		int j;
		for (j = 0; j < i; j++)
			if (ids[i] == ids[j])
			{
				_af_error(iderr, "nonunique %s id %d",
					idname, ids[i]);
				return AF_FALSE;
			}
	}

	return AF_TRUE;
}

void _af_print_channel_matrix (double *matrix, int fchans, int vchans)
{
	int v, f;

	if (!matrix)
	{
		printf("NULL");
		return;
	}

	printf("{");
	for (v=0; v < vchans; v++)
	{
		if (v) printf(" ");
		printf("{");
		for (f=0; f < fchans; f++)
		{
			if (f) printf(" ");
			printf("%5.2f", *(matrix + v*fchans + f));
		}
		printf("}");
	}
	printf("}");
}

void _af_print_frame (AFframecount frameno, double *frame, int nchannels,
	char *formatstring, int numberwidth,
	double slope, double intercept, double minclip, double maxclip)
{
	char linebuf[81];
	int wavewidth = wavewidth = 78 - numberwidth*nchannels - 6;
	int c;

	memset(linebuf, ' ', 80);
	linebuf[0] = '|';
	linebuf[wavewidth-1] = '|';
	linebuf[wavewidth] = 0;

	printf("%05" AF_FRAMECOUNT_PRINT_FMT " ", frameno);

	for (c=0; c < nchannels; c++)
	{
		double pcm = frame[c];
		printf(formatstring, pcm);
	}
	for (c=0; c < nchannels; c++)
	{
		double pcm = frame[c], volts;
		if (maxclip > minclip)
		{
			if (pcm < minclip) pcm = minclip;
			if (pcm > maxclip) pcm = maxclip;
		}
		volts = (pcm - intercept) / slope;
		linebuf[(int)((volts/2 + 0.5)*(wavewidth-3)) + 1] = '0' + c;
	}
	printf("%s\n", linebuf);
}

/*
	dumpchunk
*/
void _af_print_chunk (_AFchunk *chnk)
{
	_AudioFormat fmt = chnk->f;
	AFframecount nframes = chnk->nframes;
	AFframecount nsamps = nframes * fmt.channelCount;
	AFframecount fr;

	double *outbuf;
	char formatstring[20];
	int digits, numberwidth;

	switch (fmt.compressionType)
	{
		case AF_COMPRESSION_NONE:
			break;

		case AF_COMPRESSION_G711_ULAW:
			printf("WARNING dumping ulaw data as if it were 8-bit unsigned\n");
			fmt.compressionType = AF_COMPRESSION_NONE;
			fmt.sampleWidth = 8;
			fmt.sampleFormat = AF_SAMPFMT_UNSIGNED;
		break;

		default:
			printf("LAME-O chunk dumper cannot deal with '%s' compression\n",
				_af_compression[_af_compression_index_from_id(fmt.compressionType)].name);
			return;
	}

	if (fmt.sampleWidth > 8 && fmt.byteOrder != _AF_BYTEORDER_NATIVE)
	{
		printf("LAME-O chunk dumper cannot deal with non-native byte order\n");
		return;
	}

#define transfer(type) \
	{ \
	int s; \
	for(s=0; s < nsamps; s++) \
	outbuf[s] = (double)(((type *)chnk->buf)[s]); \
	}

	/* Make the buffer large enough to hold doubles. */
	outbuf = malloc(sizeof(double) * nsamps);

	switch (fmt.sampleFormat)
	{
		case AF_SAMPFMT_DOUBLE:
		case AF_SAMPFMT_FLOAT:
		{
			if (fmt.sampleFormat == AF_SAMPFMT_DOUBLE)
			{
				transfer(double);
			}
			else
			{
				transfer(float);
			}

			digits = (int) log10(fmt.pcm.intercept + fabs(fmt.pcm.slope)) + 1;
			/* Account for the sign character. */
			digits += 1;

			if (digits > 4)
			{
				sprintf(formatstring, "%%%d.0f ", digits);
				numberwidth = digits + 1;
			}
			else
			{
				sprintf(formatstring, "%%%d.2f ", digits+3);
				numberwidth = digits + 3 + 1;
			}
		}
		break;

		case AF_SAMPFMT_TWOSCOMP:
		case AF_SAMPFMT_UNSIGNED:
		{
			bool issigned = (fmt.sampleFormat==AF_SAMPFMT_TWOSCOMP);

			/* # of bytes taken by the value */
			int realbytes = _af_format_sample_size_uncompressed(&fmt, AF_TRUE);

			switch (realbytes)
			{
				case 1:
					if (issigned) { transfer(schar1); }
					else { transfer(uchar1); }
					break;
				case 2:
					if (issigned) { transfer(schar2); }
					else { transfer(uchar2); }
					break;
				case 4:
					if (issigned) { transfer(schar4); }
					else { transfer(uchar4); }
					break;
				default:
					printf("LAME-O chunk dumper cannot deal with %d bits\n",
						realbytes*8);
					free(outbuf);
					return;
			}

			digits = (int) log10(fmt.pcm.intercept + fabs(fmt.pcm.slope)) + 1;
			if (issigned)
				digits++;

			sprintf(formatstring, "%%%d.0f ", digits);
			numberwidth = digits + 1;
		}
		break;

		default:
			assert(0);
			return;
	}

	for (fr=0; fr < nframes; fr++)
		_af_print_frame(fr, &outbuf[fr*fmt.channelCount],
			fmt.channelCount, formatstring, numberwidth,
			fmt.pcm.slope, fmt.pcm.intercept,
			fmt.pcm.minClip, fmt.pcm.maxClip);

	free(outbuf);
}
