/* Parse command line, set up command arguments Unix-style, and call function.
 * Note: argument is modified (delimiters are overwritten with nulls)
 *
 * Copyright 1991 Phil Karn, KA9Q
 *
 * Improved error handling by Brian Boesch of Stanford University
 * Feb '91 - Bill Simpson
 *		bit16cmd for PPP
 * Mar '91 - Glenn McGregor
 *		handle string escaped sequences
 */
#ifdef MSDOS
#include <conio.h>
#endif
#include "global.h"
#include "ctype.h"
#include "proc.h"
#include "commands.h"
#include "session.h"
#include "pktdrvr.h"

#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: cmdparse.c,v 1.15 1997/08/19 01:19:22 root Exp $";
#endif

#ifdef LOCK
int Kblocked;
char *Kbpasswd;
#endif

extern long btol (char *s);

int cmdparse_newproc_type = PTYPE_NORMAL;

static void redir_cleanup (void);


struct boolcmd {
	const char *str;	/* Token */
	int val;		/* Value */
};


static struct boolcmd Boolcmds[] =
{
	{"y", 1},		/* Synonyms for "true" */
	{"yes", 1},
	{"true", 1},
	{"on", 1},
	{"1", 1},
	{"set", 1},
	{"enable", 1},

	{"n", 0},		/* Synonyms for "false" */
	{"no", 0},
	{"false", 0},
	{"off", 0},
	{"0", 0},
	{"clear", 0},
	{"disable", 0},
	{NULLCHAR, 0}
};

static char *stringparse (char *line, int braced);


static char *
stringparse (line, braced)
char *line;
int braced;
{
register char *cp = line;
unsigned long num;
char check = (braced) ? '}' : '\"';

	while (*line != '\0' && *line != check) {
		if (*line == '\\') {
			line++;
			switch (*line++) {
				case 'n':	*cp++ = '\n';
						break;
				case 't':	*cp++ = '\t';
						break;
				case 'v':	*cp++ = '\v';
						break;
				case 'b':	*cp++ = '\b';
						break;
				case 'r':	*cp++ = '\r';
						break;
				case 'f':	*cp++ = '\f';
						break;
#ifdef TNOS_68K
				case 'l':	*cp++ = '\l';
						break;
#endif
				case 'a':	*cp++ = '\007';
						break;
				case '\\':	*cp++ = '\\';
						break;
				case '\?':	*cp++ = '\?';
						break;
				case '\'':	*cp++ = '\'';
						break;
				case ';':	*cp++ = ';';
						break;
				case '{':	*cp++ = '{';
						break;
				case '}':	*cp++ = '}';
						break;
				case '>':	*cp++ = '>';
						break;
				case '\"':	*cp++ = '\"';
						break;
				case 'x':	num = strtoul (--line, &line, 16);
						*cp++ = (char) num;
						break;
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':	num = strtoul (--line, &line, 8);
						*cp++ = (char) num;
						break;
				case '\0':	return NULLCHAR;
				default:	*cp++ = *(line - 1);
						break;
			}
		} else
			*cp++ = *line++;
	}

	if (*line == check)
		line++;		/* skip final quote */
	*cp = '\0';		/* terminate string */
	return line;
}



static void
redir_cleanup (void)
{
	if (Curproc == Command->proc && Command->rfile != NULLCHAR)	{
		rflush ();
		kwait (NULL);
		Command->rfile = NULLCHAR;
		fputc ('\n', Command->record);
		(void) fclose (Command->record);
		Command->record = NULLFILE;
	}
}



int
cmdparse (cmds, line, p)
struct cmds cmds[];
register char *line;
void *p;
{
struct cmds *cmdp;
char const *argv[NARG];
char *cp, c;
char **pargv;
int argc, i, isconv;
char *nextline;
int inbrace, indquote, done;

int check4redir = 0;
char *doredir;
int inAcommentline;

	/* Remove cr/lf */
	rip (line);

	if (Curproc == Command->proc)
		check4redir = 1;

loop:			/* this section handles multi-command lines, using a ';' */
	redir_cleanup ();
	nextline = NULLCHAR;
	doredir = NULLCHAR;
	inAcommentline = 0;
	line = skipwhite (line);
	done = inbrace = indquote = 0;
	for (cp = line; *cp && !done; cp++) {
		switch (*cp) {
			case '#':	if (cp == line)
						inAcommentline = 1;
					break;
			case '{':	if (!indquote && !inAcommentline)
						inbrace += 1;
					break;
			case '}':	if (!indquote && !inAcommentline)
						inbrace -= 1;
					break;
			case '\"':	if (!inbrace && !inAcommentline)
						indquote ^= 1;
					break;
			case '\\':	if (!inAcommentline)	{
						c = *(cp + 1);
						if (c == '{' || c == '}' || c == '\"' || c == ';' || (check4redir && c == '>'))
							cp++;
					}
					break;
			case ';':	if (!inbrace && !indquote && !inAcommentline) {
						*cp++ = 0;
						nextline = cp;
						done = 1;
					}
					break;
			case '>':	if (check4redir && !inAcommentline)	{
						*cp++ = 0;
						doredir = cp;
					}
					break;
			default:	break;
		}
	}

	for (argc = 0; argc < NARG; argc++)
		argv[argc] = NULLCHAR;

	if (check4redir && doredir != NULLCHAR)	{
		/* output redirection found */
		Command->rfile = skipwhite (doredir);
		Command->record = fopen (Command->rfile, APPEND_TEXT);
		if (Command->record == NULLFILE)	{
			tprintf ("Can't open %s: %s\n", Command->rfile, SYS_ERRLIST(errno));
			Command->rfile = NULLCHAR;
		}
	}
	for (argc = 0; argc < NARG;) {
		register int qflag = FALSE;
		register int qflag2 = FALSE;

		/* Skip leading white space */
		while (*line == ' ' || *line == '\t')
			line++;
		if (*line == '\0')
			break;
		/* return if comment character first non-white */
		if (argc == 0 && *line == '#')
			goto multichk;
		/* Check for quoted token */
		if (*line == '"' && strchr (line + 1, '"')) {
			line++;	/* Suppress quote */
			qflag = TRUE;
		} else if (*line == '{' && strchr (line + 1, '}')) {	/* Check for braced token */
			line++;	/* Suppress quote */
			qflag2 = TRUE;
		}
		argv[argc++] = line;	/* Beginning of token */

		if (qflag || qflag2) {	/* Find terminating delimiter */
			if ((line = stringparse (line, qflag2)) == NULLCHAR)
				goto errret;
		} else {
			/* Find space or tab. If not present,
			 * then we've already found the last
			 * token.
			 */
			if ((cp = strpbrk (line, " \t")) == NULLCHAR)
				break;
			*cp++ = '\0';
			line = cp;
		}
	}
	if (argc < 1) {		/* empty command line */
		argc = 1;
		argv[0] = "";
	}
#ifdef LOCK
	/* Check to see if this is the Command session.
	 * If so, check to see if the keyboard is locked
	 * Added 12/12/91 WG7J
	 * Also, skips this check if it is a command from the AT queue 12/16/93 KO4KS
	 */
	if ((Curproc->input == Command->input) && strcmp ("AT handler", Curproc->name))
		if (Kblocked) {	/*check argv[0] for password!*/
			if (stricmp (argv[0], Kbpasswd)) {
				tputs ("\nKeyboard remains locked\n");
				return 0;
			}
			Command->ttystate.echo = 1;	/* turn character echo back on ! */
			Kblocked = 0;	/* correct password, so unlock */
			tputc ('\n');
			redir_cleanup ();
			return 0;
		}
#endif

	/* Look up command in table; prefix matches are OK */
	for (cmdp = cmds; cmdp->name != NULLCHAR; cmdp++) {
		if (strncmpi (argv[0], cmdp->name, strlen (argv[0])) == 0)
			break;
	}
	if (cmdp->name == NULLCHAR) {
#ifdef SCRIPTING
		if (Curproc->gotolabel)
			goto multichk;
#endif
		if (cmdp->argc_errmsg != NULLCHAR)
			tprintf ("%s\n", cmdp->argc_errmsg);
		goto errret;
	} else {
#ifdef SCRIPTING
		/* if we are in a goto command, and this is NOT a label
		   command, skip it.... */
		if (Curproc->gotolabel && strcmp ("label", cmdp->name))
			goto multichk;
#endif
		if (argc < cmdp->argcmin) {
			/* Insufficient arguments */
			tprintf ("Usage: %s\n", (cmdp->argc_errmsg) ? cmdp->argc_errmsg : "Try a '?' parameter for help");
			goto errret;
		} else {
			if (cmdp->stksize == 0) {
				i = (*cmdp->func) (argc, (char **) argv, p);
				if (i)
					return i;
				goto multichk;
			} else {
				/* Make private copy of argv and args,
				 * spawn off subprocess and return.
				 */
				isconv = (!stricmp (cmdp->name, "conference")
					  || !stricmp (cmdp->name, "rlogin")
					  || !stricmp (cmdp->name, "quote")
					  || !stricmp (cmdp->name, "tutor")
					  || !stricmp (cmdp->name, "info")
					  || !stricmp (cmdp->name, "news"));
				pargv = (char **) callocw ((unsigned) ((isconv) ? 3 : argc), sizeof (char *));

				for (i = 0; i < argc; i++)
					pargv[i] = strdup (argv[i]);
				(void) newproc (cmdp->name, (unsigned) cmdp->stksize,
					 (void (*)(int, void *, void *)) cmdp->func, (isconv) ? 3 : argc, pargv, p, 1);
				goto multichk;
			}
		}
	}

multichk:
	if (nextline != NULLCHAR) {
		line = nextline;
		goto loop;
	}
	redir_cleanup ();
	return 0;


errret:
	if (nextline != NULLCHAR)
		tprintf ("*** Didn't execute: %s\n", nextline);
	redir_cleanup ();
	return -1;
}


/* Call a subcommand based on the first token in an already-parsed line */
int
subcmd (tab, argc, argv, p)
struct cmds tab[];
int argc;
char *argv[];
void *p;
{
register struct cmds *cmdp;
struct proc *pp;
char **pargv;
int found = 0;
int i;

	/* Strip off first token and pass rest of line to subcommand */
	if (argc < 2) {
		if (argc < 1)
			tputs ("SUBCMD - Don't know what to do?\n");
		else
#if 1
			dohelper ("valid subcommands:\n", tab, NULLCHAR, NULLCHAR, NULLCHAR);
#else
			tprintf ("\"%s\" - takes at least one argument\n", argv[0]);
#endif
		return -1;
	}
	argc--;
	argv++;		/*lint !e608 */
	for (cmdp = tab; cmdp->name != NULLCHAR; cmdp++) {
		if (strncmpi (argv[0], cmdp->name, strlen (argv[0])) == 0) {
			found = 1;
			break;
		}
	}
	if (!found) {
		dohelper ("valid subcommands:\n", tab, NULLCHAR, NULLCHAR, NULLCHAR);
		return -1;
	}
	if (argc < cmdp->argcmin) {
		if (cmdp->argc_errmsg != NULLCHAR)
			tprintf ("Usage: %s\n", cmdp->argc_errmsg);
		return -1;
	}
	if (cmdp->stksize == 0)
		return (*cmdp->func) (argc, argv, p);
	else {
		/* Make private copy of argv and args */
		pargv = (char **) callocw ((unsigned) argc, sizeof (char *));

		for (i = 0; i < argc; i++)
			pargv[i] = strdup (argv[i]);
		pp = newproc (cmdp->name, (unsigned) cmdp->stksize,
			 (void (*)(int, void *, void *)) cmdp->func, argc, pargv, p, 1);
		pp->ptype = cmdparse_newproc_type;
		return (0);
	}
}


/* Subroutine for setting and displaying boolean flags */
int
setbool (var, label, argc, argv)
int *var;
char const *label;
int argc;
char *argv[];
{
struct boolcmd *bc;

	if (argc < 2) {
		tprintf ("%s: %s\n", label, *var ? "on" : "off");
		return 1;
	}
	for (bc = Boolcmds; bc->str != NULLCHAR; bc++) {
		if (strcmpi (argv[1], bc->str) == 0) {
			*var = bc->val;
			return 0;
		}
	}
	tputs ("Valid options:");
	for (bc = Boolcmds; bc->str != NULLCHAR; bc++)
		if (tprintf (" %s", bc->str) == EOF)
			return 1;
	tputc ('\n');
	return 1;
}


/* Subroutine for setting and displaying bit values */
int
bit16cmd (int16 *bits, int16 mask, const char *label, int argc, char *argv[])
{
int doing = (*bits & mask);
int result = setbool (&doing, label, argc, argv);

	if (!result) {
		if (doing)
			*bits |= mask;
		else
			*bits &= ~mask;
	}
	return result;
}


/* Subroutine for setting and displaying long variables */
int
setlong (var, label, argc, argv)
long *var;
char const *label;
int argc;
char *argv[];
{
	if (argc < 2) {
		tprintf ("%s: %ld (0x%08lx)\n", label, *var, *var);
		return 1;
	} else {
		if (!strncmp (argv[1], "0x", 2))
			*var = htol (argv[1]);
		else if (argv[1][0] == '%')
			*var = btol (argv[1]);
		else
			*var = atol (argv[1]);
		return 0;
	}

}


/* Subroutine for setting and displaying short variables */
int
setshort (var, label, argc, argv)
unsigned short *var;
char const *label;
int argc;
char *argv[];
{
	if (argc < 2) {
		tprintf ("%s: %u (0x%04x)\n", label, *var, *var);
		return 1;
	} else {
		if (!strncmp (argv[1], "0x", 2))
			*var = (int16) htoi (argv[1]);
		else if (argv[1][0] == '%')
			*var = (unsigned short) btol (argv[1]);
		else
			*var = (int16) atoi (argv[1]);
		return 0;
	}
}


/* Subroutine for setting and displaying integer variables */
int
setint (var, label, argc, argv)
int *var;
char const *label;
int argc;
char *argv[];
{
	if (argc < 2) {
		tprintf ("%s: %i (0x%04x)\n", label, *var, *var);
		return 1;
	} else {
		if (!strncmp (argv[1], "0x", 2))
			*var = htoi (argv[1]);
		else if (argv[1][0] == '%')
			*var = (int) btol (argv[1]);
		else
			*var = atoi (argv[1]);
		return 0;
	}

}


/* Subroutine for setting and displaying unsigned integer variables */
int
setuns (var, label, argc, argv)
unsigned *var;
char const *label;
int argc;
char *argv[];
{
	if (argc < 2) {
		tprintf ("%s: %u (0x%04x)\n", label, *var, *var);
		return 1;
	} else {
		if (!strncmp (argv[1], "0x", 2))
			*var = (int16) htoi (argv[1]);
		else if (argv[1][0] == '%')
			*var = (unsigned) btol (argv[1]);
		else
			*var = (int16) atoi (argv[1]);
		return 0;
	}
}


/* Subroutine for setting and displaying int variables (with range check) */
int
setintrc (int16 *var, char const *label, int argc, char *argv[], int minval, int16 maxval)
{
int tmp;

	if (argc < 2)
		tprintf ("%s: %u (0x%04x)\n", label, *var, *var);
	else {
		if (!strncmp (argv[1], "0x", 2))
			tmp = htoi (argv[1]);
		else if (argv[1][0] == '%')
			tmp = (int) btol (argv[1]);
		else
			tmp = atoi (argv[1]);
		if (isalpha (*argv[1]) || tmp < minval || tmp > maxval) {
			tprintf ("%s must be %i..%i\n", label, minval, maxval);
			return 1;
		}
		*var = (int16) tmp;
	}
	return 0;
}
