#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 -- the argument vector as passed to main().
**		argd -- the argument descriptor array.
**
**	Returns:
**		Nothing
**			Exits with return code 20 if error in args.
**			Exits with return code 10 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;
	BOOL noflags;
	BOOL error;
	extern char *getenv ARGS((char *));

	av = argv++;
	/* 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;
	ad = NULL; /* No argument requested */
	list = NULL;
	while (*++av != CHARNULL)
	{
		/* Previous keyword required a value */
		if(ad)
		{
			/* try to convert the type */
			if (!HANDLE(ad, *av, FALSE))
				error = TRUE;
			else
				ad->ad_flags |= ARGGIVEN;
			ad = NULL;
			continue;
		}
		
		/* If looking for keywords, see if this is one */
		if(!noflags) {
			for(ALL_AD)
				if(match(*av, ad->ad_prompt) == 0)
					break;
			if(ad->ad_name == '\0')
				for(ALL_DEFS)
					if(match(*av, ad->ad_prompt) == 0)
						break;
		}
		if(ad->ad_name == '\0')
			ad = NULL;

		/* If we have a keyword here */
		if(!noflags && ad)
		{
			list = NULL;
			p = strchr(*av, '=');
			if(p) /* matched NAME=VALUE */
			{
				p++;
				/* try to convert the type */
				if (!HANDLE(ad, p, FALSE))
					error = TRUE;
				else
					ad->ad_flags |= ARGGIVEN;
				ad = NULL;
			}
			else
			{
				if (ad->ad_type == argBool)
				{
					*(BOOL *) ad->ad_valp = TRUE;
					ad->ad_flags |= ARGGIVEN;
					ad = NULL;
				}
				else if (ad->ad_type == argEnd)
				{
					noflags = TRUE;
					ad->ad_flags |= ARGGIVEN;
					ad = NULL;
				}
				else if (ad->ad_flags & ARGLIST)
				{
					list = ad;
					ad = NULL;
				}
			}
		}
		else /* it's a positional argument */
		{
			if(list) {
				if (!HANDLE(list, *av, FALSE))
					error = TRUE;
				list->ad_flags |= ARGGIVEN;
				continue;
			}
			else
			{
				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 = TRUE;
					ad = NULL;
				}
				else
				{
					/* try to convert */
					if (!HANDLE(ad, *av, FALSE))
						error = TRUE;
					else
						ad->ad_flags |= ARGGIVEN;
					ad = NULL;
				}
			}
		}
	}

	/* If last argument was a keyword and required an option
	 * then complain about it
	 */
	if(ad) {
		usrerr("missing argument for %s", ad->ad_prompt);
		error = TRUE;
	}
	*argv = NULL;

	/* 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(20);
	}
	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)
	{
		char keyword[BUFSIZ];
		char name[BUFSIZ];
		int i, j;

		j = 0;
		for(i = 0; ad->ad_prompt[i]; i++) {
			if(isupper(ad->ad_prompt[i])) {
				keyword[j++] = ad->ad_prompt[i];
				name[i] = tolower(ad->ad_prompt[i]);
			}
			else
				name[i] = ad->ad_prompt[i];
		}
		name[i] = 0;
		if(j > 0)
			keyword[j] = 0;
		else
			strcpy(keyword, ad->ad_prompt);

		/* 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 = strlen(keyword);
			if (ad->ad_type != argBool)
				pl += strlen(name) + 3;/* _< > */
		}
		else
		{
			pl = strlen(name) + 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, "%s", keyword);
			if (ad->ad_type != argBool)
				fprintf(stderr, " ");
		}
		if (ad->ad_name == ' ' || ad->ad_type != argBool)
			fprintf(stderr, "<%s>", name);
		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;
}
