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

				WRITE.C
				Main write routine.  Calls the format-specific routines.

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 <errno.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <float.h>
#include <unistd.h> /* for swab() */

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

#define D	if(0)
#define D2	if(0)

#define U(x)	((unsigned)(x))

struct writer	{
	C char *name;
	BOOL (*f)(C struct image *, FILE *, C proplist&);
	C char *suffix;
	};

C struct writer writers[] = {
			{"GPK ASCII image", ascii_writefile, ".dat"},
			{"GPK image", ascii_writefile, ".dat"},
			{"GPK ASCII image", ascii_writefile, ".txt"},
			{"NASA FITS", FITS_writefile, ".fits"},
			{"FITS", FITS_writefile, ".fits"},
			{"NIST SPHERE", sphere_writefile, ".sphere"},
			{"SPHERE", sphere_writefile, ".sphere"},
			/* {"Praat", praat_writefile, ".praat"}, */
			{"HTK HParam", HTK_writefile, ".mfcc"},
			{"ESPS FEA_SD", ESPS_writefile, ".sd"},
			{"SIG", SIG_writefile, ".d"},
			{"ESPS", ESPS_writefile, ".sd"},
			{"FOCAS", focas_writefile, ".im"},
			{0, 0, 0},	/* Default. */
			};

LOCAL double xmax(double a, double b)
{
	return a>b ? a : b;
}

LOCAL BOOL suffix_match(C char *a, C char *suff)
{
 C int ls = strlen(suff);
 C int la = strlen(a);
 if(ls > la)
	return 0;
 return strcmp(suff, &a[la-ls])==0;
}


BOOL writefile(C struct image *p, C char *n2, C char *control)
{
 if(control && strcmp(control, "float")==0)	/* backwards compatibility */
	control = "BITPIX=-32";
 if(control && strcmp(control, "writefloat")==0)	/* backwards compatibility */
	control = "BITPIX=-32";

 D fprintf(stderr, "Control={%s}\n", control?control:"");
 assert(p->prop);
 proplist c = avparse(control);
 C char *dflt = getenv("_FILETYPE");
 if(!dflt)
	dflt = p->prop->gets("_FILETYPE", (C char *)0);

 /* Use type from control string, or else type from property list. */
 C char *ftype = c.gets("%_FILETYPE", (C char *)0);

 D fprintf(stderr, "Name=%s dflt=%s\n", n2, dflt?dflt:"<nil>");
 int i;
	/* Control string can override choice of output
	type.  Otherwise, use suffix to control output type.
	Failing that, use a default type from the environment,
	or keep types the same. */
 for(i=0; writers[i].name; i++)
	;

 if(ftype)
	for(i=0; writers[i].name; i++)
		if(strcmp(ftype, writers[i].name) == 0)	{
			break;
			}
 if(!writers[i].name)
	for(i=0; writers[i].suffix; i++)
		if(suffix_match(n2, writers[i].suffix))	{
			break;
			}
 if(dflt && !writers[i].name)
	for(i=0; writers[i].name; i++)
		if(strcmp(dflt, writers[i].name) == 0)	{
			break;
			}
 if(!writers[i].name)
	i = 0;
 D fprintf(stderr, "INFO: writing as %s\n", writers[i].name);

 c.add("_NAME", n2);
 FILE *out = 0;
 if(strcmp(n2, "-") == 0)
	out = stdout;
 else
	out = fopen(n2, "wb+");

 if(!out)	{
	fprintf(stderr, "ERR: writefile: can't open %s: %s", n2, strerror(errno));
	return FALSE;
	}

 BOOL rv = (*writers[i].f)(p, out, c);

 if(out != stdout)
 	fclose(out);
 return rv;
}





BOOL writefile(C struct multi_image *rv, C char *name)
{
 BOOL fail = FALSE;
 int ix;

 assert(rv);
 assert(rv->n_image > 0);
 for(ix=0;ix<rv->n_image;ix++)	{
	char nbuf[100], n2[100];
	strcpy(nbuf, name);
	if(rv->n_image > 1)
		sprintf(n2, "%s.%03d", nbuf, ix);
	else	sprintf(n2, "%s", nbuf);
	fail = fail || !writefile(&rv->image[ix], n2, 0);
	}
 return !fail;
}



typedef int fourbyte;


LOCAL unsigned char roundU(short x)
{
 if(x == SHRT_NAN)
	return UCHAR_NAN;
 else if(x > UCHAR_Max)
	return UCHAR_Max;
 else if(x < UCHAR_Min)
	return UCHAR_Min;
 return (unsigned char)(unsigned short)x;
}


typedef short twobyte;




BOOL write_out_data(FILE *out, C struct image *p, int bitpix,
			read_in_e swap,
			double sc, double sh)
{
 C char test[4] = {(char)1, (char)2, (char)3, (char)4};
 C short *ts = (C short *)test;
 C int *ti = (C int *)test;
 BOOL swapbytes = FALSE;
 BOOL swapwords = FALSE;
 switch(swap) {
 case LOCAL_ORDER:
	break;
 case NETWORK_ORDER:
 	swapbytes = ((*ts)&0377) == 1 || ((*ts)&0377)==3;
 	swapwords = ((*ti)&0377) == 1 || ((*ti)&0377)==2;
	break;
 case ANTI_NETWORK:
 	swapbytes = ((*ts)&0377) == 2 || ((*ts)&0377)==4;
 	swapwords = ((*ti)&0377) == 3 || ((*ti)&0377)==4;
	break;
 case SWAB:
	swapbytes = TRUE;
	break;
 case SWAW:
	swapwords = TRUE;
	break;
 case SWAWB:
	swapwords = TRUE;
	swapbytes = TRUE;
	break;
 default:
	assert(0);
 }

 D fprintf(stderr, "SWAPBYTES=%d SWAPWORDS=%d ts=%x ti=%x\n",
		swapbytes, swapwords, *ts, *ti);

 C BOOL scale_data = fabs(sc-1)>FLT_EPSILON  || fabs(sh)>FLT_MIN;
 D fprintf(stderr, "SCALE=%g SHIFT=%g scale_data=%d\n", sc, sh, scale_data);
 assert(image_check(p));

 assert(sizeof(short) == 2);
 assert(sizeof(fourbyte) == 4);

 if(bitpix == 0)	{
	int i;
	char fmt[20];
	C double prec = p->h ? pow(2.0, -sizeof(short)) : FLT_EPSILON;
	assert(sizeof(**p->h) == sizeof(short));
	assert(sizeof(**p->H) == sizeof(float));
	sprintf(fmt, "%%.%dg ", (int)(ceil(-log10(prec))));
	for(i=0; i<p->nlines; i++)	{
		if(scale_data)	{
			int j;
			for(j=0; j<p->npts; j++)
				fprintf(out, fmt, ((p->H ? p->H[i][j]
							: p->h[i][j])-sh)*sc);
			}
		else	{
			int j;
			if(p->H)
				for(j=0; j<p->npts; j++)
					fprintf(out, fmt, p->H[i][j]);
			else
				for(j=0; j<p->npts; j++)
					fprintf(out, "%d", p->h[i][j]);
			}
		if(fprintf(out, "\n") < 0 || ferror(out))
			return FALSE;
		}
	}
 else if(p->H && bitpix== -32 && !scale_data && !swapbytes && !swapwords)	{
	const float *H = p->H[0];
	/* Faster, on the assumption that the array is all contiguous. */
	assert(sizeof(float) == 4);
	assert(H + (p->nlines-1)*p->npts == p->H[p->nlines-1]);
	if(fwrite(H, 4, p->npts*p->nlines, out) != U(p->npts)*U(p->nlines))
		return FALSE;
	}
 else if(p->H && bitpix== -32)	{
	 int i;
	assert(sizeof(float) == 4);
	 float *H = (float *)malloc(4 * p->npts);
	 C int np = p->npts;
	 for(i=0; i<p->nlines; i++)	{
		C float *h = p->H[i];
		if(scale_data) {
			INNERLOOP(j, 0, np, H[j] = (h[j]-sh)*sc);
			}
		else	{
			memcpy(H, h, np*sizeof(float));
			}

		if(swapbytes)
			gpkio_swab_in_place((short *)H, sizeof(float)*np);
		if(swapwords)
			gpkio_swaw((char *)H, sizeof(float)*np);
		if(fwrite(H, 4, np, out) != U(p->npts))	{
			free((char *)H);
			return FALSE;
			}
 		}
	free((char *)H);
	}
 else if(p->h && bitpix== -32)	{
	 int i;
	assert(sizeof(float) == 4);
	 float *H = (float *)malloc(4 * p->npts);
	 C int np = p->npts;
	 for(i=0; i<p->nlines; i++)	{
		C short *h = p->h[i];
		if(scale_data)	{
			INNERLOOP(j, 0, np, H[j] = (h[j]-sh)*sc);
			}
		else	{
			INNERLOOP(j, 0, np, H[j] = h[j]);
			}
		if(swapbytes)
			gpkio_swab_in_place((short *)H, sizeof(float)*np);
		if(swapwords)
			gpkio_swaw((char *)H, sizeof(float)*np);
		if(fwrite((char *)H, 4, np, out) != U(np))	{
			free((char *)H);
			return FALSE;
			}
 		}
	free((char *)H);
	}
 else if(bitpix == 32)	{
	int i;
	C int np = p->npts;
	fourbyte *o = (fourbyte *)malloc(p->npts * sizeof(fourbyte));
	assert(o);
	assert(sizeof(fourbyte) == 4);
	for(i=0; i<p->nlines; i++)	{
		if(p->h)	{
			short *h = p->h[i];
			if(scale_data)	{
				/* This will work, but should never be needed. */
				INNERLOOP(j, 0, np, o[j] = roundI((h[j]-sh)*sc));
				}
			else	{
				INNERLOOP(j, 0, np, o[j] = h[j]);
				}
			}
		else if(p->H)	{
			int j;
			C float *HH = p->H[i];
			if(scale_data)
				for(j=0; j<np; j++)
					o[j] = roundI((HH[j]-sh)*sc);
			else
				for(j=0; j<np; j++)
					o[j] = roundI(HH[j]);
			}

		if(swapbytes)
			gpkio_swab_in_place((short*)o, sizeof(fourbyte)*np);
		if(swapwords)
			gpkio_swaw((char *)o, sizeof(fourbyte)*np);

		if(fwrite((char *)o, 4, np, out) != U(np))	{
			free((char *)o);
			return FALSE;
			}
 		}
	free((char *)o);
	}
 else if(p->h && bitpix == 16 && !scale_data)	{
	/* Faster, on the assumption that the array is all contiguous. */
	assert(sizeof(short) == 2);
	assert(p->h[0] + (p->nlines-1)*p->npts == p->h[p->nlines-1]);
	if(swapbytes)
		gpkio_swab_in_place((p->h[0]), p->npts*p->nlines*sizeof(short));
	if(fwrite((char *)(p->h[0]), 2, p->npts*p->nlines, out) != U(p->npts*p->nlines))
		return FALSE;
 }
 else if(bitpix == 16)	{
	int i;
	C int np = p->npts;
	short *tmp = (short *)malloc(p->npts * sizeof(short));
	assert(sizeof(short) == 2);
	assert(tmp);
	D fprintf(stderr, "BITPIX=16 and scale=%d, %c%c\n", scale_data,
			p->h?'h':' ', p->H?'H':' ');
	for(i=0; i<p->nlines; i++)	{

		if(p->h)	{	/* and scale_data... */
			int j;
			C short *HH = p->h[i];
			assert( scale_data );
			for(j=0; j<np; j++)
				tmp[j] = roundS((HH[j]-sh)*sc);
			}
		else	{
			int j;
			C float *HH = p->H[i];
			for(j=0; j<np; j++)	{
				tmp[j] = roundS((HH[j]-sh)*sc);
				}
			D2 fprintf(stderr, "round: %g %g %d\n", HH[0], (HH[0]-sh)*sc, tmp[0]);
			}

		if(swapbytes)	{
			D2 fprintf(stderr, "\tswab %d\n", tmp[0]);
			gpkio_swab_in_place(tmp, np*sizeof(short));
			D2 fprintf(stderr, "\tswabO %d\n", tmp[0]);
			}

		if(fwrite((char *)tmp, 2, np, out) != U(np))	{
			free((char *)tmp);
			return FALSE;
			}
 		}
	free((char *)tmp);
	}
 else if(bitpix == 8)	{
	int i;
	C int np = p->npts;
	assert(sizeof(unsigned char) == 1);
	unsigned char *H = (unsigned char *)malloc(p->npts * sizeof(signed char));
	assert(H);
	for(i=0; i<p->nlines; i++)	{

		if(p->h)	{
			C short *h = p->h[i];
			int j;
			if(scale_data)
				for(j=0; j<np; j++)
					H[j] = roundU((h[j]-sh)*sc);
			else
				for(j=0; j<np; j++)
					H[j] = roundU(h[j]);
			}
		else if(p->H)	{
			int j;
			C float *HH = p->H[i];
			if(scale_data)
				for(j=0;j<np;j++)
					H[j] = roundU((HH[j]-sh)*sc);
			else
				for(j=0;j<np;j++)
					H[j] = roundU(HH[j]);
			}

		if(fwrite((char *)H, 1, np, out) != U(np))	{
			free((char *)H);
			return FALSE;
			}
 		}
	free((char *)H);
	}
 else	{
	fprintf(stderr, "ERR: write.c: bad BITPIX=%d\n", bitpix);
	assert(0);
	}

 return TRUE;
}





void raw_data_minmax(C struct image *p, double *rdmin, double *rdmax)
{
 assert(p && rdmin && rdmax);
 if(p->h) {
	int i;
	C int np = p->npts;
	short maxval = SHRT_Min;
	short minval = SHRT_Max;
	/* OPENMP(omp parallel for collapse(2) schedule(guided, 32)) */
	OPENMP(omp parallel for schedule(guided, 32))
	for(i=0; i < p->nlines; i++)	{
		short *H = p->h[i];
		int j;
		for(j=0; j<np; j++)	{
			C short tmp = H[j];
			OPENMP(omp critical(raw_data_maxval))
			{
			if(tmp>maxval && tmp!=SHRT_NAN)
				maxval = tmp;
			}
			OPENMP(omp critical(raw_data_minval))
			{
			if(tmp<minval && tmp!=SHRT_NAN)
				minval = tmp;
			}
			}
		}
	D fprintf(stderr, "minmax h = %d %d\n", minval, maxval);
	*rdmin = (minval==SHRT_Min
			? -infinity()
			: (minval==SHRT_Max ? infinity() : minval));
	*rdmax = (maxval==SHRT_Min
			? -infinity()
			: (maxval==SHRT_Max ? infinity() : maxval));
	D fprintf(stderr, "minmax h2 = %g %g\n", *rdmin, *rdmax);
 }
 else {
	int i;
	C int np = p->npts;
	float maxval = -infinity();
	float minval = infinity();
	OPENMP(omp parallel for schedule(guided, 32))
	/* OPENMP(omp parallel for collapse(2) schedule(guided, 32)) */
	for(i=0; i < p->nlines; i++)	{
		float *H = p->H[i];
		int j;
		for(j=0;j<np;j++)	{
			C float tmp = H[j];
			OPENMP(omp critical(raw_data_maxval))
			{
			if(tmp>maxval && finitef(tmp))
				maxval = tmp;
			}
			OPENMP(omp critical(raw_data_minval))
			{
			if(tmp<minval && finitef(tmp))
				minval = tmp;
			}
			}
		}
	D fprintf(stderr, "minmax H = %g %g\n", minval, maxval);
	*rdmin = minval;
	*rdmax = maxval;
 }
}



/* This function DOES NOT modify the data.   It computes values for
 * bscale and bzero, and it may add things to the proplist
 * (the proplist will later become the header of the data file).
 * Bscale and bzero specify the linear transform to be applied
 * to the data when it is written out.   They are *not* the
 * values to be written into the headers.  (Those are put in
 * the proplist, of course as BSCALE and BZERO.)
 *
 * Any conversion/scaling of the data is done later, when it
 * it being written out.
 *
 * Incidentally, it also computes the min and max of the raw data,
 * if those pointers are not NULL.
 */
void scale_image_for_bitpix(C struct image *p, int bitpix,
				double *bscale, double *bzero,
				double *raw_data_min, double *raw_data_max,
				proplist *q, BOOL allow_bzero)
{
 if(p->h && bitpix>=16 && (allow_bzero || p->zshift==0.0)) {
		/* We can't always defer scaling for BITPIX=-32,
			as the FITS standard recommends against using
			BSCALE then, and IRAF doesn't read such files.
			However, this is a case where we can.
			*/

	if(raw_data_min || raw_data_max) {
		double minval, maxval;
		raw_data_minmax(p, &minval, &maxval);
		if(raw_data_min)
			*raw_data_min = minval;
		if(raw_data_max)
			*raw_data_max = maxval;
	}

	*bscale = 1;	/* Defer scaling, for speed. */
	*bzero = 0;
	q->add("BSCALE", p->zscale);
	q->add("BZERO", p->zshift);
	q->add("I_NOISE", xmax(p->i_noise, 1.0/sqrt(12.0)));
	D fprintf(stderr, "A bitpix=%d\n", bitpix);
	}
 else if(bitpix>0 && allow_bzero)	{
	double minval, maxval;
	raw_data_minmax(p, &minval, &maxval);
	if(raw_data_min)
		*raw_data_min = minval;
	if(raw_data_max)
		*raw_data_max = maxval;
	C double realmax = p->zshift + maxval*p->zscale;
	C double realmin = p->zshift + minval*p->zscale;

	C double sh = 0.5*(maxval+minval);
	C double sc = (pow(2.0, bitpix) - 3.0)/(maxval-minval);
	*bscale = sc * p->zscale;
	*bzero = sh;
	q->add("BSCALE", p->zscale/sc);
	q->add("BZERO", sh*p->zscale + p->zshift);
	q->add("DATAMIN", realmin);
	q->add("DATAMAX", realmax);
	q->add("I_NOISE", xmax(p->i_noise, (p->zscale/sc)/sqrt(12.0)));
	D fprintf(stderr, "B bitpix=%d\n", bitpix);
	}
 else if(bitpix > 0)	{
	double minval, maxval;
	raw_data_minmax(p, &minval, &maxval);
	if(raw_data_min)
		*raw_data_min = minval;
	if(raw_data_max)
		*raw_data_max = maxval;

	C double realmax = p->zshift + maxval*p->zscale;
	C double realmin = p->zshift + minval*p->zscale;
	D fprintf(stderr, "minmax=%g %g\n", minval, maxval);
	D fprintf(stderr, "realminmax=%g %g\n", realmin, realmax);

	C double absmax = realmax > -realmin ? realmax : -realmin;
	C double sc = (pow(2.0, bitpix-1) - 3.0) / absmax;
	C double sh = -p->zshift/p->zscale;
	D fprintf(stderr, "BP>0 absmax=%g sc=%g sh=%g\n", absmax, sc, sh);

	*bscale = sc * p->zscale;
	*bzero = sh;

	q->add("BSCALE", 1.0/sc);
	q->add("BZERO", (char *)0);
	q->add("DATAMIN", realmin);
	q->add("DATAMAX", realmax);
	q->add("I_NOISE", xmax(p->i_noise, (p->zscale/sc)/sqrt(12.0)));
	D fprintf(stderr, "C bitpix=%d\n", bitpix);
	}
 else if(bitpix <= 0) {
	/* We are converting into a -32 bit float format.
	Either the data comes from a 8,16,or 32 bit format,
	and we need to convert from
	   int to real anyhow, so we might as well do the arithmetic
	   to make BSCALE=1 and BZERO=0 while we are writing the
	   data.
	OR, we go from -32 bits to -32 bits,
	and might as well eliminate BSCALE,
	as the IRAF FITS reader insists that BSCALE not be present.
	*/
	double minval, maxval;
	raw_data_minmax(p, &minval, &maxval);
	if(raw_data_min)
		*raw_data_min = minval;
	if(raw_data_max)
		*raw_data_max = maxval;
	C double realmax = p->zshift + maxval*p->zscale;
	C double realmin = p->zshift + minval*p->zscale;
	*bscale = p->zscale;
	*bzero = -p->zshift/p->zscale;
	q->add("DATAMIN", realmin);
	q->add("DATAMAX", realmax);
	q->add("BSCALE", (char *)0);
	q->add("BZERO", (char *)0);
	q->add("BLANK", (char *)0);	/* FITS standard. */
	}
 else	{
	assert(0);
	}
 assert(image_check(p));
}

