/*    SCCS Id: @(#)arg.c		3.1   95/07/25
/*    Copyright (c) Kenneth Lorber, Bethesda, Maryland, 1992, 1993, 1995 */
/* NetHack may be freely redistributed.  See license for details. */

/*
 * arg.c - semi-generic argument parser
 */
#include <exec/types.h>
#include <exec/lists.h>
#ifdef _DCC
# include <clib/exec_protos.h>
#else
# include <proto/exec.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include "arg.h"

static char *a_desc;
static int a_argc;
static char **a_argv;
static int a_apos;
static char more=0;
static char *argp;

static char *arg_fillbuf(void);
static char *splitline2(char *);

char *argarg;

#ifndef ARGLEN
#define ARGLEN 134		/* longest line we can deal with */
#endif

static struct List flist;	/* stack of files to read from */
struct fentry {			/* structures on that stack */
	struct Node node;
	FILE *fp;
	char *ptr;
	char buf[ARGLEN];
};

static char *splitline(struct fentry *); /* L505 bug? can't be above def  */

#define ListHead(x) ((x).lh_Head)
#define ListEmpty(x) (!(x).lh_Head->ln_Succ)

/*
 * arg_init(description string, argc, argv)
 * Called by user to initialize system - must be called before calling
 * other entry points.  desc is getopt(3) style description string; argc and
 * argv are the arguments to main().
 */
void arg_init(char *desc,int argc,char *argv[]){
	a_desc=desc;
	a_argc=argc;
	a_argv=argv;
	a_apos=0;
	NewList(&flist);
}

/*
 * arg_next(void)
 * Called by user to return each argument.  See arg.h for exceptional return
 * values - normally returns the argument flag found; if a flag takes an
 * argument, the argument is returned in argarg.
 * An argument beginning with @ is taken to be a file name from which to read
 * further arguments - such files are held on a stack and read as found, then
 * the previous file (or the command line) is continued.
 * In an argument file, the following are recognized:
 * #  as the first character of a line only, causes the line to be ignored
 * @  recursively read arguments from another file
 * \n embedded newline
 * \r embedded carriage return
 * \f embedded formfeed
 * \b embedded backspace
 * \  literal next character (except above)
 * "  start/end double quoted string
 * '  start/end single quoted string
 */
int arg_next(){
	char *cp;
	char key;

	if(!more){				/* anything still buffered? */
		if(!(argp=arg_fillbuf())){	/* nothing more */
			argarg=0;		/* be neat */
			return(ARG_DONE);
		}
	}
	if(more ||(*argp=='-' && argp++)){
		for(cp=a_desc;*cp;cp++){
			if(*cp== *argp){
				key=*argp++;
				if(*++cp==':'){
					if(*argp){
						argarg=argp;
					} else {
						argarg=arg_fillbuf();
						if(!argarg)return(ARG_ERROR);
					}
					more=0;
				} else {
					argarg=0; /* doesn't take an arg */
					more= *argp;
				}
				return((int) key);
			}
		}
		return(ARG_ERROR);	/* no such option */
	} else {
		argarg=argp;
		more=0;
		return(ARG_FREE);
	}
}

static char *arg_fillbuf(){
    char *p,*nlp;

    if(ListEmpty(flist)){
	if(++a_apos>a_argc)return(0);
	p=a_argv[a_apos];
    } else {
	struct fentry *f=(struct fentry *)ListHead(flist);
	if(!f->ptr){
	    do{
		if(!fgets(f->buf,ARGLEN,f->fp)){
		    if(ferror(f->fp)){
			fprintf(stderr, "I/O error on @file\n");
			return(0);
		    }
		    fclose(f->fp);
		    RemHead(&flist);
		    free(f);
		    return(arg_fillbuf());
		}
	    }while(f->buf[0]=='#');	/* comment */
	    if(nlp=strchr(f->buf,'\n'))*nlp='\0';
	}
	p=splitline(f);
	if(p == (char *)-1)return(0);		/* error */
	if(!p)return(arg_fillbuf());		/* skip blank line */
    }
	if(p && *p=='@'){
		struct fentry *f=calloc(sizeof(struct fentry),1);
		f->fp=fopen(++p,"r");
		if(!(f->fp)){
			fprintf(stderr,"Can't open @file '%s'\n",p);
			free(f);
			return(0);
		}
		AddHead(&flist,(struct Node *)f);
		return(arg_fillbuf());
	}
	return(p);
}

static char *splitline(struct fentry *f){
	char *out=(f->ptr?f->ptr:f->buf);
	char *ret;

	while(*out && isspace(*out))out++;
	if(!*out)return(0);	/* blank line or spaces at end */
	ret=out;
	while(*out && !isspace(*out)){
		switch(*out){
		case '\\':
		case '\"':
		case '\'':
			out=splitline2(out);
			if(!out)return((char *)-1);	/* error */
			break;
		default:
			out++;
			break;
		}
	}
	if(!*out){
		f->ptr=0;	/* this was last arg on current line */
	} else {
		*out='\0';
		f->ptr= ++out;
		if(!(*f->ptr))f->ptr=0;
	}
	return ret;
}

static char *splitline2(char *p){
	char *out=p;
	char c;
	char dq=0, sq=0;
	while(*p){
		switch(c= *p++){
		case '\\':
			switch(c= *p++){
			case 'n':	*out++='\n';break;
			case 'r':	*out++='\r';break;
			case 'f':	*out++='\f';break;
			case 'b':	*out++='\b';break;
			case 0:		p--;break;
			default:	*out++=c;break;
			}
			break;
		case '\"':	if(sq){
					*out++=c;
				} else {
					dq=1-dq;
				}
				break;
		case '\'':	if(dq){
					*out++=c;
				} else {
					sq=1-sq;
				}
				break;
		case ' ':	if(!sq && !dq){*out=0;return(p-1);}
				*out++=' ';
				break;
		default:	*out++=c;break;
		}
	}
	if(sq ||dq){
		fprintf(stderr,"Warning - quote error in @file\n");
		return((char *)-1);
	}
	*out=0;
	return(p);
}

#ifdef DEBUG_ARG
main(int argc,char *argv[]){
	int x=0;
	arg_init(getenv("test_arg"),argc,argv);
	do{
		x=arg_next();
		printf("r=%d (%d)'%s'\n",x,argarg,argarg);
	}while(x >= 0);
}
#endif /* DEBUG_ARG */
