/**

				HTK_READ.C
				Reads in HTK HParm format data files,
				as described in "The HTK Book" by
				Steve Young, Julian Odell, Valtcho Valtchev
				and Phil Woodland (for HTK version 2.1)
				copyright 1997, Cambridge University.

Copyright (C) 2008 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 <assert.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <errno.h>
#include <math.h>

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

#define D	if(1)

static C int CHAR_BITS = 8;

static BOOL read_int_2(RBFILE& fp, int *p, BOOL swap)
{
 unsigned char c[2];
 BOOL ok = fp.fread((char *)c, 1, 2)==2;
 assert(p);
 assert(ok);
 if(ok) {
	C int msb = swap ? 1: 0;
	*p = (unsigned)c[1-msb] | ((int)(signed char)c[msb]<<CHAR_BITS);
 	}
 return ok;
}


BOOL read_long_4(RBFILE& fp, long *p, BOOL swap)
{
 unsigned char c[4];
 BOOL ok = fp.fread((char *)c, 1, 4)==4;
 assert(p);
 if(ok)	{
	int b = swap ? 3 : 0;
	C int delta = swap ? -1 : 1;
	*p = (int)(signed char)c[b];
	b += delta;
	*p = (*p<<CHAR_BITS) | (unsigned)c[b];
	b += delta;
	*p = (*p<<CHAR_BITS) | (unsigned)c[b];
	b += delta;
	*p = (*p<<CHAR_BITS) | (unsigned)c[b];
 	}
 return ok;
}

union f32 { int32_t i; float f;};

BOOL read_float_4(RBFILE& fp, float *p, BOOL swap)
{
 unsigned char c[4];
 f32 px;
 assert(sizeof(px.i) == sizeof(px.f));
 assert(sizeof(px) == sizeof(float));
 BOOL ok = fp.fread((void *)c, sizeof(f32), 1)==1;
 if(ok)	{
	int b = swap ? 3 : 0;
	C int delta = swap ? -1 : 1;
	px.i = (int)(signed char)c[b];
	b += delta;
	px.i = (px.i<<CHAR_BITS) | (unsigned)c[b];
	b += delta;
	px.i = (px.i<<CHAR_BITS) | (unsigned)c[b];
	b += delta;
	px.i = (px.i<<CHAR_BITS) | (unsigned)c[b];
	*p = px.f;
 	}
 return ok;
}

#define NParmKind 12
static C char *paramKindName[NParmKind] = {"WAVEFORM", "LPC",
	"LPCREFC", "LPCEPSTRA", "LPDELCEP", "IREFC",
	"MFCC", "FBANK", "MELSPEC", "USER", "DISCRETE", "PLP"
	};
static C char pQual[] = {'E', 'N', 'D', 'A', 'C', 'Z', 'K', 'O', '\0'};
static C int PBMASK = 077;
static C int PQ_MASK = 037700;
static C int Compressed = 02000;

static char qpkbuf[128];
static C char *QParamKind(int flags)
{
 char *q = qpkbuf;
 int i = 0100, j = 0;
 C int fm = flags&PBMASK;
 assert(fm>=0 && fm<NParmKind);
 strcpy(qpkbuf, paramKindName[flags&PBMASK]);
 for(; i&PQ_MASK; i <<= 1, j += 1)
 	if(flags & i) {
		*(q++) = '_';
		*(q++) = pQual[j];
		*q = '\0';
	}
 return qpkbuf;
}



char *getdata_HTK_guts(struct multi_image *rv, RBFILE& s, BOOL header_only, BOOL swap)
{
 char err[1024];
 struct image * p = &rv->image[0];

 s.rewind();
 multi_image_empty(rv);
 assert(rv->n_image == 0);

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

 BOOL hdr_ok;
 long int nSamples, sampPeriod;
 int sampSize, parmKind;
 hdr_ok = read_long_4(s, &nSamples, swap) && read_long_4(s, &sampPeriod, swap)
 		&& read_int_2(s, &sampSize, swap) && read_int_2(s, &parmKind, swap);
 if(!hdr_ok) {
	sprintf(err, "INFO: not HTK HParam incomplete header");
	multi_image_destr(rv);
	return strdup(err);
	}
 p->nlines = nSamples;
 if(p->nlines <= 0)	{
	sprintf(err, "INFO: not HTK HParam bad NAXIS2=%ld", nSamples);
	multi_image_destr(rv);
	return strdup(err);
	}
 p->prop->add("NAXIS2", nSamples, (C char *)0);
 D fprintf(stderr, "nSamples=%ld %lo, sampSize=%d %o, sampPeriod=%ld %lo pK=0%o\n",
 		nSamples, nSamples, sampSize, sampSize, sampPeriod, sampPeriod, parmKind);

 if(sampPeriod <= 0 || sampPeriod>1000000)	{
	sprintf(err, "not HTK HParam bad sampling interval=%ld*1e-7s", sampPeriod);
	multi_image_destr(rv);
	return strdup(err);
	}
 p->prop->add("CDELT2", sampPeriod*1e-7, (C char *)0);
 
 int basicParamKind = parmKind & PBMASK;
 if(basicParamKind<0 || basicParamKind>NParmKind ||
 	(parmKind & ~(PBMASK|PQ_MASK)))	{
	sprintf(err, "not HTK HParam bad parmKind=0%o %d %o %o", parmKind,
			basicParamKind, parmKind&~(PBMASK|PQ_MASK), PBMASK|PQ_MASK);
	multi_image_destr(rv);
	return strdup(err);
	}
 p->prop->add("BParmKind", paramKindName[basicParamKind]);
 p->prop->add("parmKind", parmKind);
 p->prop->add("QParmKind", QParamKind(parmKind), (C char *)0);
 int bitpix = -32;
 D fprintf(stderr, "Kind: %d 0%o\n", basicParamKind, parmKind);
 if((parmKind & Compressed) && strcmp("IREFC", paramKindName[basicParamKind])!=0) {
	float a, b;
 	D fprintf(stderr, "Compressed and AB: %d(%s) 0%o\n", basicParamKind, paramKindName[basicParamKind], parmKind);
	bitpix = 16;
	hdr_ok = read_float_4(s, &a, swap) && read_float_4(s, &b, swap);
 	if(!hdr_ok) {
		sprintf(err, "INFO: not HTK HParam read error on extended header");
		multi_image_destr(rv);
		return strdup(err);
	}
	if(!finite(a) || !finite(b) || a<=0.0) {
		sprintf(err, "not HTK HParam bad extended header: a=%g b=%g", a, b);
		multi_image_destr(rv);
		return strdup(err);
		}
	p->prop->add("zscale", 1.0/a);
	p->prop->add("zshift", -b/a);
	}
 else if((parmKind & Compressed) && basicParamKind == 5) {
 	D fprintf(stderr, "Compressed no AB: %d 0%o\n", basicParamKind, parmKind);
 	bitpix = 16;
	p->prop->add("zscale", 1.0);
	p->prop->add("zshift", -1.0);
	}

 p->npts = sampSize/(abs(bitpix)/CHAR_BITS);
 D fprintf(stderr, "sampSize=%d, npts=%d, bitpix=%d\n", sampSize, p->npts, bitpix);
 if(sampSize<=0 || sampSize>5000 || sampSize%(abs(bitpix)/8)!=0)	{
	sprintf(err, "not HTK HParam bad NAXIS1=%d/%d", sampSize, abs(bitpix));
	multi_image_destr(rv);
	return strdup(err);
	}
 p->prop->add("NAXIS1", sampSize, (C char *)0);

 D fprintf(stderr, "Committed to HParam\n");
 s.commit();		/* Toss out the buffer. */

 if(!header_only)	{
	char *fail = gpkio_read_in_data(s, p, p->nlines, p->npts, bitpix,
					LONG_NAN,
					swap ? ANTI_NETWORK: NETWORK_ORDER
					);
	if(fail)	{
		multi_image_destr(rv);
		sprintf(err, "not HParam: read fails after header: %s.", fail);
		free(fail);
		return strdup(err);
		}
	}
 p->xscale = p->prop->getd("xscale", 1.0);
 p->xscale = p->prop->getd("CDELT1", p->xscale);	/* Preferred. */
 p->yscale = p->prop->getd("yscale", 1.0);
 p->yscale = p->prop->getd("CDELT2", p->yscale);	/* 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);

 p->prop->add("zunits", "???");
 p->prop->add("xunits", "CTYPE1");
 p->prop->add("yunits", "seconds");
 p->prop->add("angle", 0);
 p->i_noise = 0.0;
 p->prop->add("_FILETYPE", "HTK HParam", "HTK feature vectors or audio");
 p->prop->add("Was_byteswapped", swap);
 gpkio_date_fix(p->prop);
 assert(rv->n_image == 0);
 rv->n_image++;
 assert(rv->n_image == 1);

 return 0;
}


char *getdata_HTK(struct multi_image *rv, RBFILE& s, BOOL header_only)
{
 char * err1 = getdata_HTK_guts(rv, s, header_only, FALSE);
 if(err1 == 0)
	return err1;
 char *err2 = getdata_HTK_guts(rv, s, header_only, TRUE);
 if(err2 != 0)	{
	free(err2);
	return err1;
	}
 return err2;
}
