/***********************************************************************
 *
 * TITLE:	fileio.c
 *
 * AUTHOR:	Cassie Mulnix
 *
 * DESCRIPTION:	This module is part of the OPPS editor: xopps.  It contains
 *		generic file input/output routines.
 *
 *                              CHANGE HISTORY
 *
 * $Log: fileio.c,v $
 * Revision 1.22  1995/01/18  02:40:27  kevin
 * removed i386 ifdef
 *
 * Revision 1.21  1994/05/04  23:34:36  clm
 * ported to ANSI C
 *
 * Revision 1.20  1993/05/27  16:48:57  clm
 * output process completion messages
 *
 * Revision 1.19  1993/05/18  20:58:30  clm
 * added new message.c function
 *
 * Revision 1.18  1993/03/04  17:48:33  clm
 * added init_chart if chart object is missing
 * added check_lines before adding chart for error correcting
 *
 * Revision 1.17  1993/02/20  00:05:33  clm
 * fixed save_file to shift objects into OBJ_NUL spaces before saving
 * so that precedence lines are reloaded correctly
 * moved renum_tog to load_file to changed after xlnary is loaded
 *
 * Revision 1.16  1992/12/04  17:57:17  clm
 * changed MAX_PLN to npln
 *
 * Revision 1.15  1992/11/24  22:38:44  clm
 * added $$OPPS and $$EOF to file format
 *
 * Revision 1.14  1992/11/09  16:28:43  clm
 * added plines to save and load file functions
 *
 * Revision 1.13  1992/10/14  14:40:08  clm
 * check objects for OBJ_NUL before saving them
 *
 * Revision 1.12  1992/10/12  21:15:11  clm
 * added ability to have # comments in file
 *
 * Revision 1.11  1992/08/26  19:48:03  clm
 * changed message call to warning
 *
 * Revision 1.10  1992/08/26  15:04:19  clm
 * added load and save_internals
 *
 * Revision 1.9  1992/08/25  15:41:41  clm
 * incorporated xlnary and ylnary into load and save functions
 *
 * Revision 1.8  1992/08/03  21:32:07  clm
 * added time options to record scan and record print
 *
 * Revision 1.7  1992/07/13  20:50:50  clm
 * added horizontal lines to routines
 *
 * Revision 1.5  1992/05/20  19:50:57  clm
 * cleaned up file and help format
 *
 * Revision 1.4  1992/05/11  22:38:33  kevin
 * renamed project include file to xopps.h
 *
 * Revision 1.1  1990/12/05  15:38:58  ana
 * Initial revision
 *
 ***********************************************************************
 *
 * WARNINGS:
 *
 * EXTERNAL CALLABLE COMPONENTS (PUBLIC):
 *
 * GLOBALS:
 *
 * WAIVERS:
 *
 * NOTES:
 *
 * MANPAGE:
 *
 ***********************************************************************/

#ifndef lint  
static char rcsid[] =
    "$Id: fileio.c,v 1.22 1995/01/18 02:40:27 kevin OEL $";
#endif

#include <stdarg.h>
#include <stdio.h>
#include <ctype.h>
#include <setjmp.h>
#include "xopps.h"

#define MAX_BUF 512
#define BATCH_SIZE 32
#define OPPS_KEYWORD "$$OPPS\n"
#define EOF_KEYWORD "$$EOF\n"

static jmp_buf locenv;			/* used for errors (longjmp) */
static int (*load_obj[26])(char *);		/* array of functions */
static void delete_header(void);	/* deletes headptr array */
static void add_header_line(char *);	/* adds a line to headptr array */
static void print_header(FILE *);	/* prints headptr array to file */
static int unknown_record(char *);	/* default load function */
static char *(*headptr)[] = NULL;
static int nlines = 0;
static int nslots = 0;
/***********************************************************************
 *
 * FUNCTION:	init_fileio()
 *
 * INPUTS:	
 *
 * OUTPUTS:
 *
 * RETURNS:
 *
 * EXTERNALLY READ:
 *
 * EXTERNALLY MODIFIED:
 *
 * DESCRIPTION:	must be called at the beginning of each program in order to
 * 		use these functions
 */

void 
init_fileio(void)
{
    int i;

    for (i = 0; i < 26; i++) {
	load_obj[i] = unknown_record;
    }
}

/***********************************************************************
 *
 * FUNCTION:	register_io_obj()
 *
 * INPUTS:	key	(int)
 *		load_fun	(int *)
 *
 * OUTPUTS:
 *
 * RETURNS:	O - if successful, -1 - if error occurred (an invalid key)
 *
 * EXTERNALLY READ:
 *
 * EXTERNALLY MODIFIED:
 *
 * DESCRIPTION:	registers the load function for each object with the load_file
 *		function.  This was done to remove any object dependent code
 *		from this module.
 */

int 
register_io_obj(int key, int (*load_fun)(char *))
{
    if (isupper(key)) {
	load_obj[key - 'A'] = load_fun;
	return 0;
    } else {
	return -1;	/* invalid key */
    }
}

/***********************************************************************
 *
 * FUNCTION:	load_file()
 *
 * INPUTS:	name	(char *)
 *
 * OUTPUTS:
 *
 * RETURNS:	0 if successful, -1 if error occurred
 *
 * EXTERNALLY READ:
 *
 * EXTERNALLY MODIFIED:
 *
 * DESCRIPTION:	reads records that are formatted one line per screen
 *		object.  If a bad input line is read, a warning is 
 *		issued, but the code will continue.
 */

int 
load_file(char *name)
{
    FILE *fp;			/* input file pointer */
    char buf[MAX_BUF];		/* input text buffer */
    int line = 1;		/* text line number */
    int len;			/* text line length */
    int i;			/* temp loop index */

    if ((fp = fopen(name, "r")) == NULL) {
	(void)sprintf(buf, "ERROR: Cannot open input file: %s", name);
	Warning(buf);
	return -1;
    }
    (void)sprintf(buf, "Reading: %s...", name);
    Message(buf);
    clear_all_obj();
    clear_all_lines();
    for (i = 0; i < npln; i++) {
        plnary[i].obj1 = 0;
        plnary[i].obj2 = 0;
    }
    npln = 0;
    delete_header();
    while (fgets(buf, MAX_BUF, fp) != NULL) {
	line++;
	if (!strcmp(buf, OPPS_KEYWORD)) break;
	add_header_line(buf);
    }
    if (strcmp(buf, OPPS_KEYWORD)) {
	Warning("Illegal File Format - Missing $$OPPS");
	(void)fclose(fp);
	return -1;
    }
    while (fgets(buf, MAX_BUF, fp) != NULL) {
	if (!strcmp(buf, EOF_KEYWORD)) break;
	if (setjmp(locenv)) {
	    (void)sprintf(buf, "BAD LINE #%d: ", line);
            Warning(buf);
        } else {
	    len = strlen(buf);
	    if (buf[0] == '#') {
	 	add_header_line(buf);
	    } else {
		if (buf[0] == 'C') {
		    check_lines();
    		}
	        if (buf[len - 1] != '\n') longjmp(locenv, 1);	/* overflow */
	        if (len == 1) continue;				/* blank line */
	        if (!isupper(buf[0])) longjmp(locenv, 1);	/* bad char */
	        if ((*load_obj[buf[0] - 'A'])(buf)) longjmp(locenv, 1);
	    }
	}
	++line;
    }

    if (objary[0].type == OBJ_NUL) {
	Warning("Missing chart object - default being used");
	init_chart();
    }
    
    /*
     * if renum toggle has changed have to set after
     * load is complete so that xlnary and ylnary are not null
     */
    set_renum_tog();
    if (strcmp(buf, EOF_KEYWORD)) {
	Warning("Warning - Missing EOF");
    }
    fclose(fp);
    Message("File has been read");
    return 0;
}

/***********************************************************************
 *
 * FUNCTION:	record_scan()
 *
 * INPUTS:
 *
 * OUTPUTS:
 * 	none
 *
 * RETURNS:
 * 	none
 *
 * EXTERNALLY READ:
 * 	none
 *
 * EXTERNALLY MODIFIED:
 * 	none
 *
 * DESCRIPTION:	reads an input record according to the specified format
 */

int
record_scan(char *buf, char *format, ...)
{
    va_list ap;
    int count;			/* returned count of objects */
    char *sp;			/* output string pointer */
    int *np;			/* output integer pointer */
    int tmpsign, tmpval;	/* temporary sign and value */
    utc_val *tval;		/* utc time value */
    char tbuf[80];
    int i;

    va_start(ap, format);
    count = 0;
    while ((count >= 0) && (*format)) {
	switch (*(format++)) {
	case 'd':		/* decimal integer */
	    np = va_arg(ap, int *);
	    while (isspace(*buf)) ++buf;
	    if (*buf == '+') {
		tmpsign = 1;
		++buf;
	    } else if (*buf == '-') {
		tmpsign = -1;
		++buf;
	    } else if (isdigit(*buf)) {
		tmpsign = 1;
	    } else {
		count = -1;
		goto error_out;
	    }
	    tmpval = 0;
	    while (!isspace(*buf)) {
		if (isdigit(*buf)) {
		    tmpval = 10 * tmpval + (*buf - '0');
		    ++buf;
		} else {
		    count = -1;
		    goto error_out;
		}
	    }
	    *np = tmpsign * tmpval;
	    ++count;
	    break;
	case 'u':		/* possibly unknown decimal integer */
	    np = va_arg(ap, int *);
	    while (isspace(*buf)) ++buf;
	    if (*buf == '?') {
		++buf;
		if (isspace(*buf)) {
		    ++count;
		} else {
		    count = -1;
		    goto error_out;
		}
	    } else {
		if (*buf == '+') {
		    tmpsign = 1;
		    ++buf;
		} else if (*buf == '-') {
		    tmpsign = -1;
		    ++buf;
		} else if (isdigit(*buf)) {
		    tmpsign = 1;
		} else {
		    count = -1;
		    goto error_out;
		}
		tmpval = 0;
		while (!isspace(*buf)) {
		    if (isdigit(*buf)) {
			tmpval = 10 * tmpval + (*buf - '0');
			++buf;
		    } else {
			count = -1;
			goto error_out;
		    }
		}
		*np = tmpsign * tmpval;
		++count;
	    }
	    break;
	case 'q':		/* quoted string */
	    sp = va_arg(ap, char *);
	    while (isspace(*buf)) ++buf;
	    if ((*buf) == '"') {
		++buf;
		while (*buf != '"') {
		    if (*buf == '\\') ++buf;	/* skip escape character */
		    if (*buf == '\0') {		/* reached end of input */
			count = -1;
			goto error_out;
		    }
		    *(sp++) = *(buf++);		/* copy normale character */
		}
		++buf;
		if (!isspace(*buf)) {		/* check for space after " */
		    count = -1;
		    goto error_out;
		}
		*sp = '\0';
		++count;
	    } else {
		count = -1;
		goto error_out;
	    }
	    break;
	case 's':		/* unquoted single word string */
	    sp = va_arg(ap, char *);
	    while (isspace(*buf)) ++buf;
	    while (!isspace(*buf)) {
		*(sp++) = *(buf++);
	    }
	    *sp = '\0';
	    ++count;
	    break;
	case 't':		/* standard date-time value */
	    tval = va_arg(ap, utc_val *);
            while (isspace(*buf)) ++buf;
            i = 0;
            while (!isspace(*buf)) {
                tbuf[i] = *(buf++);
                i++;
            }
            tbuf[i] = '\0';
            if ( ( (*tval) = setx_utc(tbuf)) != UTC_INVALID) {
                ++count;
            } else {
                count = -1;
                goto error_out;
            }
	    break;
	}
    }
    error_out:
    va_end(ap);
    return count;
}

/***********************************************************************
 *
 * FUNCTION:	save_file()
 *
 * INPUTS:	name	(char *)
 *
 * OUTPUTS:
 *
 * RETURNS:
 *
 * EXTERNALLY READ:
 *
 * EXTERNALLY MODIFIED:
 *
 * DESCRIPTION:	saves a file image of the internal database
 */

void 
save_file(char *name)
{
    FILE *fp;				/* output file pointer */
    int i;				/* general index */
    char text[512];			/* string buffer */
    int src = 0;
    int dest = 0;
    int j;

    if ((fp = fopen(name, "w")) == NULL) {
	(void)sprintf(text, "ERROR: Cannot open output file: %s", name);
	Warning(text);
	return;
    } else {
	(void)sprintf(text, "Saving file: %s...", name);
	Message(text);
    }

    print_header(fp);
    (void)fputs("$$OPPS\n", fp);
    save_internals(fp);

    for (i = 0; i < nxln; i++) {
	(*(xlnary[i].opsptr->sav))(i, fp, LINE_X);
    }

    for (i = 0; i < nyln; i++) {
	(*(ylnary[i].opsptr->sav))(i, fp, LINE_Y);
    }

    /*
     * save objects
     * but as objects are being saved, shift all records
     * up in file if there is a OBJ_NUL record between
     * 0 and nobj
     * also shift precedence lines attached to objects
     * being switched
     */
    for (i = 0; i < nobj; i++) {
	if (src >= nobj) {
	    break;
	} else if (objary[i].type != OBJ_NUL) {
	/*
	 * if NUL object, begin shifting
	 */
	    if (src != dest) {
		/*
		 * dest points to NUL object
		 * src is first object not equal to NUL
	 	 * copy src object to dest
	 	 */
		objary[dest].rlist = newRectList(0);
		(*(objary[src].opsptr->cpy))(&objary[dest], &objary[src]);
		/* 
		 * shift precedence lines if any are attached to 
		 * src object
		 */
		for (j = 0; j < npln; j++) {
		    if (plnary[j].obj1 ==src) {
		        plnary[j].obj1 = dest;
		    }
		    if (plnary[j].obj2 ==src) {
			plnary[j].obj2 = dest;
		    }
		}
		objary[src].precptr = NULL; 
		(*(objary[src].opsptr->rem))(src, OBJECT);
		(*(objary[dest].opsptr->sav))(dest, fp, OBJECT);
		src++;
		dest++;
	    } else {	
		/* 
		 * no shift if src points to dest object
		 */
	        (*(objary[i].opsptr->sav))(i, fp, OBJECT);
	        src++;
	        dest++;
	    }
	} else {
	    /*
	     * increment src to next non NUL object
	     */
	    src++;
	}
    }

    for (i = 0; i < npln; i++) {
	if ((plnary[i].obj1 != 0) && (plnary[i].obj2 != 0)) {
	    sav_pline(i, fp, OBJECT);
	}
    }

    for (i = 0; i < Hnobj; i++) {
	(*(Hobjary[i].opsptr->sav))(i, fp, OBJECT);
    }

    (void)fputs("$$EOF\n", fp);
    Message("File has been saved");
    fclose(fp);
}

/***********************************************************************
 *
 * FUNCTION:	record_print()
 *
 * INPUTS:
 *
 * OUTPUTS:
 *
 * RETURNS:
 *
 * EXTERNALLY READ:
 *
 * EXTERNALLY MODIFIED:
 *
 * DESCRIPTION:	writes an output record according to the specified format
 */

int
record_print(char *buf, char *format, ...)
{
    va_list ap;
    char *sp;			/* input string pointer */
    int np;			/* input integer */
    int val, digit;		/* temporary values */
    utc_val tval;		/* utc_val */
    char tbuf[UTC_TEXT_LENGTH];	/* utc time value string */
    int tlen;			/* temp length of time string */

    va_start(ap, format);
    while (*format) {
	switch (*(format++)) {
	case 'd':		/* decimal integer */
	case 'u':		/* output values are always known */
	    np = va_arg(ap, int);
	    if (np < 0) {
		*(buf++) = '-';
		np = -np;
	    }
	    for (val = 1; val <= np; val *= 10);
	    val = (val > 1) ? (val / 10) : 1;
	    while (val >= 1) {
		digit = np / val;
		*(buf++) = '0' + digit;
		np -= val * digit;
		val /= 10;
	    }
	    *(buf++) = ' ';
	    break;
	case 'q':		/* quoted string */
	    sp = va_arg(ap, char *);
	    *(buf++) = '"';
	    while (*sp) {
		switch (*sp) {
		case '\\':
		case '"':
		    *(buf++) = '\\';
		default:
		    *(buf++) = *(sp++);
		    break;
		}
	    }
	    *(buf++) = '"';
	    *(buf++) = ' ';
	    break;
	case 's':		/* unquoted single word string */
	    sp = va_arg(ap, char *);
	    while (*sp) (*(buf++) = *(sp++));
	    *(buf++) = ' ';
	    break;
	case 't':		/* standard date-time value */
	    tval = va_arg(ap, utc_val);
  	    getx_utc(tbuf, tval);
	    tlen = strlen(tbuf);
	    (void)strncpy(buf, tbuf, (unsigned)tlen);
	    buf += tlen;
	    *(buf++) = ' ';
	    break;
	}
    }
    va_end(ap);
    *(--buf) = '\n';
    *(++buf) = '\0';
    return 0;
}

/***********************************************************************
 *
 * FUNCTION:	unknown_record()
 *
 * INPUTS:	buf	(char *)
 *
 * OUTPUTS:
 *
 * RETURNS:
 *
 * EXTERNALLY READ:
 *
 * EXTERNALLY MODIFIED:
 *
 * DESCRIPTION:	used when undefined input record type is found, default
 *		load function
 */

static int 
unknown_record(char *buf)
{
    return -1;
}

/**********************************************************************
 *
 * FUNCTION:
 *	delete_header()
 *
 * INPUTS:
 * 	
 * OUTPUTS:
 *
 * RETURNS:
 *
 * EXTERNALLY READ:
 *
 * EXTERNALLY MODIFIED:
 *	headptr	- (static char (*)[]) release and set to NULL
 *	nlines 	- (int) set to 0
 *	nslots	- (int) set to 0
 *
 * DESCRIPTION:
 *	clear and release all memory used by the header storage.
 */

static void 
delete_header(void)
{
    int i;

    for (i = 0; i < nlines; ++i) {
	XtFree((char *)((*headptr)[i]));
    }
    XtFree((char *)headptr);
    headptr = NULL;
    nlines = 0;
    nslots = 0;
}

/**********************************************************************
 *
 * FUNCTION:
 *	add_header_line()
 *
 * INPUTS:
 * 	
 * OUTPUTS:
 *
 * RETURNS:
 *
 * EXTERNALLY READ:
 *
 * EXTERNALLY MODIFIED:
 *	headptr	- (static char (*)[]) add new line
 *	nlines 	- (int) incremented
 *	nslots	- (int) if more memory is needed this is updated
 *
 * DESCRIPTION:
 *	add one line to the dynamic array specified by headptr	
 */

static void 
add_header_line(char *line)
{
    if (nlines >= nslots) {
	nslots += BATCH_SIZE;
	headptr = (char *(*)[])XtRealloc((char *)headptr, 
	    (Cardinal)(nslots * sizeof(char *)));
    }
    (*headptr)[nlines] = XtNewString(line);
    ++nlines;
}

/**********************************************************************
 *
 * FUNCTION:
 *      print_header()
 *
 * INPUTS:
 * 	fp	- (FILE *) output file
 *
 * OUTPUTS:
 *
 * RETURNS:
 *
 * EXTERNALLY READ:
 *      headptr - (static char (*)[]) prints each line
 *      nlines  - (int) count of lines
 *
 * EXTERNALLY MODIFIED:
 *
 * DESCRIPTION:
 *      prints all lines in the headptr array
 */

static void 
print_header(FILE *fp)
{
    int i;
    
    for (i = 0; i < nlines; ++i) {
	fprintf(fp, "%s", (*headptr)[i]);
    }
}
