/*
 *
 * Copyright 1998-1999, University of Notre Dame.
 * Authors: Jeffrey M. Squyres, Kinis L. Meyer with M. D. McNally 
 *          and Andrew Lumsdaine
 *
 * This file is part of the Notre Dame LAM implementation of MPI.
 *
 * You should have received a copy of the License Agreement for the
 * Notre Dame LAM implementation of MPI along with the software; see
 * the file LICENSE.  If not, contact Office of Research, University
 * of Notre Dame, Notre Dame, IN 46556.
 *
 * Permission to modify the code and to distribute modified code is
 * granted, provided the text of this NOTICE is retained, a notice that
 * the code was modified is included with the above COPYRIGHT NOTICE and
 * with the COPYRIGHT NOTICE in the LICENSE file, and that the LICENSE
 * file is distributed with the modified code.
 *
 * LICENSOR MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED.
 * By way of example, but not limitation, Licensor MAKES NO
 * REPRESENTATIONS OR WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY
 * PARTICULAR PURPOSE OR THAT THE USE OF THE LICENSED SOFTWARE COMPONENTS
 * OR DOCUMENTATION WILL NOT INFRINGE ANY PATENTS, COPYRIGHTS, TRADEMARKS
 * OR OTHER RIGHTS.  
 *
 * Additional copyrights may follow.
 *
 *	Ohio Trollius
 *	Copyright 1995 The Ohio State University
 *	RBD
 *
 *	$Log: all_opt.c,v $
 *	Revision 6.2  1999/05/23 19:22:15  kmeyer1
 *	added copyright
 *	
 *	Revision 6.1  1996/11/23 19:55:26  nevin
 *	Ohio Release
 *	
 * Revision 6.0  96/02/29  13:36:24  gdburns
 * Ohio Release
 * 
 *	Function:	- generic option parsing package
 *			- uses malloc()
 */

#include <stdlib.h>
#include <string.h>

#include <all_opt.h>
#include <portable.h>
#include <terror.h>
#include <typical.h>

/*
 * local functions
 */
static int		addinst();
static int		countparam();
static int		mutexchk();
static int		strtonum();
static struct ao_opt	*findopt();

/*
 *	ao_init
 *
 *	Function:	- create an option descriptor
 *	Returns:	- ptr option desc. or NULL
 */
OPT *
ao_init()

{
	OPT		*aod;			/* ptr option desc. */

	aod = (OPT *) malloc(sizeof(OPT));

	if (aod) {
		aod->ao_ntaken = 0;
		aod->ao_opttop = 0;
		aod->ao_optend = 0;
		aod->ao_optcur = 0;
		aod->ao_tailc = 0;
		aod->ao_tailv = 0;
	}

	return(aod);
}

/*
 *	ao_free
 *
 *	Function:	- destroy an option descriptor
 *	Accepts:	- ptr option desc.
 */
void
ao_free(aod)

OPT			*aod;

{
	struct ao_inst	*pinst;			/* ptr instance */
	struct ao_opt	*p;			/* favourite pointer */
	int		i;			/* favourite index */

	if (aod == 0) return;
/*
 * Loop free'ing the options.
 */
	while ((p = aod->ao_opttop) != 0) {

		aod->ao_opttop = p->aoo_next;

		if ((pinst = p->aoo_insts) != 0) {
			for (i = 0; i < p->aoo_ninst; ++i, ++pinst) {
				free((char *) pinst->aoi_params);
			}

			free((char *) p->aoo_insts);
		}

		free((char *) p);
	}

	free((char *) aod);
}

/*
 *	ao_ntaken
 *
 *	Function:	- return # taken options
 *	Accepts:	- ptr option descriptor
 *	Returns:	- # taken options
 */
int
ao_ntaken(aod)

OPT			*aod;

{
	return((aod) ? aod->ao_ntaken : 0);
}

/*
 *	ao_taken
 *
 *	Function:	- check if option is taken
 *	Accepts:	- ptr option descriptor
 *			- option string
 *	Returns:	- TRUE or FALSE
 */
int
ao_taken(aod, opt)

OPT			*aod;
CONST char		*opt;

{
	struct ao_opt	*p;			/* favourite pointer */

	if (aod) {
		p = findopt(aod, opt);
		if (p && (p->aoo_flags & AOTAKEN)) return(TRUE);
	}

	return(FALSE);
}

/*
 *	ao_chosen
 *
 *	Function:	- find chosen option in mutex group
 *			- if not a mutex group, returns NULL
 *	Accepts:	- ptr option descriptor
 *			- an option string in mutex group
 *	Returns:	- chosen/taken option or NULL
 */
char *
ao_chosen(aod, opt)

OPT			*aod;
CONST char		*opt;

{
	struct ao_opt	*p;			/* favourite pointer */

	if (aod == 0) return((char *) 0);
/*
 * Loop locating a taken option in mutex group.
 */
	p = findopt(aod, opt);

	if (p->aoo_mutex == 0) p = 0;

	while (p) {
		if (p->aoo_flags & AOTAKEN) {
			aod->ao_optcur = p;
			break;
		}

		p = p->aoo_mutex;
		if (p == aod->ao_optcur) p = 0;
	}

	return((p) ? p->aoo_string : (char *) 0);
}

/*
 *	ao_ninsts
 *
 *	Function:	- get # instances for taken option
 *	Accepts:	- ptr option descriptor
 *			- option string
 *	Returns:	- # instances
 */
int
ao_ninsts(aod, opt)

OPT			*aod;
CONST char		*opt;

{
	struct ao_opt	*p;			/* favourite pointer */

	p = (aod) ? findopt(aod, opt) : (struct ao_opt *) 0;

	return((p && (p->aoo_ninst > 0)) ? p->aoo_ninst : 0);
}

/*
 *	ao_nparams
 *
 *	Function:	- get # parameters for taken option/instance
 *	Accepts:	- ptr option descriptor
 *			- option string
 *			- instance number
 *	Returns:	- # parameters
 */
int
ao_nparams(aod, opt, inst)

OPT			*aod;
CONST char		*opt;
int			inst;

{
	struct ao_opt	*p;			/* favourite pointer */
	int		ret = 0;		/* returned value */

	if (aod) {
		p = findopt(aod, opt);

		if (p && (p->aoo_flags & AOTAKEN) && (p->aoo_insts) &&
				(inst >= 0) && (inst < p->aoo_ninst)) {
			ret = p->aoo_insts[inst].aoi_nparam;
		}
	}

	return(ret);
}

/*
 *	ao_param
 *
 *	Function:	- return a given string parameter for an option
 *	Accepts:	- ptr option descriptor
 *			- option string
 *			- instance number
 *			- parameter index
 *	Returns:	- parameter string or NULL
 */
char *
ao_param(aod, opt, inst, idx)

OPT			*aod;
CONST char		*opt;
int			inst;
int			idx;

{
	struct ao_opt	*p = 0;			/* favourite pointer */
	struct ao_inst	*pinst;			/* ptr instance */
	char		*param = 0;		/* returned parameter */

	if (aod) p = findopt(aod, opt);

	if (p && (p->aoo_flags & AOTAKEN) && !(p->aoo_flags & AOINT) &&
		(p->aoo_insts) && (inst >= 0) && (inst < p->aoo_ninst)) {

		pinst = p->aoo_insts + inst;

		if ((idx >= 0) && (idx < pinst->aoi_nparam)) {
			param = ((char **) pinst->aoi_params)[idx];
		}
	}

	return(param);
}

/*
 *	ao_intparam
 *
 *	Function:	- return a given integer parameter for an option
 *	Accepts:	- ptr option descriptor
 *			- option string
 *			- instance number
 *			- parameter index
 *			- ptr parameter (returned value)
 *	Returns:	- 0 or LAMERROR
 */
int
ao_intparam(aod, opt, inst, idx, pparam)

OPT			*aod;
CONST char		*opt;
int			inst;
int			idx;
int			*pparam;

{
	struct ao_opt	*p = 0;			/* favourite pointer */
	struct ao_inst	*pinst;			/* ptr instance */
	int		ret = LAMERROR;		/* returned value */

	if (aod) p = findopt(aod, opt);

	if (p && (p->aoo_flags & AOTAKEN) && (p->aoo_flags & AOINT) &&
		(p->aoo_insts) && (inst >= 0) && (inst < p->aoo_ninst)) {

		pinst = p->aoo_insts + inst;

		if ((idx >= 0) && (idx < pinst->aoi_nparam)) {
			*pparam = ((int *) pinst->aoi_params)[idx];
			ret = 0;
		}
	}

	return(ret);

}

/*
 *	ao_tail
 *
 *	Function:	- return the tail argc/argv
 *	Accepts:	- ptr option descriptor
 *			- ptr tail argc (returned value)
 *			- ptr tail argv (returned value)
 *	Returns:	- 0 or LAMERROR
 */
int
ao_tail(aod, tailc, tailv)

OPT			*aod;
int			*tailc;
char			***tailv;

{
	int		ret = LAMERROR;		/* returned value */

	if (aod) {
		*tailc = aod->ao_tailc;
		*tailv = aod->ao_tailv;
		ret = 0;
	}

	return(ret);
}

/*
 *	ao_setopt
 *
 *	Function:	- define a valid option
 *			- option "#" is a special case
 *	Accepts:	- ptr option descriptor
 *			- option string
 *			- mutex option (or NULL)
 *			- # parameters (or AOVARNUM)
 *			- option flags
 *	Returns:	- 0 or LAMERROR
 */
int
ao_setopt(aod, opt, mutex, nparams, flags)

OPT			*aod;
CONST char		*opt;
CONST char		*mutex;
int			nparams;
int			flags;

{
	struct ao_opt	*p;			/* favourite pointer */
	struct ao_opt	*mtx;			/* ptr mutex option */
 
	if (aod == 0) return(LAMERROR);
/*
 * If option is "#", force AOINT flag and 1 parameter.
 */
	if (strcmp(opt, "#") == 0) {
		flags |= (flags & AODOARGS) ? 0 : AOINT;
		nparams = 1;
	}
/*
 * If new option create it.
 */
	p = findopt(aod, opt);

	if (p == 0) {
		p = (struct ao_opt *) malloc((unsigned)
				sizeof(struct ao_opt) + strlen(opt) + 1);
		if (p == 0) return(LAMERROR);

		p->aoo_string = ((char *) p) + sizeof(struct ao_opt);
		strcpy(p->aoo_string, opt);

		if (aod->ao_opttop == 0) {
			aod->ao_opttop = p;
		} else {
			aod->ao_optend->aoo_next = p;
		}

		aod->ao_optend = p;
		p->aoo_next = 0;
	}
/*
 * Otherwise, better not have mutex or instances previously defined.
 */
	else if (p->aoo_insts || p->aoo_mutex) {
		return(LAMERROR);
	}
/*
 * Fill the option information.
 */
	p->aoo_flags = flags & ~AOTAKEN;
	p->aoo_nmaxparam = nparams;
	p->aoo_ninst = 0;
	p->aoo_insts = 0;
	p->aoo_mutex = 0;

	if (mutex) {
		mtx = findopt(aod, mutex);
		if (mtx == 0) return(LAMERROR);

		p->aoo_mutex = (mtx->aoo_mutex) ? mtx->aoo_mutex : mtx;
		mtx->aoo_mutex = p;
	}

	aod->ao_optcur = p;

	return(0);
}

/*
 *	ao_setopt1
 *
 *	Function:	- defines several one-letter options
 *			- calls ao_setopt in a loop
 *	Accepts:	- ptr option descriptor
 *			- option string
 *			- mutex option (or NULL)
 *			- # parameters (or AOVARNUM)
 *			- option flags
 *	Returns:	- 0 or LAMERROR
 */
int
ao_setopt1(aod, opt, mutex, nparams, flags)

OPT			*aod;
CONST char		*opt;
CONST char		*mutex;
int			nparams;
int			flags;

{
	CONST char	*p;
	char		opt1[2];

	opt1[1] = '\0';

	for (p = opt; *p; p++) {
		opt1[0] = *p;

		if (ao_setopt(aod, opt1, mutex, nparams, flags)) {
			return(LAMERROR);
		}
	}

	return(0);
}

/*
 *	ao_parse
 *
 *	Function:	- parse the given argc/argv
 *			- remove parsed options from argc/argv
 *			- args after -- are parsed as the tail
 *			- special option "#" matches -number option
 *			- for pure one-letter options, spaces not needed
 *	Accepts:	- ptr option descriptor
 *			- ptr cmd line argc
 *			- cmd line argv
 *	Returns:	- 0 or LAMERROR
 */
int
ao_parse(aod, pcmdc, cmdv)

OPT			*aod;
int			*pcmdc;
char			**cmdv;

{
	int		num;			/* parsed number */
	int		fl_onelet;		/* one-letter options? */
	int		argc;			/* argc for parsing */
	char		optstr[2];		/* 1-let opt. string */
	char		**argv;			/* argv for parsing */
	char		*str;			/* string parsed */
	struct ao_opt	*popt;			/* ptr option */

	if (aod == 0) {
		errno = EINVAL;
		return(LAMERROR);
	}
/*
 * Check if all options are one-letter.
 */
	fl_onelet = 1;

	for (popt = aod->ao_opttop; popt; popt = popt->aoo_next) {

		if (strlen(popt->aoo_string) > 1) {
			fl_onelet = 0;
			break;
		}
	}
/*
 * Skip argv[0].
 */
	argc = *pcmdc - 1;
	argv = cmdv + 1;

	*pcmdc = 1;
/*
 * Loop parsing.
 */
	for ( ; argc > 0; --argc, ++argv) {

		str = *argv;
/*
 * If not an option, pass it through.
 */
		if (*str != '-') {
			cmdv[(*pcmdc)++] = str;
		}
/*
 * If option is "--" we found the tail.
 */
		else if (strcmp(str, "--") == 0) {
			aod->ao_tailc = argc - 1;
			aod->ao_tailv = argv + 1;
			break;
		}
/*
 * If a number, assume '#' option.
 */
		else if (strtonum(++str, &num) == 0) {

			popt = findopt(aod, "#");
			if ((popt == 0) || (mutexchk(popt))) {
				errno = EUSAGE;
				return(LAMERROR);
			}

			if ((popt->aoo_flags & AOTAKEN) == 0) {
				popt->aoo_flags |= AOTAKEN;
				++(aod->ao_ntaken);
			}

			if (addinst(popt, 1, &str)) {
				return(LAMERROR);
			}
		}
/*
 * Handle as a one-letter option.
 * Following letters may be valid options or a parameter.
 */
		else if (fl_onelet && (str[1] != '\0')) {

			while (*str) {

				optstr[0] = *str;
				optstr[1] = '\0';

				popt = findopt(aod, optstr);

				if ((popt == 0) || (mutexchk(popt))) {
					errno = EUSAGE;
					return(LAMERROR);
				}

				if ((popt->aoo_flags & AOTAKEN) == 0) {
					popt->aoo_flags |= AOTAKEN;
					++(aod->ao_ntaken);
				}
/*
 * If option takes no parameters, consider next option letter.
 */
				if (popt->aoo_nmaxparam == 0) {
					++str;
				}
/*
 * If option can be followed by one argument and we're not at
 * the end-of-string, treat the rest-of-string as a parameter.
 */
				else if ((popt->aoo_nmaxparam == 1) &&
						(str[1] != '\0')) {

					++str;
					if (addinst(popt, 1, &str)) {
						return(LAMERROR);
					}

					str += strlen(str);
				}
/*
 * Otherwise handle parameters as usual.
 */
				else {
					num = countparam(aod, popt, argv + 1);
					if (num < 0) return(LAMERROR);

					if (addinst(popt, num, argv + 1)) {
						return(LAMERROR);
					}

					argv += num;
					argc -= num;
					break;
				}
			}
		}
/*
 * Handle as a regular option.
 */
		else {
			popt = findopt(aod, str);

			if ((popt == 0) || (mutexchk(popt))) {
				errno = EUSAGE;
				return(LAMERROR);
			}

			if ((popt->aoo_flags & AOTAKEN) == 0) {
				popt->aoo_flags |= AOTAKEN;
				++(aod->ao_ntaken);
			}
/*
 * Add parameters if any.
 */
			num = countparam(aod, popt, argv + 1);
			if (num < 0) return(LAMERROR);

			if (addinst(popt, num, argv + 1)) {
				return(LAMERROR);
			}

			argv += num;
			argc -= num;
		}
	}

	cmdv[*pcmdc] = 0;
/*
 * Check the "must" take options.
 */
	for (popt = aod->ao_opttop; popt; popt = popt->aoo_next) {

		if ((popt->aoo_flags & AOMUST) &&
			((popt->aoo_flags & AOTAKEN) == 0)) {
/*
 * If mutex, check that one of them is taken.
 */
			str = 0;
			if (popt->aoo_mutex) {
				str = ao_chosen(aod, popt->aoo_string);
			}
/*
 * If no mutex taken or not a mutex case, it's an error.
 */
			if (str == 0) {
				errno = EUSAGE;
				return(LAMERROR);
			}
		}
	}

	return(0);
}

/*
 *	findopt
 *
 *	Function:	- locate given option
 *	Accepts:	- ptr option descriptor
 *			- option string
 *	Returns:	- ptr option or NULL
 */
static struct ao_opt *
findopt(desc, optstr)

OPT			*desc;
char			*optstr;

{
	struct ao_opt	*p;			/* favourite pointer */
/*
 * Check if current option matches.
 */
	p = desc->ao_optcur;

	if (p && (strcmp(p->aoo_string, optstr) == 0)) return(p);
/*
 * Otherwise search for a match.
 */
	for (p = desc->ao_opttop; p; p = p->aoo_next) {

		if (strcmp(p->aoo_string, optstr) == 0) {
			desc->ao_optcur = p;
			break;
		}
	}

	if (p == 0) errno = EINVAL;

	return(p);
}

/*
 *	strtonum
 *
 *	Function:	- convert string to number if possible
 *			- handles decimal/octal/hexadecimal
 *			- uses strtol()
 *	Accepts:	- string
 *			- ptr number (returned value)
 *	Returns:	- 0 or LAMERROR
 */
static int
strtonum(str, pnum)

char			*str;
int			*pnum;

{
	char		*endstr;		/* end of parsed string */

	*pnum = (int) strtol(str, &endstr, 0);

	if (*endstr) {
		errno = EBADASCIINUMB;
		return(LAMERROR);
	}

	return(0);
}

/*
 *	mutexchk
 *
 *	Function:	- check mutex validity
 *			- if any other taken, it's an error
 *	Accepts:	- ptr option
 *	Returns:	- 0 or LAMERROR
 */
static int
mutexchk(opt)

struct ao_opt		*opt;

{
	struct ao_opt	*p;			/* favourite pointer */

	for (p = opt->aoo_mutex; p && (p != opt); p = p->aoo_mutex) {
		if (p->aoo_flags & AOTAKEN) return(LAMERROR);
	}

	return(0);
}

/*
 *	countparam
 *
 *	Function:	- get # parameters following option
 *			- loop counting till 1st valid option
 *	Accepts:	- ptr option descriptor
 *			- ptr option
 *			- ptr remaining argv
 *	Returns:	- # parameters or LAMERROR
 */
static int
countparam(aod, opt, argv)

OPT			*aod;
struct ao_opt		*opt;
char			**argv;

{
	int		n;			/* counter */
	int		num;			/* junk number */
	char		*string;		/* arg. string */
/*
 * Loop counting till first valid option or end-of args.
 */
	for (n = 0; *argv && (n != opt->aoo_nmaxparam); ++n, ++argv) {

		string = *argv;
		if (*string != '-') continue;

		if (strcmp(string, "--") == 0) break;

		if ((strtonum(string + 1, &num) == 0) &&
				(findopt(aod, "#") != 0)) break;

		if (findopt(aod, string + 1)) break;
	}
/*
 * If expected fixed number not matching, it's an error.
 */
	if ((opt->aoo_nmaxparam > 0) && (n != opt->aoo_nmaxparam)) {
		errno = EUSAGE;
		return(LAMERROR);
	}

	return(n);
}

/*
 *	addinst
 *
 *	Function:	- add instance parameters to option
 *			- convert integer parameters if needed
 *	Accepts:	- ptr option
 *			- # parameters to add
 *			- parameter argv
 *	Returns:	- 0 or LAMERROR
 */
static int
addinst(opt, nparam, params)

struct ao_opt		*opt;
int			nparam;
char			**params;

{
	int		fl_int;			/* integer parameters */
	int		num;			/* parsed number */
	unsigned	size;			/* used for malloc() */
	struct ao_inst	*pinst;			/* ptr instance */
	char		*parmbuf;		/* tmp param buffer */

	if (nparam == 0) {
		++(opt->aoo_ninst);
		return(0);
	}

	fl_int = opt->aoo_flags & AOINT;
/*
 * Allocate the parameter buffer.
 */
	size = (fl_int) ? sizeof(int) : sizeof(char *);

	parmbuf = malloc(size * nparam);

	if (parmbuf == 0) return(LAMERROR);
/*
 * Allocate or expand instance array.
 */
	size = sizeof(struct ao_inst);

	if (opt->aoo_insts == 0) {
		pinst = (struct ao_inst *) malloc(size);
	} else {
		pinst = (struct ao_inst *) realloc((char *) opt->aoo_insts,
						(opt->aoo_ninst + 1) * size);
	}

	if (pinst == 0) {
		free((char *) parmbuf);
		return(LAMERROR);
	}

	opt->aoo_insts = pinst;
	pinst += (opt->aoo_ninst)++;
/*
 * Add the parameters, converting integers if needed.
 */
	size = (fl_int) ? sizeof(int) : sizeof(char *);

	pinst->aoi_nparam = nparam;
	pinst->aoi_params = (void *) parmbuf;

	for (; nparam > 0; --nparam, ++params, parmbuf += size) {

		if (fl_int) {
			if (strtonum(*params, &num)) {
				errno = EUSAGE;
				return(LAMERROR);
			}

			*((int *) parmbuf) = num;
		}
		else {
			*((char **) parmbuf) = *params;
		}
	}

	return(0);
}
