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

				FITS_READ.C
				Read NASA FITS format 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 <assert.h>
#include <string.h>
#include <ctype.h>
#include <math.h>

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

#define STRICT_COMMENTS


#define D	if(0)

LOCAL int read_hdr_rec(char *t, RBFILE& s);
LOCAL char * parse_hdr_record(char *hdr_rec, proplist *p, BOOL *amp);
LOCAL char * continue_last_record(char *hdr_rec, proplist *p, BOOL *amp);

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

BOOL starts_with(C char *a, C char *b)
{
	return strncmp(a, b, strlen(b)) == 0;
}


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

 s.rewind();
 multi_image_empty(rv);

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

 int rec = 0;
 char hdr_rec[81];
 BOOL amp = FALSE;
 while(1)	{
	char *hpfail;
	if(!read_hdr_rec(hdr_rec, s))	{
		multi_image_destr(rv);
		sprintf(err, "not FITS: read fails on header record %d.", rec);
		return strdup(err);
		}
	if(starts_with(hdr_rec, "END "))
		break;
	if(amp && starts_with(hdr_rec, "CONTINUE ")) {
		hpfail = continue_last_record(&hdr_rec[sizeof("CONTINUE ")-1], p->prop, &amp);
		}
	else {
		hpfail = parse_hdr_record(hdr_rec, p->prop, &amp);
		}
	if(hpfail)	{
		multi_image_destr(rv);
		sprintf(err, "not FITS: can't parse header record %d: %s", rec, hpfail);
		free(hpfail);
		return strdup(err);
		}
	rec++;
	}

 if(strcmp(p->prop->gets("SIMPLE", ""), "T") != 0)	{
	sprintf(err, "not FITS: SIMPLE=%s", p->prop->gets("SIMPLE", ""));
	multi_image_destr(rv);
	return strdup(err);
	}
 p->prop->add("%SIMPLE", (char *)0);	/* This has no information content - merely proved file to be FITS. */

 if(p->prop->geti( "NAXIS", -1) != 2)	{
	sprintf(err, "can't handle FITS NAXIS=%s, not 2", p->prop->gets( "NAXIS"));
	multi_image_destr(rv);
	return strdup(err);
	}

 int rows = p->prop->geti("NAXIS2", -1);
 if(rows <= 0)	{
	sprintf(err, "not FITS bad NAXIS2=%s", p->prop->gets( "NAXIS2"));
	multi_image_destr(rv);
	return strdup(err);
	}

 int cols = p->prop->geti("NAXIS1", -1);
 if(cols <= 0)	{
	sprintf(err, "not FITS bad NAXIS1=%s2", p->prop->gets( "NAXIS1"));
	multi_image_destr(rv);
	return strdup(err);
	}

 int bitpix = p->prop->geti( "BITPIX", 0);
 if(bitpix == 0)	{
	sprintf(err, "bad FITS: no BITPIX");
	multi_image_destr(rv);
	return strdup(err);
	}

 if(bitpix==64 || bitpix==-64)	{
	sprintf(err, "ERR: Sorry, can't yet handle BITPIX=%d", bitpix);
	multi_image_destr(rv);
	return strdup(err);
	}
 if(bitpix!=8 && bitpix!=16 && bitpix!=32 && bitpix!=-32)	{
	sprintf(err, "WARN: bad FITS BITPIX=%d, != 8, 16, or +-32",
		bitpix);
	multi_image_destr(rv);
	return strdup(err);
	}

 while(s.tell()%2880 != 0)	{	/* Go to end of tape block. */
	if(s.fgetc()==EOF && s.tell()%2880!=0)	{
		sprintf(err, "not FITS: EOF before end of header block.");
		multi_image_destr(rv);	/* If file terminates before end of header block. */
		return strdup(err);
		}
	}
 D fprintf(stderr, "INFO: at end of header block (%ld)\n", s.tell());

 s.commit();		/* Toss out the buffer. */

 C long blank = p->prop->getl("BLANK", ~(0L));

 if(!header_only)	{
	char *fail = gpkio_read_in_data(s, &rv->image[0],
			rows, cols,
			bitpix,
			blank, NETWORK_ORDER);

	if(fail)	{
		multi_image_destr(rv);
		sprintf(err, "not FITS: error after header: %s", fail);
		free(fail);
		return strdup(err);
		}
	}

 p->xscale = p->prop->getd("CDELT1", 1.0);
 p->xcenter = p->prop->getd("CRVAL1", 0.0)
		+ p->xscale*(0.5*(cols-1) - (p->prop->getd("CRPIX1", 1.0)-1.0));
 p->yscale = p->prop->getd("CDELT2", 1.0);
 p->ycenter = p->prop->getd("CRVAL2", 0.0)
		+ p->yscale*(0.5*(rows-1) - (p->prop->getd("CRPIX2", 1.0)-1.0));
 p->zscale = p->prop->getd("BSCALE", 1.0);
 p->zshift = p->prop->getd("BZERO", 0.0);
 if(p->prop->get("BUNIT"))
	p->prop->add("zunits", p->prop->gets("BUNIT", "???"));
 if(p->prop->get("CTYPE1"))
	p->prop->add("xunits", p->prop->gets("CTYPE1", "???"));
 if(p->prop->get("CTYPE2"))
	p->prop->add("yunits", p->prop->gets("CTYPE2", "???"));
 p->i_noise = p->prop->getd("I_NOISE",
				bitpix<0 ? pow(2.0, bitpix)*p->zscale : p->zscale );
 p->prop->add("_FILETYPE", "FITS", "NASA NOST 100-1.1 9/29/1995");
 gpkio_date_fix(p->prop);
 rv->n_image++;

 return 0;
}


LOCAL int read_hdr_rec(char *t, RBFILE& s)
{
 C int n = s.fread(t, 1, 80);
 if(n != 80)	{
	t[0] = '\0';
	return FALSE;
	}
 t[n] = '\0';

 return TRUE;
}



LOCAL int ascii_chars(C char *c)
{
 for(; *c; c++)
	if(!isascii(*c))
		return 0;

 return 1;
}




/* Puts a wildcard character after attribute names that may have been truncated. */
/* Also prefixes a character to make matching case-insensitive. */
LOCAL char *patch_FITS_limitations(C char *attribute)
{
 assert(attribute);
 C int l = strlen(attribute);
 assert(l <= 8);
 char *rv = new char[l+3];	/* One for '%' one for '*', one for '\0' */
 assert(rv);
 char *t = rv;
 *t++ = '%';	/* Mark as case-insensitive match. */
 while(*attribute) {
	*(t++) = *(attribute++);
	}
 if(l == 8)
	*t++ = '*';	/* Mark as a prefix match - FITS may have truncated
			the attribute name. */
 *t++ = '\0';
 return rv;
}



LOCAL BOOL is_comment_start(char c)
{
#ifdef STRICT_COMMENTS
 return c=='/';
#else
 return c=='/' || c=='#';
#endif
}


LOCAL char * continue_last_record(char *hdr_rec, proplist *p, BOOL *amp)
{
 char err[512];
 char *c = 0;
 char *v = hdr_rec;
 for(; *v==' '; v++)
 	;
 if(*v != '\'') {
	sprintf(err, "CONTINUE does not have quote: %x at pos %d",
			S(*v), (int)(v-hdr_rec));
	return strdup(err);
	}
 v++;
 char *ev, *t;
 for(t=ev=v; *ev; ev++)	{
	if(*ev=='\'' && ev[1]=='\'')	{	/* Embedded quote. */
		ev++;
		*t++ = *ev;
		}
	else if(*ev == '\'')	{	/* Quote that terminates value. */
		c = ev+1;
		break;
		}
	else	*t++ = *ev;
	}
 *t = '\0';
 c = ev+1;
 if(t > v && t[-1] == '&')		/* Ends with ampersand. */
	*amp = TRUE;
 chomp_whitespace(v);

 while(c && *c == ' ')
	c++;

 C char *oa = p->first->attr;
 assert(oa);
 if(!ascii_chars(v) || (c && !ascii_chars(c)))	{
	sprintf(err, "non-ascii character in value or comment of CONTINUE of %s", oa);
	return strdup(err);
	}

 C char *ov = p->first->val;
 assert(ov && *ov);
 C int sov = strlen(ov);
 assert(ov[sov-1] == '&');
 C char *oc = p->first->comment;
 char *vtmp = (char *)malloc(strlen(ov) + strlen(v));
 strcpy(vtmp, ov);
 strcpy(&vtmp[sov-1], v);	/* Overwrite the ampersand. */
 C char *ctmp;
 char *ctmp0 = 0;
 if(oc && *oc && (!c || !*c))
 	ctmp = oc;
 else if((!oc || !*c) && c && *c)
 	ctmp = c;
 else if(oc && c) {
 	ctmp0 = (char *)malloc(strlen(oc) + strlen(c) + 1);
	strcpy(ctmp0, oc);
	strcat(ctmp0, c);
	ctmp = ctmp0;
	}
 else
 	ctmp = (char *)0;

 p->add(oa, vtmp, ctmp);
 free(vtmp);
 if(ctmp0)
	free(ctmp0);
 return (char *) 0;
}



LOCAL char * parse_hdr_record(char *hdr_rec, proplist *p, BOOL *amp)
{
 char err[512];
 static int legal_attribute_char_set = 0;
 static char legal_attribute_char[256];

 if(!legal_attribute_char_set)	{
	C char *s = " -_QWERTYUIOPASDFGHJKLZXCVBNM1234567890qwertyuiopasdfghjklzxcvbnm";
	for(; *s; s++)
		legal_attribute_char[S(*s)] = 1;
	legal_attribute_char_set = 1;
	}

 D fprintf(stderr, "parsing {%s}\n", hdr_rec);

 char *v = 0;
 char *a = hdr_rec;
 C char *aconst = &hdr_rec[0];
 char *c = 0;
 BOOL eq = FALSE;
 *amp = FALSE;

 if(starts_with(hdr_rec, "HIERARCH ")) {
	char *q;
	for(q=&hdr_rec[sizeof("HIERARCH ")-1]; *q && *q != '='; q++)
		;
	if(*q == '=') {
		a = &hdr_rec[sizeof("HIERARCH ")-1];
		*q = '\0';
		v = q+1;
		eq = TRUE;
		for(q=a; *q; q++) {
			if( *q == ' ' )
				*q = '.';
			else if(!legal_attribute_char[S(*q)])	{
				sprintf(err, "illegal attribute char: %x at pos %d",
						S(*q), (int)(q-hdr_rec));
				return strdup(err);
				}
			}
		}
 	}
 else {
 	char *q;
	a = hdr_rec;
	for(q=hdr_rec; *q  &&  *q != '='  &&  q < &hdr_rec[8]; q++) {
		if(!legal_attribute_char[S(*q)])	{
			sprintf(err, "illegal attribute char: %x at pos %d",
					S(*q), (int)(q-hdr_rec));
			return strdup(err);
			}
		}
	if(q == hdr_rec)	{
		sprintf(err, "missing attribute");
		return strdup(err);	/* No attribute.  */
		}
	eq = TRUE;
	*q = '\0';
	v = q+1;
	}

 D fprintf(stderr, "got =\n");

 if(eq)	{
	chomp_whitespace(a);
	for(; *v == ' '; v++)		/* Skip spaces. */
		;

	if(*v == '\'') {	/* Value is quoted string. */
		v++;
		char *ev, *t;
		for(t=ev=v; *ev; ev++)	{
			if(*ev=='\'' && ev[1]=='\'')	{	/* Embedded quote. */
				ev++;
				*t++ = *ev;
				}
			else if(*ev == '\'')	{	/* Quote that terminates value. */
				c = ev+1;
				break;
				}
			else	*t++ = *ev;
			}
		*t = '\0';
		c = ev+1;
		D fprintf(stderr, "comment=[%s]\n", c);
		if(t > v && t[-1] == '&')		/* Ends with ampersand. */
			*amp = TRUE;

		if(*v)	/* Don't chomp first space. */
			chomp_whitespace(v+1);

		for(; *c; c++) {	/* Search for the beginning of a comment. */
			if(c[0]==' ' && is_comment_start(c[1])) {
				*c++ = '\0';
				*c++ = '\0';
				break;
				}
			}
		}
	else if(*v == '/' || *v == '\0') {	/* No value -- undefine the attribute. */
 		char *aa = patch_FITS_limitations(aconst);
		p->add(aa, (char *)0);			
 		delete [] aa;
		return 0;
		}
	else	{	/* Value is numbers or boolean. */
		c = v;
		for(; *c; c++) {	/* Search for the beginning of a comment. */
			if(c[0]==' ' && is_comment_start(c[1])) {
				*c++ = '\0';
				*c++ = '\0';
				break;
				}
			}
		chomp_whitespace(v);
		}


	if(c && *c)	{			/* If there is a comment, clean it up. */
		while(*c == ' ')
			c++;
		chomp_whitespace(c);
		}
	}
 else	{		/* One of those keywords without '='. */
	char *ea;
	for(ea=a; *ea!=' '; ea++)
		;
	*ea = '\0';

	if(*a == '\0')
		aconst = "COMMENT";
	else if(strcmp(a, "COMMENT") == 0 || strcmp(a, "HISTORY") == 0
			|| strcmp(a, "") == 0) {
		v = ea+1;
		while(*v == ' ')
			v++;
		chomp_whitespace(v);
		}
	else {
		return strdup("no equals sign in header/ not a comment.");
		}
	}

 if(!ascii_chars(v) || (c && !ascii_chars(c)))	{
	sprintf(err, "non-ascii character in value or comment for %s", a);
	return strdup(err);
	}

 D fprintf(stderr, "IAttr={%s}, val={%s}, comment={%s}\n", aconst, v, c);

 char *aa = patch_FITS_limitations(aconst);
 add_append_property(p, aa, v, c);
 delete [] aa;

 return 0;
}
