#include <ctype.h>
#include <stdio.h>

#include "fudgit.h"
#include "macro.h"
#include "symbol.h"
#include "code.h"
#include "math.tab.h"
#include "head.h"

#define SQUOTE '\''
#define DQUOTE '"'
#define BACKSLASH

extern int Ft_almost(char *, char *);

#define COPY(pOinter)	*cp++ = p[argno][j++] = *pOinter++;\
						if (j >= TOKENSIZE) {\
						fprintf(stderr, "Argument %d too long.\n", argno);\
						return(ERRR);\
						}

#define CHECKMODE		if (leavequotes == ERRR) {\
							leavequotes = (Ft_almost(p[0], "pm!ode") ||\
							Ft_almost(p[0], "cm!ode") ||\
							Ft_almost(p[0], "le!t") ||\
							Ft_maclook(p[0], ANALIAS) );\
							if (leavequotes) {\
								COPY(b);\
								break;\
							}\
						}\

/* given a buffered line b, a string vector p, return the number of
 * arguments found and copied. expand local variables.
 * Returns: no of arg
 * NL for newline
 * ERRR for serious errors
 * VERRR for variable errors (for history purposes)
 */

extern char Ft_Vformat[];
extern char Ft_Comchar;
extern char Ft_Home[];
extern int Ft_iolevel(void);
extern char *getenv(const char *);
static char buffer[LINESIZE];

int Ft_vgetargp(char *b, char **p, int type, char **add)
{
	static char qtstype[] = "\"'";
	int j = 0;
	int argno = 0;
	int indquotes = 0;
	int insquotes = 0;
	int inpar = 0;
	int expansion = (type & EXPANSION);
	int quotes = (type & QUOTES);
	int parenth = (type & PARENTH);
	int leavequotes = (quotes == 0? YES: ERRR);
	char *cp = buffer;
	char *np;

	if (add)
		*add = cp;

	/* remove leading blanks  */
	while (*b != '\0' && (*b == ' ' || *b == '\t' || *b == '\n'))
		b++;
	if (*b == '\0') return(NL);

	/* go along the string  */
	while (*b != '\0' && argno < MAXTOKEN) {
		switch (*b) {
#ifdef BACKSLASH
		case '\\':    /* only recognize \$ in non fmodes  */
			if (leavequotes == ERRR) {
				leavequotes = (	Ft_almost(p[0], "pm!ode") ||
								Ft_almost(p[0], "cm!ode") ||
								Ft_almost(p[0], "le!t")   );
			}
			if (expansion && (!leavequotes || b[1] == '$')) {
				b++;
			}
			COPY(b);
			break;
#endif
		case SQUOTE:  /* take following as one string, + turn off exp */
			if (leavequotes == 1 || indquotes || inpar) {
				COPY(b);
				break;
			}
			CHECKMODE;
			b++;
			insquotes = !insquotes;
			break;
		case DQUOTE:  /* take the following as one string */
			if (leavequotes == 1 || insquotes || inpar) {
				COPY(b);
				break;
			}
			CHECKMODE;
			b++;
			indquotes = !indquotes;
			break;
		case '$':  /* try to expand a numeric variable  */
			if (!insquotes && expansion &&
			(*(b+1) == '{' || *(b+1) == '[' ||
				*(b+1) == '_' || isalpha((int)*(b+1)))) {
				Symbol *sym;
				char name[TOKENSIZE];
				char format[TOKENSIZE];
				char *lp;
				int brace = 0;
				int bracket = 0;

				b++;
				if (*b == '{') {
					b++;
					brace = 1;
				}
				if (*b == '[') {
					b++;
					bracket = 1;
					np = format;
					while (*b && *b != ']') {
						*np++ = *b++;
					}
					*np = '\0';
					if (*b++ != ']') {
						fprintf(stderr, "Unmatched bracket: %s.\n", format);
						return(VERRR);
					}
				}		
				np = name;
				/* name is alphanumeric */
				while (*b == '_' || isalnum((int)*b)) { 
					*np++ = *b++;
				}
				*np = '\0';
				if (brace) {
					if (*b++ != '}') {
						fprintf(stderr, "Unmatched brace: %s.\n", name);
						return(VERRR);
					}
				}
				/* find the variable */
				if ((sym = Ft_lookup(name)) == 0) {
					fprintf(stderr,
					"Expansion error: %s: No such constant or variable.\n",
					name);
					return(VERRR);
				}
				/* The following crucially depends on the order in parse.y */
				if (sym->type >= VAR && sym->type <= BLTINCONST) {
					if (bracket)
						lp = format;
					else
						lp = Ft_Vformat;
					sprintf(name, lp, sym->u.val);
					lp = name;
				}
				else if (sym->type >= STRVAR && sym->type <= BLTINSTRCONST) {
					if (bracket) {
						sprintf(name, format, sym->u.str);
						lp = name;
					}
					else
						lp = sym->u.str;
				}
				else if (sym->type == UNDEFSTRVAR || sym->type == UNDEFVAR) {
					fprintf(stderr,
					"Expansion error: %s: Undefined variable.\n", name);
					return(VERRR);
				}
				else {
					fprintf(stderr,
					"Expansion error: %s: Not a printable variable.\n", name);
					return(VERRR);
				}
				while(*lp) {   /* replace by value */
					COPY(lp);
				}
			}
			else { /* copy $ as is */
				COPY(b);
			}
			break;
		case '(':
			if (!parenth || (leavequotes == 1 && quotes == 1)
				|| indquotes || insquotes) {
				COPY(b);
				break;
			}
			CHECKMODE;
			inpar++;
			if (inpar == 1 && j != 0) { /* start new argument on first '(' */
				p[argno][j] = '\0';
				argno++;
				j = 0;
			}
			COPY(b);
			break;
		case ')':
			if (!parenth || (leavequotes == 1 && quotes == 1)
			    || indquotes || insquotes) {
				COPY(b);
				break;
			}
			CHECKMODE;
			inpar--;
			COPY(b);
			if (inpar == 0 && !isspace(*b)) { /* end argument on last ')' */
				p[argno][j] = '\0';
				argno++;
				j = 0;
			}
			break;
		case ' ':   /* a space  */
		case '\t':
			if (indquotes || inpar || insquotes) {
				p[argno][j] = ' ';
				j++;
			}
			else {
				p[argno][j] = '\0';
				argno++;
				j = 0;
			}
			*cp = ' '; cp++;
			/* remove interargument blanks  */
			while (*b == ' ' || *b == '\t')
				b++;
			if (*b == '\n') {
				if (inpar) {
					fprintf(stderr, "Unmatched %c.\n",
					inpar>0? '(' : ')');
					return(VERRR);
				}
				if (indquotes || insquotes) {
					fprintf(stderr, "Unmatched %c.\n", qtstype[insquotes]);
					return(VERRR);
				}
				b++;
				if (*b != '\0') {
					fputs("Command contains more than one line.\n", stderr);
					return(ERRR);
				}
				*cp = '\n'; cp++; *cp = '\0';
				return(argno);
			}
			break;
		case '\n':
			if (inpar) {
				fprintf(stderr, "Unmatched %c.\n",
					inpar>0? '(' : ')');
				return(VERRR);
			}
			if (insquotes || indquotes) {
				fprintf(stderr, "Unmatched %c.\n", qtstype[insquotes]);
				return(VERRR);
			}
			*cp = '\n'; cp++;
			*cp = p[argno][j] = '\0';
			argno++; b++;
			if (*b != '\0') {
				fputs("Command contains more than one line.\n", stderr);
				return(ERRR);
			}
			return(argno);
		case '~':
			b++;
			np = Ft_Home;
			while (*np) {   /* replace by variable */
				COPY(np);
			}	
			break;
		default:
			/* ignore comment except when interactive */
			if (*b == Ft_Comchar && !indquotes && !insquotes &&
			 !inpar && (Ft_iolevel() || !expansion)) {
				*cp = '\n'; cp++;
				*cp = p[argno][j] = '\0';
				if (j != 0) argno++;
				return(argno);
			} /* otherwise go through */
			COPY(b);
			break;
		}
	}
	fputs("Tokenizer: Line too long or not terminated.\n", stderr);
	return(ERRR);
}

