/*****************************************************************************

				SPHERE READ.C
				Reads NIST SPHERE format audio files.

Copyright (C) 2002 Greg Kochanski, gpk@pribble.com .

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

This program 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <math.h>

#include "gpklib/ieee754.h"
#include "gpklib.h"
#include "gpklib/image.h"
#include "gpklib/NaN.h"
#include "gpklib/err.h"
#include "gpklib/innerloop4.h"
#include "rbfile.h"
#include "gpklib/assert.h"
#include "internals.h"
#include "rw_misc.h"
#include "read_data.h"


#define D	if(0)

#define	QUANT_MASK	(0xf)		/* Quantization field mask. */
#define	BIAS		(0x84)		/* Bias for linear code. */
#define	SEG_MASK	(0x70)		/* Segment field mask. */
#define	SEG_SHIFT	(4)		/* Left shift for segment number. */
#define	SIGN_BIT	(0x80)		/* Sign bit for a A-law byte. */

/* Use a character as an array subscript. */
#define S(x)	(0377&(unsigned)(x))


/*
 * ulaw2linear() - Convert a u-law value to 16-bit linear PCM
 *
 * First, a biased linear code is derived from the code word. An unbiased
 * output can then be obtained by subtracting 33 from the biased code.
 *
 * Note that this function expects to be passed the complement of the
 * original code word. This is in keeping with ISDN conventions.
 */
LOCAL int
ulaw2linear( unsigned char	u_val)
{
	int		t;

	/* Complement to obtain normal u-law value. */
	u_val = ~u_val;

	/*
	 * Extract and bias the quantization bits. Then
	 * shift up by the segment number and subtract out the bias.
	 */
	t = ((u_val & QUANT_MASK) << 3) + BIAS;
	t <<= ((unsigned)u_val & SEG_MASK) >> SEG_SHIFT;

	return ((u_val & SIGN_BIT) ? (BIAS - t) : (t - BIAS));
}


static char *errmsg(C RBFILE &s)
{
 return strdup(s.ferror() ? "Read error" : "File truncated");
}



LOCAL char *read_sphere_data( RBFILE& s,
			struct image *p, int rows, int cols,
			int bitpix, read_in_e swap)
{
	assert(bitpix == 8);
	C char *coding = p->prop->gets("sample_coding", "pcm");
	assert(strcmp(coding, "ulaw") == 0);
	BOOL aok;
	if(abs(bitpix) <= 16)
		aok = image_alloc(p, rows, cols);
	else
		aok = image_alloc_float(p, rows, cols);

	if(!aok)
		gpk_err("Cannot allocate memory for images", 0);


 if(bitpix == 8 && strcmp(coding, "ulaw")==0)	{
	 unsigned char *h = (unsigned char *)malloc(sizeof(unsigned char)*p->npts);
	 assert(h);
	 C int np = p->npts;
	 int i;
	 for(i=0; i<p->nlines; i++)	{
		if(s.fread((char *)h, 1, np) != np)
			return errmsg(s);

		short *H = p->h[i];
		INNERLOOP(j, 0, np, H[j] = ulaw2linear(h[j]));
		}

	if(short2test(p->h)!=0)	{
		fprintf(stderr, "Corrupted array on %d=%d\n", i, short2test(p->h));
		fprintf(stderr, "Apparent size= %d x %d\n", p->nlines, p->npts);
		assert(0);
		}

	free((char *)h);
	p->prop->add("BITPIX", (int)(sizeof(short)*8));
 	}
 else
	 	gpk_err("Not implemented", 0);

 return (char *)0;
}


LOCAL char * parse_hdr_line(char *hdr_rec, proplist *p)
{
 char err[512];
 static int legal_attribute_char_set = 0;
 static int legal_attribute_char[256], legal_int_char[256], legal_float_char[256];

 if(!legal_attribute_char_set)	{
	int i;
	C char *s = " -_QWERTYUIOPASDFGHJKLZXCVBNM1234567890qwertyuiopasdfghjklzxcvbnm";
	for(; *s; s++)
		legal_attribute_char[S(*s)] = 1;
	legal_attribute_char_set = 1;
	for(i=1; i<256; i++) {
		if(isdigit(i)) {
			legal_int_char[i] = 1;
			legal_float_char[i] = 1;
			}
		}
	legal_int_char[S('-')] = 1;
	legal_int_char[S('+')] = 1;
	legal_float_char[S('-')] = 1;
	legal_float_char[S('+')] = 1;
	legal_float_char[S('.')] = 1;
	legal_float_char[S('e')] = 1;
	legal_float_char[S('E')] = 1;
	}

 D fprintf(stderr, "parsing {%s}\n", hdr_rec);

 char *v, *f;
 char *a = &hdr_rec[0];
 char *c = 0;
 char *tmp;

 if(hdr_rec[0] == ';') {	/* Just a comment. */
	c = hdr_rec + 1;
	while(*c && isspace(*c))
		c++;
	chomp_whitespace(c);
 	if(*c)
		add_append_property(p, "_COMMENT", c, (char *)0);
	return 0;
	}

 for(tmp=hdr_rec; *tmp && !isspace(*tmp); tmp++)
	if(!legal_attribute_char[0377 & (unsigned)*tmp])	{
		sprintf(err, "illegal attribute char: %x at pos %td",
				*tmp, tmp-hdr_rec);
		return strdup(err);
		}
 if(tmp == hdr_rec)	{
	sprintf(err, "missing attribute");
	return strdup(err);	/* No attribute.  */
	}
 if(! *tmp) {
	sprintf(err, "cannot parse");
	return strdup(err);	/* No attribute.  */
 	}

 D fprintf(stderr, "got =\n");

 *tmp++ = '\0';
	/* At this point, a is a good string. */

 for(f=tmp; isspace(*f); f++)		/* Skip spaces. */
	;
 if(*f != '-') {
	sprintf(err, "cannot parse: no type indicator");
	return strdup(err);
 	}
 f++;
 if(!(f[0]=='i' && f[1] && isspace(f[1]))
 		&& !(f[0]=='r' && f[1] && isspace(f[1]))
		&& !(f[0]=='s' && f[1])) {
	sprintf(err, "cannot parse: unrecognized type indicator: %c(%d)", *f, *f);
	return strdup(err);
	}
 if(f[0]=='s') {
	for(tmp=f+1; *tmp && isdigit(*tmp); tmp++)
		;
	if(*tmp && !isspace(*tmp)) {
		sprintf(err, "cannot parse: messed up -s%%d type indicator: %c(%d)", *tmp, *tmp);
		return strdup(err);
		}
 	}

 for(v=f+1; *v && !isspace(*v); v++)		/* Skip spaces. */
	;
 if(!*v) {
	sprintf(err, "cannot parse: need space after type indicator");
	return strdup(err);
 	}
 *v++ = '\0';
	/* At this point, the type string is OK. */

 if(f[0] == 'i' || f[0]=='r') {
	BOOL *lc;
 	if(f[0] == 'i')
		lc = legal_int_char;
	else
		lc = legal_float_char;

 	for(tmp=v; *tmp && lc[0377 & (unsigned)*tmp]; tmp++)
		;
	if(*tmp  && !isspace(*tmp) && *tmp!=';') {
		sprintf(err, "cannot parse: bad integer: %c(%d)", *tmp, *tmp);
		return strdup(err);
		}
	while(*tmp && isspace(*tmp))
		*(tmp++) = '\0';
	if(*tmp && *tmp == ';') {
		*(tmp++) = '\0';
		while(*tmp && isspace(*tmp))
			tmp++;
		c = tmp;
		chomp_whitespace(c);
		}
	else if(*tmp) {
		sprintf(err, "cannot parse: expecting semicolon after %c value, got %c(%d)", f[0], *tmp, *tmp);
		return strdup(err);
		}
	else
		c = (char *)0;
	add_append_property(p, a, v, (c&&*c) ? c : (char *)0);
	}
 else if(f[0] == 's') {
	unsigned nc = atoi(&(f[1]));
	if(nc <= 0 || nc>strlen(v)) {
		sprintf(err, "bad string length: %d", nc);
		return strdup(err);
		}
	if(v[nc] && v[nc]!=';' && !isspace(v[nc])) {
		sprintf(err, "string value ends in middle: %d %c(%d)", nc, v[nc], v[nc]);
		return strdup(err);
		}
	v[nc] = '\0';
	c = &( v[nc+1] );
	while(*c && isspace(*c))
		c++;
	chomp_whitespace(c);
	add_append_property(p, a, v, c);
 	}
 return 0;
}



char *getdata_sphere(struct multi_image *rv, RBFILE& s, BOOL header_only)
{
 char err[1024], *etmp;
 struct image * p = &rv->image[0];
 multi_image_empty(rv);
 int hdr_line;

 s.rewind();
 char hdr_rec[10000];
 C long startheaderposition = s.tell();
 if(!s.fgets(hdr_rec, sizeof(hdr_rec))) {
	multi_image_destr(rv);
	sprintf(err, "not NIST sphere format: can't read magic number.");
	return strdup(err);
 }
 if( strcmp(hdr_rec, "NIST_1A\n")!=0 && strcmp(hdr_rec, "NIST_1A\r\n")!=0) {
	multi_image_destr(rv);
	sprintf(err, "not NIST sphere format: bad magic number.");
	return strdup(err);
 }
 if(!s.fgets(hdr_rec, sizeof(hdr_rec))) {
	multi_image_destr(rv);
	sprintf(err, "not NIST sphere format: can't read header length.");
	return strdup(err);
 }
 C int headersize = safe_atoi(hdr_rec);
 if(headersize<=0 || headersize==INT_NAN  || headersize==INT_Max) {
	multi_image_destr(rv);
	sprintf(err, "not NIST sphere format: silly header length: %d.", headersize);
	return strdup(err);
 }

 assert(rv->n_image == 0);

 p->prop = new proplist;
 assert(p->prop);

 /* We terminate conservatively, to allow for miscounting in the
 header length.  We know that there are no lines shorter than 'x -i 0\n'
 */
 hdr_line = 2;
 while(s.tell() < headersize + startheaderposition - 5) {
	hdr_line++;
	if(!s.fgets(hdr_rec, sizeof(hdr_rec)))	{
		multi_image_destr(rv);
		sprintf(err, "not NIST sphere format: can't read header, line %d.",
				hdr_line);
		return strdup(err);
	}
	D fprintf(stderr, "DBG: s.fgets done: {%s}\n", hdr_rec);

	if(hdr_rec[0] == '\0')	{
		multi_image_destr(rv);
		sprintf(err, "not NIST sphere: null header line %d.",
				hdr_line);
		return strdup(err);
	}

	if(!chomp_newline(hdr_rec) < 0)	{
		multi_image_destr(rv);
		sprintf(err, "not NIST sphere: header missing newline "
				"(line %d too long or null?)", hdr_line);
		return strdup(err);
	}

	if(hdr_rec[0] == '\0') {
		multi_image_destr(rv);
		sprintf(err, "not NIST sphere: empty header line %d.", hdr_line);
		return strdup(err);
	}

	if(strcmp(hdr_rec, "end_head") == 0)
		break;

	if((etmp=parse_hdr_line(hdr_rec, p->prop)) != 0) {
		multi_image_destr(rv);
		return etmp;
	}
	}
 assert(rv->n_image == 0);


 if(p->prop->get("NAXIS") && p->prop->geti( "NAXIS", -1) != 2)	{
	fprintf(stderr, "INFO: can't handle ascii Naxis=%s, not 2\n", p->prop->gets( "NAXIS", ""));
	multi_image_destr(rv);
	assert(0);
	}

 p->nlines = p->prop->geti("sample_count", -1);
 if(p->nlines <= 0)	{
	sprintf(err, "not NIST sphere bad sample_count=%s", p->prop->gets( "NAXIS2", ""));
	multi_image_destr(rv);
	return strdup(err);
	}
 p->prop->add("sample_count", (char *)0);

 p->npts = p->prop->geti("channel_count", 1);
 if(p->npts <= 0)	{
	sprintf(err, "not NIST sphere bad channel_count=%s", p->prop->gets( "NAXIS1", ""));
	multi_image_destr(rv);
	return strdup(err);
	}
 p->prop->add("channel_count", (char *)0);
 p->prop->add("sample_max", (char *)0);
 p->prop->add("sample_min", (char *)0);

 while(s.tell() < headersize + startheaderposition)
	s.fgetc();

 assert(rv->n_image == 0);
 D fprintf(stderr, "Committed to ascii_image\n");
 s.commit();		/* Toss out the buffer. */

 p->prop->add("BITPIX", p->prop->geti("sample_n_bytes", 0)*8);
 p->prop->add("sample_n_bytes", (char *)0);
 
 if(!header_only)	{
 	C int bitpix = p->prop->geti("BITPIX", 0);
	char *fail = (char *)0;
	C read_in_e order = strcmp(p->prop->gets("sample_byte_format", ""), "01")==0 ? ANTI_NETWORK : NETWORK_ORDER;
	if(strcmp(p->prop->gets("sample_coding", "pcm"), "pcm") == 0)
		fail = gpkio_read_in_data(s, p, p->nlines, p->npts, bitpix, LONG_NAN, order);
	else
		fail = read_sphere_data(s, p, p->nlines, p->npts, bitpix, order);

	if(fail)	{
		multi_image_destr(rv);
		sprintf(err, "not NIST SPHERE: read fails after header: %s.", fail);
		free(fail);
		return strdup(err);
		}
	}
 p->yscale = 1.0/p->prop->getd("sample_rate", 1.0);
 p->prop->add("sample_rate", (char *)0);
 p->xscale = p->prop->getd("xscale", 1.0);
 p->xscale = p->prop->getd("CDELT1", p->xscale);	/* Preferred. */
 p->zscale = p->prop->getd("zscale", 1.0);
 p->zscale = p->prop->getd("BSCALE", p->zscale);	/* Preferred. */
 p->zshift = p->prop->getd("zshift", 0.0);
 p->zshift = p->prop->getd("BZERO", p->zshift);		/* Preferred. */

 D fprintf(stderr, "zscale=%g from %s or %s\n", p->zscale,
	p->prop->gets("zscale", "<nil>"), p->prop->gets("BSCALE", "<nil>"));
 D fprintf(stderr, "zshift=%g from %s or %s\n", p->zshift,
	p->prop->gets("zshift", "<nil>"), p->prop->gets("BZERO", "<nil>"));

 p->xcenter = p->prop->getd("CRVAL1", 0.0)
		+ p->xscale*(0.5*(p->npts-1) - (p->prop->getd("CRPIX1", 1.0)-1.0));
 p->ycenter = p->prop->getd("CRVAL2", 0.0)
		+ p->yscale*(0.5*(p->nlines-1) - (p->prop->getd("CRPIX2", 1.0)-1.0));
 D fprintf(stderr, "ycenter=%g from v=%g sc=%g pix=%g rows=%d\n",
		p->ycenter, p->prop->getd("CRVAL2", 0.0), p->yscale,
		p->prop->getd("CRPIX2", 1.0), p->nlines);

 if(p->prop->get("BUNIT"))
	p->prop->add("zunits", p->prop->gets("BUNIT", "???"));
 if(p->prop->get("CTYPE1"))
	p->prop->add("xunits", p->prop->gets("CTYPE1", "???"));
 if(p->prop->get("CTYPE2"))
	p->prop->add("yunits", p->prop->gets("CTYPE2", "???"));
 if(p->prop->get("CROTA1"))
	p->prop->add("angle", p->prop->gets("CROTA1", "???"));
 p->i_noise = p->prop->getd("I_NOISE", 0.0);
 p->prop->add("_FILETYPE", "NIST SPHERE", "Audio format.");
 p->prop->add("sample_byte_format", (char *)0);
 p->prop->add("sample_coding", (char *)0);
 gpkio_date_fix(p->prop);
 assert(rv->n_image == 0);
 rv->n_image++;
 assert(rv->n_image == 1);

 return 0;
}
