#include <useful.h>
#include <parseargs.h>
#include <ctype.h>

VERSIONID("$Header: parseargs.c,v 2.1 89/12/30 20:59:48 eric Exp $");

/*
**  PARSEARGS -- parse an argument vector, given a description
**
**	Parameters:
**		argv -- pointer to the argument vector as passed to main().
**		argd -- the argument descriptor array.
**
**	Returns:
**		Nothing.
**			Exits with return code 2 if error in args.
**			Exits with return code 1 if system error.
**
**	Side Effects:
**		Converts and stores arguments into variables as
**		described by argd.
**
**	Globals:
**		DefaultPath -- the pathname of a set of places to
**			look for system files, set from the ROOTPATH
**			environment variable, or a default.
**		ProgName -- the name of this program, saved for error
**			messages and the like.
**
**	Author:
**		Eric Allman
**		University of California, Berkeley
*/

#define ALL_AD		ad = argd; ad->ad_name != '\0'; ad++
#define ALL_DEFS	ad = _DefaultArgs; ad->ad_name != '\0'; ad++

#define HANDLE(a,n,f) ((*(a)->ad_type)(a,n,f))

char	*ProgName;

extern BOOL	argEnd ARGS((ARGDESC *, char *, BOOL));

/* default arguments -- apply to all programs */
STATIC ARGDESC	_DefaultArgs[] =
{
     /* name	flags	type		valp		prompt		*/
	'-',	ARGOPT,	argEnd,		ARBNULL,	"ARGS",
	ENDOFARGS
};

/* override argument descriptor, if none given by user */
STATIC ARGDESC	_NullArgDesc[] =
{
	ENDOFARGS
};

VOID
parseargs(argv, argd)
	char **argv;
	ARGDESC argd[];
{
	register ARGDESC *ad, *list;
	register char **av;
	register char *p;
	int argc;
	BOOL noflags;
	BOOL error;
	extern char *getenv ARGS((char *));

	av = argv++;
	argc = 1;

	/* save the name of this program (for error messages) */
	ProgName = *av;

	/* allow null argument descriptor */
	if (argd == (ARGDESC *) NULL)
		argd = _NullArgDesc;

	/* clear out any cruft in the argument descriptor */
	for (ALL_AD)
	{
		ad->ad_flags &= ~ARGGIVEN;
	}
	for (ALL_DEFS)
	{
		ad->ad_flags &= ~ARGGIVEN;
	}

	/* run through the argument vector */
	noflags = FALSE;
	error = FALSE;
	list = NULL;
	while ((p = *++av) != CHARNULL)
	{
		if(*p == '+' && !noflags)
		{
			char *s;
			
			s = strchr(p, '=');
			if(s) {
				*s++ = 0;
			}

			if(*++p == '+' && p[1] == '\0')
			{
				/* ++ indicates end of flags */
				noflags = TRUE;
				list = NULL;
				continue;
			}
			for(ALL_AD)
			{
				if(match(p, ad->ad_prompt) == 0)
					break;
			}
			if (ad->ad_name == '\0')
			{
				for (ALL_DEFS)
				{
					if(match(p, ad->ad_prompt) == 0)
						break;
				}
				if (ad->ad_name == '\0')
				{
					usrerr("option %s unknown", p);
					error = TRUE;
					continue;
				}
			}

			/* booleans are special, having no value */
			if (ad->ad_type == argBool)
			{
				if(s) /* Error... no arg to bool */
				{
					usrerr("option %s takes no arguments", p);
					error = TRUE;
					continue;
				}
				*(BOOL *) ad->ad_valp = TRUE;
				ad->ad_flags |= ARGGIVEN;
				continue;
			}

			/* now get the real value */
			if (!s)
			{
				s = *++av;
				if (s == CHARNULL || *s == '-' || *s == '+')
				{
					av--;
					usrerr("option %s requires an argument",
						ad->ad_prompt);
					error = TRUE;
					continue;
				}
			}

			/* try to convert the type */
			if (!HANDLE(ad, s, FALSE))
				error = TRUE;
			else
				ad->ad_flags |= ARGGIVEN;
			if(ad->ad_flags & ARGLIST)
				list = ad;
			else
				list = NULL;
			continue;
		}
		else if (*p == '-' && !noflags)
		{
			/* flag argument */
			if (*++p == '-' && p[1] == '\0')
			{
				/* -- indicates end of flags */
				noflags = TRUE;
				list = NULL;
				continue;
			}
			while (*p != '\0')
			{
				/* find the flag in the list */
				for (ALL_AD)
				{
					if (ad->ad_name == *p)
						break;
				}
				if (ad->ad_name == '\0')
				{
					for (ALL_DEFS)
					{
						if (ad->ad_name == *p)
							break;
					}
					if (ad->ad_name == '\0')
					{
						usrerr("flag -%c unknown", *p++);
						error = TRUE;
						continue;
					}
				}

				/* move p up to point to the (possible) value */
				p++;

				/* booleans are special, having no value */
				if (ad->ad_type == argBool)
				{
					*(BOOL *) ad->ad_valp = TRUE;
					ad->ad_flags |= ARGGIVEN;
					continue;
				}

				/* now get the real value */
				if (*p == '\0')
				{
					p = *++av;
					if (p == CHARNULL || *p == '-' || *p == '+')
					{
						av--;
						usrerr("%s required for -%c flag",
							ad->ad_prompt, ad->ad_name);
						error = TRUE;
						break;
					}
				}

				/* try to convert the type */
				if (!HANDLE(ad, p, FALSE))
					error = TRUE;
				else
					ad->ad_flags |= ARGGIVEN;
				if(ad->ad_flags & ARGLIST)
					list = ad;
				else
					list = NULL;
				break;
			}
		}
		else
		{
			/* parsing a list of arguments */
			if(list) {
				if (!HANDLE(list, p, FALSE))
					error = TRUE;
				continue;
			}
			/* positional argument */
			for (ALL_AD)
			{
				if (ad->ad_name == ' ' &&
				    ( (ad->ad_flags & ARGLIST) ||
				     !BITSET(ARGGIVEN, ad->ad_flags))
				    )
					break;
			}
			if (ad->ad_name == '\0')
			{
				usrerr("too any arguments");
				error = 1;
				continue;
			}

			/* try to convert */
			if (!HANDLE(ad, p, FALSE))
				error = TRUE;
			else
				ad->ad_flags |= ARGGIVEN;
		}
	}

	/* now rescan for missing required arguments */
	for (ALL_AD)
	{
		if (BITSET(ARGREQ, ad->ad_flags) && !BITSET(ARGGIVEN, ad->ad_flags))
		{
			if (!BITSET(ARGGIVEN, ad->ad_flags))
			{
				/* still didn't get a value... sigh */
				if (ad->ad_name == ' ')
				{
					usrerr("%s required",
						ad->ad_prompt);
				}
				else
				{
					usrerr("%s required for -%c flag",
						ad->ad_prompt, ad->ad_name);
				}
				error = TRUE;
			}
		}
	}

	if (error)
	{
		usage(argd);
		exit(2);
	}

	cleanup_lists(argd);
}
/*
**  USAGE -- print a usage message
**
**	Parameters:
**		argd -- the description of expected arguments.
**
**	Returns:
**		none
**
**	Side Effects:
**		prints on stderr
**
**	Globals:
**		MaxOutputLine -- the length of the maximum output line
**			allowed before wrapping.  This should be fetched
**			from the terminal driver on systems that support
**			this sort of thing.
*/

int	MaxOutputLine = 72;

VOID
usage(argd)
	ARGDESC *argd;
{
	register ARGDESC *ad;
	int ll;
	int pl;

	fprintf(stderr, "Usage: %s", ProgName);
	ll = strlen(ProgName) + 7;

	for (ALL_AD)
	{
		/* don't display hidden arguments */
		if (BITSET(ARGHIDDEN, ad->ad_flags))
			continue;

		/* figure out how wide this parameter is (for printing) */
		if (ad->ad_name != ' ')
		{
			pl = 2;					/* -x */
			pl += strlen(ad->ad_prompt) + 3;/* _< >  or _( ) */
		}
		else
		{
			pl = strlen(ad->ad_prompt) + 2;		/* < > */
		}
		if (!BITSET(ARGREQ, ad->ad_flags))
			pl += 2;				/* [ ] */
		if (ad->ad_flags & ARGLIST)			/* ... */
			pl += 3;
		pl += 1;					/* leading sp */

		/* see if this will fit */
		if (ll + pl > MaxOutputLine)
		{
			/* no... start a new line */
			fprintf(stderr, " \\\n\t");
			ll = 7;
		}
		else
		{
			/* yes... just throw in a space */
			fprintf(stderr, " ");
		}
		ll += pl;

		/* show the argument */
		if (!BITSET(ARGREQ, ad->ad_flags))
			fprintf(stderr, "[");
		if (ad->ad_name != ' ')
		{
			fprintf(stderr, "-%c ", ad->ad_name);
		}
		if (ad->ad_name == ' ' || ad->ad_type != argBool)
			fprintf(stderr, "<%s>", ad->ad_prompt);
		else
			fprintf(stderr, "(%s)", ad->ad_prompt);
		if (!BITSET(ARGREQ, ad->ad_flags))
			fprintf(stderr, "]");
		if (ad->ad_flags & ARGLIST)
			fprintf(stderr, "...");
	}
	fprintf(stderr, "\n");
}

/* match(s1, s2)
**
** Compares two strings, returning >0, <0, or =0 if they match. First a
** check is done on letters capitalised in the second word, and if this
** fails then a complete match is done. Case is ignored in both matches.
** This lets you use case to indicate what part of a keyword is significant.
*/
int match(candidate, target)
char *target, *candidate;
{
	int i, j;
	char c;

	i = j = 0;

	while(target[i] || candidate[i]) {
		while(islower(target[i])) i++;
		if(!target[i]) {
			if(!candidate[j]) return 0;
			return dictcmp(target, candidate);
		}
		c = islower(candidate[j])
		    ? toupper(candidate[j])
		    : candidate[j];
		if(target[i] != c) return dictcmp(target, candidate);
		i++;
		j++;
	}
	return 0;
}

int dictcmp(s1, s2)	/* "Dictionary" comparison of two strings */
char *s1, *s2;
{
	char c1, c2;

	while(*s1 || *s2) {
		c1 = *s1++;
		c2 = *s2++;
		if(!c1 || !c2) return c1 - c2;
		if(isupper(c1)) c1 = tolower(c1);
		if(isupper(c2)) c2 = tolower(c2);
		if(c1 != c2) return c1 - c2;
	}
	return 0;
}
