/*
 * Copyright (c) 1994 David I. Bell
 * Permission is granted to use, distribute, or modify this source,
 * provided that this copyright notice remains intact.
 *
 * Modified for the Amiga by Steve Leblanc, Oct 1995
 *
 * Nested input source file reader.
 * For terminal input, this also provides a simple command stack.
 */

#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "calc.h"
#include "config.h"

extern int stdin_tty;		/* TRUE if stdin is a tty */

#define TTYSIZE		100	/* reallocation size for terminal buffers */
#define DEPTH		10	/* maximum depth of input */
#define IS_READ		1	/* reading normally */
#define IS_REREAD	2	/* reread current character */
#define chartoint(ch)	((ch) & 0xff)	/* make sure char is not negative */
#define READSET_ALLOC	8	/* readset to allocate chunk size */


typedef struct {
	int i_state;		/* state (read, reread) */
	int i_char;		/* currently read char */
	long i_line;		/* line number */
	char *i_str;		/* current string for input (if not NULL) */
	char *i_origstr;	/* original string so it can be freed */
	char *i_ttystr;		/* current character of tty line (or NULL) */
	FILE *i_fp;		/* current file for input (if not NULL) */
	char *i_name;		/* file name if known */
} INPUT;


/* files that calc has read or included */
typedef struct {
	int active;		/* != 0 => active entry, 0 => unused entry */
	char *name;		/* name used to read file */
	char *path;		/* real path used to open file */
	struct stat inode;	/* inode information for file */
} READSET;

static READSET *readset = NULL;		/* array of files read */
static int maxreadset = 0;		/* length of readset */

static int linesize;		/* current max size of input line */
static char *linebuf;		/* current input line buffer */
static char *prompt;		/* current prompt for terminal */
static BOOL noprompt;		/* TRUE if should not print prompt */

static int depth;		/* current input depth */
static INPUT *cip;		/* current input source */
static INPUT inputs[DEPTH];	/* input sources */

static int openfile MATH_PROTO((char *name));
static int ttychar MATH_PROTO((void));
static void closeinput MATH_PROTO((void));
static int isinoderead MATH_PROTO((struct stat *sbuf));
static int findfreeread MATH_PROTO((void));
static int addreadset MATH_PROTO((char *name, char *path, struct stat *sbuf));

/*
 * Read a line into the specified buffer.  The line ends in a newline,
 * and is NULL terminated.  Returns the number of characters read, or
 * zero on an end of file or error.  The prompt is printed before reading
 * the line.
 *
 * AMIGA: This routine taken out of the hist.c file and modified.
 *        The Amiga version relies on the Console handler for history
 *        and line editing (i.e. KingCON) so stuff in hist.c was not
 *        required.
 */
int
getline(char *prompt,char *buf,int len)
{
	fputs(prompt,stdout);
	fflush(stdout);

	if (fgets(buf, len, stdin) == NULL)
		return 0;
	return strlen(buf);
}

/*
 * Open an input file by possibly searching through a path list
 * and also possibly applying the specified extension.  For example:
 * opensearchfile("barf", ".:/tmp", ".c") searches in order for the
 * files "./barf", "./barf.c", "/tmp/barf", and "/tmp/barf.c".
 *
 * Returns -1 if we could not open a file or error.  
 * Returns 1 if file was opened and added to/updated in the readset.   
 * Returns 0 if file was already in the readset and reopen was 0.
 */
int
opensearchfile(name, pathlist, extension, rd_once)
	char *name;		/* file name to be read */
	char *pathlist;		/* list of colon separated paths (or NULL) */
	char *extension;	/* extra extension to try (or NULL) */
	int rd_once;		/* TRUE => do not reread a file */
{
	int i;
	char *cp;
	char path[PATHSIZE+1];	/* name being searched for */
	struct stat statbuf;	/* stat of the path */

	/*
	 * Don't try the extension if the filename already contains it.
	 */
	if (extension) {
		i = strlen(name) - strlen(extension);
		if ((i >= 0) && (strcmp(&name[i], extension) == 0))
			extension = NULL;
	}
	/*
	 * If the name is absolute, or if there is no path list, then
	 * make one which just searches for the name straight.  Then
	 * search through the path list for the file, without and with
	 * the specified extension.
   *
   * AMIGA: Modified to recognize a full path as one that looks like
   *          XXX:qwerty/uiops
   */
	if(strchr(name,':') || pathlist == NULL)
		pathlist = "";

  pathlist--;
	do {
		pathlist++;
		cp = path;
		while (*pathlist && (*pathlist != LISTCHAR))
			*cp++ = *pathlist++;
		strcpy(cp, name);
		i = openfile(path);
		if ((i < 0) && extension) {
			strcat(path, extension);
			i = openfile(path);
		}
	} while ((i < 0) && *pathlist);

	/* examine what our search produced */
	if (i < 0 || cip->i_fp == NULL) {
		/* cannot find a file to open */
		return -1;
	}
	/*
     AMIGA: use stat rather than fstat, since it's not in the Lattice
            C library.
  */
  if (stat(path,&statbuf) < 0) {
		/* unable to stat the open file */
		return -1;
	}

	/* note if we will reopen a file and if that is allowed */
	if (rd_once == TRUE && isinoderead(&statbuf) >= 0) {
		/* file is in readset and reopen is false */
		closeinput();
		return 1;
	}

	/* add this name to the readset */
	if (addreadset(name, path, &statbuf) < 0) {
		/* cannot add to readset */
		closeinput();
		return -1;
	}

	/* file was added to/updated in readset */
	return 0;
}

/*
 * Setup for reading from a input file.
 * Returns -1 if file could not be opened.
 */
static int
openfile(name)
	char *name;		/* file name to be read */
{
	FILE *fp;		/* open file descriptor */

	if (depth >= DEPTH)
		 return -1;
	fp = fopen(name, "r");
	if (fp == NULL)
		 return -1;
	cip++;
	cip->i_state = IS_READ;
	cip->i_char = '\0';
	cip->i_str = NULL;
	cip->i_origstr = NULL;
	cip->i_ttystr = NULL;
	cip->i_fp = fp;
	cip->i_line = 1;
	cip->i_name = (char *)malloc(strlen(name) + 1);
	strcpy(cip->i_name, name);
	depth++;
	return 0;
}


/*
 * Open a string for scanning. String is ended by a null character.
 * String is copied into local memory so it can be trashed afterwards.
 * Returns -1 if cannot open string.
 */
int
openstring(str)
	char *str;		/* string to be opened */
{
	char *cp;		/* copied string */

	if ((depth >= DEPTH) || (str == NULL))
		 return -1;
	cp = (char *)malloc(strlen(str) + 1);
	if (cp == NULL)
		 return -1;
	strcpy(cp, str);
	cip++;
	cip->i_state = IS_READ;
	cip->i_char = '\0';
	cip->i_str = cp;
	cip->i_origstr = cp;
	cip->i_fp = NULL;
	cip->i_name = NULL;
	cip->i_ttystr = NULL;
	cip->i_line = 1;
	depth++;
	return 0;
}


/*
 * Set to read input from the terminal.
 * Returns -1 if there is no more depth for input.
 */
int
openterminal()
{
	if (depth >= DEPTH)
		 return -1;
	cip++;
	cip->i_state = IS_READ;
	cip->i_char = '\0';
	cip->i_str = NULL;
	cip->i_origstr = NULL;
	cip->i_ttystr = NULL;
	cip->i_fp = NULL;
	cip->i_name = NULL;
	cip->i_line = 1;
	depth++;
	return 0;
}


/*
 * Close the current input source.
 */
static void
closeinput()
{
	if (depth <= 0)
		return;
	if (cip->i_origstr)
		free(cip->i_origstr);
	if (cip->i_fp)
		fclose(cip->i_fp);
	if (cip->i_name)
		free(cip->i_name);
	cip--;
	depth--;
}


/*
 * Reset the input sources back to the initial state.
 */
void
resetinput()
{
	while (depth > 0)
		closeinput();
	cip = inputs;
	noprompt = FALSE;
}


/*
 * Set the prompt for terminal input.
 */
void
setprompt(str)
	char *str;
{
	prompt = str;
	noprompt = FALSE;
}


/*
 * Read the next character from the current input source.
 * End of file closes current input source, and returns EOF character.
 */
int
nextchar()
{
	int ch;			/* current input character */

	if (depth == 0)		/* input finished */
		 return EOF;
	if (cip->i_state == IS_REREAD) {	/* rereading current char */
		 ch = cip->i_char;
		 cip->i_state = IS_READ;
		 if (ch == '\n')
			cip->i_line++;
		 return ch;
	}
	if (cip->i_str) {		/* from string */
		ch = chartoint(*cip->i_str++);
		if (ch == '\0')
			ch = EOF;
	} else if (cip->i_fp) {		/* from file */
		ch = fgetc(cip->i_fp);
	} else if (!stdin_tty) {		/* from file */
		ch = fgetc(stdin);
	} else {			/* from terminal */
		ch = ttychar();
	}
	if (ch == EOF) {		/* fix up end of file */
		closeinput();
		ch = EOF;
	}
	if (depth > 0)
		cip->i_char = ch;	/* save for rereads */
	if (ch == '\n')
		cip->i_line++;
	return ch;
}


/*
 * Read in the next line of input from the current input source.
 * The line is terminated with a null character, and does not contain
 * the final newline character.  The returned string is only valid
 * until the next such call, and so must be copied if necessary.
 * Returns NULL on end of file.
 */
char *
nextline()
{
	char *cp;
	int ch;
	int len;

	cp = linebuf;
	if (linesize == 0) {
		cp = (char *)malloc(TTYSIZE + 1);
		if (cp == NULL)
			math_error("Cannot allocate line buffer");
		linebuf = cp;
		linesize = TTYSIZE;
	}
	len = 0;
	for (;;) {
		noprompt = TRUE;
		ch = nextchar();
		noprompt = FALSE;
		if (ch == EOF)
			return NULL;
		if (ch == '\0')
			continue;
		if (ch == '\n')
			break;
		if (len >= linesize) {
			cp = (char *)realloc(cp, linesize + TTYSIZE + 1);
			if (cp == NULL)
				math_error("Cannot realloc line buffer");
			linebuf = cp;
			linesize += TTYSIZE;
		}
		cp[len++] = (char)ch;
	}
	cp[len] = '\0';
	return linebuf;
}


/*
 * Read the next character from the terminal.
 */
static int
ttychar()
{
	int ch;			/* current char */
	int len;		/* length of current command */
	static char charbuf[1024];

	/*
	 * If we have more to read from the saved command line, then do that.
	 * When we see a newline character, then clear the pointer so we will
	 * read a new line on the next call.
	 */
	if (cip->i_ttystr) {
		ch = chartoint(*cip->i_ttystr++);
		if (ch == '\n')
			cip->i_ttystr = NULL;
		return ch;
	}

	/*
	 * We need another complete line.
	 */
	abortlevel = 0;
	inputwait = TRUE;
	len = getline(noprompt ? "" : prompt, charbuf, sizeof(charbuf));
	if (len == 0) {
		inputwait = FALSE;
		return EOF;
	}
	inputwait = FALSE;

	/*
	 * Handle shell escape if present
	 */
	if (charbuf[0] == '!') {		/* do a shell command */
		char *cmd;

		cmd = charbuf + 1;
		system(cmd);
		return '\n';
	}

	/*
	 * Return the first character of the line, and set up to
	 * return the rest of it with later calls.
	 */
	ch = chartoint(charbuf[0]);
	if (ch != '\n')
		cip->i_ttystr = charbuf + 1;
	return ch;
}


/*
 * Return whether or not the input source is the terminal.
 */
BOOL
inputisterminal()
{
	return ((depth <= 0) || ((cip->i_str == NULL) && (cip->i_fp == NULL)));
}


/*
 * Return the name of the current input file.
 * Returns NULL for terminal or strings.
 */
char *
inputname()
{
	if (depth <= 0)
		return NULL;
	return cip->i_name;
}


/*
 * Return the current line number.
 */
long
linenumber()
{
	if (depth > 0)
		return cip->i_line;
	return 1;
}


/*
 * Restore the next character to be read again on the next nextchar call.
 */
void
reread()
{
	if ((depth <= 0) || (cip->i_state == IS_REREAD))
		return;
	cip->i_state = IS_REREAD;
	if (cip->i_char == '\n')
		cip->i_line--;
}


/*
 * Process all startup files found in the $CALCRC path.
 */
void
runrcfiles()
{
	char path[PATHSIZE+1];	/* name being searched for */
	char *cp;
	char *newcp;
	char *p;
	int i;

	/* execute each file in the list */
	for (cp=calcrc, newcp=(char *)strchr(calcrc, LISTCHAR);
	     cp != NULL && *cp;
	     cp = newcp, 
		 newcp=(newcp) ? (char *)strchr(newcp+1, LISTCHAR) : NULL) {

		/* load file name into the path */
		if (newcp == NULL) {
			strcpy(path, cp);
		} else {
			strncpy(path, cp, newcp-cp);
			path[newcp-cp] = '\0';
		}

		/* find the start of the path */
		/* Bug fixed. This comparison checked for ':', not LISTCHAR */
    p = (path[0] == LISTCHAR) ? path+1 : path;
		if (p[0] == '\0') {
			continue;
		}

		/* process the current file in the list */
		i = openfile(p);
		if (i < 0)
			continue;
		getcommands(FALSE);
	}
}


/*
 * isinoderead - determine if we have read a given dev/inode
 *
 * This function returns the index of the readset element that matches
 * a given device/inode, -1 otherwise.
 */
static int
isinoderead(sbuf)
	struct stat *sbuf;		/* stat of the inode in question */
{
	int i;

	/* deal with the empty case */
	if (readset == NULL || maxreadset <= 0) {
		/* readset is empty */
		return -1;
	}

	/* scan the entire readset */
	for (i=0; i < maxreadset; ++i) { 
		if (readset[i].active && 
		    sbuf->st_dev == readset[i].inode.st_dev &&
		    sbuf->st_ino == readset[i].inode.st_ino) {
			/* found a match */
			return i;
		}
	}

	/* no match found */
	return -1;
}


/*
 * findfreeread - find the next free readset element
 *
 * This function will return the index of the next free readset element.
 * If needed, this function will allocate new readset elements.
 *
 * This function returns the index of the next free element, or -1.
 */
static int
findfreeread()
{
	int i;

	/* deal with an empty readset case */
	if (readset == NULL || maxreadset <= 0) {

		/* malloc a new readset */
		readset = (READSET *)malloc((READSET_ALLOC+1)*sizeof(READSET));
		if (readset == NULL) {
			return -1;
		}
		maxreadset = READSET_ALLOC;
		for (i=0; i < READSET_ALLOC; ++i) {
			readset[i].active = 0;
		}

		/* return first entry */
		return 0;
	}

	/* try to find a free readset entry */
	for (i=0; i < maxreadset; ++i) {
		if (readset[i].active == 0) {
			/* found a free readset entry */
			return i;
		}
	}

	/* all readset entries are in use, allocate more */
	readset = (READSET *)realloc(readset, 
	    (maxreadset+READSET_ALLOC) * sizeof(READSET));
	if (readset == NULL) {
		return -1;
	}
	for (i=0; i < READSET_ALLOC; ++i) {
		readset[i+maxreadset].active = 0;
	}
	maxreadset += READSET_ALLOC;

	/* return the furst newly allocated free entry */
	return maxreadset-READSET_ALLOC;
}


/*
 * addreadset - add a entry to the readset array if it is not already there
 *
 * This function attempts to add a file into the readset.  If the readset
 * has an entry with a matching dev/inode, then that entry is updated with
 * the new name and path.  If no such readset entry is found, a new entry
 * is added.
 *
 * This function returns the index of the readset entry, or -1 if error.
 */
static int
addreadset(name, path, sbuf)
	char *name;	/* name given to read or include */
	char *path;	/* full pathname of file */
	struct stat *sbuf;	/* stat of the path */
{
	int ret;		/* index to return */

	/* find the inode */
	ret = isinoderead(sbuf);
	if (ret < 0) {
		/* not in readset, find a free node */
		ret = findfreeread();
		if (ret < 0) {
			/* cannot find/form a free readset entry */
			return -1;
		}
	} else {
		/* found an readset entry, free old readset data */
		if (readset[ret].name != NULL) {
			free(readset[ret].name);
		}
		if (readset[ret].path != NULL) {
			free(readset[ret].path);
		}
	}

	/* load our information into the readset entry */
	readset[ret].name = (char *)malloc(strlen(name)+1);
	if (readset[ret].name == NULL) {
		return -1;
	}
	strcpy(readset[ret].name, name);
	readset[ret].path = (char *)malloc(strlen(path)+1);
	if (readset[ret].path == NULL) {
		return -1;
	}
	strcpy(readset[ret].path, path);
	readset[ret].inode = *sbuf;
	readset[ret].active = 1;

	/* return index of the newly added entry */
	return ret;
}


/* END CODE */
