/**
	FITS_WRITE.C
	Write NASA FITS format 2-dimensional data files.
   
Copyright (C) 2002-2009 Greg Kochanski, gpk@kochanski.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 <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

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

#define D	if(0)


C unsigned RECLENGTH =	80;
C int FITS_blocksize = 2880;

C int val_start = 11;	/* Counting as the FITS standard does, from one.
				The value starts in the 11th column. */

/* The returned array has space for a FITS value [columns 11-80] and
a terminating null. The null would be in FITS column 81, if there
were such a place. */

LOCAL char *blank()
{
 char *o = (char *)malloc(82-val_start);
 assert(o);
 memset(o, ' ', 81-val_start);
 o[81-val_start] = '\0';
 return o;
}





LOCAL char *prep_logical(C char *s)
{
 if(strcmp(s, "T")==0 || strcmp(s, "F")==0 ||
		strcmp(s, "t")==0 || strcmp(s, "f")==0)	{
	char *o = blank();
	o[30-val_start] = toupper(s[0]);
	o[31-val_start] = '\0';
	return o;
	}

 return 0;
}


LOCAL char *prep_string(C char *s)
{
 char *o = blank();
 int col = 11;
 char *oo = &o[col-val_start];
 *oo++ = '\''; col++;
 while(*s && col<80)	{
	if(*s == '\'' && col>=79)
		break;		/* No room to double up the quote. */
	else if(*s == '\'')	{
		*oo++ = '\'';
		col++;
		}

	if(*s<0 || *s>127) {
		s++;
		*oo++ = 127;
		}
	else {
		*oo++ = *s++;
		}

	col++;
	}

 while(col < 20)	{
	*oo++ = ' ';
	col++;
	}
 *oo++ = '\'';
 col++;
 assert(col <= 81);
 *oo++ = '\0';		/* We left room for a null in col 81 if necessary. */
 return o;
}


LOCAL char *prep_comment(C char *a)
{
 char *p = strdup(a ? a : "");
 assert(p);

 /* Force into FITS character set. */
 char *q;
 for(q=p; *q; q++)	{
	if(*q>=127 || *q<0x20)
		*q = '?';
	}

 return p;
}



LOCAL char *prep_irregular(C char *ss)
{
 while(*ss && (*ss==' ' || *ss=='\t'))
	ss++;
 char *s0 = prep_comment(ss);
 char *o = blank();
 int col = 11;
 char *oo = &o[col-val_start];

 char *s = s0;
 while(*s && col<80)	{
	*oo++ = *s++;
	col++;
	}
 assert(col <= 81);
 *oo++ = '\0';		/* We left room for a null in col 81 if necessary. */
 free(s0);
 return o;
}




LOCAL char *prep_real(C char *s)
{
 errno = 0;
 char *endptr;
 double x = strtod(s, &endptr);
 if(errno!=0 || *endptr!='\0')
	return 0;

 char *o = blank();
 sprintf(o, "%20g", x);	/* Columns 11-30, inclusive. */
 return o;
}



LOCAL char *prep_integer(C char *s)
{
 char *endptr;
 errno = 0;
 long x = strtol(s, &endptr, 0);
 if(errno != 0 || *endptr!='\0')
	return errno==ERANGE ? prep_real(s) : 0;
 char *o = blank();
 sprintf(o, "%20ld", x);	/* Columns 11-30, inclusive. */
 return o;
}


LOCAL void write_entry(FILE *out, C char *a, C char *v, C char *c)
{
 assert(a);
 assert(v);
 assert(out);
 unsigned i;
 for(i=1; i<=8 && *a; i++, a++)
	putc(*a, out);
 for(; i<=8; i++)
	putc(' ', out);

 D fflush(out);

 putc('=', out); i++;	/* column 9 */
 putc(' ', out); i++;	/* column 10 */

 for(; i<=RECLENGTH && *v; i++, v++)
	putc(*v, out);

 D fflush(out);

 if(c && *c && i+2<=RECLENGTH)	{
	putc(' ', out); i++;
	putc('/', out); i++;
	if(c && strlen(c)+i<RECLENGTH)	{
		/* Put a space in, if there is room, just to make comment
		look better. */
		putc(' ', out);
		i++;
		}
	for(; i<=RECLENGTH && *c; i++, c++)
		putc(*c, out);
	}

 D fflush(out);

 for(; i<=RECLENGTH; i++)
	putc(' ', out);
 D fflush(out);
}



LOCAL void write_irr_entry(FILE *out, C char *a, C char *v)
{
 assert(a);
 assert(v);
 assert(out);
 unsigned i;
 for(i=1; i<=8 && *a; i++, a++)
	putc(*a, out);
 for(; i<=8; i++)
	putc(' ', out);

 putc(' ', out); i++;
 putc(' ', out); i++;

 D fflush(out);

 for(; i<=RECLENGTH && *v; i++, v++)
	putc(*v, out);

 D fflush(out);

 for(; i<=RECLENGTH; i++)
	putc(' ', out);
 D fflush(out);
}


LOCAL char *prep_attribute(C char *a)
{
 if(*a == '%')	/* This is a symbol for case-invariance. */
	a++;
 char *p = strdup(a ? a : "");
 assert(p);

 if(*p && p[strlen(p)-1]=='*')	/* This is a symbol for possible truncation. */
	p[strlen(p)-1] = '\0';

 if(strcmp(p, "        ") == 0)		/* A Wart on the FITS standard. */
	return p;

 /* Force into FITS attribute character set. */
 char *q;
 for(q=p; *q; q++)	{
	if(*q>='a' && *q<='z')
		*q -= 'a' - 'A';	/* FITS requires upper case. */
	else if(*q>='A' && *q<='Z')
		;
	else if(*q>='0' && *q<='9')
		;
	else if(*q=='_' || *q=='-')
		;
	else	*q = '_';	/* Any illegal characters. */
	}

 return p;
}





LOCAL void print_irregular_property(FILE *out, C property *p)
{
	D fprintf(stderr, "Z1\n");
	char *a = prep_attribute(p->attr);
	D fprintf(stderr, "Z2\n");
	char *vv = strdup(p->val ? p->val : "");
	D fprintf(stderr, "Z3\n");
#if 0
	char **s0 = (char **)malloc(sizeof(char *)*2);
	s0[0] = vv;
	s0[1] = 0;
#else
	char **s0 = csplit(vv, "\n");
#endif
	D fprintf(stderr, "Z4\n");
	char **s = s0;
	assert(s);
	for(; *s; s++)	{
		char *v = prep_irregular(*s);
		write_irr_entry(out, a, v);
		free(v);
		}
	D fprintf(stderr, "Z5\n");
	if(p->comment && *p->comment)	{
		char *v = prep_irregular(p->comment);
		write_irr_entry(out, a, v);
		free(v);
		}
	assert(s0);
	free(s0);
	assert(vv);
	free(vv);
	assert(a);
	free(a);
}


LOCAL void print_regular_property(FILE *out, C property *p, C proplist &prop)
{
	char *tmp, *v = 0;
	if((tmp=prep_logical(p->val)))
		v = tmp;
	else if((tmp=prep_integer(p->val)))
		v = tmp;
	else if((tmp=prep_real(p->val)))
		v = tmp;
	else
		v = prep_string(p->val);

	char *a = prep_attribute(p->attr);
	C property *pm = prop.get(a);
	if(pm && pm != p)	{
		/* Warn when attributes that were different,
		   before mapping into FITS standard, turn
		   out to overlap. */
		fprintf(stderr, "WARN: attribute %s=%s is hidden by"
				" %s=%s, due to mapping into FITS "
				"standard.\n",
			p->attr, p->val?p->val:"",
			pm->attr, pm->val?pm->val:"");
		if(v)
			free(v);
		if(a)
			free(a);
		return;
		}

	char *c = prep_comment(p->comment);

	write_entry(out, a, v, c);

	if(v)
		free(v);
	if(a)
		free(a);
	if(c)
		free(c);
}


LOCAL void print_properties(FILE *out, C proplist& prop)
{
 proplist q(prop);
 print_regular_property(out, q.get("SIMPLE"), prop);
 q.add("SIMPLE", (C char *)0);
 print_regular_property(out, q.get("BITPIX"), prop);
 q.add("BITPIX", (C char *)0);
 C property *na = q.get("NAXIS");
 print_regular_property(out, na, prop);
 q.add("NAXIS", (C char *)0);
 int i;
 for(i=1;i <= na->getd();i++)	{
	char buf[128];
	sprintf(buf, "NAXIS%d", i);
	C property *tmp = q.get(buf);
	if(!tmp)
		gpk_err("Missing axis:", buf);
	print_regular_property(out, tmp, prop);
	q.add(buf, (C char *)0);
	}

 C property *p = q.first;
 for(; p; p=p->next)	{
	D fprintf(stderr, "A=%s; V=%s; C=%s\n", p->attr?p->attr:"(nil)",
			p->val?p->val:"(nil)", p->comment?p->comment:"(nil)");
	if(!*p->attr || !p->val)
		continue;
		/* Drop duplicates and 'covered' attributes. */
	if(q.get(p->attr) != p)
		continue;

	D fprintf(stderr, "X\n");
	if(proplist::matches(p->attr, "%COMMENT")
			|| proplist::matches(p->attr, "%HISTORY")
			|| proplist::matches(p->attr, "        "))	{
		print_irregular_property(out, p);
		}
	else	{
		print_regular_property(out, p, prop);
		}
	}
}




LOCAL void fill_with_blanks(FILE *out)
{
 long pos = ftell(out);
 C long nxt = (long)((pos+FITS_blocksize-1)/FITS_blocksize) * FITS_blocksize;
 while(pos < nxt)	{
	putc(' ', out);
	pos++;
	}
 assert(ftell(out) == nxt);
}




BOOL FITS_writefile(C struct image *p, FILE *out, C proplist& control)
{
 assert(p);
 assert(out);
 assert(p->prop);
 proplist q(*p->prop);
 normalize_proplist(&q, p);
 q += control;

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

 double bscale, bzero;
 scale_image_for_bitpix(p, bitpix, &bscale, &bzero, 0, 0, &q, bitpix>0);

 q.add("NAXIS", 2);
 q.add("BITPIX", bitpix);
 q.add("SIMPLE", "T");
 q.add("_FILETYPE", (C char *)0);
 gpkio_date_fix(&q);

 print_properties(out, q);
 D fprintf(stderr, "INFO: properties printed\n");
 fprintf(out, "%-80s", "END");
 D fprintf(stderr, "INFO: END printed\n");
 fill_with_blanks(out);
 D fprintf(stderr, "INFO: blanks filled\n");
 if(ferror(out))
	return 0;

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

