// Filename: panda_getopt_impl.cxx
// Created by:  drose (19Jul11)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University.  All rights reserved.
//
// All use of this software is subject to the terms of the revised BSD
// license.  You should have received a copy of this license along
// with this source code in a file named "LICENSE."
//
////////////////////////////////////////////////////////////////////

#include "panda_getopt_impl.h"
#include "pvector.h"

#if defined(HAVE_GETOPT) && defined(HAVE_GETOPT_LONG_ONLY)
// If the system provides both of these functions, we don't need to
// provide our own implementation, so in that case this file does
// nothing.

#else
// If the system does lack one or the other of these functions, then
// we'll go ahead and provide it instead.


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

////////////////////////////////////////////////////////////////////
//       Class : PandaGetopt
// Description : The implementation within this file of the various
//               getopt() functions.  This class is not visible
//               outside of this file; instead, the interface is via
//               the getopt() functions themselves.
////////////////////////////////////////////////////////////////////
class PandaGetopt {
public:
  PandaGetopt(int argc, char *const argv[], const char *optstring,
              const struct option *longopts, bool allow_one_hyphen_long);

  void permute(int argc, char **mutable_argv);
  int process(int opterr, int *longindex, char *&optarg, int &optind, int &optopt);

private:
  size_t find_short_option(char short_option);
  size_t find_long_option(const string &long_option);

  void scan_options(const char *optstring, const struct option *longopts);
  void scan_args(int argc, char *const argv[]);

  // We build a list of Options, which correspond to the input defined
  // in optstring and/or in the longopts list.  These are the short
  // and long options that are available, whether or not the user
  // tries to use any of them.  This list is populated by
  // scan_options().
  class Option {
  public:
    Option(char short_option, int has_arg);
    Option(const struct option *longopts, int longopts_index);

    char _short_option;
    string _long_option;
    int _has_arg;
    const struct option *_option;
    int _longopts_index;
  };

  // We next build a list of Params, which are the parameter options
  // that are parsed out of the argv array--those options that the
  // user has actually specified.  This list does not contain the
  // non-option arguments, the words that follow the options on the
  // command line (those end up in the _arguments list instead).  This
  // list is populated by scan_args().
  class Param {
  public:
    Param(size_t opt_index, size_t argv_index, 
          char short_option, char *argument = NULL);

    size_t _opt_index;
    size_t _argv_index;
    char _short_option;
    char *_argument;
  };

  // The list of available options.
  typedef pvector<Option> Options;
  Options _options;

  // The list of invoked options.
  typedef pvector<Param> Params;
  Params _params;

  typedef pvector<char *> Arguments;

  // The list of option arguments on the command line, with pointers
  // back into the original argv array.  This is similar to the
  // _params list, above, but it is the pointers to the original
  // unprocessed strings.  We use this list to premute the argv array
  // into proper order if needed.
  Arguments _output_argv;

  // The list of non-option arguments on the command line, following
  // the options.  The vector contains the actual pointers back into
  // the original argv array; we use it to permute the argv array into
  // proper order if needed.
  Arguments _arguments;

  // See the PandaGetopt constructor for an explanation of these
  // two flags.
  bool _return_in_order;
  bool _require_order;

  // If we are invoked via getopt_long_only(), then a single hyphen is
  // allowed to introduce a long option, as well as a double hyphen.
  bool _allow_one_hyphen_long;

  // This member is used to hold our place in the parameters list
  // across multiple calls to process().
  size_t _next_param;

  // This is the index of the first non-option argument in the argv
  // list.  It's filled into optind when process() reaches the end of
  // its processing.
  size_t _next_argv_index;
};

// This global pointer is used to differentiate between getopt() being
// called the first time, vs. subsequent times.
static PandaGetopt *pgetopt = NULL;

////////////////////////////////////////////////////////////////////
//     Function: PandaGetopt::Constructor
//       Access: Public
//  Description:
////////////////////////////////////////////////////////////////////
PandaGetopt::
PandaGetopt(int argc, char *const argv[], const char *optstring,
            const struct option *longopts, bool allow_one_hyphen_long) {
  assert(optstring != NULL);

  _return_in_order = false;
  _require_order = false;
  _allow_one_hyphen_long = allow_one_hyphen_long;
  _next_param = 0;

  // _options[0] is used for invalid characters.
  _options.push_back(Option('?', no_argument));

  if (optstring[0] == '-') {
    // RETURN_IN_ORDER: Non-option arguments (operands) are handled as
    // if they were the argument to an option with the value 1
    // ('\001').
    ++optstring;
    _return_in_order = true;

    // _options[1] is option '\001'.
    _options.push_back(Option('\001', required_argument));

  } else if (optstring[0] == '+') {
    // REQUIRE_ORDER: option processing stops when the first
    // non-option argument is reached, or when the element of argv is
    // "--".
    ++optstring;
    _require_order = true;

  } else if (getenv("POSIXLY_CORRECT") != NULL) {
    // REQUIRE_ORDER.
    _require_order = true;

  } else {
    // PERMUTE: the order of arguments in argv is altered so that all
    // options (and their arguments) are moved in front of all of the
    // operands.
  }

  scan_options(optstring, longopts);
  scan_args(argc, argv);
}

////////////////////////////////////////////////////////////////////
//     Function: PandaGetopt::permute
//       Access: Public
//  Description: Permutes the argv array so that the non-option
//               arguments are at the end of the list (if
//               POSIXLY_CORRECT is not set), as the gnu
//               implementation does.
////////////////////////////////////////////////////////////////////
void PandaGetopt::
permute(int argc, char **mutable_argv) {
  if (!_require_order && !_return_in_order) {
    // Rebuild the argv array to reflect the reordered options.
    size_t i = 1;
    Arguments::const_iterator gi;
    for (gi = _output_argv.begin(); gi != _output_argv.end(); ++gi) {
      assert((int)i < argc);
      mutable_argv[i] = (*gi);
      ++i;
    }
    _next_argv_index = i;
    for (gi = _arguments.begin(); gi != _arguments.end(); ++gi) {
      assert((int)i < argc);
      mutable_argv[i] = (*gi);
      ++i;
    }
    assert((int)i == argc);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: PandaGetopt::process
//       Access: Public
//  Description: Can be called repeatedly to extract out the option
//               arguments scanned from the argv list, one at a time.
//               Sets *longindex, optarg, optind, optopt.
//               Returns EOF when finished.
////////////////////////////////////////////////////////////////////
int PandaGetopt::
process(int opterr, int *longindex, char *&optarg, int &optind, int &optopt) {
  if (_next_param >= _params.size()) {
    optind = _next_argv_index;
    return EOF;
  }

  const Param &param = _params[_next_param];
  ++_next_param;
  const Option &option = _options[param._opt_index];

  optarg = param._argument;
  optind = (int)param._argv_index;
  if (longindex != NULL) {
    *longindex = option._longopts_index;
  }

  if (option._option != NULL) {
    // This was a long option.  Check the special longopt handling
    // parameters.
    if (option._option->flag == NULL) {
      return option._option->val;
    }
    *(option._option->flag) = option._option->val;
    return 0;
  }

  if (param._opt_index == 0 && opterr) {
    // This was an invalid character.
    optopt = param._short_option;
    cerr << "Illegal option: -" << param._short_option << "\n";
    return '?';
  }

  // This was a short option.  Return the option itself.
  return param._short_option;
}

////////////////////////////////////////////////////////////////////
//     Function: PandaGetopt::find_short_option
//       Access: Private
//  Description: Returns the index within the _options array of the
//               option with the indicated short_option letter, or 0
//               if the option is not found.
////////////////////////////////////////////////////////////////////
size_t PandaGetopt::
find_short_option(char short_option) {
  size_t opt_index = 1;
  while (opt_index < _options.size()) {
    if (_options[opt_index]._short_option == short_option) {
      return opt_index;
    }
    ++opt_index;
  }

  return 0;
}

////////////////////////////////////////////////////////////////////
//     Function: PandaGetopt::find_long_option
//       Access: Private
//  Description: Returns the index within the _options array of the
//               option with the indicated long_option word, or 0
//               if the option is not found.  If the word contains an
//               '=' sign, only the text before this sign is
//               considered.
////////////////////////////////////////////////////////////////////
size_t PandaGetopt::
find_long_option(const string &long_option) {
  string search = long_option;
  size_t equals = search.find('=');
  if (equals != string::npos) {
    search = search.substr(0, equals);
  }

  size_t opt_index = 1;
  while (opt_index < _options.size()) {
    if (_options[opt_index]._long_option == search) {
      return opt_index;
    }
    ++opt_index;
  }

  return 0;
}

////////////////////////////////////////////////////////////////////
//     Function: PandaGetopt::scan_options
//       Access: Private
//  Description: Parses the optstring and longopts list to understand
//               the options we should be searching for, and populate
//               the internal _options array.
////////////////////////////////////////////////////////////////////
void PandaGetopt::
scan_options(const char *optstring, const struct option *longopts) {
  const char *p = optstring;
  while (*p != '\0') {
    char short_option = *p;
    int has_arg = no_argument;
    ++p;
    if (*p == ':') {
      has_arg = required_argument;
      ++p;
      if (*p == ':') {
        has_arg = optional_argument;
        ++p;
      }
    }
    
    _options.push_back(Option(short_option, has_arg));
  }

  if (longopts != NULL) {
    int longopts_index = 0;
    while (longopts[longopts_index].name != NULL) {
      _options.push_back(Option(longopts, longopts_index));
      ++longopts_index;
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: PandaGetopt::scan_args
//       Access: Private
//  Description: Parses the argv list to understand the arguments
//               passed by the user, and populates the _params and
//               _arguments arrays.
////////////////////////////////////////////////////////////////////
void PandaGetopt::
scan_args(int argc, char *const argv[]) {
  size_t ai = 1;
  bool end_of_processing = false;

  while ((int)ai < argc) {
    assert(argv[ai] != NULL);

    if (argv[ai][0] != '-' || end_of_processing) {
      // This is a non-option argument.
      if (_require_order) {
        break;
      }
      if (_return_in_order) {
        // Record it as an argument of _options[1], which is '\001'.
        _params.push_back(Param(1, ai, '\001', argv[ai]));
        _output_argv.push_back(argv[ai]);
      } else {
        // Push the non-option argument onto its list, and continue scanning.
        _arguments.push_back(argv[ai]);
      }

    } else if (strcmp(argv[ai], "--") == 0) {
      // Special case: this ends processing.  Everything after this
      // is a non-option argument.
      _output_argv.push_back(argv[ai]);
      end_of_processing = true;

    } else {
      // An option argument.

      char *option = NULL;
      char *argument = NULL;
      size_t opt_index = 0;
      bool is_long_option = false;
      bool has_argument = false;

      if (argv[ai][1] == '-') {
        // This is a long option.
        option = argv[ai] + 2;
        opt_index = find_long_option(option);
        is_long_option = true;
      } else {
        // This is one or more short options, or a short option and
        // its argument.
        option = argv[ai] + 1;
        if (_allow_one_hyphen_long) {
          // Or maybe it's a long option.
          opt_index = find_long_option(option);
          if (opt_index != 0) {
            is_long_option = true;
          }
        }
        if (!is_long_option) {
          opt_index = find_short_option(option[0]);
          while (opt_index != 0 && 
                 _options[opt_index]._has_arg == no_argument && 
                 option[1] != '\0') {
            // There are multiple short options jammed into a single word.
            _params.push_back(Param(opt_index, ai, option[0]));
            ++option;
            opt_index = find_short_option(option[0]);
          }

          if (opt_index != 0 && _options[opt_index]._has_arg != no_argument) {
            if (option[1] != '\0') {
              // There's an argument embedded in the same word.
              argument = option + 1;
              has_argument = true;
            }
          }
        }
      }

      if (is_long_option) {
        char *equals = strchr(option, '=');
        if (equals != NULL) {
          argument = equals + 1;
          has_argument = true;
        }
      }

      size_t argv_index = ai;

      if (opt_index != 0 && _options[opt_index]._has_arg == required_argument && 
          !has_argument) {
        // Check the next word for an argument.
        _output_argv.push_back(argv[ai]);
        ++ai;
        if ((int)ai < argc) {
          argument = argv[ai];
          has_argument = true;
        }
      }

      _params.push_back(Param(opt_index, argv_index, option[0], argument));
      _output_argv.push_back(argv[ai]);
    }
    ++ai;
  }

  _next_argv_index = ai;

  // Now record the non-option arguments that followed the option arguments.
  while ((int)ai < argc) {
    assert(argv[ai] != NULL);
    _arguments.push_back(argv[ai]);
    ++ai;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: PandaGetopt::Option::Constructor
//       Access: Public
//  Description: The constructor for a short_option.  Receives the
//               letter that is the short option, and one of
//               no_argument, required_argument, or optional_argument.
////////////////////////////////////////////////////////////////////
PandaGetopt::Option::
Option(char short_option, int has_arg) :
  _short_option(short_option),
  _has_arg(has_arg),
  _option(NULL),
  _longopts_index(-1)
{
}

////////////////////////////////////////////////////////////////////
//     Function: PandaGetopt::Option::Constructor
//       Access: Public
//  Description: The constructor for a long_option.  Receives the
//               longopts array and the index within the array for
//               this particular option.
////////////////////////////////////////////////////////////////////
PandaGetopt::Option::
Option(const struct option *longopts, int longopts_index) :
  _short_option(0),
  _long_option(longopts[longopts_index].name),
  _has_arg(longopts[longopts_index].has_arg),
  _option(&longopts[longopts_index]),
  _longopts_index(longopts_index)
{
}

////////////////////////////////////////////////////////////////////
//     Function: PandaGetopt::Param::Constructor
//       Access: Public
//  Description: 
////////////////////////////////////////////////////////////////////
PandaGetopt::Param::
Param(size_t opt_index, size_t argv_index, char short_option, char *argument) :
  _opt_index(opt_index),
  _argv_index(argv_index),
  _short_option(short_option),
  _argument(argument)
{
}

int
getopt(int argc, char *const argv[], const char *optstring) {
  if (pgetopt == NULL) {
    pgetopt = new PandaGetopt(argc, argv, optstring, NULL, false);
    pgetopt->permute(argc, (char **)argv);
  }
  return pgetopt->process(opterr, NULL, optarg, optind, optopt);
}

int
getopt_long(int argc, char *const argv[], const char *optstring, 
            const struct option *longopts, int *longindex) {
  if (pgetopt == NULL) {
    pgetopt = new PandaGetopt(argc, argv, optstring, longopts, false);
    pgetopt->permute(argc, (char **)argv);
  }
  return pgetopt->process(opterr, longindex, optarg, optind, optopt);
}

int
getopt_long_only(int argc, char *const argv[], const char *optstring, 
                 const struct option *longopts, int *longindex) {
  if (pgetopt == NULL) {
    pgetopt = new PandaGetopt(argc, argv, optstring, longopts, true);
    pgetopt->permute(argc, (char **)argv);
  }
  return pgetopt->process(opterr, longindex, optarg, optind, optopt);
}

#endif  // defined(HAVE_GETOPT) && defined(HAVE_GETOPT_LONG_ONLY)
