/*
 * SIG_WRITE.c
 * Write Bell Laboratories SIG format audio data files.
 *
 *
Copyright (C) 2002 Greg Kochanski, gpk@ieee.org .

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 "gpk_std.h"
#include "gpklib/err.h"
#include "gpklib.h"
#include "gpklib/image.h"
#include "rw_misc.h"
#include "rbfile.h"
#include "internals.h"
#include "gpklib/assert.h"

#define D if(0)

static inline unsigned U(char c)
{
	return 0377 & (unsigned)c;
}


#define LOCALORDER	0

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<256; i++)
			legal[i] = 'X';
		for(i=0; i<sizeof(ok)-1; i++)
			legal[U(ok[i])] = 0;
		legal[U('.')] = '_';
		legal[U('-')] = '_';
		legal[U(':')] = '_';
		legal[U('/')] = '_';
		legal[U('_')] = 0;
		initted = 1;
	}

	if(!c)
		return 0;

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

	char *rv = strdup(c);
	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[U(*p)])
			*p = legal[U(*p)];
		if(last == '_' && *p=='_')
			*p = 'X';
		if(lower)
			*p = tolower(*p);
		last = *p;
	}
	return rv;
}



static void print_it(FILE *fp, C char *a, C char *v, C char *c)
{
	char *aa = prep_attribute(a);
	fputs("remark ", fp);
	fputs(aa, fp);
	free(aa);
	putc('=', fp);
	char *vv = encode_av(v);

	assert(vv);
	fputs(vv, fp);
	putc('#', fp);
	char *cc = encode_av(c);
	fputs(cc, fp);
	putc('\n', fp);
	free(vv);
	free(cc);
}


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;

		print_it(out, p->attr, p->val, p->comment);
	}
}



void print_comments(FILE *out, C char *prefix, C char *x)
{
	char *xc = strdup(x);
	char **s0 = csplit(xc, "\n");
	char **s = s0;
	for(; *s; s++)	{
		char *q = encode_av(*s);
		fprintf(out, "%s %s\n", prefix, q);
		free(q);
	}
	free(s0);
	free(xc);
}



char *strdup_legal(C char *s)
{
 char *o = strdup(s);
 char *p = o;
 for(; *p; p++)
	if(!isalnum(*p))
		*p = '_';
 return o;
}


/* We convert all the TTYPE#= attributes into elements of
a list of names in an identifiers= attribute. */
char * process_TTYPE(proplist* prop, int naxis1)
{
 int maxtt = 0;
 int i;
 char **idl = (char **)malloc(naxis1 * sizeof(char *));
 assert(idl);
 for(i=0; i<naxis1; i++)
	idl[i] = strdup("null");
 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;
	int tt;
	if(sscanf(p->attr, "TTYPE%d", &tt) == 1) {
		if(tt > maxtt && tt<=naxis1)
			maxtt = tt;
		if(tt > 0 && tt <= naxis1)
			idl[tt-1] = strdup_legal(p->val);
		}
	}

 char *o = 0;
 if(maxtt > 0) {
	for(i=0; i<maxtt; i++) {	/* Erase TTYPE1 from property list */
		char buf[512];
		sprintf(buf, "TTYPE%1d", i+1);
		prop->add(buf, (char *)0);
		}
	int idsl = 0;
	for(i=0; i<naxis1; i++)
		idsl += strlen(idl[i]) + 1;
	o = (char *)malloc(idsl + 1);
	o[0] = '\0';
	for(i=0; i<naxis1; i++)	{
		strcat(o, idl[i]);
		if(i < naxis1 -1 )
			strcat(o, " ");
		}
	}
 for(i=0; i<naxis1; i++)
	free(idl[i]);
 free(idl);
 return o;
}


#if LOCALORDER == 1

BOOL write_sig_magic(FILE *out, int bitpix)
{
 /* Because some software can't handle word swaps,
    we only emit a header that indicates a word swap
    if the data requires swapping.   Two byte data
    never needs word swaps, so we let the reader
    remain blissfully ignorant.  One byte data allows
    even more brainless readers to succeed.
    */

 if(bitpix >= 32 || bitpix <= -32) {
	assert(sizeof(unsigned) == 4);
	C unsigned sig_magic = ('S'<<24) | ('I' << 16) | ('G' << 8) | '\n';
 	return fwrite(&sig_magic, 4, 1, out) == 1;
 }
 else if(bitpix == 16 || bitpix==-16) {
	assert(sizeof(short) == 2);
	short sig_magic[2];
	sig_magic[0] = ('S'<<8) | 'I';
	sig_magic[1] = ('G' << 8) | '\n';
 	return fwrite(sig_magic, 2, 2, out) == 2;
 }
 else if(bitpix==8 || bitpix==-8) {
	C char sig_magic[] = "SIG\n";
 	return fwrite(sig_magic, 1, 4, out) == 4;
 }
 else {
	assert(0);
 }
}

#else
BOOL write_sig_magic(FILE *out, int bitpix)
{
 C char sig_magic[] = "SIG\n";
 return fwrite(sig_magic, 1, 4, out) == 4;
}
#endif

BOOL SIG_writefile(C struct image *p, FILE *out, C proplist& control)
{
 assert(image_check(p));
 if(ftell(out) < 0)	{
	fprintf(stderr, "ERR: SIG_write: Sorry.  Code can't write SIG file unless it can seek.\n");
	return 0;
	}
 assert(p);
 assert(out);
 assert(p->prop);

 proplist q(*p->prop);
 normalize_proplist(&q, p);
 q += control;

 int bitpix = q.geti("BITPIX", 16);
 if(bitpix == 0)
	bitpix = -32;

 C int SPECIAL_SIGNALS = 0xff000;		/* Signals.h */
 int typecode = (q.geti("SIG_SPECIAL_SIGNALS", 0) & SPECIAL_SIGNALS);
 switch( bitpix) {
	case -32: typecode |= 0x7; break;
	case 32: typecode |= 0x5; break;
	case 16: typecode |= 0x4; break;
	case 8: typecode |= 0x1; break;
	default:
		gpk_err("illegal or unsupported bitpix", 0);
 }
 q.add("BITPIX", (char *)0);

 write_sig_magic(out, bitpix);
 C long sizeposition = ftell(out);
 fprintf(out, "%7d\n", 0);
 C long startheaderposition = ftell(out);
 D fprintf(stderr, "sizepos=%ld starthdrpos=%ld\n", sizeposition, startheaderposition);

 q.add("NAXIS1", (char *)0);
 q.add("NAXIS2", (char *)0);
 q.add("NAXIS", (char *)0);
 fprintf(out, "samples %d\n", p->nlines);
 fprintf(out, "dimensions %d\n", p->npts);
 q.add("xscale", (char *)0);
 q.add("yscale", (char *)0);
 fprintf(out, "start_time %.10g\n", p->ycenter - (p->nlines-1)*p->yscale*0.5);
 fprintf(out, "end_time %.10g\n", p->ycenter + (p->nlines-1)*p->yscale*0.5);
 if(p->npts > 1)
	q.add("I_NOISE", (char *)0);  /* Noise may be different from one
					channel to another.  Not too
					meaningful. */


 assert(image_check(p));
 q.add("%DATAMIN", (char *)0);
 q.add("%DATAMAX", (char *)0);
 q.add("CRVAL1", (char *)0);
 q.add("CDELT1", (char *)0);
 q.add("CRPIX1", (char *)0);
 q.add("CRVAL2", (char *)0);
 q.add("CDELT2", (char *)0);
 q.add("CRPIX2", (char *)0);

 char *idents = process_TTYPE(&q, p->npts);
 if(idents) {
	fprintf(out, "identifiers ; %s ;\n", idents);
	free(idents);
	}

 assert(image_check(p));
 q.add("_FILETYPE", (char *)0);

 fprintf(out, "type_code %x\n", typecode );	/* .../h/Signals.h in SIG package. */

 C int version = q.geti("%version", 0);
 q.add("%version", (char *)0);
 fprintf(out, "version %d\n", version+1);

 fprintf(out, "frequency %.10g\n", 1.0/p->yscale);

 double bscale, bzero, rawmin, rawmax;
 scale_image_for_bitpix(p, bitpix, &bscale, &bzero, &rawmin, &rawmax, &q, TRUE);

 fprintf(out, "minimum %g\n", bscale>0 ? (rawmin-bzero)/bscale : (rawmax-bzero)/bscale);
 fprintf(out, "maximum %g\n", bscale<0 ? (rawmin-bzero)/bscale : (rawmax-bzero)/bscale);
 q.add("%COMMENT", (char *)0);
 q.add("%HISTORY", (char *)0);

 assert(image_check(p));
 if(q.get("TIME"))
	fprintf(out, "time %s\n", q.gets("TIME"));
 q.add("%TIME", (char *)0);

 C char *date = q.gets("DATE", (char *)0);
 if(!date)
	date = q.gets("DATE-OBS", (char *)0);
 if(date)
	fprintf(out, "date %s\n", date);
 q.add("%DATE", (char *)0);

 print_properties(out, q);
 if(ferror(out)) {
	fprintf(stderr, "ERR: SIG_write: write error on header\n");
	return FALSE;
	}

 assert(image_check(p));
 C long endheaderposition = ftell(out);
 D fprintf(stderr, "endheaderposition=%ld\n", endheaderposition);
 if(fseek(out, sizeposition, SEEK_SET) == 0) {
	fprintf(out, "%7ld\n", endheaderposition - startheaderposition);
	fseek(out, endheaderposition, SEEK_SET);
	}
 else
	fprintf(stderr, "WARN: SIG_write: can't seek to write header size.\n");
 
 assert(image_check(p));
 D fprintf(stderr, "seeked\n");

 if(ferror(out)) {
	fprintf(stderr, "ERR: SIG_write: write error on header after fill.\n");
	return FALSE;
	}

 assert(image_check(p));
 return write_out_data(out, p, bitpix,
			LOCALORDER ? LOCAL_ORDER : NETWORK_ORDER,
			bscale, bzero);
}
