
/*
	getopts builtin
*/

#include "rc.h"
#include <string.h>

extern char *strtok (char *s1, const char *s2);


enum opt_type_T {invalid_opt, noarg_opt, arg_opt};
typedef enum opt_type_T opt_type_T;


static opt_type_T
is_valid_option (char *opts, char opt)
{
	int pos = 0;
	while (opts [pos] != '\0') {
		if (opts [pos] == opt) {
			return (opts [pos + 1] == ':') ? arg_opt : noarg_opt;
		} else {
			pos += (opts [pos + 1] == ':') ? 2 : 1;
		}
	}
	return invalid_opt;
}


/* based on listassign */

static void
getopts_assign (char *name, char *def, bool stack) {
	List *val, *r;
	char *w;
	if (def == NULL) {
		varassign (name, NULL, stack);
		return;
	}
	r = val = enew (List);
	r->w = NULL;
	w = strtok (def, " ,");
	while (w != NULL) {
		r->w = ecpy (w);
		w = strtok (NULL, " ,");
		if (w != NULL) {
			r->n = enew (List);
			r = r->n;
		}
	}
	r->n = NULL;
	varassign (name, val, stack);
}


/*
	optind is set to (1) on entry to the shell or shell functions (in fn.c and var.c)
	shift (builtins.c) and the deletion of $* (var.c) do not alter optind
*/

extern void
b_getopts (char **av)
{
	static int internal_optind = 0, suboptind = 0;
	char *current_arg, *next_arg, *optname, *return_buffer;
	List *list;
	int loop, optind;
	
#define GETOPTS_RETURN \
	return_buffer = mprint ("%d", optind);\
	varassign ("optind", word (return_buffer, NULL), FALSE);\
	internal_optind = optind;\
	return
	
	if (av [1] == NULL || av [2] == NULL) arg_count ("getopts");
	list = varlookup ("optind");
	if (list == NULL || list->w == NULL) {
		optind = 1;
	} else {
		{
			char *s = list->w;
			optind = stoi (&s);
		}
		if (optind < 1) optind = 1;
	}
	if (internal_optind != optind) suboptind = 0;
	if (av [3] == NULL) {
		/* parse shell context parameters - $* */
		list = varlookup ("*")->n;
		for (loop = 1; loop < optind; loop ++) {
			if (list != NULL) list = list->n;
		}
		if (list == NULL) {
			/* optind has been altered to point beyond the end of the arguments */
			set (FALSE);
			GETOPTS_RETURN;
		}
		current_arg = list->w;
		if (current_arg == NULL) current_arg = "";
		if (list->n == NULL) {
			next_arg = NULL;
		} else {
			next_arg = list->n->w;
		}
	} else {
		/* parse given arguments */
		for (loop = 1; loop < optind; loop ++) {
			if (av [2 + loop] == NULL) {
				/* optind has been altered to point beyond the end of the arguments */
				set (FALSE);
				GETOPTS_RETURN;
			}
		}
		current_arg = av [2 + optind];
		next_arg = av [3 + optind];
	}
	optname = current_arg + suboptind;
	if (suboptind > 0 || *(optname ++) == '-') {
		if (*optname == '-') {
			++ optind;
			set (FALSE);
			GETOPTS_RETURN;
		} else {
			char *return_arg = NULL;
			switch (is_valid_option (av [1], *optname)) {
			case invalid_opt:
				fprint (2, "%s: illegal option '%c'\n", varlookup ("0")->w, *optname);
				/* fallthrough */
			case noarg_opt:
				if (optname [1] == '\0') {
					++ optind;
					suboptind = 0;
				} else {
					suboptind += 1;
				}
				break;
			case arg_opt:
				if (suboptind > 0 || optname [1] != '\0') {
					fprint (2, "%s: options, such as '%c', which take arguments should not be grouped with other options\n", varlookup ("0")->w, *optname);
					if (optname [1] != '\0') {
						return_arg = optname + 1;
						++ optind;
					}
				}
				if (return_arg == NULL) {
					return_arg = next_arg;
					if (return_arg == NULL) {
						return_arg = "";
						fprint (2, "%s: option '%c' requires an argument\n", varlookup ("0")->w, *optname);
						++ optind;
					} else {
						optind += 2;
					}
				}
				suboptind = 0;
				getopts_assign ("optarg", return_arg, FALSE);
				break;
			}
			return_buffer = ealloc (2);
			return_buffer [0] = *optname;
			return_buffer [1] = '\0';
			varassign (av [2], word (return_buffer, NULL), FALSE);
		}
	} else {
		set (FALSE);
		GETOPTS_RETURN;
	}
	set (TRUE);
	GETOPTS_RETURN;
}
