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

/******************************************************************************
 *
 *  file:  Arg.h
 *
 *  Copyright (c) 2003, Michael E. Smoot .
 *  Copyright (c) 2004, Michael E. Smoot, Daniel Aarno .
 *  Copyright (c) 2017 Google Inc.
 *  All rights reserved.
 *
 *  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_ARG_H
#define TCLAP_ARG_H

#ifdef HAVE_TCLAP_CONFIG_H
#include <tclap/TCLAPConfig.h>
#endif

#include <tclap/ArgException.h>
#include <tclap/ArgTraits.h>
#include <tclap/CmdLineInterface.h>
#include <tclap/StandardTraits.h>
#include <tclap/Visitor.h>
#include <tclap/sstream.h>

#include <cstdio>
#include <iomanip>
#include <iostream>
#include <list>
#include <string>
#include <vector>

namespace TCLAP {

/**
 * A virtual base class that defines the essential data for all arguments.
 * This class, or one of its existing children, must be subclassed to do
 * anything.
 */
class Arg {
private:
    /**
     * Prevent accidental copying.
     */
    Arg(const Arg &rhs);

    /**
     * Prevent accidental copying.
     */
    Arg &operator=(const Arg &rhs);

    /**
     * The delimiter that separates an argument flag/name from the
     * value.
     */
    static char &delimiterRef() {
        static char delim = ' ';
        return delim;
    }

protected:
    /**
     * The single char flag used to identify the argument.
     * This value (preceded by a dash {-}), can be used to identify
     * an argument on the command line.  The _flag can be blank,
     * in fact this is how unlabeled args work.  Unlabeled args must
     * override appropriate functions to get correct handling. Note
     * that the _flag does NOT include the dash as part of the flag.
     */
    std::string _flag;

    /**
     * A single word namd identifying the argument.
     * This value (preceded by two dashed {--}) can also be used
     * to identify an argument on the command line.  Note that the
     * _name does NOT include the two dashes as part of the _name. The
     * _name cannot be blank.
     */
    std::string _name;

    /**
     * Description of the argument.
     */
    std::string _description;

    /**
     * Indicating whether the argument is required.
     */
    const bool _required;

    /**
     * Label to be used in usage description.  Normally set to
     * "required", but can be changed when necessary.
     */
    std::string _requireLabel;

    /**
     * Indicates whether a value is required for the argument.
     * Note that the value may be required but the argument/value
     * combination may not be, as specified by _required.
     */
    bool _valueRequired;

    /**
     * Indicates whether the argument has been set.
     * Indicates that a value on the command line has matched the
     * name/flag of this argument and the values have been set accordingly.
     */
    bool _alreadySet;

    /** Indicates the value specified to set this flag (like -a or --all).
     */
    std::string _setBy;

    /**
     * A pointer to a visitor object.
     * The visitor allows special handling to occur as soon as the
     * argument is matched.  This defaults to NULL and should not
     * be used unless absolutely necessary.
     */
    Visitor *_visitor;

    /**
     * Whether this argument can be ignored, if desired.
     */
    bool _ignoreable;

    bool _acceptsMultipleValues;

    /**
     * Indicates if the argument is visible in the help output (e.g.,
     * when specifying --help).
     */
    bool _visibleInHelp;

    /**
     * Performs the special handling described by the Visitor.
     */
    void _checkWithVisitor() const;

    /**
     * Primary constructor. YOU (yes you) should NEVER construct an Arg
     * directly, this is a base class that is extended by various children
     * that are meant to be used.  Use SwitchArg, ValueArg, MultiArg,
     * UnlabeledValueArg, or UnlabeledMultiArg instead.
     *
     * \param flag - The flag identifying the argument.
     * \param name - The name identifying the argument.
     * \param desc - The description of the argument, used in the usage.
     * \param req - Whether the argument is required.
     * \param valreq - Whether the a value is required for the argument.
     * \param v - The visitor checked by the argument. Defaults to NULL.
     */
    Arg(const std::string &flag, const std::string &name,
        const std::string &desc, bool req, bool valreq, Visitor *v = NULL);

public:
    /**
     * Destructor.
     */
    virtual ~Arg();

    /**
     * Adds this to the specified list of Args.
     * \param argList - The list to add this to.
     */
    virtual void addToList(std::list<Arg *> &argList) const;

    /**
     * The delimiter that separates an argument flag/name from the
     * value.
     */
    static char delimiter() { return delimiterRef(); }

    /**
     * The char used as a place holder when SwitchArgs are combined.
     * Currently set to the bell char (ASCII 7).
     */
    static char blankChar() { return '\a'; }

/**
 * The char that indicates the beginning of a flag.  Defaults to '-', but
 * clients can define TCLAP_FLAGSTARTCHAR to override.
 */
#ifndef TCLAP_FLAGSTARTCHAR
#define TCLAP_FLAGSTARTCHAR '-'
#endif
    static char flagStartChar() { return TCLAP_FLAGSTARTCHAR; }

/**
 * The sting that indicates the beginning of a flag.  Defaults to "-", but
 * clients can define TCLAP_FLAGSTARTSTRING to override. Should be the same
 * as TCLAP_FLAGSTARTCHAR.
 */
#ifndef TCLAP_FLAGSTARTSTRING
#define TCLAP_FLAGSTARTSTRING "-"
#endif
    static const std::string flagStartString() { return TCLAP_FLAGSTARTSTRING; }

/**
 * The sting that indicates the beginning of a name.  Defaults to "--", but
 *  clients can define TCLAP_NAMESTARTSTRING to override.
 */
#ifndef TCLAP_NAMESTARTSTRING
#define TCLAP_NAMESTARTSTRING "--"
#endif
    static const std::string nameStartString() { return TCLAP_NAMESTARTSTRING; }

    /**
     * The name used to identify the ignore rest argument.
     */
    static const std::string ignoreNameString() { return "ignore_rest"; }

    /**
     * Sets the delimiter for all arguments.
     * \param c - The character that delimits flags/names from values.
     */
    static void setDelimiter(char c) { delimiterRef() = c; }

    /**
     * Pure virtual method meant to handle the parsing and value assignment
     * of the string on the command line.
     * \param i - Pointer the the current argument in the list.
     * \param args - Mutable list of strings. What is
     * passed in from main.
     */
    virtual bool processArg(size_t *i, std::vector<std::string> &args) = 0;

    /**
     * Operator ==.
     * Equality operator. Must be virtual to handle unlabeled args.
     * \param a - The Arg to be compared to this.
     */
    virtual bool operator==(const Arg &a) const;

    /**
     * Returns the argument flag.
     */
    const std::string &getFlag() const;

    /**
     * Returns the argument name.
     */
    const std::string &getName() const;

    /**
     * Returns the argument description.
     */
    std::string getDescription() const { return getDescription(_required); }

    /**
     * Returns the argument description.
     *
     * @param required if the argument should be treated as
     * required when described.
     */
    std::string getDescription(bool required) const {
        return (required ? "(" + _requireLabel + ") " : "") + _description;
    }

    /**
     * Indicates whether the argument is required.
     */
    virtual bool isRequired() const;

    /**
     * Indicates whether a value must be specified for argument.
     */
    bool isValueRequired() const;

    /**
     * Indicates whether the argument has already been set.  Only true
     * if the arg has been matched on the command line.
     */
    bool isSet() const;

    /**
     * Returns the value specified to set this flag (like -a or --all).
     */
    const std::string &setBy() const { return _setBy; }

    /**
     * Indicates whether the argument can be ignored, if desired.
     */
    bool isIgnoreable() const;

    /**
     * A method that tests whether a string matches this argument.
     * This is generally called by the processArg() method.  This
     * method could be re-implemented by a child to change how
     * arguments are specified on the command line.
     * \param s - The string to be compared to the flag/name to determine
     * whether the arg matches.
     */
    virtual bool argMatches(const std::string &s) const;

    /**
     * Returns a simple string representation of the argument.
     * Primarily for debugging.
     */
    virtual std::string toString() const;

    /**
     * Returns a short ID for the usage.
     * \param valueId - The value used in the id.
     */
    virtual std::string shortID(const std::string &valueId = "val") const;

    /**
     * Returns a long ID for the usage.
     * \param valueId - The value used in the id.
     */
    virtual std::string longID(const std::string &valueId = "val") const;

    /**
     * Trims a value off of the flag.
     * \param flag - The string from which the flag and value will be
     * trimmed. Contains the flag once the value has been trimmed.
     * \param value - Where the value trimmed from the string will
     * be stored.
     */
    virtual void trimFlag(std::string &flag, std::string &value) const;

    /**
     * Checks whether a given string has blank chars, indicating that
     * it is a combined SwitchArg.  If so, return true, otherwise return
     * false.
     * \param s - string to be checked.
     */
    bool _hasBlanks(const std::string &s) const;

    /**
     * Used for MultiArgs to determine whether args can still be
     * set.
     */
    virtual bool allowMore();

    /**
     * Use by output classes to determine whether an Arg accepts
     * multiple values.
     */
    virtual bool acceptsMultipleValues();

    /**
     * Clears the Arg object and allows it to be reused by new
     * command lines.
     */
    virtual void reset();

    /**
     * Hide this argument from the help output (e.g., when
     * specifying the --help flag or on error.
     */
    virtual void hideFromHelp(bool hide = true) { _visibleInHelp = !hide; }

    /**
     * Returns true if this Arg is visible in the help output.
     */
    virtual bool visibleInHelp() const { return _visibleInHelp; }

    virtual bool hasLabel() const { return true; }
};

/**
 * Typedef of an Arg list iterator.
 */
typedef std::list<Arg *>::const_iterator ArgListIterator;

/**
 * Typedef of an Arg vector iterator.
 */
typedef std::vector<Arg *>::const_iterator ArgVectorIterator;

/**
 * Typedef of a Visitor list iterator.
 */
typedef std::list<Visitor *>::const_iterator VisitorListIterator;

/*
 * Extract a value of type T from it's string representation contained
 * in strVal. The ValueLike parameter used to select the correct
 * specialization of ExtractValue depending on the value traits of T.
 * ValueLike traits use operator>> to assign the value from strVal.
 */
template <typename T>
void ExtractValue(T &destVal, const std::string &strVal, ValueLike vl) {
    static_cast<void>(vl);  // Avoid warning about unused vl
    istringstream is(strVal.c_str());

    int valuesRead = 0;
    while (is.good()) {
        if (is.peek() != EOF)
#ifdef TCLAP_SETBASE_ZERO
            is >> std::setbase(0) >> destVal;
#else
            is >> destVal;
#endif
        else
            break;

        valuesRead++;
    }

    if (is.fail())
        throw(
            ArgParseException("Couldn't read argument value "
                              "from string '" +
                              strVal + "'"));

    if (valuesRead > 1)
        throw(
            ArgParseException("More than one valid value parsed from "
                              "string '" +
                              strVal + "'"));
}

/*
 * Extract a value of type T from it's string representation contained
 * in strVal. The ValueLike parameter used to select the correct
 * specialization of ExtractValue depending on the value traits of T.
 * StringLike uses assignment (operator=) to assign from strVal.
 */
template <typename T>
void ExtractValue(T &destVal, const std::string &strVal, StringLike sl) {
    static_cast<void>(sl);  // Avoid warning about unused sl
    SetString(destVal, strVal);
}

//////////////////////////////////////////////////////////////////////
// BEGIN Arg.cpp
//////////////////////////////////////////////////////////////////////

inline Arg::Arg(const std::string &flag, const std::string &name,
                const std::string &desc, bool req, bool valreq, Visitor *v)
    : _flag(flag),
      _name(name),
      _description(desc),
      _required(req),
      _requireLabel("required"),
      _valueRequired(valreq),
      _alreadySet(false),
      _setBy(),
      _visitor(v),
      _ignoreable(true),
      _acceptsMultipleValues(false),
      _visibleInHelp(true) {
    if (_flag.length() > 1)
        throw(SpecificationException(
            "Argument flag can only be one character long", toString()));

    if (_name != ignoreNameString() &&
        (_flag == Arg::flagStartString() || _flag == Arg::nameStartString() ||
         _flag == " "))
        throw(SpecificationException(
            "Argument flag cannot be either '" + Arg::flagStartString() +
                "' or '" + Arg::nameStartString() + "' or a space.",
            toString()));

    if ((_name.substr(0, Arg::flagStartString().length()) ==
         Arg::flagStartString()) ||
        (_name.substr(0, Arg::nameStartString().length()) ==
         Arg::nameStartString()) ||
        (_name.find(" ", 0) != std::string::npos))
        throw(SpecificationException("Argument name begin with either '" +
                                         Arg::flagStartString() + "' or '" +
                                         Arg::nameStartString() + "' or space.",
                                     toString()));
}

inline Arg::~Arg() {}

inline std::string Arg::shortID(const std::string &valueId) const {
    std::string id = "";

    if (_flag != "")
        id = Arg::flagStartString() + _flag;
    else
        id = Arg::nameStartString() + _name;

    if (_valueRequired) id += std::string(1, Arg::delimiter()) + valueId;

    return id;
}

inline std::string Arg::longID(const std::string &valueId) const {
    std::string id = "";

    if (_flag != "") {
        id += Arg::flagStartString() + _flag;

        if (_valueRequired) id += std::string(1, Arg::delimiter()) + valueId;

        id += ",  ";
    }

    id += Arg::nameStartString() + _name;

    if (_valueRequired) id += std::string(1, Arg::delimiter()) + valueId;

    return id;
}

inline bool Arg::operator==(const Arg &a) const {
    if ((_flag != "" && _flag == a._flag) || _name == a._name)
        return true;
    else
        return false;
}

inline const std::string &Arg::getFlag() const { return _flag; }

inline const std::string &Arg::getName() const { return _name; }

inline bool Arg::isRequired() const { return _required; }

inline bool Arg::isValueRequired() const { return _valueRequired; }

inline bool Arg::isSet() const { return _alreadySet; }

inline bool Arg::isIgnoreable() const { return _ignoreable; }

inline bool Arg::argMatches(const std::string &argFlag) const {
    if ((argFlag == Arg::flagStartString() + _flag && _flag != "") ||
        argFlag == Arg::nameStartString() + _name)
        return true;
    else
        return false;
}

inline std::string Arg::toString() const {
    std::string s = "";

    if (_flag != "") s += Arg::flagStartString() + _flag + " ";

    s += "(" + Arg::nameStartString() + _name + ")";

    return s;
}

inline void Arg::_checkWithVisitor() const {
    if (_visitor != NULL) _visitor->visit();
}

/**
 * Implementation of trimFlag.
 */
inline void Arg::trimFlag(std::string &flag, std::string &value) const {
    size_t stop = 0;
    for (size_t i = 0; static_cast<unsigned int>(i) < flag.length(); i++)
        if (flag[i] == Arg::delimiter()) {
            stop = i;
            break;
        }

    if (stop > 1) {
        value = flag.substr(stop + 1);
        flag = flag.substr(0, stop);
    }
}

/**
 * Implementation of _hasBlanks.
 */
inline bool Arg::_hasBlanks(const std::string &s) const {
    for (size_t i = 1; static_cast<unsigned int>(i) < s.length(); i++)
        if (s[i] == Arg::blankChar()) return true;

    return false;
}

/**
 * Overridden by Args that need to added to the end of the list.
 */
inline void Arg::addToList(std::list<Arg *> &argList) const {
    argList.push_front(const_cast<Arg *>(this));
}

inline bool Arg::allowMore() { return false; }

inline bool Arg::acceptsMultipleValues() { return _acceptsMultipleValues; }

inline void Arg::reset() { _alreadySet = false; }

//////////////////////////////////////////////////////////////////////
// END Arg.cpp
//////////////////////////////////////////////////////////////////////

}  // namespace TCLAP

#endif  // TCLAP_ARG_H
