/*
 * Copyright (c) 1990, 1993
 *	The Regents of the University of California.  All rights reserved.
 * Copyright (c) 2000 The NetBSD Foundation, Inc.
 *      All rights reserved.
 * Copyright (c) 2002 Todd C. Miller <Todd.Miller@courtesan.com>
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Dieter Baron and Thomas Klausner.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS”
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * Sponsored in part by the Defense Advanced Research Projects
 * Agency (DARPA) and Air Force Research Laboratory, Air Force
 * Materiel Command, USAF, under agreement number F39502-99-1-0512.
 */

/* https://git.datadissipation.net */
#ifndef LCE_GETOPT_LONG_H
 #define LCE_GETOPT_LONG_H         1

 #ifdef LCE_GETOPT_LONG_INCLUDE_LIBC
  #include <stdarg.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
 #endif /* LCE_GETOPT_LONG_INCLUDE_LIBC */

 #ifndef HAVE_GETOPT_LONG
  #define HAVE_GETOPT_LONG      1
  #ifndef HAVE_GETOPT
   #define HAVE_GETOPT          1
  #endif /* !HAVE_GETOPT */

  #define no_argument           0
  #define required_argument     1
  #define optional_argument     2

/*
 * structs are in their own namespace, so this should be OK
 * the worst it can do is make compiler messages worse
 */
  #define option                lce_option_

  #define optarg                lce_optarg_
  #define suboptarg             lce_suboptarg_
  #define optind                lce_optind_
  #define opterr                lce_opterr_
  #define optopt                lce_optopt_
  #define optreset              lce_optreset_

  #define getopt                lce_getopt_
  #define getsubopt             lce_getsubopt_
  #define getopt_long           lce_getopt_long_
  #define getopt_long_only      lce_getopt_long_only_

  #ifndef LCE_GETOPT_LONG_IMPLEMENTATION
extern char *lce_optarg_;
extern char *lce_suboptarg_;
extern int lce_optind_, lce_opterr_, lce_optopt_, lce_optreset_;
  #endif /* !LCE_GETOPT_LONG_IMPLEMENTATION */

struct lce_option_ {
	const char *name;
	/*
	 * one of no_argument, required_argument, and optional_argument:
	 * whether option takes an argument
	 */
	int has_arg;
	/* if not NULL, set *flag to val when option found */
	int *flag;
	/* if flag not NULL, value to set *flag to; else return value */
	int val;
};

int   lce_getopt_(int argc, char * const *argv, const char *optstring);
int   lce_getsubopt_(char **optionp, char * const *tokens, char **valuep);
int   lce_getopt_long_(int argc, char * const *argv, const char *optstring,
		     const struct option *longopts, int *longindex);
int   lce_getopt_long_only_(int argc, char * const *argv, const char *optstring,
			  const struct option *longopts, int *longindex);

 #endif /* !HAVE_GETOPT_LONG */

 #ifdef LCE_GETOPT_LONG_IMPLEMENTATION
  #define LCE_PRINT_ERROR_	((lce_opterr_) && (*options != ':'))

  #define LCE_FLAG_PERMUTE_	0x01    /* permute non-options to the end of argv */
  #define LCE_FLAG_ALLARGS_	0x02    /* treat non-options as args to option "-1" */
  #define LCE_FLAG_LONGONLY_	0x04    /* operate as getopt_long_only */

/* return values */
  #define	LCE_BADCH_	(int) '?'
  #define	LCE_BADARG_	((*options == ':') ? (int) ':' : (int) '?')
  #define	LCE_INORDER_	(int) 1

  #define	LCE_EMSG_		""

int lce_opterr_ = 1;                      /* if error message should be printed */
int lce_optind_ = 1;                      /* index into parent argv vector */
int lce_optopt_ = '?';                    /* character checked for validity */
int lce_optreset_;                        /* reset getopt */
char  *lce_optarg_;                       /* argument associated with option */
char  *lce_suboptarg_;                    /* argument associated with suboption */

/* XXX: set optreset to 1 rather than these two */
static int lce_nonopt_start_ = -1;        /* first non option argument (for permute) */
static int lce_nonopt_end_ = -1;          /* first option after non options (for permute) */
static char   *lce_place_ = LCE_EMSG_;      /* option letter processing */
static char   *lce_progname_;             /* program name == argv[0] */

/* Error messages */
static const char lce_recargchar_[] = "option requires an argument -- %c";
static const char lce_recargstring_[] = "option requires an argument -- %s";
static const char lce_ambig_[] = "ambiguous option -- %.*s";
static const char lce_noarg_[] = "option doesn't take an argument -- %.*s";
static const char lce_illoptchar_[] = "unknown option -- %c";
static const char lce_illoptstring_[] = "unknown option -- %s";

static void lce_warnx_(const char *, ...);
static int  lce_getopt_internal_(int, char * const *, const char *,
			       const struct option *, int *, int);
static int  lce_parse_long_options__(char * const *, const char *,
				   const struct option *, int *, int);
static int  lce_gcd_(int, int);
static void lce_permute_args_(int, int, int, char * const *);

/*
 * Own warnx() for portability
 */
void
lce_warnx_(const char *fmt, ...)
{
	va_list args;

	va_start(args, fmt);
	fprintf(stderr, "%s: ", lce_progname_);
	vfprintf(stderr, fmt, args);
	fputc('\n', stderr);
	va_end(args);
}

/*
 * Compute the greatest common divisor of a and b.
 */
static int
lce_gcd_(int a, int b)
{
	int c;

	c = a % b;
	while (c != 0) {
		a = b;
		b = c;
		c = a % b;
	}

	return (b);
}

/*
 * Exchange the block from nonopt_start to nonopt_end with the block
 * from nonopt_end to opt_end (keeping the same order of arguments
 * in each block).
 */
static void
lce_permute_args_(int panonopt_start, int panonopt_end, int opt_end,
		char * const *nargv)
{
	int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos;
	char *swap;

	/*
	 * compute lengths of blocks and number and size of cycles
	 */
	nnonopts = panonopt_end - panonopt_start;
	nopts = opt_end - panonopt_end;
	ncycle = lce_gcd_(nnonopts, nopts);
	cyclelen = (opt_end - panonopt_start) / ncycle;

	for (i = 0; i < ncycle; i++) {
		cstart = panonopt_end+i;
		pos = cstart;
		for (j = 0; j < cyclelen; j++) {
			if (pos >= panonopt_end)
				pos -= nnonopts;
			else
				pos += nopts;
			swap = nargv[pos];
			/* LINTED const cast */
			((char **) nargv)[pos] = nargv[cstart];
			/* LINTED const cast */
			((char **) nargv)[cstart] = swap;
		}
	}
}

/*
 * lce_parse_long_options__ --
 *	Parse long options in argc/argv argument vector.
 * Returns -1 if short_too is set and the option does not match long_options.
 */
static int
lce_parse_long_options__(char * const *nargv, const char *options,
		       const struct option *long_options, int *idx, int
		       short_too)
{
	char *current_argv, *has_equal;
	size_t current_argv_len;
	int i, match;

	current_argv = lce_place_;
	match = -1;

	lce_optind_++;

	if ((has_equal = strchr(current_argv, '=')) != NULL) {
		/* argument found (--option=arg) */
		current_argv_len = has_equal - current_argv;
		has_equal++;
	} else {
		current_argv_len = strlen(current_argv);
	}
	for (i = 0; long_options[i].name; i++) {
		/* find matching long option */
		if (strncmp(current_argv, long_options[i].name,
			    current_argv_len))
			continue;

		if (strlen(long_options[i].name) == current_argv_len) {
			/* exact match */
			match = i;
			break;
		}
		/*
		 * If this is a known short option, don't allow
		 * a partial match of a single character.
		 */
		if (short_too && current_argv_len == 1)
			continue;

		if (match == -1) {      /* partial match */
			match = i;
		} else {
			/* ambiguous abbreviation */
			if (LCE_PRINT_ERROR_)
				lce_warnx_(lce_ambig_, (int) current_argv_len,
					 current_argv);
			lce_optopt_ = 0;
			return (LCE_BADCH_);
		}
	}
	if (match != -1) {              /* option found */
		if (long_options[match].has_arg == no_argument
		    && has_equal) {
			if (LCE_PRINT_ERROR_)
				lce_warnx_(lce_noarg_, (int) current_argv_len,
					 current_argv);
			/*
			 * XXX: GNU sets optopt to val regardless of flag
			 */
			if (long_options[match].flag == NULL)
				lce_optopt_ = long_options[match].val;
			else
				lce_optopt_ = 0;
			return (LCE_BADARG_);
		}
		if (long_options[match].has_arg == required_argument ||
		    long_options[match].has_arg == optional_argument) {
			if (has_equal){
				lce_optarg_ = has_equal;
			} else if (long_options[match].has_arg ==
				   required_argument) {
				/*
				 * optional argument doesn't use next nargv
				 */
				lce_optarg_ = nargv[lce_optind_++];
			}
		}
		if ((long_options[match].has_arg == required_argument)
		    && (lce_optarg_ == NULL)) {
			/*
			 * Missing argument; leading ':' indicates no error
			 * should be generated.
			 */
			if (LCE_PRINT_ERROR_)
				lce_warnx_(lce_recargstring_,
					 current_argv);
			/*
			 * XXX: GNU sets optopt to val regardless of flag
			 */
			if (long_options[match].flag == NULL)
				lce_optopt_ = long_options[match].val;
			else
				lce_optopt_ = 0;
			--lce_optind_;
			return (LCE_BADARG_);
		}
	} else {                        /* unknown option */
		if (short_too) {
			--lce_optind_;
			return (-1);
		}
		if (LCE_PRINT_ERROR_)
			lce_warnx_(lce_illoptstring_, current_argv);
		lce_optopt_ = 0;
		return (LCE_BADCH_);
	}
	if (idx)
		*idx = match;
	if (long_options[match].flag) {
		*long_options[match].flag = long_options[match].val;
		return (0);
	} else {
		return (long_options[match].val);
	}
}

/*
 * lce_getopt_internal_ --
 *	Parse argc/argv argument vector.  Called by user level routines.
 */
static int
lce_getopt_internal_(int nargc, char *const *nargv, const char *options,
		   const struct option *long_options, int *idx, int flags)
{
	char *oli;                              /* option letter list index */
	int optchar, short_too;
	static int posix_me_harder = -1;

	if (options == NULL)
		return (-1);

	lce_progname_ = nargv[0];

	/*
	 * XXX Some GNU programs (like cvs) set optind to 0 instead of
	 * XXX using optreset. Work around this braindamage.
	 */
	if (lce_optind_ == 0)
		lce_optind_ = lce_optreset_ = 1;

	/*
	 * Disable GNU extensions if POSIXLY_CORRECT is set or options
	 * string begins with a '+'.
	 */
	if (posix_me_harder == -1 || lce_optreset_)
		posix_me_harder = (getenv("POSIXLY_CORRECT") != NULL);
	if (*options == '-')
		flags |= LCE_FLAG_ALLARGS_;
	else if (posix_me_harder || *options == '+')
		flags &= ~LCE_FLAG_PERMUTE_;
	if (*options == '+' || *options == '-')
		options++;

	lce_optarg_ = NULL;
	if (lce_optreset_)
		lce_nonopt_start_ = lce_nonopt_end_ = -1;
start:
	if (lce_optreset_ || !*lce_place_) {                /* update scanning pointer */
		lce_optreset_ = 0;
		if (lce_optind_ >= nargc) {               /* end of argument vector */
			lce_place_ = LCE_EMSG_;
			if (lce_nonopt_end_ != -1) {
				/* do permutation, if we have to */
				lce_permute_args_(lce_nonopt_start_, lce_nonopt_end_,
						lce_optind_, nargv);
				lce_optind_ -= lce_nonopt_end_ - lce_nonopt_start_;
			} else if (lce_nonopt_start_ != -1) {
				/*
				 * If we skipped non-options, set optind
				 * to the first of them.
				 */
				lce_optind_ = lce_nonopt_start_;
			}
			lce_nonopt_start_ = lce_nonopt_end_ = -1;
			return (-1);
		}
		if (*(lce_place_ = nargv[lce_optind_]) != '-' ||
		    (lce_place_[1] == '\0' && strchr(options, '-') == NULL)) {
			lce_place_ = LCE_EMSG_;             /* found non-option */
			if (flags & LCE_FLAG_ALLARGS_) {
				/*
				 * GNU extension:
				 * return non-option as argument to option 1
				 */
				lce_optarg_ = nargv[lce_optind_++];
				return (LCE_INORDER_);
			}
			if (!(flags & LCE_FLAG_PERMUTE_)) {
				/*
				 * If no permutation wanted, stop parsing
				 * at first non-option.
				 */
				return (-1);
			}
			/* do permutation */
			if (lce_nonopt_start_ == -1) {
				lce_nonopt_start_ = lce_optind_;
			} else if (lce_nonopt_end_ != -1) {
				lce_permute_args_(lce_nonopt_start_, lce_nonopt_end_,
						lce_optind_, nargv);
				lce_nonopt_start_ = lce_optind_ -
						  (lce_nonopt_end_ -
						   lce_nonopt_start_);
				lce_nonopt_end_ = -1;
			}
			lce_optind_++;
			/* process next argument */
			goto start;
		}
		if (lce_nonopt_start_ != -1 && lce_nonopt_end_ == -1)
			lce_nonopt_end_ = lce_optind_;

		/*
		 * If we have "-" do nothing, if "--" we are done.
		 */
		if (lce_place_[1] != '\0' && *++lce_place_ == '-' && lce_place_[1] ==
		    '\0') {
			lce_optind_++;
			lce_place_ = LCE_EMSG_;
			/*
			 * We found an option (--), so if we skipped
			 * non-options, we have to permute.
			 */
			if (lce_nonopt_end_ != -1) {
				lce_permute_args_(lce_nonopt_start_, lce_nonopt_end_,
						lce_optind_, nargv);
				lce_optind_ -= lce_nonopt_end_ - lce_nonopt_start_;
			}
			lce_nonopt_start_ = lce_nonopt_end_ = -1;
			return (-1);
		}
	}

	/*
	 * Check long options if:
	 *  1) we were passed some
	 *  2) the arg is not just "-"
	 *  3) either the arg starts with -- we are lce_getopt_long_only()
	 */
	if (long_options != NULL && lce_place_ != nargv[lce_optind_] &&
	    (*lce_place_ == '-' || (flags & LCE_FLAG_LONGONLY_))) {
		short_too = 0;
		if (*lce_place_ == '-')
			lce_place_++;     /* --foo long option */
		else if (*lce_place_ != ':'
			 && strchr(options, *lce_place_) != NULL)
			short_too = 1;  /* could be short option too */

		optchar = lce_parse_long_options__(nargv, options, long_options,
						 idx, short_too);
		if (optchar != -1) {
			lce_place_ = LCE_EMSG_;
			return (optchar);
		}
	}

	if ((optchar = (int) *lce_place_++) == (int) ':' ||
	    (optchar == (int) '-' && *lce_place_ != '\0') ||
	    (oli = strchr(options, optchar)) == NULL) {
		/*
		 * If the user specified "-" and  '-' isn't listed in
		 * options, return -1 (non-option) as per POSIX.
		 * Otherwise, it is an unknown option character (or ':').
		 */
		if (optchar == (int) '-' && *lce_place_ == '\0')
			return (-1);
		if (!*lce_place_)
			++lce_optind_;
		if (LCE_PRINT_ERROR_)
			lce_warnx_(lce_illoptchar_, optchar);
		lce_optopt_ = optchar;
		return (LCE_BADCH_);
	}
	if (long_options != NULL && optchar == 'W' && oli[1] == ';') {
		/* -W long-option */
		if (*lce_place_) {                        /* no space */
			/* NOTHING */
			;
		} else if (++lce_optind_ >= nargc) {      /* no arg */
			lce_place_ = LCE_EMSG_;
			if (LCE_PRINT_ERROR_)
				lce_warnx_(lce_recargchar_, optchar);
			lce_optopt_ = optchar;
			return (LCE_BADARG_);
		} else {                                /* white space */
			lce_place_ = nargv[lce_optind_];
		}
		optchar = lce_parse_long_options__(nargv, options, long_options,
						 idx, 0);
		lce_place_ = LCE_EMSG_;
		return (optchar);
	}
	if (*++oli != ':') {                            /* doesn't take argument */
		if (!*lce_place_)
			++lce_optind_;
	} else {                                        /* takes (optional) argument */
		lce_optarg_ = NULL;
		if (*lce_place_){                         /* no white space */
			lce_optarg_ = lce_place_;
		} else if (oli[1] != ':'){              /* arg not optional */
			if (++lce_optind_ >= nargc) {     /* no arg */
				lce_place_ = LCE_EMSG_;
				if (LCE_PRINT_ERROR_)
					lce_warnx_(lce_recargchar_, optchar);
				lce_optopt_ = optchar;
				return (LCE_BADARG_);
			} else {
				lce_optarg_ = nargv[lce_optind_];
			}
		}
		lce_place_ = LCE_EMSG_;
		++lce_optind_;
	}
	/* dump back option letter */
	return (optchar);
}

int
lce_getsubopt_(char **optionp, char * const *tokens, char **valuep)
{
	int cnt;
	char *p;

	lce_suboptarg_ = *valuep = NULL;

	if (!optionp || !*optionp)
		return (-1);

	/* skip leading white-space, commas */
	for (p = *optionp; *p && (*p == ',' || *p == ' ' || *p == '\t'); ++p) ;

	if (!*p) {
		*optionp = p;
		return (-1);
	}

	/* save the start of the token, and skip the rest of the token. */
	for (lce_suboptarg_ = p;
	     *++p && *p != ',' && *p != '=' && *p != ' ' && *p != '\t';) ;

	if (*p) {
		/*
		 * If there's an equals sign, set the value pointer, and
		 * skip over the value part of the token.  Terminate the
		 * token.
		 */
		if (*p == '=') {
			*p = '\0';
			for (*valuep = ++p;
			     *p && *p != ',' && *p != ' ' && *p != '\t'; ++p) ;
			if (*p)
				*p++ = '\0';
		} else {
			*p++ = '\0';
		}
		/* Skip any whitespace or commas after this token. */
		for (; *p && (*p == ',' || *p == ' ' || *p == '\t'); ++p) ;
	}

	/* set optionp for next round. */
	*optionp = p;

	for (cnt = 0; *tokens; ++tokens, ++cnt)
		if (!strcmp(lce_suboptarg_, *tokens))
			return (cnt);
	return (-1);
}

int
lce_getopt_(int argc, char * const *argv, const char *optstring)
{
#ifdef LCE_GETOPT_PERMUTE_ARGS
	return (lce_getopt_internal_(argc, argv, optstring, NULL, NULL,
				   LCE_FLAG_PERMUTE_));
#else
	return (lce_getopt_internal_(argc, argv, optstring, NULL, NULL, 0));
#endif /* LCE_GETOPT_PERMUTE_ARGS */
}

int
lce_getopt_long_(int argc, char * const *argv, const char *optstring,
	       const struct option *longopts, int *longindex)
{
#ifdef LCE_GETOPT_LONG_PERMUTE_ARGS
	return (lce_getopt_internal_(argc, argv, optstring, longopts, longindex,
				   LCE_FLAG_PERMUTE_));
#else
	return (lce_getopt_internal_(argc, argv, optstring, longopts, longindex, 0
				   ));
#endif /* LCE_GETOPT_LONG_PERMUTE_ARGS */
}

int
lce_getopt_long_only_(int argc, char * const *argv, const char *optstring,
		    const struct option *longopts, int *longindex)
{
#ifdef LCE_GETOPT_LONG_ONLY_PERMUTE_ARGS
	return (lce_getopt_internal_(argc, argv, optstring, longopts, longindex,
				   LCE_FLAG_PERMUTE_ | I_FLAG_LONGONLY_));
#else
	return (lce_getopt_internal_(argc, argv, optstring, longopts, longindex,
				   LCE_FLAG_LONGONLY_));
#endif /* LCE_GETOPT_LONG_ONLY_PERMUTE_ARGS */
}
 #endif /* LCE_GETOPT_LONG_IMPLEMENTATION */
#endif /* !LCE_GETOPT_LONG_H */
