
	/* data I/O routines for the pseudo-FITS headers that are used
	   as request files by the BAIT software. */

#include <stdio.h>
#include "pf.h"

#ifndef COMMENT_CHAR
#define COMMENT_CHAR '/'	/* ignore all on a header line after this char */
#endif

	/* various characteristics of header lines */
#define NICE_LEN        30	/* attempt to make all values, when expressed as */
                          	/*   strings in the header, line up so that their */
                         	/*   last character appears this many places to */
                         	/*   right of the DELIM_CHAR */

	/* used by the pf_putval() routine */
#define NO_END    0
#define INSERT    1		/* insert a new line */
#define MODIFY    2		/* modify an existing line */

static int  num_open = 0;
static int  is_used[PF_NUM_HANDLE];	/* 1 if handle is used, 0 if free */
static int  hdr_num[PF_NUM_HANDLE];	/* header-number-within-file for handle */
static char name[PF_NUM_HANDLE][PF_LEN + 1];
static FILE *fp[PF_NUM_HANDLE];
static char line[PF_LEN + 5];

static skip_ends();
static insert_line();

	/* try to open the 'num'-th header inside the given file; usually, there
	   will only be 1 (and so 'num' should be 1), but sometimes several
	   headers will be concatenated into a single file, in which case this
	   routine only looks at the 'num'-th such header.

	   if status == PF_EXIST, then the file/header must already exist,
	      status == PF_CREATE, the file/header must NOT exist, and is created

	   if successful, it returns a non-zero value, the handle for that header.
	   if not successful, it returns a negative value */

	/* 
	 *  2/7/91 - fixed a bug in 'pf_getcomment()'  MWR
	 *
	 */

	/* 
	 *  9/11/91 - modified pf_open so that, if it cannot open a file for
	 *            reading and writing ("r+"), it then tries to open the
	 *            file for reading only ("r").  If readonly works, then
	 *            an error will occur later if the user tries to change the
	 *            contents of the file.
	 */

	/*
	 *  9/17/91 - fixed a problem in 'pf_valtodouble()' that seems to 
	 *            be due to a bug in the 'sscanf' routine.  MWR
	 */

pf_handle
pf_open(filename, num, status)
char *filename;
int num, status;
{
	static int first_time = 1;
	pf_handle ret_err = PF_ERROR;
	pf_handle handle;
	int n;

	if (first_time == 1) {
		first_time = 0;
		for (n = 0; n < PF_NUM_HANDLE; n++) {
			is_used[n] = 0;
			hdr_num[n] = 1;
			name[n][PF_LEN] = '\0';
		}
	}

	if (num_open >= PF_NUM_HANDLE - 1)
		return(ret_err);
	for (handle = 0; handle < PF_NUM_HANDLE; handle++) {
		if (is_used[handle] == 0)
			break;
	}
	if (handle == PF_NUM_HANDLE)
		return(ret_err);
	if ((num == 1) && (status == PF_CREATE)) {
		if ((fp[handle] = fopen(filename, "w+")) == NULL)
			return(ret_err);
	}
	else {
		if ((fp[handle] = fopen(filename, "r+")) == NULL)
			if ((fp[handle] = fopen(filename, "r")) == NULL)
				return(ret_err);
	}

	/* now check, if 'num' > 1, if there really are that many headers 
	   stuck together inside this file */
	n = 0;
	while (fgets(line, PF_LEN, fp[handle]) != NULL) {
			if (strncmp(line, "END", 3) == 0)
				n++;
	}
	if (status == PF_EXIST) {
		if (n < num) {
			fclose(fp[handle]);
			return(ret_err);
		}
	}
	else if (status == PF_CREATE) {
		if (n >= num) {
			fclose(fp[handle]);
			return(ret_err);
		}
		fprintf(fp[handle], "END\n");
		fflush(fp[handle]);
	}
	else {
		fclose(fp[handle]);
		return(ret_err);
	}

	/* okay, it's a valid request and all is fine. */
	num_open++;
	is_used[handle] = 1;
	hdr_num[handle] = num;
	strncpy(name[handle], filename, PF_LEN);
	return(handle);
}

	/* attempt to close the given header file - return 0 if all OK, or
	   PF_ERROR if there is an error (like an invalid handle number) */

int 
pf_close(handle)
pf_handle handle;
{
	if (is_used[handle] != 1)
		return(PF_ERROR);
	fclose(fp[handle]);
	is_used[handle] = 0;
	strcpy(name[handle], "");
	num_open--;
	return(0);
}


	/* try to find the given keyword in the given header file, and place
	   the entire string which contains the value for that keyword into
	   the string 'value'. 

	   This routine just copies into 'value' all the 
	   characters after the '=' in the header line that contains the keyword.

	   if the given keyword doesn't exist in the header, it returns NULL.
	   otherwise, returns a pointer to 'value'. */
	   

char *
pf_getval(handle, keyword, value) 
pf_handle handle;
char *keyword, *value;
{
	int len;
	
	len = strlen(keyword);

	if (is_used[handle] == 0)
		return((char *) NULL);
	if (skip_ends(handle) == PF_ERROR) {
		fprintf(stderr, "error in skip_ends\n");
		return((char *) NULL);
	}
	while (fgets(line, PF_LEN, fp[handle]) != NULL) {
		if (strncmp(line, "END", 3) == 0) {
			return((char *) NULL);
		}
		if (strncmp(line, keyword, len) == 0) {
			pf_copyvalue(line, value);
			return(value);
		}
	}

	/* execution should never get here, but just in case it does... */
	return((char *) NULL);
}

	/* put a line containing the given (keyword, value) pair into
	   the given file.  If a line with the given keyword already
	   exists, replace the value of that line with the new value.

	   returns (char *)NULL if there is a problem, or a pointer to
	   the new value otherwise. */

char *
pf_putval(handle, keyword, value)
pf_handle handle;
char *keyword, *value;
{
	char new_line[PF_LEN + 1], buf[PF_LEN + 1];
	int i, status, len;
	long f_pos;

	if (is_used[handle] == 0)
		return((char *) NULL);
	if (skip_ends(handle) == PF_ERROR) {
		fprintf(stderr, "error in skip_ends\n");
		return((char *) NULL);
	}

	len = strlen(keyword);
	status = NO_END;
	f_pos = ftell(fp[handle]);
	while (fgets(line, PF_LEN, fp[handle]) != NULL) {
		if (strncmp(line, "END", 3) == 0) {
			status = INSERT;
			break;
		}
		if (strncmp(line, keyword, len) == 0) {
			status = MODIFY;
			break;
		}
		f_pos = ftell(fp[handle]);
	}
	if (status == NO_END)
		return((char *) NULL);

	/* now we insert a line at f_pos within the file, moving all other
	   lines down to make room for it. */
	if (pf_valtype(value) == PF_STRING) {
		for (i = 0; value[i] == ' '; i++)
			;
		sprintf(new_line, "%-8s%c %s", keyword, DELIM_CHAR, value + i);
	}
	else {
		sprintf(new_line, "%-8s%c", keyword, DELIM_CHAR);
		for (i = 0; value[i] == ' '; i++)
			;
		strcpy(buf, value + i);
		len = strlen(buf);
		for (i = 0; new_line[i] != DELIM_CHAR; i++)
			;
		for (i++; i < NICE_LEN - len; i++)
			new_line[i] = ' ';
		new_line[i] = '\0';
		strcat(new_line, buf);
	}
	if (insert_line(handle, f_pos, new_line, status) < 0)
		return((char *) NULL);
	return(value);
}

	/* given a file name, return the number of headers inside the
	   file (found simply by counting the END lines). return the 
	   number of headers found (0 means the file is probably not
	   a pseudo-FITS header file), or PF_ERROR on error.  */

int
pf_headers(filename)
char *filename;
{
	FILE *pfp;
	int n;

	if ((pfp = fopen(filename, "r")) == NULL) {
		return(PF_ERROR);
	}
	n = 0;
	while (fgets(line, PF_LEN, pfp) != NULL) {
			if (strncmp(line, "END", 3) == 0)
				n++;
	}
	fclose(pfp);
	return(n);
}

	/* look for the next COMMENT line in the header - if it exists, copy
	   the whole line into the passed string, and return a pointer to the
	   string.  if no more COMMENT lines, return NULL.

	   this probably fails if unless you do a simple sequence of
	              pf_open(handle_1)
	              pf_open(handle_2)
	              for ... {
	                 pf_getcomment(handle_1)
	                 pf_putcomment(handle_2)
	              }
	              pf_close(handle_1)
	              pf_close(handle_2)
	   but that's all I want to do, so there!. */

char *
pf_getcomment(handle, str)
pf_handle handle;
char *str;
{
	static FILE *last_fp;
	static int last_hdr;

	if (is_used[handle] == 0)
		return((char *) NULL);
	if ((fp[handle] != last_fp) || (hdr_num[handle] != last_hdr)) {
		last_fp = fp[handle];
		last_hdr = hdr_num[handle];
		if (skip_ends(handle) == PF_ERROR) {
			fprintf(stderr, "error in skip_ends\n");
			return((char *) NULL);
		}
	}
	*str = '\0';
	while (fgets(line, PF_LEN, fp[handle]) != NULL) {
		if (strncmp(line, "END", 3) == 0)
			return((char *) NULL);
		if (strncmp(line, "COMMENT", 7) == 0) {
			strcpy(str, line);
			return(str);
		}
	}
	return((char *) NULL);
}

	/* copy the whole line pointed to by 'str' into the given header
	   just before the END statement.
	   returns a pointer to 'str' if OK, or NULL if there was a
	   problem. */

char *
pf_putcomment(handle, str)
pf_handle handle;
char *str;
{
	int status, len;
	long f_pos;

	if (is_used[handle] == 0)
		return((char *) NULL);
	if (skip_ends(handle) == PF_ERROR) {
		fprintf(stderr, "error in skip_ends\n");
		return((char *) NULL);
	}

	status = NO_END;
	f_pos = ftell(fp[handle]);
	while (fgets(line, PF_LEN, fp[handle]) != NULL) {
		if (strncmp(line, "END", 3) == 0) {
			status = INSERT;
			break;
		}
		f_pos = ftell(fp[handle]);
	}
	if (status == NO_END)
		return((char *) NULL);
	/* get rid of any newline character that's already at the end of the line */
	len = strlen(str);
	if (str[len - 1] == '\n')
		str[len - 1] = '\0';
	if (insert_line(handle, f_pos, str, status) < 0)
		return((char *) NULL);

	return(str);
}

	/* return the type of the variable stored in the given string, 
	   or PF_ERROR if there is a problem */

	/* wierd problem: if 'val'="I", then the pf_valtodouble() call changes
	   the value of 'val'.  Why?  Must be some bug in the 'sscanf' routine,
	   I guess.  So make a copy of 'val' to pass to each pf_valto function.
	             - MWR  9/17/91 */

pf_valtype(val)
char *val;
{
	int bool, inum;
	double dnum;
	char str[PF_LEN + 1], *strchr();
	char copy[PF_LEN + 1];

	strcpy(copy, val);
	if (pf_valtobool(copy, &bool) != PF_ERROR)
		return(PF_BOOLEAN);
	strcpy(copy, val);
	if ((pf_valtoint(copy, &inum) != PF_ERROR) && (strchr(val, '.') == NULL))
		return(PF_INTEGER);
	strcpy(copy, val);
	if (pf_valtodouble(copy, &dnum) != PF_ERROR)
		return(PF_DOUBLE);
	strcpy(copy, val);
	if (pf_valtostr(copy, str) != PF_ERROR)
		return(PF_STRING);
	
	return(PF_ERROR);
}

	/* convert the string in 'value', which has single-quote marks around 
	   it, to a "normal" string

	   returns 0 if successful, or PF_ERROR if there was some error. */

pf_valtostr(value, str)
char *value, *str;
{
	char *p;

	p = value;
	while ((*p != QUOTE_CHAR) && (*p != '\0'))
		p++;
	if (*p == '\0')
		return(PF_ERROR);
	p++;
	while ((*p != QUOTE_CHAR) && (*p != '\0'))
		*str++ = *p++;
	*str = '\0';
	if (*p != QUOTE_CHAR)
		return(PF_ERROR);
	else
		return(0);
}
	
	/* attempt to convert the passed string as a true/false value,
	   where a single 'T' means true and 'F' means false.  Place
	   either TRUE or FALSE into the 'bool' argument.

	   return 0 if all OK, or PF_ERROR if there is a problem (such as the
	   value not either 'T' or 'F'). */

pf_valtobool(value, bool)
char *value;
int *bool;
{
	while (*value == ' ')
		value++;
	if (*value == 'T') {
		*bool = TRUE;
		return(0);
	}
	else if (*value == 'F') {
		*bool = FALSE;
		return(0);
	}
	else
		return(PF_ERROR);
}

	/* attempt to convert the passed string as an integer value; if
	   successful, put the integer into the second argument and return
	   0.  otherwise, return PF_ERROR. */

pf_valtoint(value, intp)
char *value;
int *intp;
{
	if (sscanf(value, "%d", intp) != 1)
		return(PF_ERROR);
	else
		return(0);
}

	/* attempt to convert the passed string as a floating point (double)
	   value. If successful, put the number into the second argument and
	   return 0 - otherwise, return PF_ERROR. */

pf_valtodouble(value, doublep)
char *value;
double *doublep;
{
	if (sscanf(value, "%lf", doublep) != 1)
		return(PF_ERROR);
	else
		return(0);
}

	/* put the given boolean into a nicely-formatted string for inclusion
	   into a header line */

void
pf_booltoval(bool, str)
int bool;
char *str;
{
	int i;

	for (i = 0; i < NICE_LEN - 1; i++)
		*str++ = ' ';
	if (bool == TRUE)
		*str++ = 'T';
	else
		*str++ = 'F';
	*str = '\0';
}

	/* put the given string into a FITS-format string - delimited by
	   QUOTE_CHARS - in the second argument. try to make the length
	   of the value string by NICE_LEN */

void
pf_strtoval(str, value)
char *str, *value;
{
	int i, len;

	sprintf(value, " %c%s%c", QUOTE_CHAR, str, QUOTE_CHAR);
}

	/* put the given integer into a nicely-formatted string, for 
	   inclusion in a header line. */

void
pf_inttoval(inum, value)
int inum;
char *value;
{
	int i, len; 

	sprintf(line, "%d", inum);
	len = strlen(line);
	for (i = 0; i < NICE_LEN - len; i++) 
		*value++ = ' ';
	for (i = 0; i < len; i++)
		*value++ = line[i];
	*value = '\0';
}

	/* put the given floating-point value (double) into a nicely-formatted
	   string. */

void
pf_doubletoval(dnum, value)
double dnum;
char *value;
{
	int i, len; 

	sprintf(line, "%lf", dnum);
	len = strlen(line);
	for (i = 0; i < NICE_LEN - len; i++) 
		*value++ = ' ';
	for (i = 0; i < len; i++)
		*value++ = line[i];
	*value = '\0';
}


	/* copy the contents of line to the right of the '=' sign, as far
	   as a '\0', or a '\n', or comment character which isn't quoted, into the 
	   'value' string. If there is no '=' sign, return an empty string
	   (i.e. a string whose first character is '\0').

	   returns 0 in all cases.  */

#define INSIDE_QUOTE    0		/* current character in line is inside a */
                        		/*   quoted string */
#define OUTSIDE_QUOTE   1		/* or is not */

void
pf_copyvalue(line, value)
char *line, *value;
{
	char *p;
	int i, start, end, state;

	p = value;

	for (i = 0; i < PF_LEN; i++) {
		if ((line[i] == DELIM_CHAR) || (line[i] == '\0') || (line[i] == '\n'))
			break;
	}	
	if (line[i] != DELIM_CHAR) {
		*value = '\0';
		return;
	}
	start = i + 1;
	state = OUTSIDE_QUOTE;
	end = PF_LEN - 1;
	for (i = start; i < PF_LEN; i++) {
		if ((line[i] == '\0') || (line[i] == '\n')) {
			end = i - 1;
			break;
		}
		if (line[i] == QUOTE_CHAR)
			state = (state == INSIDE_QUOTE ? OUTSIDE_QUOTE : INSIDE_QUOTE);	
		if ((line[i] == COMMENT_CHAR) && (state == OUTSIDE_QUOTE)) {
			end = i - 1;
			break;
		}
	}
	for (i = start; i <= end; i++)
		*p++ = line[i];
	*p = '\0';
}
			
	/* position the file pointer for the given handle to just at the beginning
	   of the appropriate header. If something is wrong, and there aren't as
	   many "END" lines as there should be, return PF_ERROR. Otherwise, return 0. */

static
skip_ends(handle)
pf_handle handle;
{
	int n;

	n = hdr_num[handle] - 1;
	rewind(fp[handle]);
	while ((n > 0) && (fgets(line, PF_LEN, fp[handle]) != NULL)) {
		if (strncmp(line, "END", 3) == 0)
			n--;
	}
	if (n != 0)
		return(PF_ERROR);
	else
		return(0);
}

	/* insert a line containing the information contained in the 
	   (keyword, value) pair at the given position in the file.

	   return PF_ERROR on some error, 0 otherwise. */

static 
insert_line(handle, f_pos, new_line, status)
pf_handle handle;
long f_pos;
char *new_line;
int status;
{
	FILE *temp_fp;
	char tempname[PF_LEN], *mktemp();

	strcpy(tempname, "pf_XXXXXX");
	if (mktemp(tempname) == NULL)
		return(PF_ERROR);
	if (tempname[0] == '\0')
		return(PF_ERROR);
	if ((temp_fp = fopen(tempname, "w")) == NULL)
		return(PF_ERROR);

	rewind(fp[handle]);
	while (ftell(fp[handle]) < f_pos) {
		if (fgets(line, PF_LEN + 1, fp[handle]) == NULL)
			return(PF_ERROR);
		fprintf(temp_fp, "%s", line);
	}
	fprintf(temp_fp, "%s\n", new_line);
	if (status == MODIFY) {
		if (fgets(line, PF_LEN + 1, fp[handle]) == NULL)
			return(PF_ERROR);
	}
	while (fgets(line, PF_LEN + 1, fp[handle]) != NULL)
		fprintf(temp_fp, "%s", line);
	fclose(fp[handle]);
	fclose(temp_fp);

	/* we have to an ugly copying operation here to get the contents of 
	   the temporary file back into the original file, since we can't
	   simply link one to the other (could be on different file systems). */
	if (unlink(name[handle]) < 0) {
		fprintf(stderr, "error unlinking %s in insert_line\n", name[handle]);
		return(PF_ERROR);
	}
	if ((temp_fp = fopen(tempname, "r")) == NULL) {
		fprintf(stderr, "error re-opening temp file %s in insert_line \n",
				tempname);
		return(PF_ERROR);
	}
	if ((fp[handle] = fopen(name[handle], "w")) == NULL) {
		fprintf(stderr, "can't re-create file %s in insert_line\n", 
				name[handle]);
		return(PF_ERROR);
	}
	rewind(temp_fp);
	while (fgets(line, PF_LEN + 1, temp_fp) != NULL)
		fprintf(fp[handle], "%s", line);
	fclose(fp[handle]);
	fclose(temp_fp);

	if (unlink(tempname) < 0) {
		fprintf(stderr, "error unlinking %s in insert_line\n", tempname);
		return(PF_ERROR);
	}
	if ((fp[handle] = fopen(name[handle], "r+")) == NULL) {
		return(PF_ERROR);
	}

	return(0);
}
		
