#ifndef __NCBIARGS_DESC_HPP
#define __NCBIARGS_DESC_HPP

#include "ncbiargs_types.hpp"
#include "ncbiargs_allow.hpp"

BEGIN_NCBI_SCOPE

class CArgDesc;
class CArgs;
class CArgDependencyGroup;

/////////////////////////////////////////////////////////////////////////////
///
/// CArgs --
///
/// Defines parsed arguments.
///
/// Argument values are obtained from the unprocessed command-line arguments
/// (via CNcbiArguments) and then verified and processed according to the
/// argument descriptions defined by user in "CArgDescriptions".
///
/// NOTE:  the extra arguments can be accessed using virtual names:
///           "#1", "#2", "#3", ..., "#<GetNExtra()>"
///        in the order of insertion (by method Add).
///

class NCBI_XNCBI_EXPORT CArgs
{
public:
    /// Constructor.
    CArgs(void);

    /// Destructor.
    ~CArgs(void);

    /// Creating copy of this object usually makes no sense
    /// if it is really required, please use Assign method
    CArgs(const CArgs& other) = delete;

    /// Creating copy of this object usually makes no sense
    /// if it is really required, please use Assign method
    CArgs& operator=(const CArgs& other) = delete;

    /// Copy contents of another object into this one
    CArgs& Assign(const CArgs& other);

    /// Check existence of argument description.
    ///
    /// Return TRUE if arg "name" was described in the parent CArgDescriptions.
    bool Exist(const string& name) const;

    /// Get value of argument by name. If the name starts with '-'
    /// (e.g. '-arg') the argument can also be found by 'arg' name if there
    /// is no another argument named 'arg'.
    ///
    /// Throw an exception if such argument does not exist (not described
    /// in the CArgDescriptions).
    ///
    /// @attention  CArgValue::operator bool() can return TRUE even if the
    ///             argument was not specified in the command-line -- if the
    ///             argument has a default value.
    /// @sa
    ///   Exist() above.
    const CArgValue& operator[] (const string& name) const;

    /// Get the number of unnamed positional (a.k.a. extra) args.
    size_t GetNExtra(void) const { return m_nExtra; }

    /// Return N-th extra arg value,  N = 1 to GetNExtra().
    const CArgValue& operator[] (size_t idx) const;

    /// Get all available arguments
    vector< CRef<CArgValue> > GetAll(void) const;

    /// Print (append) all arguments to the string "str" and return "str".
    string& Print(string& str) const;

    /// Add new argument name and value.
    ///
    /// Throw an exception if the "name" is not an empty string, and if
    /// there is an argument with this name already and "update" parameter is 
    /// not set.
    ///
    /// HINT: Use empty "name" to add extra (unnamed) args, and they will be
    /// automagically assigned with the virtual names: "#1", "#2", "#3", etc.
    ///
    /// @param arg
    ///    argument value added to the collection
    /// @param update
    ///    when TRUE and argument already exists it will be replaced
    ///    when FALSE throws an exception 
    /// @param add_value
    ///    when TRUE and argument already exists the value is
    ///    added to the string list (multiple argument)
    void Add(CArgValue* arg, 
             bool       update    = false,
             bool       add_value = false);

    /// Check if there are no arguments in this container.
    bool IsEmpty(void) const;

    /// Remove argument of name "name"
    void Remove(const string& name);

    /// Remove all arguments
    void Reset(void);

    /// Get current command
    /// @sa CCommandArgDescriptions
    string GetCommand(void) const
    {
        return m_Command;
    }

protected:
    /// Set current command
    /// @sa CCommandArgDescriptions
    CArgs* SetCommand(const string& command)
    {
        m_Command = command;
        return this;
    }

private:
    typedef set< CRef<CArgValue>, CArgValueCRefLessThan >  TArgs;   ///< Type for arguments
    typedef TArgs::iterator         TArgsI;  ///< Type for iterator
    typedef TArgs::const_iterator   TArgsCI; ///< Type for const iterator

    TArgs  m_Args;    ///< Assoc. map of arguments' name/value
    size_t m_nExtra;  ///< Cached # of unnamed positional arguments 
    string m_Command;

    /// Find argument value with name "name".
    TArgsCI x_Find(const string& name) const;
    TArgsI  x_Find(const string& name);
};

/////////////////////////////////////////////////////////////////////////////
///
/// CArgDescriptions --
///
/// Description of unparsed arguments.
///
/// Container to store the command-line argument descriptions. Provides the
/// means for the parsing and verification of command-line arguments against
/// the contained descriptions.
///
/// Example: Translating "CNcbiArguments" ---> "CArgs".
/// Can also be used to compose and print out the USAGE info.
///
/// @sa CInputStreamSource
///   CInputStreamSource helper class makes it possible to supply a list of
///   input files, or list of directories

class NCBI_XNCBI_EXPORT CArgDescriptions
{
public:
    /// Constructor.
    ///
    /// If "auto_help" is passed TRUE, then a special flag "-h" will be added
    /// to the list of accepted arguments. Passing "-h" in the command line
    /// will printout USAGE and ignore all other passed arguments.
    /// Error handler is used to process errors when parsing arguments.
    /// If not set the default handler is used.
    CArgDescriptions(bool auto_help = true);

    /// Destructor.
    virtual ~CArgDescriptions(void);

    /// Type of CArgDescriptions
    /// For a CGI application positional arguments and flags does not make
    /// sense (this syntax cannot be expressed by CGI protocol)
    enum EArgSetType {
        eRegularArgs,  ///< Regular application
        eCgiArgs       ///< CGI application
    };

    /// Set type of argument description (cmdline vs CGI).
    /// Method performs verification of arguments, 
    /// throws an exception if it finds positional args set for a CGI
    void SetArgsType(EArgSetType args_type);

    EArgSetType GetArgsType() const { return m_ArgsType; }

    /// Processing of positional arguments.
    /// In strict mode any value starting with '-' is treated as a key/flag
    /// unless any positional arguments have already been found (e.g. after
    /// '--' argument). In loose mode any argument is treated as positional
    /// if it can not be processed as a valid key or flag.
    enum EArgPositionalMode {
        ePositionalMode_Strict,  ///< Strict mode (default)
        ePositionalMode_Loose    ///< Loose mode
    };

    /// Select mode for processing positional arguments.
    void SetPositionalMode(EArgPositionalMode positional_mode)
        { m_PositionalMode = positional_mode; }

    EArgPositionalMode GetPositionalMode() const { return m_PositionalMode; }

    /// Available argument types.
    enum EType {
        eString = 0, ///< An arbitrary string
        eBoolean,    ///< {'true', 't', 'false', 'f'},  case-insensitive
        eInt8,       ///< Convertible into an integer number (Int8 only)
        eInteger,    ///< Convertible into an integer number (int or Int8)
        eIntId,      ///< Convertible to TIntId (int or Int8 depending on NCBI_INT8_GI)
        eDouble,     ///< Convertible into a floating point number (double)
        eInputFile,  ///< Name of file (must exist and be readable)
        eOutputFile, ///< Name of file (must be writable)
        eIOFile,     ///< Name of file (must be writable)
        eDirectory,  ///< Name of file directory
        eDataSize,   ///< Integer number with possible "software" qualifiers (KB, KiB, et al)
        eDateTime,   ///< DateTime string, formats:
                     ///< "M/D/Y h:m:s", "Y-M-DTh:m:g", "Y/M/D h:m:g", "Y-M-D h:m:g".
                     ///< Time string can have trailing 'Z' symbol, specifying that
                     ///< it represent time in the UTC format.
        k_EType_Size ///< For internal use only
    };

    /// Get argument type's name.
    static const char* GetTypeName(EType type);

    /// Additional flags, the first group is file related flags.
    ///
    /// Must match the argument type, or an exception will be thrown.
    /// ( File related are for eInputFile and eOutputFiler argument types.)
    enum EFlags {
        // File related flags:

        /// Open file right away; for eInputFile, eOutputFile, eIOFile
        fPreOpen = (1 << 0),
        /// Open as binary file; for eInputFile, eOutputFile, eIOFile
        fBinary  = (1 << 1), 
        /// Append to end-of-file; for eOutputFile or eIOFile 
        fAppend    = (1 << 2),
        /// Delete contents of an existing file; for eOutputFile or eIOFile 
        fTruncate  = (1 << 12),
        /// If the file does not exist, do not create it; for eOutputFile or eIOFile 
        fNoCreate = (1 << 11),
        /// If needed, create directory where the file is located
        fCreatePath = (1 << 8),

        /// Mask for all file-related flags
        fFileFlags = fPreOpen | fBinary | fAppend | fTruncate | fNoCreate | fCreatePath,
        // multiple keys flag:

        /// Repeated key arguments are legal (use with AddKey)
        fAllowMultiple = (1 << 3),

        // Error handling flags:

        /// Ignore invalid argument values. If not set, exceptions will be
        /// thrown on invalid values.
        fIgnoreInvalidValue = (1 << 4),
        /// Post warning when an invalid value is ignored (no effect
        /// if fIgnoreInvalidValue is not set).
        fWarnOnInvalidValue = (1 << 5),

        /// Allow to ignore separator between the argument's name and value.
        /// Usual ' ' or '=' separators can still be used with the argument.
        /// The following restrictions apply to a no-separator argument:
        ///   - the argument must be a key (including optional or default);
        ///   - the argument's name must be a single char;
        ///   - no other argument's name can start with the same char,
        ///     unless fOptionalSeparatorAllowConflict is also specified.
        fOptionalSeparator = (1 << 6),
        /// For arguments with fOptionalSeparator flag, allow
        /// other arguments which names begin with the same char.
        fOptionalSeparatorAllowConflict = (1 << 9),
        
        /// Require '=' separator
        fMandatorySeparator = (1 << 7),

        /// Hide it in Usage
        fHidden = (1 << 10),
        
        /// Confidential argument
        /// Such arguments can be read from command line, from file, or from
        /// console.
        /// On command line, they can appear in one of the following forms:
        ///   -key                 -- read value from console, with automatically
        ///                           generated prompt
        ///   -key-file fname      -- read value from file 'fname',
        ///                           if 'fname' equals '-',  read value from
        ///                           standard input (stdin) without any prompt
        ///   -key-verbatim value  -- read value from the command line, as is
        fConfidential  = (1 << 13)
    };
    typedef unsigned int TFlags;  ///< Bitwise OR of "EFlags"

    /// Add description for mandatory key.
    ///
    /// Mandatory key has the syntax:
    ///
    ///   arg_key := -<key> <value>
    ///
    /// Will throw exception CArgException if:
    ///  - description with name "name" already exists
    ///  - "name" contains symbols other than {alnum, '-', '_'}
    ///  - "name" starts with more than one '-'
    ///  - "synopsis" contains symbols other than {alnum, '_'}
    ///  - "flags" are inconsistent with "type"
    ///
    /// Any argument can be later referenced using its unique name "name".
    void AddKey(const string& name,       ///< Name of argument key
                const string& synopsis,   ///< Synopsis for argument
                const string& comment,    ///< Argument description
                EType         type,       ///< Argument type
                TFlags        flags = 0   ///< Optional flags
               );

    /// Add description for optional key without default value.
    ///
    /// Optional key without default value has the following syntax:
    ///
    ///   arg_key_opt := [-<key> <value>]
    ///
    /// Will throw exception CArgException if:
    ///  - description with name "name" already exists
    ///  - "name" contains symbols other than {alnum, '-', '_'}
    ///  - "name" starts with more than one '-'
    ///  - "synopsis" contains symbols other than {alnum, '_'}
    ///  - "flags" are inconsistent with "type"
    ///
    /// Any argument can be later referenced using its unique name "name".
    void AddOptionalKey(const string& name,     ///< Name of argument key 
                        const string& synopsis, ///< Synopsis for argument
                        const string& comment,  ///< Argument description
                        EType         type,     ///< Argument type
                        TFlags        flags = 0 ///< Optional flags
                       );

    /// Add description for optional key with default value.
    ///
    /// Optional key with default value has the following syntax:
    ///
    ///   arg_key_dflt := [-<key> <value>]
    ///
    /// Will throw exception CArgException if:
    ///  - description with name "name" already exists
    ///  - "name" contains symbols other than {alnum, '-', '_'}
    ///  - "name" starts with more than one '-'
    ///  - "synopsis" contains symbols other than {alnum, '_'}
    ///  - "flags" are inconsistent with "type"
    ///
    /// Any argument can be later referenced using its unique name "name".
    void AddDefaultKey(const string& name,          ///< Name of argument key 
                       const string& synopsis,      ///< Synopsis for argument
                       const string& comment,       ///< Argument description
                       EType         type,          ///< Argument type
                       const string& default_value, ///< Default value
                       TFlags        flags = 0,     ///< Optional flags
                       /// Optional name of environment variable that
                       /// contains default value
                       const string& env_var = kEmptyStr,
                       /// Default value shown in Usage
                       const char*   display_value = nullptr
                      );

    /// Define how flag presence affect CArgValue::HasValue().
    /// @sa AddFlag
    enum EFlagValue {
        eFlagHasValueIfMissed = 0,
        eFlagHasValueIfSet    = 1
    };

    /// Add description for flag argument.
    ///
    /// Flag argument has the following syntax:
    ///
    ///  arg_flag  := -<flag>,     <flag> := "name"
    ///
    /// If argument "set_value" is eFlagHasValueIfSet (TRUE), then:
    ///    - if the flag is provided (in the command-line), then the resultant
    ///      CArgValue::HasValue() will be TRUE;
    ///    - else it will be FALSE.
    ///
    /// Setting argument "set_value" to FALSE will reverse the above meaning.
    ///
    /// NOTE: If CArgValue::HasValue() is TRUE, then AsBoolean() is
    /// always TRUE.
    void AddFlag(const string& name,      ///< Name of argument
                 const string& comment,   ///< Argument description
                 CBoolEnum<EFlagValue> set_value = eFlagHasValueIfSet
                 );

    /// Add description of mandatory opening positional argument.
    ///
    /// Mandatory opening argument has the following syntax:
    ///   arg_pos := <value>
    ///
    /// NOTE:
    ///   In command line, mandatory opening arguments must go first,
    ///   before any other arguments; their order is defined by the order
    ///   in which they were described and added into CArgDescriptions.
    ///
    /// Will throw exception CArgException if:
    ///  - description with name "name" already exists
    ///  - "name" contains symbols other than {alnum, '-', '_'}
    ///  - "name" starts with more than one '-'
    ///  - "flags" are inconsistent with "type"
    ///
    /// Any argument can be later referenced using its unique name "name".
    void AddOpening(const string& name,     ///< Name of argument
                    const string& comment,  ///< Argument description
                    EType         type,     ///< Argument type
                    TFlags        flags = 0 ///< Optional flags
                    );

    /// Add description for mandatory positional argument.
    ///
    /// Mandatory positional argument has the following syntax:
    ///
    ///   arg_pos := <value>
    ///
    /// NOTE: For all types of positional arguments:
    /// - The order is important! That is, the N-th positional argument passed
    ///   in the cmd.-line will be matched against (and processed according to)
    ///   the N-th added named positional argument description.
    /// - Mandatory positional args always go first.
    ///
    /// Will throw exception CArgException if:
    ///  - description with name "name" already exists
    ///  - "name" contains symbols other than {alnum, '-', '_'}
    ///  - "name" starts with more than one '-'
    ///  - "flags" are inconsistent with "type"
    ///
    /// Any argument can be later referenced using its unique name "name".
    void AddPositional(const string& name,     ///< Name of argument
                       const string& comment,  ///< Argument description
                       EType         type,     ///< Argument type
                       TFlags        flags = 0 ///< Optional flags
                      );

    /// Add description for optional positional argument without default
    /// value.
    ///
    /// Optional positional argument, without default value has the following
    /// syntax:
    ///
    ///  arg_pos_opt := [<value>]
    ///
    /// Will throw exception CArgException if:
    ///  - description with name "name" already exists
    ///  - "name" contains symbols other than {alnum, '-', '_'}
    ///  - "name" starts with more than one '-'
    ///  - "flags" are inconsistent with "type"
    ///
    /// Any argument can be later referenced using its unique name "name".
    /// @sa
    ///   NOTE for AddPositional()
    void AddOptionalPositional(const string& name,     ///< Name of argument
                               const string& comment,  ///< Argument descr.
                               EType         type,     ///< Argument type
                               TFlags        flags = 0 ///< Optional flags
                              );

    /// Add description for optional positional argument with default value.
    ///
    /// Optional positional argument with default value has the following
    /// syntax:
    ///
    ///  arg_pos_dflt := [<value>]
    ///
    /// Will throw exception CArgException if:
    ///  - description with name "name" already exists
    ///  - "name" contains symbols other than {alnum, '-', '_'}
    ///  - "name" starts with more than one '-'
    ///  - "flags" are inconsistent with "type"
    ///
    /// @sa
    ///   NOTE for AddPositional()
    void AddDefaultPositional(const string& name,   ///< Name of argument
                              const string& comment,///< Argument description
                              EType         type,   ///< Argument type
                              const string& default_value, ///< Default value
                              TFlags        flags = 0, ///< Optional flags
                              /// Optional name of environment variable that
                              /// contains default value
                              const string& env_var = kEmptyStr,
                              /// Default value shown in Usage
                              const char*   display_value = nullptr
                             );

    /// Add description for the extra, unnamed positional arguments.
    ///
    /// The name of this description is always an empty string.
    /// Names of the resulting arg.values will be:  "#1", "#2", ...
    /// By default, no extra args are allowed.
    ///
    /// To allow an unlimited # of optional argumens pass
    /// "n_optional" = kMax_UInt.
    ///
    /// Will throw exception CArgException if:
    ///  - description with name "name" already exists
    ///  - "flags" are inconsistent with "type"
    void AddExtra(unsigned      n_mandatory, ///< Number of mandatory args
                  unsigned      n_optional,  ///< Number of optional args
                  const string& comment,     ///< Argument description
                  EType         type,        ///< Argument type
                  TFlags        flags = 0    ///< Optional flags
                 );

    /// Add argument alias. The alias can be used in the command line instead
    /// of the original argument. Accessing argument value by its alias is
    /// not allowed (will be reported as an unknown argument). The alias will
    /// be printed in USAGE after the original argument name.
    /// @param alias
    ///    New alias for a real argument.
    /// @param arg_name
    ///    The real argument's name.
    void AddAlias(const string& alias, const string& arg_name);
    /// Add negated alias for a flag argument. Using the alias in the
    /// command line produces the same effect as using the original
    /// flag with the opposite value. If 'arg_name' does not describe
    /// a flag argument, an exception is thrown.
    /// @sa
    ///   AddAlias()
    void AddNegatedFlagAlias(const string& alias,
                             const string& arg_name,
                             const string& comment = kEmptyStr);

    /// Add a dependency group.
    /// The argument constraints specified by the dependency group(s)
    /// will be processed only after all regular dependencies for arguments and
    /// dependency groups have been processed.
    /// @attention
    ///  The "dep_group" will be added by reference, and its lifetime will then
    ///  be managed according to the usual CObject/CRef rules.
    /// @sa SetDependency()
    void AddDependencyGroup(CArgDependencyGroup* dep_group);

    /// Flag to invert constraint logically
    enum EConstraintNegate {
        eConstraintInvert,  ///< Logical NOT
        eConstraint         ///< Constraint is not inverted (taken as is)
    };

    /// Set additional user defined constraint on argument value.
    ///
    /// Constraint is defined by CArgAllow and its derived classes.
    /// The constraint object must be allocated by "new", and it must NOT be
    /// freed by "delete" after it has been passed to CArgDescriptions!
    ///
    /// @param name
    ///    Name of the parameter(flag) to check
    /// @param constraint
    ///    The constraint object.
    ///    NOTE: A CRef will always be taken on the object, and its lifetime
    ///    will be controlled by the CObject's smart-pointer mechanism.
    /// @param negate
    ///    Flag indicates if this is inverted(NOT) constaint
    /// 
    /// @sa
    ///   See "CArgAllow_***" classes for some pre-defined constraints
    void SetConstraint(const string&      name,
                       const CArgAllow*   constraint,
                       EConstraintNegate  negate = eConstraint);

    /// This version of SetConstraint doesn't take the ownership of object
    /// 'constraint'. Rather, it creates and uses a clone of the object.
    void SetConstraint(const string&      name,
                       const CArgAllow&   constraint,
                       EConstraintNegate  negate = eConstraint);

    /// Dependencies between arguments.
    enum EDependency {
        eRequires, ///< One argument requires another
        eExcludes  ///< One argument excludes another
    };

    /// Define a dependency. If arg1 was specified and requires arg2,
    /// arg2 is treated as a mandatory one even if was defined as optional.
    /// If arg1 excludes arg2, arg2 must not be set even if it's mandatory.
    /// This allows to create a set of arguments exactly one of which
    /// must be set.
    void SetDependency(const string& arg1,
                       EDependency   dep,
                       const string& arg2);

    /// Set current arguments group name. When printing descriptions for
    /// optional arguments (on -help command), they will be arranged by
    /// group name. Empty group name resets the group. Arguments without
    /// group are listed first immediately after mandatory arguments.
    void SetCurrentGroup(const string& group);

    /// Check if there is already an argument description with specified name.
    bool Exist(const string& name) const;

    /// Delete description of argument with name "name".
    /// Extra arguments get deleted by the name passed as "".
    ///
    /// Throw the CArgException (eSynopsis error code) exception if the
    /// specified name cannot be found.
    void Delete(const string& name);

    /// Set extra info to be used by PrintUsage().
    /// @sa SetDetailedDescription
    void SetUsageContext(const string& usage_name,           ///< Program name  
                         const string& usage_description,    ///< Usage descr.
                         bool          usage_sort_args = false,///< Sort args.
                         SIZE_TYPE     usage_width = 78);    ///< Format width

    /// Set detailed usage description
    ///
    /// In short help message, program will print short
    /// description defined in SetUsageContext method.
    /// In detailed help message, program will use detailed
    /// description defined here.
    ///
    /// @param usage_description
    ///    Detailed usage description
    /// @sa SetUsageContext
    void SetDetailedDescription( const string& usage_description);

    /// Print usage and exit.
    ///
    /// Force to print USAGE unconditionally (and then exit) if no
    /// command-line args are present.
    /// @deprecated Use SetMiscFlags(fUsageIfNoArgs) instead.
    NCBI_DEPRECATED void PrintUsageIfNoArgs(bool do_print = true);

    /// Miscellaneous flags.
    enum EMiscFlags {
        fNoUsage        = 1 << 0,  ///< Do not print USAGE on argument error.
        fUsageIfNoArgs  = 1 << 1,  ///< Force printing USAGE (and then exit)
                                   ///< if no command line args are present.
        fUsageSortArgs  = 1 << 2,  ///< Sort args when printing USAGE.
        fDupErrToCerr   = 1 << 3,  ///< Print arg error to both log and cerr.

        fMisc_Default   = 0
    };
    typedef int TMiscFlags;  ///< Bitwise OR of "EMiscFlags"

    /// Set the selected flags.
    void SetMiscFlags(TMiscFlags flags)
    {
        m_MiscFlags |= flags;
    }
    
    /// Clear the selected usage flags.
    void ResetMiscFlags(TMiscFlags flags)
    {
        m_MiscFlags &= ~flags;
    }

    /// Check if the flag is set.
    bool IsSetMiscFlag(EMiscFlags flag) const
    {
        return (m_MiscFlags & flag) != 0;
    }

    /// Print usage message to end of specified string.
    ///
    /// Printout USAGE and append to the string "str" using  provided
    /// argument descriptions and usage context.
    /// @return
    ///   Appended "str"
    virtual string& PrintUsage(string& str, bool detailed = false) const;

    virtual string& HbnPrintUsage(const string& main_usage, string& str, bool detailed = false) const;

    /// Print argument description in XML format
    ///
    /// @param out
    ///   Print into this output stream
    virtual void PrintUsageXml(CNcbiOstream& out) const;

    /// Verify if argument "name" is spelled correctly.
    ///
    /// Argument name can contain only alphanumeric characters, dashes ('-')
    /// and underscore ('_'), or be empty. If the leading dash is present,
    /// it must be followed by a non-dash char ('-' or '--foo' are not valid
    /// names).
    static bool VerifyName(const string& name, bool extended = false);

    /// See if special flag "-h" is activated
    bool IsAutoHelpEnabled(void) const
    {
        return m_AutoHelp;
    }

private:
    typedef set< AutoPtr<CArgDesc> >  TArgs;    ///< Argument descr. type
    typedef TArgs::iterator           TArgsI;   ///< Arguments iterator
    typedef TArgs::const_iterator     TArgsCI;  ///< Const arguments iterator
    typedef /*deque*/vector<string>   TPosArgs; ///< Positional arg. vector
    typedef list<string>              TKeyFlagArgs; ///< List of flag arguments
    typedef vector<string>            TArgGroups;   ///< Argument groups

    // Dependencies
    struct SArgDependency
    {
        SArgDependency(const string arg, EDependency dep)
            : m_Arg(arg), m_Dep(dep) {}
        string      m_Arg;
        EDependency m_Dep;
    };
    // Map arguments to their dependencies
    typedef multimap<string, SArgDependency> TDependencies;
    typedef TDependencies::const_iterator TDependency_CI;

private:
    EArgSetType  m_ArgsType;     ///< Type of arguments
    TArgs        m_Args;         ///< Assoc.map of arguments' name/descr
    TPosArgs     m_PosArgs;      ///< Pos. args, ordered by position in cmd.-line
    TPosArgs     m_OpeningArgs;  ///< Opening args, ordered by position in cmd.-line
    TKeyFlagArgs m_KeyFlagArgs;  ///< Key/flag args, in order of insertion
    string       m_NoSeparator;  ///< Arguments allowed to use no separator
    unsigned     m_nExtra;       ///> # of mandatory extra args
    unsigned     m_nExtraOpt;    ///< # of optional  extra args
    TArgGroups   m_ArgGroups;    ///< Argument groups
    size_t       m_CurrentGroup; ///< Currently selected group (0 = no group)
    EArgPositionalMode m_PositionalMode; ///< Processing of positional args
    TDependencies      m_Dependencies;   ///< Arguments' dependencies
    TMiscFlags   m_MiscFlags;    ///< Flags for USAGE, error handling etc.
    set< CConstRef<CArgDependencyGroup> > m_DependencyGroups;

    // Extra USAGE info
protected:
    string    m_UsageName;         ///< Program name
    string    m_UsageDescription;  ///< Program description
    string    m_DetailedDescription;  ///< Program long description
    SIZE_TYPE m_UsageWidth;        ///< Maximum length of a usage line
    bool      m_AutoHelp;          ///< Special flag "-h" activated
    friend class CCommandArgDescriptions;

private:

    // Internal methods

    void x_PrintAliasesAsXml(CNcbiOstream& out, const string& name,
                                                bool negated=false) const;

    /// Helper method to find named parameter.
    /// 'negative' (if provided) will indicate if the name referred to a
    /// negative alias.
    TArgsI  x_Find(const string& name,
                   bool*         negative = NULL);

    /// Helper method to find named parameter -- const version.
    /// 'negative' (if provided) will indicate if the name referred to a
    /// negative alias.
    TArgsCI x_Find(const string& name,
                   bool*         negative = NULL) const;

    /// Get group index. Returns group index in the m_ArgGroups, 0 for empty
    /// group name or the next group number for undefined group.
    size_t x_GetGroupIndex(const string& group) const;

    /// Helper method for adding description.
    void x_AddDesc(CArgDesc& arg); 

    /// Helper method for doing pre-processing consistency checks.
    void x_PreCheck(void) const; 

    void x_PrintComment(list<string>&   arr,
                        const CArgDesc& arg,
                        SIZE_TYPE       width) const;

    /// Process arguments.
    ///
    /// Helper method to process arguments and build a CArgs object that is
    /// passed as the args parameter.
    /// @return
    ///   TRUE if specified "arg2" was used.
    bool    x_CreateArg(const string& arg1, ///< Argument to process 
                        bool have_arg2, ///< Is there an arg. that follows?
                        const string& arg2, ///< Following argument
                        unsigned* n_plain,  ///< Indicates number of args 
                        CArgs& args         ///< Contains processed args
                       ) const;

    /// @return
    ///   TRUE if specified "arg2" was used.
    bool x_CreateArg(const string& arg1,
                     const string& name, 
                     bool          have_arg2,
                     const string& arg2,
                     unsigned int  n_plain,
                     CArgs&        args,
                     bool          update = false,
                     CArgValue**   new_value = 0) const;

    /// @sa x_PostCheck()
    enum EPostCheckCaller {
        eCreateArgs,  ///< called by CreateArgs()
        eConvertKeys  ///< called by ConvertKeys()
    };
    /// Helper method for doing post-processing consistency checks.
    void x_PostCheck(CArgs&           args,
                     unsigned int     n_plain,
                     EPostCheckCaller caller)
        const;

    /// Returns TRUE if parameter supports multiple arguments
    bool x_IsMultiArg(const string& name) const;

protected:

    /// Helper method for checking if auto help requested and throw
    /// CArgHelpException if help requested.
    void x_CheckAutoHelp(const string& arg) const;

// PrintUsage helpers
    class CPrintUsage
    {
    public:
        CPrintUsage(const CArgDescriptions& desc);
        ~CPrintUsage();
        void AddSynopsis(list<string>& arr, const string& intro, const string& prefix) const;
        void AddDescription(list<string>& arr, bool detailed) const;
        void AddCommandDescription(list<string>& arr, const string& cmd, 
            const map<string,string>* aliases, size_t max_cmd_len, bool detailed) const;
        void AddDetails(list<string>& arr) const;
    private:
        const CArgDescriptions& m_desc;
        list<const CArgDesc*> m_args;
    };
    class CPrintUsageXml
    {
    public:
        CPrintUsageXml(const CArgDescriptions& desc, CNcbiOstream& out);
        ~CPrintUsageXml();
        void PrintArguments(const CArgDescriptions& desc) const;
    private:
        const CArgDescriptions& m_desc;
        CNcbiOstream& m_out;
    };

public:

    /// Helper method to find named parameter.
    /// 'negative' (if provided) will indicate if the name referred to a
    /// negative alias.
    TArgsI  Find(const string& name,
                   bool*         negative = NULL) {
        return x_Find(name, negative);
    }

    /// Helper method to find named parameter -- const version.
    /// 'negative' (if provided) will indicate if the name referred to a
    /// negative alias.
    TArgsCI Find(const string& name,
                   bool*         negative = NULL) const {
        return x_Find(name, negative);
    }

    TArgs& GetArgs() {
        return m_Args;
    }

    /// Create parsed arguments in CArgs object.
    ///
    /// Parse command-line arguments, and create "CArgs" args object 
    /// from the passed command-line arguments "argc" and "argv".
    ///
    /// Throw 
    ///  - CArgException on error
    ///  - CArgHelpException if USAGE printout was requested ("-h" flag)
    ///
    /// @note Deallocate the resulting "CArgs" object using 'delete'.
    ///
    /// @attention
    ///  This function is not suitable for parsing URL-encoded _CGI_ arg
    ///  outside of the CCgiApplication framework.
    template<class TSize, class TArray>
    CArgs* CreateArgs(TSize argc, TArray argv) const
    {
        // Check the consistency of argument descriptions
        x_PreCheck();

        // Create new "CArgs" to fill up, and parse cmd.-line args into it
        unique_ptr<CArgs> args(new CArgs());

        // Special case for CGI -- a lone positional argument
        if (GetArgsType() == eCgiArgs  &&  argc == 2) {
            x_CheckAutoHelp(argv[1]);
            return args.release();
        }

        // Regular case for both CGI and non-CGI
        unsigned int n_plain = kMax_UInt;
        for (TSize i = 1;  i < argc;  i++) {
            bool have_arg2 = (i + 1 < argc);
            if ( x_CreateArg(argv[i], have_arg2,
                             have_arg2 ? (string) argv[i+1] : kEmptyStr,
                             &n_plain, *args) ) {
                i++;
            }
        }

        // Check if there were any arguments at all
        if (n_plain == kMax_UInt) {
            n_plain = 0;
        }

        // Extra checks for the consistency of resultant argument values
        x_PostCheck(*args, n_plain, eCreateArgs);
        return args.release();
    }

    /// Parse command-line arguments 'argv' out of CNcbiArguments
    //virtual CArgs* CreateArgs(const CNcbiArguments& argv) const;
    virtual CArgs* CreateArgs(int argc, char* argv[]) const;

    /// Convert argument map (key-value pairs) into arguments in accordance
    /// with the argument descriptions
    template<class T>
    void ConvertKeys(CArgs* args, const T& arg_map, bool update) const
    {
        // Check the consistency of argument descriptions
        x_PreCheck();

        // Retrieve the arguments and their values
        ITERATE(TKeyFlagArgs, it, m_KeyFlagArgs) {
            const string& param_name = *it;

            // find first element in the input multimap
            typename T::const_iterator vit  = arg_map.find(param_name);
            typename T::const_iterator vend = arg_map.end();

            if (vit != vend) {   // at least one value found
                CArgValue* new_arg_value;
                x_CreateArg(param_name, param_name, 
                            true, /* value is present */
                            vit->second,
                            1,
                            *args,
                            update,
                            &new_arg_value);

                if (new_arg_value  &&  x_IsMultiArg(param_name)) {
                    CArgValue::TStringArray& varr =
                        new_arg_value->SetStringList();

                    // try to add all additional arguments to arg value
                    for (++vit;  vit != vend;  ++vit) {
                        if (vit->first != param_name)
                            break;
                        varr.push_back(vit->second);
                    }
                }
            }
        } // ITERATE

        // Extra checks for the consistency of resultant argument values
        x_PostCheck(*args, 0, eConvertKeys);
    }
};

/////////////////////////////////////////////////////////////////////////////
///
/// CArgDesc --
///
/// Base class for the description of various types of argument.
///
/// This was a pre-declaration; in MSVC, a predeclaration here causes a heap
/// corruption on termination because this class's virtual destructor isn't
/// defined at the moment the compiler instantiates the destructor of
/// AutoPtr<CArgDesc>.

class NCBI_XNCBI_EXPORT CArgDesc
{
public:
    /// Constructor.
    CArgDesc(const string& name,    ///< Argument name
             const string& comment  ///< Argument description
            );

    /// Destructor.
    virtual ~CArgDesc(void);

    /// Get argument name.
    const string& GetName   (void) const { return m_Name; }

    /// Get argument description.
    const string& GetComment(void) const { return m_Comment; }

    /// Get argument group
    virtual size_t GetGroup(void) const { return 0; }
    /// Set argument group
    virtual void SetGroup(size_t /* group */) {}

    /// Get usage synopsis.
    virtual string GetUsageSynopsis(bool name_only = false) const = 0;

    /// Get usage comment attribute.
    virtual string GetUsageCommentAttr(void) const = 0;

    /// Process argument with specified value.
    virtual CArgValue* ProcessArgument(const string& value) const = 0;

    /// Process argument default.
    virtual CArgValue* ProcessDefault(void) const = 0;

    /// Verify argument default value.
    virtual void VerifyDefault (void) const;

    /// Set argument constraint.
    /// @param constraint
    ///    The constraint object.
    ///    ATTN: A CRef must always be taken on the object by the
    ///          derived class's implementation of this method!
    virtual 
    void SetConstraint(const CArgAllow*                     constraint,
                       CArgDescriptions::EConstraintNegate  negate 
                                    = CArgDescriptions::eConstraint);

    /// Returns TRUE if associated constraint is inverted (NOT)
    /// @sa SetConstraint
    virtual bool IsConstraintInverted() const { return false; }

    /// Get argument constraint.
    virtual const CArgAllow* GetConstraint(void) const;

    /// Get usage constraint.
    string GetUsageConstraint(void) const;

    /// Get argument flags
    virtual CArgDescriptions::TFlags GetFlags(void) const { return 0; }

    /// Print description in XML format
    string PrintXml(CNcbiOstream& out) const;

private:
    string m_Name;      ///< Argument name
    string m_Comment;   ///< Argument description
};

/////////////////////////////////////////////////////////////////////////////

class NCBI_XNCBI_EXPORT CArgDependencyGroup
{
public:
    /// Create new dependency group.
    /// @param name
    ///  Name of the group 
    /// @param description
    ///  User-provided description of the dependency group (for Usage).
    ///  A generated description will be added to it.
    static CRef<CArgDependencyGroup> Create(
        const string& name, const string& description = kEmptyStr);

    virtual ~CArgDependencyGroup(void);

    /// @param min_members
    ///  Mark this group as "set" (in the context of
    ///  CArgDescriptions::EDependency) if at least "min_members" of its
    ///  members (args or groups listed in this group) are set.
    /// @note This condition can be weakened by "eInstantSet" mechanism.
    /// @sa EInstantSet
    /// @return "*this"
    CArgDependencyGroup& SetMinMembers(size_t min_members);

    /// @param max_members
    ///  No more than "max_members" of members (args or immediate groups
    ///  listed in this group) are allowed to be in the "set" state.
    ///  If this condition is not met, then this group will be marked
    ///  as "not set".
    /// @return "*this"
    CArgDependencyGroup& SetMaxMembers(size_t max_members);

    /// Control whether the "setting" of this particular member marks the
    /// whole group as "set" regardless of the value passed to  SetMinMembers()
    /// @sa SetMinMembers(), Add()
    enum EInstantSet {
        eNoInstantSet,
        eInstantSet
    };

    /// Make a regular argument a member of this dependency group.
    /// An argument with this name will need to be added separately using
    /// CArgDescriptions::AddXXX().
    /// @param arg_name
    ///  Name of the argument, as specified in CArgDescriptions::AddXXX()
    /// @param instant_set
    ///  "eInstantSet" means that if the added argument ("arg_name") is
    ///  set, then the SetMinMembers() condition doesn't apply anymore.
    /// @return  "*this"
    CArgDependencyGroup& Add(const string& arg_name,
                             EInstantSet  instant_set = eNoInstantSet);

    /// Make another dependency group a member of this dependency group.
    /// @attention
    ///  The "dep_group" will be added by reference, and its lifetime will
    ///  be managed according to the usual CObject/CRef rules.
    /// @param instant_set
    ///  "eInstantSet" means that if the added group ("dep_group") is
    ///  set, then the SetMinMembers() condition doesn't apply anymore.
    /// @return  "*this"
    CArgDependencyGroup& Add(CArgDependencyGroup* dep_group,
                             EInstantSet instant_set = eNoInstantSet);

private:
    bool x_Evaluate( const CArgs& args, string* arg_set, string* arg_unset) const;

    string m_Name;
    string m_Description;
    size_t m_MinMembers, m_MaxMembers;
    map<string,                         EInstantSet> m_Arguments;
    map<CConstRef<CArgDependencyGroup>, EInstantSet> m_Groups;

    // prohibit unwanted ctors and assignments
    CArgDependencyGroup(void);
    CArgDependencyGroup( const CArgDependencyGroup& dep_group);
    CArgDependencyGroup& operator= (const CArgDependencyGroup&);

public:
    void PrintUsage(list<string>& arr, size_t offset) const;
    void PrintUsageXml(CNcbiOstream& out) const;
    void Evaluate( const CArgs& args) const;
};

/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
//  CArgDesc***::   abstract base classes for argument descriptions
//
//    CArgDesc
//
//    CArgDescMandatory  : CArgDesc
//    CArgDescOptional   : virtual CArgDescMandatory
//    CArgDescDefault    : virtual CArgDescOptional
//
//    CArgDescSynopsis
//



class CArgDescMandatory : public CArgDesc
{
public:
    CArgDescMandatory(const string&            name,
                      const string&            comment,
                      CArgDescriptions::EType  type,
                      CArgDescriptions::TFlags flags);
    virtual ~CArgDescMandatory(void);

    CArgDescriptions::EType  GetType (void) const { return m_Type; }
    virtual CArgDescriptions::TFlags GetFlags(void) const { return m_Flags; }

    virtual string GetUsageSynopsis(bool name_only = false) const = 0;
    virtual string GetUsageCommentAttr(void) const;

    virtual CArgValue* ProcessArgument(const string& value) const;
    virtual CArgValue* ProcessDefault(void) const;

    virtual 
    void SetConstraint(const CArgAllow*                    constraint, 
                       CArgDescriptions::EConstraintNegate negate);
    virtual const CArgAllow* GetConstraint(void) const;
    virtual bool IsConstraintInverted() const;

private:
    CArgDescriptions::EType              m_Type;
    CArgDescriptions::TFlags             m_Flags;
    CConstRef<CArgAllow>                 m_Constraint;
    CArgDescriptions::EConstraintNegate  m_NegateConstraint;
};


class CArgDescOptional : virtual public CArgDescMandatory
{
public:
    CArgDescOptional(const string&            name,
                     const string&            comment,
                     CArgDescriptions::EType  type,
                     CArgDescriptions::TFlags flags);
    virtual ~CArgDescOptional(void);
    virtual CArgValue* ProcessDefault(void) const;
    virtual size_t GetGroup(void) const { return m_Group; }
    virtual void SetGroup(size_t group) { m_Group = group; }

private:
    size_t m_Group;
};



class CArgDescDefault : virtual public CArgDescOptional
{
public:
    CArgDescDefault(const string&            name,
                    const string&            comment,
                    CArgDescriptions::EType  type,
                    CArgDescriptions::TFlags flags,
                    const string&            default_value,
                    const string&            env_var,
                    const char*              display_value);
    virtual ~CArgDescDefault(void);

    const string& GetDefaultValue(void) const;
    const string& GetDisplayValue(void) const;

    virtual CArgValue* ProcessDefault(void) const;
    virtual void       VerifyDefault (void) const;

private:
    string m_DefaultValue;
    string m_EnvVar;
    string m_DisplayValue;
    bool   m_use_display;
};



class CArgDescSynopsis
{
public:
    CArgDescSynopsis(const string& synopsis);
    const string& GetSynopsis(void) const { return m_Synopsis; }
private:
    string m_Synopsis;
};

/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
//  CArgDesc_***::   classes for argument descriptions
//
//    CArgDesc_Flag    : CArgDesc
//
//    CArgDesc_Pos     : virtual CArgDescMandatory
//    CArgDesc_PosOpt  : virtual CArgDescOptional, CArgDesc_Pos
//    CArgDesc_PosDef  :         CArgDescDefault,  CArgDesc_PosOpt
//
//    CArgDescSynopsis
//
//    CArgDesc_Key     : CArgDesc_Pos,    CArgDescSynopsis
//    CArgDesc_KeyOpt  : CArgDesc_PosOpt, CArgDescSynopsis
//    CArgDesc_KeyDef  : CArgDesc_PosDef, CArgDescSynopsis
//


class CArgDesc_Flag : public CArgDesc
{
public:
    CArgDesc_Flag(const string& name,
                  const string& comment,
                  bool          set_value = true);
    virtual ~CArgDesc_Flag(void);

    virtual string GetUsageSynopsis(bool name_only = false) const;
    virtual string GetUsageCommentAttr(void) const;

    virtual CArgValue* ProcessArgument(const string& value) const;
    virtual CArgValue* ProcessDefault(void) const;
    virtual size_t GetGroup(void) const { return m_Group; }
    virtual void SetGroup(size_t group) { m_Group = group; }
    
    bool GetSetValue(void) const { return m_SetValue;}

private:
    size_t  m_Group;
    bool    m_SetValue;  // value to set if the arg is provided  
};



class CArgDesc_Pos : virtual public CArgDescMandatory
{
public:
    CArgDesc_Pos(const string&            name,
                 const string&            comment,
                 CArgDescriptions::EType  type,
                 CArgDescriptions::TFlags flags);
    virtual ~CArgDesc_Pos(void);
    virtual string GetUsageSynopsis(bool name_only = false) const;
};



class CArgDesc_Opening : virtual public CArgDescMandatory
{
public:
    CArgDesc_Opening(const string&            name,
                 const string&            comment,
                 CArgDescriptions::EType  type,
                 CArgDescriptions::TFlags flags);
    virtual ~CArgDesc_Opening(void);
    virtual string GetUsageSynopsis(bool name_only = false) const;
};



class CArgDesc_PosOpt : virtual public CArgDescOptional,
                        public CArgDesc_Pos
{
public:
    CArgDesc_PosOpt(const string&            name,
                    const string&            comment,
                    CArgDescriptions::EType  type,
                    CArgDescriptions::TFlags flags);
    virtual ~CArgDesc_PosOpt(void);
};



class CArgDesc_PosDef : public CArgDescDefault,
                        public CArgDesc_PosOpt
{
public:
    CArgDesc_PosDef(const string&            name,
                    const string&            comment,
                    CArgDescriptions::EType  type,
                    CArgDescriptions::TFlags flags,
                    const string&            default_value,
                    const string&            env_var,
                    const char*              display_value);
    virtual ~CArgDesc_PosDef(void);
};



class CArgDesc_Key : public CArgDesc_Pos, public CArgDescSynopsis
{
public:
    CArgDesc_Key(const string&            name,
                 const string&            comment,
                 CArgDescriptions::EType  type,
                 CArgDescriptions::TFlags flags,
                 const string&            synopsis);
    virtual ~CArgDesc_Key(void);
    virtual string GetUsageSynopsis(bool name_only = false) const;
};



class CArgDesc_KeyOpt : public CArgDesc_PosOpt, public CArgDescSynopsis
{
public:
    CArgDesc_KeyOpt(const string&            name,
                    const string&            comment,
                    CArgDescriptions::EType  type,
                    CArgDescriptions::TFlags flags,
                    const string&            synopsis);
    virtual ~CArgDesc_KeyOpt(void);
    virtual string GetUsageSynopsis(bool name_only = false) const;
};



class CArgDesc_KeyDef : public CArgDesc_PosDef, public CArgDescSynopsis
{
public:
    CArgDesc_KeyDef(const string&            name,
                    const string&            comment,
                    CArgDescriptions::EType  type,
                    CArgDescriptions::TFlags flags,
                    const string&            synopsis,
                    const string&            default_value,
                    const string&            env_var,
                    const char*              display_value);
    virtual ~CArgDesc_KeyDef(void);
    virtual string GetUsageSynopsis(bool name_only = false) const;
};


// Special case - arg synonym. Can be used e.g. to create short and
// long argument names.

class CArgDesc_Alias : public CArgDesc
{
public:
    // Create an argument alias.
    // alias is a new name for the existing argument, arg_name is
    // its original name. Any search functions will return the original
    // argument rather than the alias.
    CArgDesc_Alias(const string& alias,
                   const string& arg_name,
                   const string& comment);
    virtual ~CArgDesc_Alias(void);

    const string& GetAliasedName(void) const;

    // Dummy methods - to make the class not abstract
    virtual string GetUsageSynopsis(bool name_only) const;
    virtual string GetUsageCommentAttr(void) const;
    virtual CArgValue* ProcessArgument(const string& value) const;
    virtual CArgValue* ProcessDefault(void) const;

    void SetNegativeFlag(bool value) { m_NegativeFlag = value; }
    bool GetNegativeFlag(void) const { return m_NegativeFlag; }
private:
    string m_ArgName;
    bool   m_NegativeFlag;
};

bool ArgDescIsKey(const CArgDesc& arg);
bool ArgDescIsPositional(const CArgDesc& arg);
bool ArgDescIsOpening(const CArgDesc& arg);
bool ArgDescIsOptional(const CArgDesc& arg);
bool ArgDescIsFlag(const CArgDesc& arg);
bool ArgDescIsAlias(const CArgDesc& arg);

END_NCBI_SCOPE

#endif // __NCBIARGS_DESC_HPP