/**
 * @file /include/ecl/command_line/arg.hpp
 *
 * @brief TCLAP command line argument parser classes.
 *
 * TCLAP command line argument parser classes.
 *
 * @author Michael E. Smoot, Daniel J. Stonier
 * @date May 2009
 **/
/*****************************************************************************
** Ifdefs
*****************************************************************************/


#ifndef TCLAP_ARGUMENT_H
#define TCLAP_ARGUMENT_H

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

#include "arg_exception.hpp"
#include "visitor.hpp"
#include "cmd_line_interface.hpp"

namespace ecl {

/**
 * @brief Virtual parent for all the different argument classes.
 *
 * 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:

		/**
		 * Indicates whether the rest of the arguments should be ignored.
		 */
		static bool& ignoreRestRef() { static bool ign = false; return ign; }

		/**
		 * 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 work namd indentifying 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.
		 */
		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;

		/**
		 * A pointer to a vistitor 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;

		/**
		 * Indicates that the arg was set as part of an XOR and not on the
		 * command line.
		 */
		bool _xorSet;

		bool _acceptsMultipleValues;

		/**
		 * Performs the special handling described by the Vistitor.
		 */
		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;

		/**
		 * Begin ignoring arguments since the "--" argument was specified.
		 */
		static void beginIgnoring() { ignoreRestRef() = true; }

		/**
		 * Whether to ignore the rest.
		 */
		static bool ignoreRest() { return ignoreRestRef(); }

		/**
		 * 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 '*', which shouldn't cause many problems since
		 * *'s are expanded by most shells on the command line.
		 */
		static char blankChar() { return '*'; }

		/**
		 * The char that indicates the beginning of a flag.  Currently '-'.
		 */
		static char flagStartChar() { return '-'; }

		/**
		 * The sting that indicates the beginning of a flag.  Currently "-".
		 * Should be identical to flagStartChar.
		 */
		static std::string flagStartString() { return "-"; }

		/**
		 * The sting that indicates the beginning of a name.  Currently "--".
		 * Should be flagStartChar twice.
		 */
		static std::string nameStartString() { return "--"; }

		/**
		 * The name used to identify the ignore rest argument.
		 */
		static 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(int *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;

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

		/**
		 * Sets _required to true. This is used by the XorHandler.
		 * You really have no reason to ever use it.
		 */
		void forceRequired();

		/**
		 * Sets the _alreadySet value to true.  This is used by the XorHandler.
		 * You really have no reason to ever use it.
		 */
		void xorSet();

		/**
		 * 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;

		/**
		 * 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;

		/**
		 * Sets the requireLabel. Used by XorHandler.  You shouldn't ever
		 * use this.
		 * \param s - Set the requireLabel to this value.
		 */
		void setRequireLabel( const std::string& s );

		virtual bool allowMore();
		virtual bool acceptsMultipleValues();

};

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

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

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


//////////////////////////////////////////////////////////////////////
//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),
  _visitor( v ),
  _ignoreable(true),
  _xorSet(false),
  _acceptsMultipleValues(false)
{
	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.find( Arg::flagStartString(), 0 ) != std::string::npos ) ||
		 ( _name.find( Arg::nameStartString(), 0 ) != std::string::npos ) ||
		 ( _name.find( " ", 0 ) != std::string::npos ) )
		throw(SpecificationException("Argument name cannot contain 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;

	std::string delim = " ";
	delim[0] = Arg::delimiter(); // ugly!!!

	if ( _valueRequired )
		id += delim + "<" + valueId  + ">";

	if ( !_required )
		id = "[" + id + "]";

	return id;
}

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

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

		if ( _valueRequired )
			id += " <" + valueId + ">";

		id += ",  ";
	}

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

	if ( _valueRequired )
		id += " <" + 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 std::string Arg::getDescription() const
{
	std::string desc = "";
	if ( _required )
		desc = "(" + _requireLabel + ")  ";

//	if ( _valueRequired )
//		desc += "(value required)  ";

	desc += _description;
	return desc;
}

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
{
	if ( _alreadySet && !_xorSet )
		return true;
	else
		return false;
}

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

inline void Arg::setRequireLabel( const std::string& s)
{
	_requireLabel = s;
}

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
{
	int stop = 0;
	for ( int 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 ( int i = 1; static_cast<unsigned int>(i) < s.length(); i++ )
		if ( s[i] == Arg::blankChar() )
			return true;

	return false;
}

inline void Arg::forceRequired()
{
	_required = true;
}

inline void Arg::xorSet()
{
	_alreadySet = true;
	_xorSet = true;
}

/**
 * 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;
}

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

}; // namespace ecl

#endif

