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

				SPHERE_WRITE.C
				Writes 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 <ctype.h>
#include <errno.h>
#include <math.h>
#include <time.h>
#include <sys/types.h>
#include <limits.h>

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


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

static char *prep_attribute(C char *c)
{
	static int initted = 0;
	static char legal[256];
	static char ok[] = "abcdefghijklmnopqrstuvwxyz"
			"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
	if(!initted) {
		unsigned i;
		for(i=0; i<sizeof(legal); i++)
			legal[i] = 'X';
		for(i=0; i<sizeof(ok)-1; i++)
			legal[S(ok[i])] = 0;
		legal[S('.')] = '_';
		legal[S('-')] = '_';
		legal[S(':')] = '_';
		legal[S('/')] = '_';
		legal[S('_')] = 0;
		initted = 1;
	}

	if(!c)
		return 0;

	BOOL lower = 0;
	if(*c == '%') {	/* used as a marker of case-insensitivity. */
		c++;
		lower = 1;	/* Sphere format seems to
					default to lower case. */
	}

	char *rv = strdup(c);
	if(*rv == '_' || isdigit(*rv)) {
		char *old = strdup(rv);
		*rv = 'Y';
		fprintf(stderr, "WARN: sphere_write: attribute '%s' isn't legal: mapping to '%s'.\n", old, rv);
		free(old);
		}
	char *p;
	int last = -1;
	for(p=rv; *p; p++) {
		if(*p=='*' && p[1]=='\0') {
			*p = '\0';	/* Used as a marker that attribute
						may be truncated.
						No way of reconstructing it,
						hope any truncation is
						unimportant. */
			break;
		}
		if(legal[S(*p)])
			*p = legal[S(*p)];
		if(last == '_' && *p=='_')
			*p = 'X';
		if(lower)
			*p = tolower(*p);
		last = *p;
	}
	return rv;
}


LOCAL char *prep_real(C char *s)
{
 double x = safe_atof(s);
 if(!finite(x))
	return 0;

 char o[300], fmt[50];
 int decimals = x!=0 ? 5 - roundI(log10(fabs(x))) : 5;
 if(decimals > 14)
	decimals = 14;
 if(decimals < 0)
	decimals = 0;
 sprintf(fmt, "-r %%.%1df", decimals);
 sprintf(o, fmt, x);
 return strdup(o);
}


LOCAL char *prep_integer(C char *s)
{
 errno = 0;
 char *ep = 0;
 long x = strtol(s, &ep, 0);
 if(errno != 0 || *ep!='\0')
	return errno==ERANGE ? prep_real(s) : 0;

 char o[100];
 sprintf(o, "-i %ld", x);	/* Columns 11-30, inclusive. */
 return strdup(o);
}


LOCAL char *prep_string(C char *s)
{
 char *o = (char *)malloc(strlen(s)+20);
 size_t isl = strlen(s);
 assert( isl < INT_MAX);
 sprintf(o, "-s%1d ", (int)isl);
 strcat(o, s);
 return o;
}


static void print_comment(C char *cc, FILE *fp)
{
	putc(' ', fp);
	putc(';', fp);
	for(; *cc; cc++) {
		if(*cc != '\n') {
			putc(*cc, fp);
		}
		else {
			putc(' ', fp);
		}
	}
}


static void print_comment_group(C char *cc, FILE *fp)
{
	char *tmp = (char *)malloc(strlen(cc));
	while(cc && *cc) {
		char *t = tmp;
		while(*cc && *cc!='\n')
			*(t++) = *(cc++);
		*(t++) = '\0';
		putc(';', fp);
		fputs(tmp, fp);
		putc('\n', fp);
		while(*cc && *cc=='\n')
			cc++;
	}
	free(tmp);
}


static void print_it(FILE *fp, C char *a, C char *v, C char *c)
{
	char *aa = prep_attribute(a);
	fputs(aa, fp);
	free(aa);
	putc(' ', fp);
	char *vv = prep_integer(v);
	if(!vv)
		vv = prep_real(v);
	if(!vv)
		vv = prep_string(v);

	assert(vv);
	fputs(vv, fp);
	if(c && *c)
		print_comment(c, fp);
	putc('\n', fp);
	free(vv);
}


LOCAL void print_properties(FILE *out, C proplist& prop)
{
	C property *p = prop.first;
	for(; p; p=p->next)	{
		if(!*p->attr || !p->val)
			continue;

		/* Drop duplicates and 'covered' attributes. */
		if(prop.get(p->attr) != p)
			continue;

		/* We ignore comments, as sphere headers
		   seem to be limited to 1024 characters. */
		if(strcmp(p->attr, "_COMMENT") == 0)
			print_comment_group(p->val, out);
		else
			print_it(out, p->attr, p->val, p->comment);
	}
}



char *get_convert_date(C proplist& q)
{
 C static char *m[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
			"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
 C char *d = q.gets("DATE-OBS");
 if(!d)
	d = q.gets("DATE");
 if(!d)	{
	time_t clk;
	time(&clk);
	struct tm *tb = localtime(&clk);
	char buf[100];
	sprintf(buf, "%02d-%s-%04d", tb->tm_mday, m[tb->tm_mon], tb->tm_year+1900);
	return strdup(buf);
	}

 int dd, mm, yy;
 if(sscanf(d, "%d/%d/%d", &dd, &mm, &yy) == 3
		&& dd>0 && dd<=31 && mm>0 && mm<=12 && yy>0)	{
	char *rv = (char *)malloc(strlen(d)+5);
	sprintf(rv, "%02d-%s-%04d", dd, m[mm-1], yy<100?yy+1900:yy);
	return rv;
	}
 return strdup(d);
}



void back_up_to_newline(FILE *f, int before_where)
{
	int c;
	long n=0, last_nl = -1;
	assert(!ferror(f));
	if(fseek(f, 0L, SEEK_SET) < 0) {
		fprintf(stderr, "ERR: back_up_to_newline: fseek1 err\n");
		exit(1);
	}
	assert(!ferror(f));
	while((c=getc(f)) != EOF) {
		if(n >= before_where)
			break;
		if(c == '\n')
			last_nl = n;
		n++;
	}
	assert(!ferror(f));
	if(c == EOF) {
		fprintf(stderr, "ERR: back_up_to_newline: error at n=%ld\n", n);
		exit(1);
	}
	if(last_nl < 0) {
		fprintf(stderr, "ERR: back_up_to_newline: No newline in first %d chars!\n", before_where);
		exit(1);
	}
	if(fseek(f, last_nl+1, SEEK_SET)<0) {
		fprintf(stderr, "ERR: back_up_to_newline: fseek2 err at n=%ld\n", n);
		exit(1);
	}
	assert(!ferror(f));
}




BOOL sphere_writefile(C struct image *p, FILE *out, C proplist& control)
{
 if(ftell(out) < 0)	{
	fprintf(stderr, "ERR: sphere_write: Sorry.  Code can't write sphere file unless it can seek.\n");
	return 0;
	}
 assert(p);
 assert(out);
 assert(p->prop);
 proplist q(*p->prop);
 normalize_proplist(&q, p);
 q.add("channel_count", p->npts, "NAXIS1 for FITS");
 q.add("NAXIS1", (char *)0);
 q.add("NAXIS2", (char *)0);
 q.add("NAXIS", (char *)0);
 q.add("sample_count", p->nlines, "Signal length (NAXIS2 for FITS)");
 q.add("xscale", (char *)0);
 q.add("yscale", (char *)0);
 if(p->npts > 1)
	q.add("I_NOISE", (char *)0);  /* Noise may be different from one
					channel to another.  Not too
					meaningful. */
 q.add("CRVAL1", (char *)0);
 q.add("CDELT1", (char *)0);
 q.add("CRPIX1", (char *)0);
 q.add("%COMMENT", (char *)0);
 q.add("%HISTORY", (char *)0);
 q.add("%DATAMIN", (char *)0);
 q.add("%DATAMAX", (char *)0);
 q.add("%DATE_OBS", (char *)0);
 q.add("sample_rate", roundI(1.0/p->yscale));
 char *dt = get_convert_date(q);
 q.add("recording_date", dt);
 free(dt);

 q += control;

 q.add("_FILETYPE", (char *)0);
 int bitpix = q.geti("BITPIX", 0);
 if(bitpix!=16 || bitpix!=8)
	bitpix = 16;
 q.add("BITPIX", (char *)0);
 q.add("sample_n_bytes", bitpix/8);
 q.add("sample_coding", "pcm");
 double bscale, bzero, rawmin, rawmax;
 scale_image_for_bitpix(p, bitpix, &bscale, &bzero, &rawmin, &rawmax, &q, FALSE);

 q.add("sample_min", roundI(bscale>0 ? (rawmin-bzero)/bscale : (rawmax-bzero)/bscale));
 q.add("sample_max", roundI(bscale<0 ? (rawmin-bzero)/bscale : (rawmax-bzero)/bscale));

 fprintf(out, "NIST_1A\n   1024\n");
 fprintf(out, "sample_byte_format -s2 10\n");
 print_properties(out, q);
 fprintf(out, "end_head\n");
 if(ferror(out)) {
	fprintf(stderr, "ERR: sphere_write: write error on header\n");
	return FALSE;
	}
 int fill = 1024 - ftell(out);
 if(fill < 0) {
	fprintf(stderr, "WARN: header length=%ld  is greater than 1024 characters.\n", ftell(out));
	back_up_to_newline(out, 1024-strlen("end_head\n"));
	fprintf(out, "end_head\n");
	fill = 1024 - ftell(out);
	}
 while(fill>0)	{
	fputc( fill%72==0 ? '\n' : ';', out);
	fill--;
	}
 if(ferror(out)) {
	fprintf(stderr, "ERR: sphere_write: write error on header after fill.\n");
	return FALSE;
	}

 return write_out_data(out, p, bitpix, NETWORK_ORDER, bscale, bzero);
}
