/*
 * Copyright (c) 2002 Todd C. Miller <Todd.Miller@courtesan.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * 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.
 */

/*
 * Copyright (c) 2000 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
 */

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

 #ifdef GETOPT_LONG_INCLUDE_LIBC
  #include <stdarg.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
 #endif

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

  struct i_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	i_getopt_(int argc, char *argv[], const char *optstring);
  int	i_getsubopt_(char *restrict optionp[], const char *restrict tokens[],
                     char *restrict valuep[]);
  int	i_getopt_long_(int argc, char *argv[], const char *optstring,
	               const struct option *longopts, int *longindex);
  int	i_getopt_long_only_(int argc, char *argv[], const char *optstring,
	                    const struct option *longopts, int *longindex);

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

  #define getopt(c,v,o)               i_getopt_(c,v,o)
  #define getsubopt(o,t,v)            i_getsubopt_(o,t,v)
  #define getopt_long(c,v,o,l,i)      i_getopt_long_(c,v,o,l,i)
  #define getopt_long_only(c,v,o,l,i) i_getopt_long_only_(c,v,o,l,i)

 #endif /* !HAVE_GETOPT_LONG */
 
 #ifdef GETOPT_LONG_IMPLEMENTATION
  #define I_PRINT_ERROR_	((i_opterr_) && (*options != ':'))

  #define I_FLAG_PERMUTE_	0x01	/* permute non-options to the end of argv */
  #define I_FLAG_ALLARGS_	0x02	/* treat non-options as args to option "-1" */
  #define I_FLAG_LONGONLY_	0x04	/* operate as i_getopt_long_only */

  /* return values */
  #define	I_BADCH_		(int)'?'
  #define	I_BADARG_		((*options == ':') ? (int)':' : (int)'?')
  #define	I_INORDER_		(int)1

  #define	I_EMSG_			""

  static void i_warnx_(const char *, ...);
  static int i_getopt_internal_(int, char **, const char *,
                            const struct option *, int *, int);
  static int i_parse_long_options__(char * const *, const char *,
                              const struct option *, int *, int);
  static int i_gcd_(int, int);
  static void i_permute_args_(int, int, int, char * const *);


  /* XXX: set i_optreeset_ to 1 rather than these two */
  static int 	i_nonopt_start_ = -1; 	/* first non option argument (for permute) */
  static int 	i_nonopt_end_ = -1;  	/* first option after non options (for permute) */
  static int	i_opterr_ = 1;		/* if error message should be printed */
  static int	i_optind_ = 1;		/* index into parent argv vector */
  static int	i_optopt_ = '?';	/* character checked for validity */
  static int	i_optreeset_;		/* reset getopt */
  static char   *i_optarg_;		/* argument associated with option */
  static char   *i_place_ = I_EMSG_;    /* option letter processing */
  
  /* Error messages */
  static const char i_recargchar_[] = "option requires an argument -- %c";
  static const char i_recargstring_[] = "option requires an argument -- %s";
  static const char i_ambig_[] = "i_ambig_uous option -- %.*s";
  static const char i_noarg_[] = "option doesn't take an argument -- %.*s";
  static const char i_illoptchar_[] = "unknown option -- %c";
  static const char i_illoptstring_[] = "unknown option -- %s";

  /*
   * Own warnx() for portability
   */
  void
  i_warnx_(const char *fmt, ...)
  {
	  va_list args;
	
	  /* Linux "program_invocation_short_name" is a GNU extension, so we don't use it */
	  #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
	   #define I_GET_PROG_NAME_() getprogname()
	  #else
	   #define I_GET_PROG_NAME_() "warnx"
	  #endif

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

  /*
   * Compute the greatest common divisor of a and b.
   */
  static int
  i_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 i_nonopt_start_ to i_nonopt_end_ with the block
   * from i_nonopt_end_ to opt_end (keeping the same order of arguments
   * in each block).
   */
  static void
  i_permute_args_(int pai_nonopt_start_, int pai_nonopt_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 = pai_nonopt_end_ - pai_nonopt_start_;
	  nopts = opt_end - pai_nonopt_end_;
	  ncycle = i_gcd_(nnonopts, nopts);
	  cyclelen = (opt_end - pai_nonopt_start_) / ncycle;

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

  /*
   * i_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
  i_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 = i_place_;
	  match = -1;

	  i_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 {
		      /* i_ambig_uous abbreviation */
		      if (I_PRINT_ERROR_)
		          i_warnx_(i_ambig_, (int)current_argv_len,
				     current_argv);
		      i_optopt_ = 0;
		      return (I_BADCH_);
		  }
	  }
	  if (match != -1) {		/* option found */
		  if (long_options[match].has_arg == no_argument
		       && has_equal) {
		      if (I_PRINT_ERROR_)
		          i_warnx_(i_noarg_, (int)current_argv_len,
		                   current_argv);
		      /*
		       * XXX: GNU sets i_optopt_ to val regardless of flag
		       */
		      if (long_options[match].flag == NULL)
		          i_optopt_ = long_options[match].val;
		      else
		          i_optopt_ = 0;
		      return (I_BADARG_);
		  }
		  if (long_options[match].has_arg == required_argument ||
		      long_options[match].has_arg == optional_argument) {
		      if (has_equal)
		          i_optarg_ = has_equal;
		      else if (long_options[match].has_arg ==
		               required_argument) {
				/*
				 * optional argument doesn't use next nargv
				 */
		          i_optarg_ = nargv[i_optind_++];
		      }
		  }
		  if ((long_options[match].has_arg == required_argument)
		       && (i_optarg_ == NULL)) {
			/*
			 * Missing argument; leading ':' indicates no error
			 * should be generated.
			 */
			if (I_PRINT_ERROR_)
				i_warnx_(i_recargstring_,
				    current_argv);
			/*
			 * XXX: GNU sets i_optopt_ to val regardless of flag
			 */
			if (long_options[match].flag == NULL)
				i_optopt_ = long_options[match].val;
			else
				i_optopt_ = 0;
			--i_optind_;
			return (I_BADARG_);
		  }
	  } else {			/* unknown option */
		  if (short_too) {
		      --i_optind_;
		      return (-1);
		  }
		  if (I_PRINT_ERROR_)
		      i_warnx_(i_illoptstring_, current_argv);
		  i_optopt_ = 0;
		  return (I_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);
  }

  /*
   * i_getopt_internal_ --
   *	Parse argc/argv argument vector.  Called by user level routines.
   */
  static int
  i_getopt_internal_(int nargc, char **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);

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

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

	  i_optarg_ = NULL;
	  if (i_optreeset_)
		  i_nonopt_start_ = i_nonopt_end_ = -1;
	  start:
	  if (i_optreeset_ || !*i_place_) {		/* update scanning pointer */
		  i_optreeset_ = 0;
		  if (i_optind_ >= nargc) {          /* end of argument vector */
		      i_place_ = I_EMSG_;
		      if (i_nonopt_end_ != -1) {
		          /* do permutation, if we have to */
		          i_permute_args_(i_nonopt_start_, i_nonopt_end_,
		                          i_optind_, nargv);
		          i_optind_ -= i_nonopt_end_ - i_nonopt_start_;
		      } else if (i_nonopt_start_ != -1) {
			  /*
			   * If we skipped non-options, set i_optind_
			   * to the first of them.
			   */
			  i_optind_ = i_nonopt_start_;
		      }
		      i_nonopt_start_ = i_nonopt_end_ = -1;
		      return (-1);
		  }
		  if (*(i_place_ = nargv[i_optind_]) != '-' ||
		     (i_place_[1] == '\0' && strchr(options, '-') == NULL)) {
		      i_place_ = I_EMSG_;		/* found non-option */
		      if (flags & I_FLAG_ALLARGS_) {
		          /*
		           * GNU extension:
		           * return non-option as argument to option 1
		           */
		          i_optarg_ = nargv[i_optind_++];
		          return (I_INORDER_);
		      }
		      if (!(flags & I_FLAG_PERMUTE_)) {
				/*
				 * If no permutation wanted, stop parsing
				 * at first non-option.
				 */
				return (-1);
		      }
		      /* do permutation */
		      if (i_nonopt_start_ == -1) {
		          i_nonopt_start_ = i_optind_;
		      } else if (i_nonopt_end_ != -1) {
		          i_permute_args_(i_nonopt_start_, i_nonopt_end_,
		                          i_optind_, nargv);
		          i_nonopt_start_ = i_optind_ -
		                        (i_nonopt_end_ - i_nonopt_start_);
		          i_nonopt_end_ = -1;
		      }
		      i_optind_++;
		      /* process next argument */
		      goto start;
		  }
		  if (i_nonopt_start_ != -1 && i_nonopt_end_ == -1)
		      i_nonopt_end_ = i_optind_;

		  /*
		   * If we have "-" do nothing, if "--" we are done.
		   */
		  if (i_place_[1] != '\0' && *++i_place_ == '-' && i_place_[1] == '\0') {
		      i_optind_++;
		      i_place_ = I_EMSG_;
		      /*
		       * We found an option (--), so if we skipped
		       * non-options, we have to permute.
		       */
		  if (i_nonopt_end_ != -1) {
		      i_permute_args_(i_nonopt_start_, i_nonopt_end_,
		                      i_optind_, nargv);
		      i_optind_ -= i_nonopt_end_ - i_nonopt_start_;
		  }
		      i_nonopt_start_ = i_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 i_getopt_long_only()
	   */
	  if (long_options != NULL && i_place_ != nargv[i_optind_] &&
	     (*i_place_ == '-' || (flags & I_FLAG_LONGONLY_))) {
		  short_too = 0;
		  if (*i_place_ == '-')
		      i_place_++;		/* --foo long option */
		  else if (*i_place_ != ':' 
		           && strchr(options, *i_place_) != NULL)
		      short_too = 1;	/* could be short option too */

		  optchar = i_parse_long_options__(nargv, options, long_options,
		                                 idx, short_too);
		  if (optchar != -1) {
		      i_place_ = I_EMSG_;
	          return (optchar);
	      }
	  }

	  if ((optchar = (int)*i_place_++) == (int)':' ||
	    (optchar == (int)'-' && *i_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)'-' && *i_place_ == '\0')
		      return (-1);
		  if (!*i_place_)
		      ++i_optind_;
		  if (I_PRINT_ERROR_)
		      i_warnx_(i_illoptchar_, optchar);
		  i_optopt_ = optchar;
		  return (I_BADCH_);
	  }
	  if (long_options != NULL && optchar == 'W' && oli[1] == ';') {
		  /* -W long-option */
		  if (*i_place_) {			/* no space */
		      /* NOTHING */;
		  } else if (++i_optind_ >= nargc) {	/* no arg */
		      i_place_ = I_EMSG_;
		      if (I_PRINT_ERROR_)
		          i_warnx_(i_recargchar_, optchar);
		      i_optopt_ = optchar;
		      return (I_BADARG_);
		  } else {				/* white space */
		      i_place_ = nargv[i_optind_];
		  }
		  optchar = i_parse_long_options__(nargv, options, long_options,
		                                   idx, 0);
		  i_place_ = I_EMSG_;
		  return (optchar);
	  }
	  if (*++oli != ':') {			/* doesn't take argument */
		  if (!*i_place_)
		      ++i_optind_;
	  } else {				/* takes (optional) argument */
		  i_optarg_ = NULL;
		  if (*i_place_)		/* no white space */
		      i_optarg_ = i_place_;
		  else if (oli[1] != ':') {	/* arg not optional */
		      if (++i_optind_ >= nargc) {	/* no arg */
		          i_place_ = I_EMSG_;
		          if (I_PRINT_ERROR_)
		              i_warnx_(i_recargchar_, optchar);
		          i_optopt_ = optchar;
		          return (I_BADARG_);
		      } else {
		          i_optarg_ = nargv[i_optind_];
		      }
		  }
		  i_place_ = I_EMSG_;
		  ++i_optind_;
	  }
	  /* dump back option letter */
	  return (optchar);
  }

  int
  i_getopt_(int argc, char *argv[], const char *optstring)
  {
	  #ifdef GETOPT_PERMUTE_ARGS
	   return (i_getopt_internal_(argc, argv, optstring, NULL, NULL,
	           I_FLAG_PERMUTE_));
	  #else
	   return (i_getopt_internal_(argc, argv, optstring, NULL, NULL, 0));
	  #endif
  }

  int
  i_getopt_long_(int argc, char *argv[], const char *optstring,
	     const struct option *longopts, int *longindex)
  {  
	  #ifdef GETOPT_LONG_PERMUTE_ARGS
	   return (i_getopt_internal_(argc, argv, optstring, longopts, longindex,
	           I_FLAG_PERMUTE_));
	  #else
	   return (i_getopt_internal_(argc, argv, optstring, longopts, longindex, 0));
	  #endif

  }

  int
  i_getopt_long_only_(int argc, char *argv[], const char *optstring,
	            const struct option *longopts, int *longindex)
  {
	  #ifdef GETOPT_LONG_ONLY_PERMUTE_ARGS
	   return (i_getopt_internal_(argc, argv, optstring, longopts, longindex,
	           I_FLAG_PERMUTE_ | I_FLAG_LONGONLY_));
	  #else
	   return (i_getopt_internal_(argc, argv, optstring, longopts, longindex,
	           I_FLAG_LONGONLY_));
	  #endif
  }
 #endif /* GETOPT_LONG_IMPLEMENTATION */
#endif /* !GETOPT_LONG_H_ */
