// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-

/******************************************************************************
 *
 *  file:  CmdLine.h
 *
 *  Copyright (c) 2003, Michael E. Smoot .
 *  Copyright (c) 2004, Michael E. Smoot, Daniel Aarno.
 *  All rights reverved.
 *
 *  See the file COPYING in the top directory of this distribution for
 *  more information.
 *
 *  THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS
 *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 *  DEALINGS IN THE SOFTWARE.
 *
 *****************************************************************************/

#ifndef TCLAP_CMDLINE_H
#define TCLAP_CMDLINE_H

#include <tclap/SwitchArg.h>
#include <tclap/MultiSwitchArg.h>
#include <tclap/UnlabeledValueArg.h>
#include <tclap/UnlabeledMultiArg.h>

#include <tclap/XorHandler.h>
#include <tclap/HelpVisitor.h>
#include <tclap/VersionVisitor.h>
#include <tclap/IgnoreRestVisitor.h>

#include <tclap/CmdLineOutput.h>
#include <tclap/StdOutput.h>

#include <tclap/Constraint.h>
#include <tclap/ValuesConstraint.h>

#include <string>
#include <vector>
#include <list>
#include <iostream>
#include <iomanip>
#include <algorithm>
#include <cstdlib> // Needed for exit(), which isn't defined in some envs.

namespace TCLAP
{

template <typename T>
void
DelPtr(T ptr)
{
  delete ptr;
}

template <typename C>
void
ClearContainer(C & c)
{
  using value_type = typename C::value_type;
  std::for_each(c.begin(), c.end(), DelPtr<value_type>);
  c.clear();
}


/**
 * The base class that manages the command line definition and passes
 * along the parsing to the appropriate Arg classes.
 */
class CmdLine : public CmdLineInterface
{
protected:
  /**
   * The list of arguments that will be tested against the
   * command line.
   */
  std::list<Arg *> _argList;

  /**
   * The name of the program.  Set to argv[0].
   */
  std::string _progName;

  /**
   * A message used to describe the program.  Used in the usage output.
   */
  std::string _message;

  /**
   * The version to be displayed with the --version switch.
   */
  std::string _version;

  /**
   * The number of arguments that are required to be present on
   * the command line. This is set dynamically, based on the
   * Args added to the CmdLine object.
   */
  int _numRequired;

  /**
   * The character that is used to separate the argument flag/name
   * from the value.  Defaults to ' ' (space).
   */
  char _delimiter;

  /**
   * The handler that manages xoring lists of args.
   */
  XorHandler _xorHandler;

  /**
   * A list of Args to be explicitly deleted when the destructor
   * is called.  At the moment, this only includes the three default
   * Args.
   */
  std::list<Arg *> _argDeleteOnExitList;

  /**
   * A list of Visitors to be explicitly deleted when the destructor
   * is called.  At the moment, these are the Vistors created for the
   * default Args.
   */
  std::list<Visitor *> _visitorDeleteOnExitList;

  /**
   * Object that handles all output for the CmdLine.
   */
  CmdLineOutput * _output;

  /**
   * Should CmdLine handle parsing exceptions internally?
   */
  bool _handleExceptions;

  /**
   * Throws an exception listing the missing args.
   */
  void
  missingArgsException();

  /**
   * Checks whether a name/flag string matches entirely matches
   * the Arg::blankChar.  Used when multiple switches are combined
   * into a single argument.
   * \param s - The message to be used in the usage.
   */
  bool
  _emptyCombined(const std::string & s);

  /**
   * Perform a delete ptr; operation on ptr when this object is deleted.
   */
  void
  deleteOnExit(Arg * ptr);

  /**
   * Perform a delete ptr; operation on ptr when this object is deleted.
   */
  void
  deleteOnExit(Visitor * ptr);

private:
  /**
   * Encapsulates the code common to the constructors
   * (which is all of it).
   */
  void
  _constructor();


  /**
   * Is set to true when a user sets the output object. We use this so
   * that we don't delete objects that are created outside of this lib.
   */
  bool _userSetOutput;

  /**
   * Whether or not to automatically create help and version switches.
   */
  bool _helpAndVersion;

public:
  /**
   * Command line constructor. Defines how the arguments will be
   * parsed.
   * \param message - The message to be used in the usage
   * output.
   * \param delimiter - The character that is used to separate
   * the argument flag/name from the value.  Defaults to ' ' (space).
   * \param version - The version number to be used in the
   * --version switch.
   * \param helpAndVersion - Whether or not to create the Help and
   * Version switches. Defaults to true.
   */
  CmdLine(const std::string & message,
          const char          delimiter = ' ',
          const std::string & version = "none",
          bool                helpAndVersion = true);

  /**
   * Deletes any resources allocated by a CmdLine object.
   */
  virtual ~CmdLine();

  /**
   * Adds an argument to the list of arguments to be parsed.
   * \param a - Argument to be added.
   */
  void
  add(Arg & a);

  /**
   * An alternative add.  Functionally identical.
   * \param a - Argument to be added.
   */
  void
  add(Arg * a);

  /**
   * Add two Args that will be xor'd.  If this method is used, add does
   * not need to be called.
   * \param a - Argument to be added and xor'd.
   * \param b - Argument to be added and xor'd.
   */
  void
  xorAdd(Arg & a, Arg & b);

  /**
   * Add a list of Args that will be xor'd.  If this method is used,
   * add does not need to be called.
   * \param xors - List of Args to be added and xor'd.
   */
  void
  xorAdd(std::vector<Arg *> & xors);

  /**
   * Parses the command line.
   * \param argc - Number of arguments.
   * \param argv - Array of arguments.
   */
  void
  parse(int argc, const char * const * argv);

  /**
   * Parses the command line.
   * \param args - A vector of strings representing the args.
   * args[0] is still the program name.
   */
  void
  parse(std::vector<std::string> & args);

  /**
   *
   */
  CmdLineOutput *
  getOutput();

  /**
   *
   */
  void
  setOutput(CmdLineOutput * co);

  /**
   *
   */
  std::string &
  getVersion();

  /**
   *
   */
  std::string &
  getProgramName();

  /**
   *
   */
  std::list<Arg *> &
  getArgList();

  /**
   *
   */
  XorHandler &
  getXorHandler();

  /**
   *
   */
  char
  getDelimiter();

  /**
   *
   */
  std::string &
  getMessage();

  /**
   *
   */
  bool
  hasHelpAndVersion();

  /**
   * Disables or enables CmdLine's internal parsing exception handling.
   *
   * @param state Should CmdLine handle parsing exceptions internally?
   */
  void
  setExceptionHandling(const bool state);

  /**
   * Returns the current state of the internal exception handling.
   *
   * @retval true Parsing exceptions are handled internally.
   * @retval false Parsing exceptions are propagated to the caller.
   */
  bool
  getExceptionHandling() const;

  /**
   * Allows the CmdLine object to be reused.
   */
  void
  reset();
};


///////////////////////////////////////////////////////////////////////////////
// Begin CmdLine.cpp
///////////////////////////////////////////////////////////////////////////////

inline CmdLine::CmdLine(const std::string & m, char delim, const std::string & v, bool help)
  : _progName("not_set_yet")
  , _message(m)
  , _version(v)
  , _numRequired(0)
  , _delimiter(delim)
  , _handleExceptions(true)
  , _userSetOutput(false)
  , _helpAndVersion(help)
{
  _constructor();
}

inline CmdLine::~CmdLine()
{
  ClearContainer(_argDeleteOnExitList);
  ClearContainer(_visitorDeleteOnExitList);

  if (!_userSetOutput)
  {
    delete _output;
    _output = 0;
  }
}

inline void
CmdLine::_constructor()
{
  _output = new StdOutput;

  Arg::setDelimiter(_delimiter);

  Visitor * v;

  if (_helpAndVersion)
  {
    v = new HelpVisitor(this, &_output);
    SwitchArg * help = new SwitchArg("h", "help", "Displays usage information and exits.", false, v);
    add(help);
    deleteOnExit(help);
    deleteOnExit(v);

    v = new VersionVisitor(this, &_output);
    SwitchArg * vers = new SwitchArg("", "version", "Displays version information and exits.", false, v);
    add(vers);
    deleteOnExit(vers);
    deleteOnExit(v);
  }

  v = new IgnoreRestVisitor();
  SwitchArg * ignore = new SwitchArg(Arg::flagStartString(),
                                     Arg::ignoreNameString(),
                                     "Ignores the rest of the labeled arguments following this flag.",
                                     false,
                                     v);
  add(ignore);
  deleteOnExit(ignore);
  deleteOnExit(v);
}

inline void
CmdLine::xorAdd(std::vector<Arg *> & ors)
{
  _xorHandler.add(ors);

  for (ArgVectorIterator it = ors.begin(); it != ors.end(); it++)
  {
    (*it)->forceRequired();
    (*it)->setRequireLabel("OR required");

    add(*it);
  }
}

inline void
CmdLine::xorAdd(Arg & a, Arg & b)
{
  std::vector<Arg *> ors;
  ors.push_back(&a);
  ors.push_back(&b);
  xorAdd(ors);
}

inline void
CmdLine::add(Arg & a)
{
  add(&a);
}

inline void
CmdLine::add(Arg * a)
{
  for (ArgListIterator it = _argList.begin(); it != _argList.end(); it++)
    if (*a == *(*it))
      throw(SpecificationException("Argument with same flag/name already exists!", a->longID()));

  a->addToList(_argList);

  if (a->isRequired())
    _numRequired++;
}


inline void
CmdLine::parse(int argc, const char * const * argv)
{
  // this step is necessary so that we have easy access to
  // mutable strings.
  std::vector<std::string> args;
  for (int i = 0; i < argc; i++)
    args.push_back(argv[i]);

  parse(args);
}

inline void
CmdLine::parse(std::vector<std::string> & args)
{
  bool shouldExit = false;
  int  estat = 0;

  try
  {
    _progName = args.front();
    args.erase(args.begin());

    int requiredCount = 0;

    for (int i = 0; static_cast<unsigned int>(i) < args.size(); i++)
    {
      bool matched = false;
      for (ArgListIterator it = _argList.begin(); it != _argList.end(); it++)
      {
        if ((*it)->processArg(&i, args))
        {
          requiredCount += _xorHandler.check(*it);
          matched = true;
          break;
        }
      }

      // checks to see if the argument is an empty combined
      // switch and if so, then we've actually matched it
      if (!matched && _emptyCombined(args[i]))
        matched = true;

      if (!matched && !Arg::ignoreRest())
        throw(CmdLineParseException("Couldn't find match "
                                    "for argument",
                                    args[i]));
    }

    if (requiredCount < _numRequired)
      missingArgsException();

    if (requiredCount > _numRequired)
      throw(CmdLineParseException("Too many arguments!"));
  }
  catch (ArgException & e)
  {
    // If we're not handling the exceptions, rethrow.
    if (!_handleExceptions)
    {
      throw;
    }

    try
    {
      _output->failure(*this, e);
    }
    catch (ExitException & ee)
    {
      estat = ee.getExitStatus();
      shouldExit = true;
    }
  }
  catch (ExitException & ee)
  {
    // If we're not handling the exceptions, rethrow.
    if (!_handleExceptions)
    {
      throw;
    }

    estat = ee.getExitStatus();
    shouldExit = true;
  }

  if (shouldExit)
    exit(estat);
}

inline bool
CmdLine::_emptyCombined(const std::string & s)
{
  if (s.length() > 0 && s[0] != Arg::flagStartChar())
    return false;

  for (int i = 1; static_cast<unsigned int>(i) < s.length(); i++)
    if (s[i] != Arg::blankChar())
      return false;

  return true;
}

inline void
CmdLine::missingArgsException()
{
  int count = 0;

  std::string missingArgList;
  for (ArgListIterator it = _argList.begin(); it != _argList.end(); it++)
  {
    if ((*it)->isRequired() && !(*it)->isSet())
    {
      missingArgList += (*it)->getName();
      missingArgList += ", ";
      count++;
    }
  }
  missingArgList = missingArgList.substr(0, missingArgList.length() - 2);

  std::string msg;
  if (count > 1)
    msg = "Required arguments missing: ";
  else
    msg = "Required argument missing: ";

  msg += missingArgList;

  throw(CmdLineParseException(msg));
}

inline void
CmdLine::deleteOnExit(Arg * ptr)
{
  _argDeleteOnExitList.push_back(ptr);
}

inline void
CmdLine::deleteOnExit(Visitor * ptr)
{
  _visitorDeleteOnExitList.push_back(ptr);
}

inline CmdLineOutput *
CmdLine::getOutput()
{
  return _output;
}

inline void
CmdLine::setOutput(CmdLineOutput * co)
{
  _userSetOutput = true;
  _output = co;
}

inline std::string &
CmdLine::getVersion()
{
  return _version;
}

inline std::string &
CmdLine::getProgramName()
{
  return _progName;
}

inline std::list<Arg *> &
CmdLine::getArgList()
{
  return _argList;
}

inline XorHandler &
CmdLine::getXorHandler()
{
  return _xorHandler;
}

inline char
CmdLine::getDelimiter()
{
  return _delimiter;
}

inline std::string &
CmdLine::getMessage()
{
  return _message;
}

inline bool
CmdLine::hasHelpAndVersion()
{
  return _helpAndVersion;
}

inline void
CmdLine::setExceptionHandling(const bool state)
{
  _handleExceptions = state;
}

inline bool
CmdLine::getExceptionHandling() const
{
  return _handleExceptions;
}

inline void
CmdLine::reset()
{
  for (ArgListIterator it = _argList.begin(); it != _argList.end(); it++)
  {
    (*it)->reset();
  }

  _progName.clear();
}

///////////////////////////////////////////////////////////////////////////////
// End CmdLine.cpp
///////////////////////////////////////////////////////////////////////////////


} // namespace TCLAP
#endif
