/* xlfio.c - xlisp file i/o */
/*		Copyright (c) 1985, by David Michael Betz
		All Rights Reserved
		Permission is granted for unrestricted non-commercial use		*/

#include "xlisp.h"
#include <string.h>

/* external variables */
extern LVAL k_direction,k_input,k_output;
extern LVAL s_stdin,s_stdout,true;
extern int xlfsize;

#ifdef BETTERIO
extern LVAL k_io, k_elementtype;
extern LVAL a_fixnum, a_char;
#endif

/* forward declarations */
#ifdef ANSI
LVAL getstroutput(LVAL stream);
LVAL printit(int pflag, int tflag);
LVAL flatsize(int pflag);
#else
FORWARD LVAL getstroutput();
FORWARD LVAL printit();
FORWARD LVAL flatsize();
#endif

/* xread - read an expression */
LVAL xread()
{
	LVAL fptr,eof,val;

	/* get file pointer and eof value */
	fptr = (moreargs() ? xlgetfile() : getvalue(s_stdin));
	eof = (moreargs() ? xlgetarg() : NIL);
	if (moreargs()) xlgetarg(); /* toss now unused argument */
	xllastarg();

	/* read an expression */
	if (!xlread(fptr,&val))
		val = eof;

	/* return the expression */
	return (val);
}

/* xprint - built-in function 'print' */
LVAL xprint()
{
	return (printit(TRUE,TRUE));
}

/* xprin1 - built-in function 'prin1' */
LVAL xprin1()
{
	return (printit(TRUE,FALSE));
}

/* xprinc - built-in function princ */
LVAL xprinc()
{
	return (printit(FALSE,FALSE));
}

/* xterpri - terminate the current print line */
LVAL xterpri()
{
	LVAL fptr;

	/* get file pointer */
	fptr = (moreargs() ? xlgetfile() : getvalue(s_stdout));
	xllastarg();

	/* terminate the print line and return nil */
	xlterpri(fptr);
	return (NIL);
}

/* printit - common print function */
LOCAL LVAL printit(pflag,tflag)
  int pflag,tflag;
{
	LVAL fptr,val;

	/* get expression to print and file pointer */
	val = xlgetarg();
	fptr = (moreargs() ? xlgetfile() : getvalue(s_stdout));
	xllastarg();

	/* print the value */
	xlprint(fptr,val,pflag);

	/* terminate the print line if necessary */
	if (tflag)
		xlterpri(fptr);

	/* return the result */
	return (val);
}

/* xflatsize - compute the size of a printed representation using prin1 */
LVAL xflatsize()
{
	return (flatsize(TRUE));
}

/* xflatc - compute the size of a printed representation using princ */
LVAL xflatc()
{
	return (flatsize(FALSE));
}

/* flatsize - compute the size of a printed expression */
LOCAL LVAL flatsize(pflag)
  int pflag;
{
	LVAL val;

	/* get the expression */
	val = xlgetarg();
	xllastarg();

	/* print the value to compute its size */
	xlfsize = 0;
	xlprint(NIL,val,pflag);

	/* return the length of the expression */
	return (cvfixnum((FIXTYPE)xlfsize));
}

/* xopen - open a file */
LVAL xopen()
{
	char *name,*mode;
	FILE *fp;
	LVAL dir;
#ifdef BETTERIO
	LVAL typ;
#endif

	/* get the file name and direction */
	name = (char *)getstring(xlgetfname());
	if (!xlgetkeyarg(k_direction,&dir))
		dir = k_input;

#ifdef BETTERIO
	if (xlgetkeyarg(k_elementtype,&typ)) {
		if (typ != a_fixnum && typ != a_char)
			xlerror("illegal stream element type",typ);
	}
	else
		typ = a_char;
#endif



	/* get the mode */
	if (dir == k_input)
		mode = "r";
	else if (dir == k_output)
		mode = "w";
#ifdef BETTERIO
	else if (dir == k_io) {
		mode = "r+";	/* try for existing file */
#ifdef __ZTC__
		if ((fp = ((typ == a_fixnum? &osbopen : &osaopen)(name,mode))) != 0)
			return cvfile(fp);
#else
		if ((fp = ((typ == a_fixnum? osbopen : osaopen)(name,mode))) != 0)
			return cvfile(fp);
#endif
		mode = "w+";	/* create new file */
	}
#endif
	else
		xlerror("bad direction",dir);



	/* try to open the file */
#ifdef BETTERIO
#ifdef __ZTC__
	return (((fp = ((typ == a_fixnum ? &osbopen : &osaopen)(name,mode))) != 0)
		? cvfile(fp) : NIL);
#else
	return (((fp = ((typ == a_fixnum ? osbopen : osaopen)(name,mode))) != 0)
		? cvfile(fp) : NIL);
#endif
#else
	return (((fp = osaopen(name,mode)) != 0) ? cvfile(fp) : NIL);
#endif
}

#ifdef BETTERIO
/* xfileposition - get position of file stream */
LVAL xfileposition()
{
	long i,j;
	int t=0;
	LVAL fptr;
	FILE *fp;
	/* get file pointer */
	fp = getfile(fptr = xlgastream());

	/* make sure the file exists */
	if (fp == NULL)
		xlfail("file not open");

/* get current position, adjusting for posible "unget" */
	j = ftell(fp) + (getsavech(fptr) ? -1L : 0L);

	if (moreargs()) { /* must be set position */
		i = getfixnum(xlgafixnum());
		xllastarg();
		setsavech(fptr,'\0');	/* toss unget character, if any */
		fptr->n_sflags = 0;		/* neither reading or writing currently */
		if (i < 0 ||
			(t=fseek(fp,i,SEEK_SET))!=0 ||
			ftell(fp) != i) {
			if (t) return NIL;
			fseek(fp,j,SEEK_SET);
			xlfail("position outside of file");
		}
		return true;
	}

	return (j == -1L ? NIL : cvfixnum(j));
}

/* xfilelength - returns length of file */
LVAL xfilelength()
{
	FILE *fp;
	long i,j;

	/* get file pointer */
	fp = getfile(xlgastream());
	xllastarg();

	/* make sure the file exists */
	if (fp == NULL)
		xlfail("file not open");

	if ((i=ftell(fp)) == -1L ||
		fseek(fp,0L,SEEK_END) ||
		(j = ftell(fp)) == -1L ||
		fseek(fp,i,SEEK_SET)) {
		return NIL;
	}
	
	return cvfixnum(j);
}


#endif


/* xclose - close a file */
LVAL xclose()
{
	LVAL fptr;

	/* get file pointer */
	fptr = xlgastream();
	xllastarg();

	/* make sure the file exists */
	if (getfile(fptr) == NULL)
		xlfail("file not open");

	/* close the file */
	osclose(getfile(fptr));
	setfile(fptr,NULL);

	/* return nil */
	return (NIL);
}

/* xrdchar - read a character from a file */
LVAL xrdchar()
{
	LVAL fptr;
	int ch;

	/* get file pointer */
	fptr = (moreargs() ? xlgetfile() : getvalue(s_stdin));
	xllastarg();

	/* get character and check for eof */
	return ((ch = xlgetc(fptr)) == EOF ? NIL : cvchar(ch));
}

/* xrdbyte - read a byte from a file */
LVAL xrdbyte()
{
	LVAL fptr;
	int ch;

	/* get file pointer */
	fptr = (moreargs() ? xlgetfile() : getvalue(s_stdin));
	xllastarg();

	/* get character and check for eof */
	return ((ch = xlgetc(fptr)) == EOF ? NIL : cvfixnum((FIXTYPE)ch));
}

/* xpkchar - peek at a character from a file */
LVAL xpkchar()
{
	LVAL flag,fptr;
	int ch;

	/* peek flag and get file pointer */
	flag = (moreargs() ? xlgetarg() : NIL);
	fptr = (moreargs() ? xlgetfile() : getvalue(s_stdin));
	xllastarg();

	/* skip leading white space and get a character */
	if (flag)
		while ((ch = xlpeek(fptr)) != EOF && isspace(ch))
			xlgetc(fptr);
	else
		ch = xlpeek(fptr);

	/* return the character */
	return (ch == EOF ? NIL : cvchar(ch));
}

/* xwrchar - write a character to a file */
LVAL xwrchar()
{
	LVAL fptr,chr;

	/* get the character and file pointer */
	chr = xlgachar();
	fptr = (moreargs() ? xlgetfile() : getvalue(s_stdout));
	xllastarg();

	/* put character to the file */
	xlputc(fptr,getchcode(chr));

	/* return the character */
	return (chr);
}

/* xwrbyte - write a byte to a file */
LVAL xwrbyte()
{
	LVAL fptr,chr;

	/* get the byte and file pointer */
	chr = xlgafixnum();
	fptr = (moreargs() ? xlgetfile() : getvalue(s_stdout));
	xllastarg();

	/* put byte to the file */
	xlputc(fptr,(int)getfixnum(chr));

	/* return the character */
	return (chr);
}

/* xreadline - read a line from a file */
LVAL xreadline()
{
	char buf[STRMAX+1],*p,*sptr;
	LVAL fptr,str,newstr;
	int len,blen,ch;

	/* protect some pointers */
	xlsave1(str);

	/* get file pointer */
	fptr = (moreargs() ? xlgetfile() : getvalue(s_stdin));
	xllastarg();

	/* get character and check for eof */
	len = blen = 0; p = buf;
	while ((ch = xlgetc(fptr)) != EOF && ch != '\n') {

		/* check for buffer overflow */
		if (blen >= STRMAX) {
			newstr = newstring(len + STRMAX + 1);
			sptr = getstring(newstr); *sptr = '\0';
			if (str) strcat((char *)sptr,(char *)getstring(str));
			*p = '\0'; strcat((char *)sptr,(char *)buf);
			p = buf; blen = 0;
			len += STRMAX;
			str = newstr;
		}

		/* store the character */
		*p++ = ch; ++blen;
	}

	/* check for end of file */
	if (len == 0 && p == buf && ch == EOF) {
		xlpop();
		return (NIL);
	}

	/* append the last substring */
	if (str == NIL || blen) {
		newstr = newstring(len + blen + 1);
		sptr = getstring(newstr); *sptr = '\0';
		if (str) strcat((char *)sptr,(char *)getstring(str));
		*p = '\0'; strcat((char *)sptr,(char *)buf);
		str = newstr;
	}

	/* restore the stack */
	xlpop();

	/* return the string */
	return (str);
}


/* xmkstrinput - make a string input stream */
LVAL xmkstrinput()
{
	int start,end,len,i;
	char *str;
	LVAL string,val;

	/* protect the return value */
	xlsave1(val);
	
	/* get the string and length */
	string = xlgastring();
	str = getstring(string);
	len = getslength(string) - 1;

	/* get the starting offset */
	if (moreargs()) {
		val = xlgafixnum();
		start = (int)getfixnum(val);
	}
	else start = 0;

	/* get the ending offset */
	if (moreargs()) {		/* TAA mod to allow NIL for end offset */
		val = nextarg();
		if (val == NIL) end = len;
		else if (fixp(val)) end = (int)getfixnum(val);
		else xlbadtype(val);
	}
	else end = len;
	xllastarg();

	/* check the bounds */
	if (start < 0 || start > len)
		xlerror("string index out of bounds",cvfixnum((FIXTYPE)start));
	if (end < 0 || end > len)
		xlerror("string index out of bounds",cvfixnum((FIXTYPE)end));

	/* make the stream */
	val = newustream();

	/* copy the substring into the stream */
	for (i = start; i < end; ++i)
		xlputc(val,str[i]);

	/* restore the stack */
	xlpop();

	/* return the new stream */
	return (val);
}

/* xmkstroutput - make a string output stream */
LVAL xmkstroutput()
{
	return (newustream());
}

/* xgetstroutput - get output stream string */
LVAL xgetstroutput()
{
	LVAL stream;
	stream = xlgaustream();
	xllastarg();
	return (getstroutput(stream));
}

/* xgetlstoutput - get output stream list */
LVAL xgetlstoutput()
{
	LVAL stream,val;

	/* get the stream */
	stream = xlgaustream();
	xllastarg();

	/* get the output character list */
	val = gethead(stream);

	/* empty the character list */
	sethead(stream,NIL);
	settail(stream,NIL);

	/* return the list */
	return (val);
}
#ifdef ENHFORMAT
/* decode prefix parameters and modifiers for a format directive */
#ifdef ANSI
static char *decode_pp(char *fmt, FIXTYPE *pp, int maxnpp, 
                       int *npp, int *colon, int *atsign)
#else
LOCAL char *decode_pp( fmt, pp, maxnpp, npp, colon, atsign )
char	*fmt;
FIXTYPE	pp[];			/* prefix parameters */
int		maxnpp;			/* maximum number of them */
int		*npp;			/* actual number of them */
int		*colon;			/* colon modifier given? */
int		*atsign;		/* atsign modifier given? */
#endif
{
	int gotpp = 0;		/* set to 1 when pp encountered */
	
	*npp = 0;
	*colon = 0;
	*atsign = 0;
	pp[0] = 0;
	do {
		if( *fmt == ':' )
			*colon = 1;
		else if( *fmt == '@' )
			*atsign = 1;
		else if( *colon || *atsign )	/* nothing else may follow : or @ */
		   break;
		else if( isdigit(*fmt) ) {
			pp[*npp] = (pp[*npp] * 10) + (int)(*fmt - '0');
			gotpp = 1;
		}
		else if( *fmt == ',' ) {
			if( ++(*npp) >= maxnpp )
				xlerror("too many prefix parameters in format",cvstring(fmt));
			pp[*npp] = 0;
			gotpp = 1;
		}
		else if( *fmt == '\'' ) {
			pp[*npp] = *(++fmt);
			gotpp = 1;
		}
		else if( *fmt == 'v' || *fmt == 'V' ) {
			pp[*npp] = getfixnum(xlgafixnum());
			gotpp = 1;
		}
		else 
			break;
		fmt++;
	} while( 1 );
	*npp += gotpp;				/* fix up the count */
	return fmt;
}

#define mincol	pp[0]
#define colinc	pp[1]
#define minpad	pp[2]
#define padchar pp[3]

/* opt_print - print a value using prefix parameter options */
#ifdef ANSI
static VOID opt_print(LVAL stream, LVAL val, int pflag, FIXTYPE *pp,
					int npp, int colon, int atsign)
#else
LOCAL VOID opt_print(stream,val,pflag,pp,npp,colon,atsign)
LVAL	stream;
LVAL	val;
int		pflag;			/* quoting or not */
FIXTYPE	pp[];			/* prefix parameters */
int		npp;			/* number of them */
int		colon;			/* colon modifier given? */
int		atsign;			/* at-sign modifier given? */
#endif
{
	int flatsize;
	int i;
	
	switch( npp ) {		/* default values of prefix parameters */
	case 0: mincol = 0;			 /* see CLtL, page 387 */
	case 1: colinc = 1;
	case 2: minpad = 0;
	case 3: padchar = ' ';
	}
	if( colinc <= 1 )
			colinc = 1;
	if( mincol < minpad )
			mincol = minpad;

	if( mincol > 0 && atsign ) {		/* padding may be required on left */
		xlfsize = 0;
		xlprint(NIL,val,pflag);			/* print to get the flat size */
		flatsize = xlfsize;
		for( i = 0; i < minpad; flatsize++, i++ )
			xlputc(stream,(int)padchar);
		while( flatsize < mincol ) {
			for( i = 0; i < colinc; i++ )
				xlputc(stream,(int)padchar);
			flatsize += (int)colinc;
		}
	}

	xlfsize = 0;				/* print the value */
	if( colon && val == NIL )
		xlputstr(stream,"()");
	else
		xlprint(stream,val,pflag);
	flatsize = xlfsize;
	
	if( mincol > 0 && !atsign ) {		/* padding required on right */
		for( i = 0; i < minpad; flatsize++, i++ )
			xlputc(stream,(int)padchar);
		while( flatsize < mincol ) {
			for( i = 0; i < colinc; i++ )
				xlputc(stream,(int)padchar);
			flatsize += (int)colinc;
		}
	}
}

#define MAXNPP	4
#endif

/* xformat - formatted output function */
LVAL xformat()
{
	char *fmt;
	LVAL stream,val;
	int ch;
#ifdef ENHFORMAT
	int npp;			/* number of prefix parameters */
	FIXTYPE pp[MAXNPP];		/* list of prefix parameters */
	int colon, atsign;	/* : and @ modifiers given? */
#endif

	xlsave1(val);						/* TAA fix */

	/* get the stream and format string */
	stream = xlgetarg();
	if (stream == NIL) {
		val = stream = newustream();
	}
	else {
		if (stream == true)
			stream = getvalue(s_stdout);
																/* fix from xlispbug.417 */
		else if (streamp(stream)) {		/* copied from xlgetfile() */
				if (getfile(stream) == NULL)
						xlfail("file not open");
		}
		else if (!ustreamp(stream))
				xlbadtype(stream);
		val = NIL;
	}
	fmt = getstring(xlgastring());

	/* process the format string */
	while ((ch = *fmt++) != 0)
		if (ch == '~') {
#ifdef ENHFORMAT
			fmt = decode_pp( fmt, pp, MAXNPP, &npp, &colon, &atsign );
#endif
			switch (*fmt++) {
			case '\0':
				xlerror("expecting a format directive",cvstring(fmt-1));
			case 'a': case 'A':
#ifdef ENHFORMAT
				opt_print(stream,xlgetarg(),FALSE,pp,npp,colon,atsign);
#else
				xlprint(stream,xlgetarg(),FALSE);
#endif
				break;
			case 's': case 'S':
#ifdef ENHFORMAT
				opt_print(stream,xlgetarg(),TRUE,pp,npp,colon,atsign);
#else
				xlprint(stream,xlgetarg(),TRUE);
#endif
				break;
			case '%':
#ifdef ENHFORMAT
				if( pp[0] <= 0 ) pp[0] = 1;
				while( (pp[0])-- > 0 )
					xlterpri(stream);
#else
				xlterpri(stream);
#endif
				break;
			case '~':
#ifdef ENHFORMAT
				if( pp[0] <= 0 ) pp[0] = 1;
				while( (pp[0])-- > 0 )
					xlputc(stream,'~');
#else
				xlputc(stream,'~');
#endif
				break;
			case '\n':
#ifdef ENHFORMAT
				if( colon )
					break;
				if( atsign )
					 xlterpri(stream);
#endif
				while (*fmt && *fmt != '\n' && isspace(*fmt))
					++fmt;
				break;
			default:
				xlerror("unknown format directive",cvstring(fmt-1));
			}
		}
		else
			xlputc(stream,ch);
		
	/* unprotect */
	xlpop();

	/* return the value */
	return (val ? getstroutput(val) : NIL);
}


/* getstroutput - get the output stream string (internal) */
LOCAL LVAL getstroutput(stream)
  LVAL stream;
{
	char *str;
	LVAL next,val;
	int len,ch;

	/* compute the length of the stream */
	for (len = 0, next = gethead(stream); next != NIL; next = cdr(next))
		++len;

	/* create a new string */
	val = newstring(len + 1);
	
	/* copy the characters into the new string */
	str = getstring(val);
	while ((ch = xlgetc(stream)) != EOF)
		*str++ = ch;
	*str = '\0';

	/* return the string */
	return (val);
}

