
/**************************************************************************
 *                                                                        *
 * Copyright (c) 1989 Michael Richmond, Richard Treffers and              *
 *                    The Regents of the University of California         *
 *                                                                        *
 *                    This software may be copied and distributed for     *
 *                    educational, research and not for profit services   *
 *                    provided that this copyright and statement are      *
 *                    included in all such copies.                        *
 *                                                                        *
 **************************************************************************/


/***************************************************************************
        fits

        Package of utility procedures for reading and writing FITS-format
        files.
***************************************************************************/

	/* this version has the fits_put_data_fast() bug fixed.  MWR 11/10/1991 */
	/* also has the fits_put_symbol() bug fixed.  MWR  11/12/1991 */
	/* also has the >36-line header bug fixed.  MWR 1/22/1992 */


#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "fits.h"
#include "pcvista.h"

#define HEADER_LENGTH 2880      /* Standard header size for PC operations */
#define CARD_LEN 80             /* Size of header card image entry */
#define NAME_LEN 80             /* Size of header card image entry */
#define SYMBOL_LEN	8			/* Length of FITS symbol		*/
#define OBJECT_LEN  22			/* Length of FITS symbol value 	*/

static char f_name[NHANDLE][NAME_LEN];	/* the names of the FITS files */
static FILE *ff[NHANDLE];       /* File handles for FITS files */
static short int ncol[NHANDLE];       /* Number of columns in data array */
static short int nrow[NHANDLE];       /* Number of rows in data array */
static long header_length[NHANDLE];
                                /* Actual header size for file */
#define OLD
#ifdef OLD
static char *header[NHANDLE];   /* In-core version of header */
#else
static char header[NHANDLE][10*HEADER_LENGTH];
#endif
static int fast_done[NHANDLE];	/* indicates whether lseek necessary in the */
                              	/*    fits_put_data_fast() routine */
static int handle = 0;          /* Number of file handle last opened */
static int bitpix = 16;         /* Number of bits/pixel */
static put_symbol_flag[NHANDLE];/* flags if header has been modified */

extern char *malloc();

/************************
    Private Procedures
************************/

#ifdef PROTO
static char *get_line(FITS_HANDLE, int);
static void fits_seek(int, int, int);
static int compar(char *, char *);
static void inject(char *, char *, int);
static void enlarge_header(FITS_HANDLE);
#else
static char *get_line();
static void fits_seek();
static int compar();
static void inject();
static void enlarge_header();
#endif

/******************************************************************************
        open_fits

        Open FITS file for processing.  3 i/o modes supported:
                "r"  -- Read only
                "w"  -- Write only
                "r+" -- Read and update
                "x"  -- Read update and and change the number of rows and cols
        For the "r" and "r+" cases, a header is read from the file into
        an internal header buffer; for the "w" case a dummy header is
        written, and then re-written at close time (to update any user
        changes to the header).
******************************************************************************/

FITS_HANDLE 
fits_open(fname, mode, nrows, ncols)
char *fname;    /* Name of FITS file */
char *mode;     /* i/o mode */
unsigned *nrows;/* Number of rows of pixels */
unsigned *ncols;/* Size of a row, in pixels */
{
	char str[128], val[40], *p;
	char lname[128];
	int new_hdr_length;
	static int i, first = 0;

	if (first == 0) {
		first = 1;
		for (i = 0; i < NHANDLE; i++)
			header_length[i] = 2880;
	}
	if (handle == NHANDLE) 
		error(-1, "too many FITS files open"); 

		strncpy(lname, fname, 80); 
		exten(lname, FITS_EXTENSION);
		strcpy(f_name[handle], lname);
        if (strcmp( mode, "r") == 0) {
            if ((ff[handle] = fopen(lname, "rb")) == NULL) {
                sprintf(str, "fits_open -- Can't open file %s", lname);
                error(-1, str);
            }
        }
        else if ((strcmp(mode, "r+") == 0) || (strcmp(mode,"x") == 0)) {
            if ((ff[handle] = fopen(lname, "r+b")) == NULL) {
   	           	sprintf(str, "fits_open -- Can't update file %s", lname);
               	error(-1, str);
            }
        }
        else if (strcmp(mode, "w") == 0) {
            if ((ff[handle] = fopen(lname, "wb")) == NULL) {
                sprintf(str, "fits_open -- Can't write file %s", lname);
                error(-1, str);
            }
        }
        else {
            sprintf(str, "fits_open -- Illegal i/o mode: %s for file %s",
                    mode, lname );
            error(-1, str);
        }

#ifdef OLD
        header[handle] = (char *)malloc(HEADER_LENGTH);
        if (header[handle] == NULL) {
            sprintf(str, "fits_open -- Can't allocate header for %s", lname);
            error(-1, str);
        }
#endif

        if ((strcmp(mode, "r")  == 0) ||
        	(strcmp(mode, "r+") == 0) ||
        	(strcmp(mode, "x")  == 0)) {
            if (fread(header[handle], 1, HEADER_LENGTH, ff[handle])
                                                        != HEADER_LENGTH) {
                sprintf(str, "fits_open -- Bad read of file header for %s",
                        lname );
                error(-1, str);
            }

            fits_get_symbol(handle, "SIMPLE", val);
            if (!compar(val, "T")) {
                sprintf(str, "fits_open -- File %s not of type SIMPLE=T",
                        lname );
                error(-1, str);
            }

            fits_get_symbol(handle, "BITPIX", val);
            if (atoi(val) != 16) {
                sprintf(str, "fits_open -- Can't handle BITPIX = %s; file %s",
                        val, lname);
                error(-1, str);
            }

            fits_get_symbol(handle, "NAXIS", val);
            if (atoi(val) != 2) {
                sprintf(str, "fits_open -- NAXIS = %s not supported, file %s",
                        val, lname);
                error(-1, str);
            }

			if (strcmp(mode, "x") == 0) {
                sprintf(str, "%20d", *ncols);
                fits_put_symbol(handle, "NAXIS1", str);
                sprintf(str, "%20d", *nrows);
                fits_put_symbol(handle, "NAXIS2", str);
			}
            fits_get_symbol(handle, "NAXIS1", val);
            *ncols = atoi(val);
            fits_get_symbol(handle, "NAXIS2", val);
            *nrows = atoi(val);

			/* keep reading in blocks of header until one contains an 
			   "END" card. */
			while (fits_get_symbol(handle, "END", val) == FITS_FAIL) {
				new_hdr_length = header_length[handle] + HEADER_LENGTH;
				if ((p = (char *)malloc(new_hdr_length)) == NULL) {
					sprintf(str, "fits_open -- can't malloc for header");
					error(-1, str);
				}
				strncpy(p, header[handle], header_length[handle]);
				memset(p + header_length[handle], ' ', HEADER_LENGTH);
				free(header[handle]);
				header[handle] = p;
				fseek(ff[handle], (long) header_length[handle], 0);
				if (fread(header[handle] + header_length[handle],
						1, HEADER_LENGTH, ff[handle]) != HEADER_LENGTH) {
					sprintf(str, "fits_open -- bad read in file header of %s",
							lname);
					error(-1, str);
				}
				header_length[handle] = new_hdr_length;
			}
        } 
        else {                                /* mode = "w" */
            memset(header[handle], ' ', HEADER_LENGTH);	/* fill with blanks */		
			inject(header[handle], "END", 3);
            fits_put_symbol(handle, "SIMPLE", "                   T");
            fits_put_symbol(handle, "BITPIX", "                  16");
            fits_put_symbol(handle, "NAXIS",  "                   2");
            sprintf(str, "%20d", *ncols);
            fits_put_symbol(handle, "NAXIS1", str);
            sprintf(str, "%20d", *nrows);
            fits_put_symbol(handle, "NAXIS2", str);
        }
        ncol[handle] = *ncols;
        nrow[handle] = *nrows;
		fast_done[handle] = 0;
		return(handle++);
}

/******************************************************************************
        fits_get_data

        Read signed integer data from FITS file, starting with (row, col),
        and proceeding for npix pixels.
******************************************************************************/

int
fits_get_data(h, row, col, data, npix)
FITS_HANDLE h;
unsigned row;
unsigned col;
int16 *data;
unsigned npix;
{
        fits_seek(h, row, col);
        if (fread(data, (bitpix/8), npix, ff[h]) != npix)
                return(FITS_FAIL);
        return(FITS_PASS);
}

/******************************************************************************
        fits_put_data

        Write signed integer data to FITS file, starting with (row, col),
        and proceeding for npix pixels.
******************************************************************************/

void 
fits_put_data(h, row, col, data, npix)
FITS_HANDLE h;
unsigned row;
unsigned col;
int16 *data;
unsigned npix;
{
        fits_seek(h, row, col);
        if (fwrite(data, (bitpix/8), npix, ff[h]) != npix)
            error(-1, "fits_write -- Write failure.");
}

/******************************************************************************
        fits_put_data_fast

        Write data to FITS file,
        with no intermediate seeking
        and proceeding for nwords
******************************************************************************/

void 
fits_put_data_fast(h, data, nwords)
FITS_HANDLE h;
int16 *data;
int nwords;
{

	if (!fast_done[h]) {
		fits_seek(h, 0, 0);
		fast_done[h] = 1;
	}
	if (fwrite(data, 2, nwords, ff[h]) != nwords)
		error(-1, "fits_fast -- Write failure.");
}

/*****************************************************************************
        fits_get_symbol

        Retrieve a FITS header symbol from the header storage area in core.
        Return it in string form.  If symbol is of type string, enclosed in
        quotes, the quotes will be included as part of the string.  Leading
        and trailing blanks otherwise will be trimmed.
****************************************************************************/

int 
fits_get_symbol(h, symbol_name, contents)
FITS_HANDLE h;
char *symbol_name;
char *contents;
{
	int ncards;
	char *p;
	int i;

        ncards = (int) (header_length[h] / (long) CARD_LEN);
        p = get_line(h, 1);
        i = 1;
        while (!compar(p, "END") && !compar(p, symbol_name) && (i <= ncards)) {
            i++;
            p = get_line(h, i);
        }
        if (compar(p, symbol_name)) {
			int i, j;
			for (i = 10; p[i] == ' '; i++)		/* skip over leading blanks */
				;
			for (j = 0; i < CARD_LEN; j++, i++)			/* copy string */
				contents[j] = p[i];
			contents[j] = 0;
			for (j--; (contents[j] == ' ') && (j > 0); j--)	/* remove trailing blanks */
				contents[j] = 0;
            return(FITS_PASS);
        } 
        else
            return(FITS_FAIL);
}

/*****************************************************************************
        fits_get_headerline

        Retrieve all FITS header line
****************************************************************************/

char *
fits_get_headerline(h,  card_number)
FITS_HANDLE h;
int card_number;
{
	int ncards;

	ncards = (int) (header_length[h] / (long) CARD_LEN);
	if ((card_number > ncards) || (card_number < 1))
		return(NULL);
	return(get_line(h, card_number));
}

/*****************************************************************************
        fits_put_headerline

        Install FITS header line
		in last place and moves the END statement
****************************************************************************/

int 
fits_put_headerline(h,  msg)
FITS_HANDLE h;
char *msg;
{
	char *p, *q;
	int i, card, ncards;

	ncards = (int) (header_length[h] / (long) CARD_LEN);
	put_symbol_flag[h] = 1;
	for (card = 1, p = get_line(h, 1); !compar(p, "END"); card++) {
		if (card == ncards) {
			enlarge_header(h);
			ncards = (int) (header_length[h] / (long) CARD_LEN);
			card = 1;
		}	
		p = get_line(h, card);
	}
	memset(p, ' ', 80);
	for (i = 0; msg[i] && (i < 80) ; i++)
		p[i] = msg[i];
	p = get_line(h, card++);
	inject(p, "END", 3); 
	return(FITS_PASS);
}

/*****************************************************************************
        fits_put_symbol

        Update a FITS header symbol, or create a new one in the storage
        area in core.
****************************************************************************/

int 
fits_put_symbol(h, symbol_name, contents)
FITS_HANDLE h;
char *symbol_name;
char *contents;
{
	int ncards;
	char *p, *q;
	char str[128];
	int i, len;
	
TOP:
        ncards = (int) (header_length[h] / (long) CARD_LEN);
        p = get_line(h, 1);
        i = 1;
        put_symbol_flag[h] = 1;
        while (!compar(p, "END") && !compar(p, symbol_name) && (i <= ncards)) {
            i++;
            p = get_line(h, i);
        }
        if (i >= ncards) {
			enlarge_header(h);
			goto TOP;
 		}
        if (compar(p, symbol_name)) {
				memset(p+9, ' ', 70);		/* fill out with blanks */
                len = strlen(contents);
                if (len > OBJECT_LEN)
                	len = OBJECT_LEN;
                inject(p + 10, contents, len);
        } 
        else {
				memset(p, ' ', 80);		/* fill out with blanks */
                len = strlen(symbol_name);
                if (len > SYMBOL_LEN)
                	len = SYMBOL_LEN;
                inject(p, symbol_name, len);
                p[8] = '=';
                len = strlen(contents);
                if (len > (CARD_LEN - (SYMBOL_LEN + 4)))
                	len = CARD_LEN - (SYMBOL_LEN + 4);
                inject(p + 10, contents, len);
                p = get_line(h, i+1);
                inject(p, "END", 3); 
		}
        return(FITS_PASS);
}

/*****************************************************************************
        fits_cut

		cuts file opened in "x" mode to max length. Fill the space between
		the last pixel's data and the end of that record (of 2880 bytes) with
		zeros.
*****************************************************************************/

void 
fits_cut(h)
FITS_HANDLE h;
{
	int16 i;
	 	long pos, last_data_pos, num_rec;

        last_data_pos = 2*((long)nrow[h]*ncol[h]) + header_length[h];
		
		num_rec = (last_data_pos + HEADER_LENGTH - 1)/HEADER_LENGTH;
        pos = num_rec*HEADER_LENGTH;	/* position to exact multiple of header */
        if (fseek(ff[h], last_data_pos, 0))
                error(-1, "seek error");
		i = 0;
		while (last_data_pos < pos) {
			if (fwrite((char *)&i, 2, 1, ff[h]) != 1) 
				error(-1, "fits_cut -- error padding file with zeros");	
			last_data_pos += 2;
		}
        fflush(ff[h]);
        ftruncate(fileno(ff[h]), pos);
}

/*****************************************************************************
        fits_close

        Close designated FITS file.  Update header.
*****************************************************************************/

void 
fits_close(h)
FITS_HANDLE h;
{
        if (put_symbol_flag[h]) {
        	fseek(ff[h], (long) 0, 0);
        	if (fwrite(header[h], 1, header_length[h], ff[h]) != header_length[h])
        		error(-1, "error writing header");
			fflush(ff[h]);
			fits_cut(h);		/* pad end of file with zeros */
		}
		fast_done[h] = 0;
		free(header[h]);	/* release header buffer area */
        fclose(ff[h]);
}


/***************************
        Private...
***************************/

static char *
get_line(h, card)
FITS_HANDLE h;
int card;
{
        return(&(header[h][(card - 1)*CARD_LEN]));
}

static void 
fits_seek(h, row, col)
FITS_HANDLE h;
int row, col;
{
	long pos;

    pos = 2*((long)row*ncol[h] + col) + header_length[h];
        if (fseek(ff[h], pos, 0))
                error(-1, "seek error");
}


static int 
compar(str1, str2)
char str1[], str2[];
{
	int i, n1, n2;
	int outcome;

        n1 = strlen(str1);
        n2 = strlen(str2);
        i = 0;
        while (isalnum(str1[i]) && (i <= n1)) 
        	i++;
        n1 = i;
        i = 0;
        while (isalnum(str2[i]) && (i <= n2)) 
        	i++;
        n2 = i;
        outcome = (n1 == n2) && (n1 > 0);
        if (outcome) 
        	outcome = (strncmp(str1, str2, n1) == 0);
		return(outcome);
}

static void 
inject(p, string, nchar)		/* Non-zero terminated copy */
char *p, *string;
int nchar;
{
    int i;

	for (i = 0; i < nchar; i++)
		p[i] = string[i];
}


	/* make the header for a FITS file larger by HEADER_LENGTH, copying the
	   header that already exists into the new one and filling the new 
	   area with blanks. */

	/* bug fixed by MWR 1/22/92: we must also copy all the DATA in the file
	   since we're about to overwrite a chunk of it. */

static void
enlarge_header(h)
FITS_HANDLE h;
{
	char str[128], *p, newfile[128];
	int16 buf[NMAX];
	int i, new_hdr_length, nh, row, col, nc, nr;
	long pos;

	pos = ftell(ff[h]);
	new_hdr_length = header_length[h] + HEADER_LENGTH;

	/* create a new FITS file */
	for (i = 0; ((i < strlen(f_name[h])) && (f_name[h][i] != '.')); i++) {
		newfile[i] = f_name[h][i];
	}
	newfile[i] = '\0';
	strcat(newfile, "Q.fts");
	nc = ncol[h];
	nr = nrow[h];
	nh = fits_open(newfile, "w", &nr, &nc);

#ifdef OLD
	if ((p = (char *)malloc(new_hdr_length)) == NULL) {
		sprintf(str, "enlarge_header -- can't malloc for header");
		error(-1, str);
	}
	strncpy(p, header[h], header_length[h]);
	memset(p + header_length[h], ' ', HEADER_LENGTH);
	header[nh] = p;
#else
	strncpy(header[nh], header[h], header_length[h]);
#endif
	header_length[nh] = new_hdr_length;
#ifdef OLD
	free(header[h]);
#endif

	/* copy the old header information into this new file */
	fseek(ff[nh], (long) 0, 0);
	fwrite(header[nh], 1, header_length[nh], ff[nh]); 
	
	/* copy all the data from the old file to the new file */
	for (row = 0; row < nrow[h]; row++) {
		fits_get_data(h, row, 0, buf, ncol[h]);
		fits_put_data_fast(nh, buf, ncol[h]);
	}
	fclose(ff[h]);
	fclose(ff[nh]);
	handle--;

	/* now, close the old file, and unlink it, then link the new file
	   into its place */
	if (unlink(f_name[h]) != 0) {
    	sprintf(str, "enlarge_header -- can't unlink file %s", f_name[h]);
    	error(-1, str);
    }
	if (link(newfile, f_name[h]) != 0) {
    	sprintf(str, "enlarge_header -- can't link file %s and %s", 
					newfile, f_name[h]);
    	error(-1, str);
    }
	if (unlink(newfile) != 0) {
    	sprintf(str, "enlarge_header -- can't unlink file %s", newfile);
    	error(-1, str);
    }

	if ((ff[h] = fopen(f_name[h], "r+")) == NULL) {
    	sprintf(str, "enlarge_header -- Can't open file %s", f_name[h]);
    	error(-1, str);
    }

#ifdef OLD
	header[h] = p;
#else
	strncpy(header[h], header[nh], header_length[nh]);
#endif
	header_length[h] = new_hdr_length;
	fseek(ff[h], pos, 0);
}
