/*
 *  smoke16/src/getopt.c - An implementation of GNU-style getopt() functions
 *  $Revision: 1.16 $
 *  Copyright 1997, 2000, Benjamin Sittler
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "getopt.h"

int optind=1, opterr=1, optopt=0;
char *optarg=0;

/* this is the plain old UNIX getopt, with GNU-style extensions. */
/* if you're porting some piece of UNIX software, this is all you need. */
/* this supports GNU-style permution and optional arguments */

int my_getopt(int argc, char * argv[], const char *opts)
{
  static int charind=0;
  const char *s;
  char mode, colon_mode;
  int off = 0, opt = -1;

  if(getenv("POSIXLY_CORRECT")) colon_mode = mode = '+';
  else {
    if((colon_mode = *opts) == ':') off ++;
    if(((mode = opts[off]) == '+') || (mode == '-')) {
      off++;
      if((colon_mode != ':') && ((colon_mode = opts[off]) == ':'))
	off ++;
    }
  }
  optarg = 0;
  if(charind) {
    optopt = argv[optind][charind];
    for(s=opts+off; *s; s++) if(optopt == *s) {
      charind++;
      if((*(++s) == ':') || ((optopt == 'W') && (*s == ';'))) {
	if(argv[optind][charind]) {
	  optarg = &(argv[optind++][charind]);
	  charind = 0;
	} else if(*(++s) != ':') {
	  charind = 0;
	  if(++optind >= argc) {
	    if(opterr) fprintf(stderr,
				"%s: option requires an argument -- %c\n",
				argv[0], optopt);
	    opt = (colon_mode == ':') ? ':' : '?';
	    goto my_getopt_ok;
	  }
	  optarg = argv[optind++];
	}
      }
      opt = optopt;
      goto my_getopt_ok;
    }
    if(opterr) fprintf(stderr,
			"%s: illegal option -- %c\n",
			argv[0], optopt);
    opt = '?';
    if(argv[optind][++charind] == '\0') {
      optind++;
      charind = 0;
    }
  my_getopt_ok:
    if(charind && ! argv[optind][charind]) {
      optind++;
      charind = 0;
    }
  } else if((optind >= argc) ||
	     ((argv[optind][0] == '-') &&
	      (argv[optind][1] == '-') &&
	      (argv[optind][2] == '\0'))) {
    optind++;
    opt = -1;
  } else if((argv[optind][0] != '-') ||
	     (argv[optind][1] == '\0')) {
    char *tmp;
    int i, j, k;

    if(mode == '+') opt = -1;
    else if(mode == '-') {
      optarg = argv[optind++];
      charind = 0;
      opt = 1;
    } else {
      for(i=j=optind; i<argc; i++) if((argv[i][0] == '-') &&
					(argv[i][1] != '\0')) {
	optind=i;
	opt=my_getopt(argc, argv, opts);
	while(i > j) {
	  tmp=argv[--i];
	  for(k=i; k+1<optind; k++) argv[k]=argv[k+1];
	  argv[--optind]=tmp;
	}
	break;
      }
      if(i == argc) opt = -1;
    }
  } else {
    charind++;
    opt = my_getopt(argc, argv, opts);
  }
  if (optind > argc) optind = argc;
  return opt;
}

/* this is the extended getopt_long{,_only}, with some GNU-like
 * extensions. Implements _getopt_internal in case any programs
 * expecting GNU libc getopt call it.
 */

int _my_getopt_internal(int argc, char * argv[], const char *shortopts,
		     const struct option *longopts, int *longind,
		     int long_only)
{
  char mode, colon_mode = '\0';
  int shortoff = 0, opt = -1;

  if(getenv("POSIXLY_CORRECT")) mode = '+';
  else {
    if((colon_mode = *shortopts) == ':') shortoff ++;
    if(((mode = shortopts[shortoff]) == '+') || (mode == '-')) {
      shortoff++;
      if((colon_mode != ':') && ((colon_mode = shortopts[shortoff]) == ':'))
	shortoff ++;
    }
  }
  optarg = 0;
  if((optind >= argc) ||
      ((argv[optind][0] == '-') &&
       (argv[optind][1] == '-') &&
       (argv[optind][2] == '\0'))) {
    optind++;
    opt = -1;
  } else if((argv[optind][0] != '-') ||
	    (argv[optind][1] == '\0')) {
    char *tmp;
    int i, j, k;

    opt = -1;
    if(mode == '+') return -1;
    else if(mode == '-') {
      optarg = argv[optind++];
      return 1;
    }
    for(i=j=optind; i<argc; i++) if((argv[i][0] == '-') &&
				    (argv[i][1] != '\0')) {
      optind=i;
      opt=_my_getopt_internal(argc, argv, shortopts,
			      longopts, longind,
			      long_only);
      while(i > j) {
	tmp=argv[--i];
	for(k=i; k+1<optind; k++)
	  argv[k]=argv[k+1];
	argv[--optind]=tmp;
      }
      break;
    }
  } else if((!long_only) && (argv[optind][1] != '-'))
    opt = my_getopt(argc, argv, shortopts);
  else {
    char *arg;
    int charind, offset;
    int found = 0, ind, hits = 0;

    if(((optopt = argv[optind][1]) != '-') && ! argv[optind][2]) {
      int c;
      
      ind = shortoff;
      while((c = shortopts[ind++])) {
	if(((shortopts[ind] == ':') ||
	    ((c == 'W') && (shortopts[ind] == ';'))) &&
	   (shortopts[++ind] == ':'))
	  ind ++;
	if(optopt == c) return my_getopt(argc, argv, shortopts);
      }
    }
    if(!(arg = strdup(argv[optind])))
      perror("strdup"), exit(1);
    offset = 2 - (arg[1] != '-');
    for(charind = offset;
	(arg[charind] != '\0') && (arg[charind] != '=');
	charind++);
    arg[charind] = '\0';
    for(ind = 0; longopts[ind].name && !hits; ind++)
      if(strcmp(longopts[ind].name, arg + offset) == 0)	found = ind, hits++;
    if(!hits) for(ind = 0; longopts[ind].name; ind++)
      if(strncmp(longopts[ind].name, arg + offset, charind - offset) == 0)
	found = ind, hits++;
    if(hits == 1) {
      opt = 0;

      if(argv[optind][charind] == '=') {
	if(longopts[found].has_arg == 0) {
	  opt = '?';
	  if(opterr) fprintf(stderr,
			     "%s: option `--%s' doesn't allow an argument\n",
			     argv[0], longopts[found].name);
	} else {
	  optarg = argv[optind] + ++charind;
	  charind = 0;
	}
      } else if(longopts[found].has_arg == 1) {
	if(++optind >= argc) {
	  opt = (colon_mode == ':') ? ':' : '?';
	  if(opterr) fprintf(stderr,
			     "%s: option `--%s' requires an argument\n",
			     argv[0], longopts[found].name);
	} else optarg = argv[optind];
      }
      if(!opt) {
	*longind = found;
	if(!longopts[found].flag) opt = longopts[found].val;
	else *(longopts[found].flag) = longopts[found].val;
      }
      optind++;
    } else if(!hits) {
      if(offset == 1) opt = my_getopt(argc, argv, shortopts);
      else {
	opt = '?';
	if(opterr) fprintf(stderr,
			   "%s: unrecognized option `%s'\n",
			   argv[0], argv[optind++]);
      }
    } else {
      opt = '?';
      if(opterr) fprintf(stderr,
			 "%s: option `%s' is ambiguous\n",
			 argv[0], argv[optind++]);
    }
    free(arg);
  }
  if (optind > argc) optind = argc;
  return opt;
}

int my_getopt_long(int argc, char * argv[], const char *shortopts,
		const struct option *longopts, int *longind)
{
  return _my_getopt_internal(argc, argv, shortopts, longopts, longind, 0);
}

int my_getopt_long_only(int argc, char * argv[], const char *shortopts,
		const struct option *longopts, int *longind)
{
  return _my_getopt_internal(argc, argv, shortopts, longopts, longind, 1);
}
