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

				RW_MISC.C
				Misc stuff.

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 <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <math.h>
#include <gpk_std.h>
#include "gpklib/ieee754.h"
#include "gpklib/image.h"
#include "gpklib/err.h"
#include "gpklib/assert.h"
#include "gpklib/NaN.h"
#include "gpklib/innerloop4.h"
#include "rw_misc.h"

#define D if(0)

static char *stringify(double x)
{
	char *rv = (char *)malloc(32);
	assert(rv);
	sprintf(rv, "%.14g", x);
	return rv;
}

static C char use_real = 0;


static double convert(double x, C char *s)
{
	if(s == &use_real) {
		return x;
	}
	assert(s);
	return atof(s);
}


static void check_and_set_center(struct image *i,
				C char *val, C char *pix,
				double *center, double scale, int naxis)
{
	if(!isnan(i->prop->getd(val))
			&& !isnan(i->prop->getd(pix))
			&& !isnan(scale)) {
		*center = i->prop->getd(val)
			+ scale * (0.5*(naxis-1) - (i->prop->getd(pix,1.0)-1.0));
	}
}



static void set_attr_guts(struct image  *i, C char *attr,
			double val, C char *cval)
{
	if(strcmp(attr, "xscale")==0 || strcmp(attr, "CDELT1")==0) {
		i->xscale = convert(val, cval);
		check_and_set_center(i, "CRVAL1", "CRPIX1",
					&(i->xcenter), i->xscale, i->npts);
	}
	else if(strcmp(attr, "yscale")==0 || strcmp(attr, "CDELT2")==0) {
		i->yscale = convert(val, cval);
		check_and_set_center(i, "CRVAL2", "CRPIX2",
					&(i->ycenter), i->yscale, i->nlines);
	}
	else if(strcmp(attr, "xcenter")==0)
		i->xcenter = convert(val, cval);
	else if(strcmp(attr, "ycenter")==0)
		i->ycenter = convert(val, cval);
	else if(strcmp(attr, "CRPIX1")==0 
			|| strcmp(attr, "CRPIX2")==0
			|| strcmp(attr, "CRVAL1")==0
			|| strcmp(attr, "CRVAL2")==0) {
		if(cval != &use_real)
			i->prop->add(attr, cval);
		else
			i->prop->add(attr, val);
		check_and_set_center(i, "CRVAL1", "CRPIX1",
					&(i->xcenter), i->xscale, i->npts);
		check_and_set_center(i, "CRVAL2", "CRPIX2",
					&(i->ycenter), i->yscale, i->nlines);
	}
	else if(strcmp(attr, "i_noise")==0)
		i->i_noise = convert(val, cval);
	else if(strcmp(attr, "NAXIS")==0 || strcmp(attr, "NAXIS1")==0
			|| strcmp(attr, "NAXIS2")==0)
		gpk_err("Can't set this attribute:", attr);
	else if(cval != &use_real)
		i->prop->add(attr, cval);
	else
		i->prop->add(attr, val);
}


void set_attr(struct image  *i, C char *attr, C char *val)
{
	set_attr_guts(i, attr, quiet_nan(0), val);
}

void set_attr(struct image  *i, C char *attr, double val)
{
	set_attr_guts(i, attr, val, &use_real);
}


char *get_attr(C struct image *i, C char *attr)
{
	assert(i);

	if(strcmp(attr, "xscale")==0 || strcmp(attr, "CDELT1")==0)
		return stringify(i->xscale);
	else if(strcmp(attr, "yscale")==0 || strcmp(attr, "CDELT2")==0)
		return stringify(i->yscale);
	else if(strcmp(attr, "xcenter")==0)
		return stringify(i->xcenter);
	else if(strcmp(attr, "ycenter")==0)
		return stringify(i->ycenter);
	else if(strcmp(attr, "CRPIX1")==0)
		return stringify(i->prop->getd(attr, 0.5*(i->npts+1)));
	else if(strcmp(attr, "CRPIX2")==0)
		return stringify(i->prop->getd(attr, 0.5*(i->nlines+1)));
	else if(strcmp(attr, "CRVAL1")==0) {
		char *crpixs = get_attr(i, "CRPIX1");
		C double crpix = atof(crpixs);
		free(crpixs);
		return stringify( i->xcenter
				+ i->xscale*((crpix-1.0) - 0.5*(i->npts-1)));

	}
	else if(strcmp(attr, "CRVAL2")==0) {
		char *crpixs = get_attr(i, "CRPIX2");
		C double crpix = atof(crpixs);
		free(crpixs);
		C double crv2 = i->ycenter
				+ i->yscale*((crpix-1.0) - 0.5*(i->nlines-1));
		return stringify(crv2);
	}
	else if(strcmp(attr, "i_noise")==0)
		return stringify(i->i_noise);

	else if(strcmp(attr, "NAXIS")==0)
		return strdup("2");
	else if(strcmp(attr, "NAXIS1")==0)
		return stringify(i->npts);
	else if(strcmp(attr, "NAXIS2")==0)
		return stringify(i->nlines);
	return strdup(i->prop->gets(attr, ""));
}




/* This takes those properties that are stored in the image structure
as numbers, and puts them onto a property list.
This operation is usually done prior to writing out an image.
If you use normalize_proplist(), then you can just write out
the image's proplist, and not worry about the rest of the structure.
Normally, the property list p is a copy of the image's property list.
*/

void normalize_proplist(proplist *p, C struct image *i)
{
 assert(p != i->prop);

 if(isnan(i->xcenter)) {
	p->add("CRVAL1", (char *)0);
 } else {
	char *crval1 = get_attr(i, "CRVAL1");
	char *crpix1 = get_attr(i, "CRPIX1");
	p->add("CRVAL1", crval1, "Value at pixel CRPIX1");
	p->add("CRPIX1", crpix1, "X of the reference pixel");
	free(crval1);
	free(crpix1);
 }
 if(isnan(i->ycenter)) {
	p->add("CRVAL2", (char *)0);
 } else {
	char *crval2 = get_attr(i, "CRVAL2");
	char *crpix2 = get_attr(i, "CRPIX2");
	p->add("CRVAL2", crval2, "Value at pixel CRPIX2");
	p->add("CRPIX2", crpix2, "Y of the reference pixel");
	free(crval2);
	free(crpix2);
 }

#if 0
 {
 char tm[100], *p;
 time_t ct = img->constr_time;
 strncpy(tm, ctime(&ct), sizeof(tm));
 for(p=tm;*p && *p!='\n';p++)
 	;
 *p = '\0';
 printf("%sRecorded_at= %ld; Recorded_at_string= %s;\n", prefix, img->constr_time, tm);
 }
#endif

 p->add("_NAME", (char *)0);
 p->add("CDELT1", i->xscale, "X scale");
 p->add("xscale", (char *)0);
 p->add("CDELT2", i->yscale, "Y scale");
 p->add("yscale", (char *)0);
 p->add("I_NOISE", i->i_noise, "Noise, in real units");

 p->add("NAXIS2", i->nlines, "Y, or rows");
 p->add("NAXIS1", i->npts, "X, or columns, or points in a row");
}



/* Fixes dates to follow the amended FITS standard
(approved by IAU FITS working group 1997-11-10),
see http://www.cv.nrao.edu/fits/documents/standards/year2000.txt .
*/

LOCAL void one_date_fix(proplist *q, C char *attr)
{
	C char *b = q->gets(attr, (C char *)0);
	int dd, mm, yy;
	char rbuf[256];
	int tmp = sscanf(b, "%2d/%2d/%d %256s", &dd, &mm, &yy, rbuf);
	if(tmp==3 && yy>=0 && mm>0 && dd>0 && mm<=12 && dd<=31) {
		char buf[128];
		if(yy < 100)
			yy += 1900;
		if(yy < 1000) {
			sprintf(buf, "%s=%s", attr, b);
			gpk_warn("Implausible date in date_fix:", buf);
			}
		sprintf(buf, "%d-%02d-%02d", yy, mm, dd);
		fprintf(stderr, "Converting date from %s to %s\n", b, buf);
		q->add(attr, buf);
		return;
		}
	long t = safe_atol(b);
	if(t != LONG_NAN && t!=LONG_MAX && t!=LONG_MIN && t>=100000)	{
		char buf[128];
		struct tm *tb = gmtime(&t);
		sprintf(buf, "%d-%02d-%02dT%02d:%02d:%02d",
			tb->tm_year+1900, tb->tm_mon+1, tb->tm_mday,
			tb->tm_hour, tb->tm_min, tb->tm_sec);
		fprintf(stderr, "Converting date from %s to %s\n", b, buf);
		q->add(attr, buf);
		return;
		}
	/* Could add other date formats here. */
}



void gpkio_date_fix(proplist *q)
{
	C property *p = q->first;
	for(; p; p=p->next)	{
		if(!*p->attr || !p->val)
			continue;

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

		if(proplist::matches(p->attr, "%DATE*"))	{
			one_date_fix(q, p->attr);
		}
	}
}





/* Concatenates two strings, with a newline in between (if both strings are non-empty): */

static char * join_em(C char *a, C char *b, int junction)
{
 C int la = a ? strlen(a) : 0;
 C int lb = b ? strlen(b) : 0;
 C int length = la + lb + ((la&&lb) ? 1 : 0) + 1;
 char *s = (char *)malloc(length);
 char *p = s;
 if(a)
	while(*a)
		*(p++) = *(a++);
 if(la && lb)
	*(p++) = junction;
 if(b)
	while(*b)
		*(p++) = *(b++);
 *(p++) = '\0';
 assert(p-s == length);
 return s;
}



void add_append_property(proplist *p, C char *a, C char *v, C char *c)
{
 assert(a);
 assert(p);

 property *t = p->get( a );
 if(t)	{
	char *vv = join_em(t->val, v, '\n');
	char *cc = join_em(t->comment, c, '\n');
	assert(vv && cc);
	
	D fprintf(stderr, "FAttr={%s}, val={%s}, comment={%s}\n", a, v?v:"", c?c:"");
	D fprintf(stderr, "t->val={%s} v={%s}\n", t->val?t->val:"", v?v:"");
	D fprintf(stderr, "t->comment={%s} c={%s}\n", t->comment?t->comment:"", c?c:"");
	p->add(a, vv, cc);
	free(vv);
	free(cc);
	}
 else
	p->add(a, v, c);
}




void gpkio_swaw(char *a, int nb)
{
 assert(sizeof(twobyte) == 2);
 twobyte *p = (twobyte *)a;
 int j;
 OPENMP(parallel for schedule(dynamic, 512))
 for(j=0; j<nb/4; j++) {
 	twobyte tmp=p[2*j];
	p[2*j]=p[2*j+1];
	p[2*j+1]=tmp;
 	}
 /*
 twobyte tmp;
 INNERLOOP(j, 0, nb/4, (tmp=p[2*j],p[2*j]=p[2*j+1],p[2*j+1]=tmp));
 */
}



void gpkio_swab_in_place(short *pp, int nb)
{
 assert(sizeof(twobyte) == 2);
 twobyte *p = (twobyte *)pp;
 int j;
 OPENMP(parallel for schedule(dynamic, 512))
 for(j=0; j<nb/2; j++) {
	twobyte pj = p[j];
 	p[j] = ((pj&0377)<<8)|((pj>>8)&0377);
	}
 /* OLD:
 INNERLOOP(j, 0, nb/2, p[j]=((p[j]&0377)<<8)|((p[j]>>8)&0377))
 */
/* ANCIENT:
 INNERLOOP(j, 0, nb/2, (tmp=p[2*j],p[2*j]=p[2*j+1],p[2*j+1]=tmp));
*/
}


