/* Copyright (c) 2016-2017 Taylor C. Richberger <taywee@gmx.com> and Pavel
 * Belikov
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * 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.
 */

/** \file args.hxx
 * \brief this single-header lets you use all of the args functionality
 *
 * The important stuff is done inside the args namespace
 */

#ifndef ARGS_HXX
#define ARGS_HXX

#include <algorithm>
#include <iterator>
#include <exception>
#include <functional>
#include <sstream>
#include <string>
#include <tuple>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <type_traits>

#ifdef ARGS_TESTNAMESPACE
namespace argstest
{
#else

/** \namespace args
 * \brief contains all the functionality of the args library
 */
namespace args
{
#endif
    /** Getter to grab the value from the argument type.
     *
     * If the Get() function of the type returns a reference, so does this, and
     * the value will be modifiable.
     */
    template <typename Option>
    auto get(Option &option_) -> decltype(option_.Get())
    {
        return option_.Get();
    }

    /** (INTERNAL) Count UTF-8 glyphs
     *
     * This is not reliable, and will fail for combinatory glyphs, but it's
     * good enough here for now.
     *
     * \param string The string to count glyphs from
     * \return The UTF-8 glyphs in the string
     */
    inline std::string::size_type Glyphs(const std::string &string_)
    {
        std::string::size_type length = 0;
        for (const char c: string_)
        {
            if ((c & 0xc0) != 0x80)
            {
                ++length;
            }
        }
        return length;
    }

    /** (INTERNAL) Wrap a vector of words into a vector of lines
     *
     * Empty words are skipped. Word "\n" forces wrapping.
     *
     * \param begin The begin iterator
     * \param end The end iterator
     * \param width The width of the body
     * \param firstlinewidth the width of the first line, defaults to the width of the body
     * \param firstlineindent the indent of the first line, defaults to 0
     * \return the vector of lines
     */
    template <typename It>
    inline std::vector<std::string> Wrap(It begin,
                                         It end,
                                         const std::string::size_type width,
                                         std::string::size_type firstlinewidth = 0,
                                         std::string::size_type firstlineindent = 0)
    {
        std::vector<std::string> output;
        std::string line(firstlineindent, ' ');
        bool empty = true;

        if (firstlinewidth == 0)
        {
            firstlinewidth = width;
        }

        auto currentwidth = firstlinewidth;

        for (auto it = begin; it != end; ++it)
        {
            if (it->empty())
            {
                continue;
            }

            if (*it == "\n")
            {
                if (!empty)
                {
                    output.push_back(line);
                    line.clear();
                    empty = true;
                    currentwidth = width;
                }

                continue;
            }

            auto itemsize = Glyphs(*it);
            if ((line.length() + 1 + itemsize) > currentwidth)
            {
                if (!empty)
                {
                    output.push_back(line);
                    line.clear();
                    empty = true;
                    currentwidth = width;
                }
            }

            if (itemsize > 0)
            {
                if (!empty)
                {
                    line += ' ';
                }

                line += *it;
                empty = false;
            }
        }

        if (!empty)
        {
            output.push_back(line);
        }

        return output;
    }

    namespace detail
    {
        template <typename T>
        std::string Join(const T& array, const std::string &delimiter)
        {
            std::string res;
            for (auto &element : array)
            {
                if (!res.empty())
                {
                    res += delimiter;
                }

                res += element;
            }

            return res;
        }
    }

    /** (INTERNAL) Wrap a string into a vector of lines
     *
     * This is quick and hacky, but works well enough.  You can specify a
     * different width for the first line
     *
     * \param width The width of the body
     * \param firstlinewid the width of the first line, defaults to the width of the body
     * \return the vector of lines
     */
    inline std::vector<std::string> Wrap(const std::string &in, const std::string::size_type width, std::string::size_type firstlinewidth = 0)
    {
        // Preserve existing line breaks
        const auto newlineloc = in.find('\n');
        if (newlineloc != in.npos)
        {
            auto first = Wrap(std::string(in, 0, newlineloc), width);
            auto second = Wrap(std::string(in, newlineloc + 1), width);
            first.insert(
                std::end(first),
                std::make_move_iterator(std::begin(second)),
                std::make_move_iterator(std::end(second)));
            return first;
        }

        std::istringstream stream(in);
        std::string::size_type indent = 0;

        for (char c : in)
        {
            if (!isspace(c))
            {
                break;
            }
            ++indent;
        }

        return Wrap(std::istream_iterator<std::string>(stream), std::istream_iterator<std::string>(),
                    width, firstlinewidth, indent);
    }

#ifdef ARGS_NOEXCEPT
    /// Error class, for when ARGS_NOEXCEPT is defined
    enum class Error
    {
        None,
        Usage,
        Parse,
        Validation,
        Required,
        Map,
        Extra,
        Help,
        Subparser,
        Completion,
    };
#else
    /** Base error class
     */
    class Error : public std::runtime_error
    {
        public:
            Error(const std::string &problem) : std::runtime_error(problem) {}
            virtual ~Error() {}
    };

    /** Errors that occur during usage
     */
    class UsageError : public Error
    {
        public:
            UsageError(const std::string &problem) : Error(problem) {}
            virtual ~UsageError() {}
    };

    /** Errors that occur during regular parsing
     */
    class ParseError : public Error
    {
        public:
            ParseError(const std::string &problem) : Error(problem) {}
            virtual ~ParseError() {}
    };

    /** Errors that are detected from group validation after parsing finishes
     */
    class ValidationError : public Error
    {
        public:
            ValidationError(const std::string &problem) : Error(problem) {}
            virtual ~ValidationError() {}
    };

    /** Errors that when a required flag is omitted
     */
    class RequiredError : public ValidationError
    {
        public:
            RequiredError(const std::string &problem) : ValidationError(problem) {}
            virtual ~RequiredError() {}
    };

    /** Errors in map lookups
     */
    class MapError : public ParseError
    {
        public:
            MapError(const std::string &problem) : ParseError(problem) {}
            virtual ~MapError() {}
    };

    /** Error that occurs when a singular flag is specified multiple times
     */
    class ExtraError : public ParseError
    {
        public:
            ExtraError(const std::string &problem) : ParseError(problem) {}
            virtual ~ExtraError() {}
    };

    /** An exception that indicates that the user has requested help
     */
    class Help : public Error
    {
        public:
            Help(const std::string &flag) : Error(flag) {}
            virtual ~Help() {}
    };

    /** (INTERNAL) An exception that emulates coroutine-like control flow for subparsers.
     */
    class SubparserError : public Error
    {
        public:
            SubparserError() : Error("") {}
            virtual ~SubparserError() {}
    };

    /** An exception that contains autocompletion reply
     */
    class Completion : public Error
    {
        public:
            Completion(const std::string &flag) : Error(flag) {}
            virtual ~Completion() {}
    };
#endif

    /** A simple unified option type for unified initializer lists for the Matcher class.
     */
    struct EitherFlag
    {
        const bool isShort;
        const char shortFlag;
        const std::string longFlag;
        EitherFlag(const std::string &flag) : isShort(false), shortFlag(), longFlag(flag) {}
        EitherFlag(const char *flag) : isShort(false), shortFlag(), longFlag(flag) {}
        EitherFlag(const char flag) : isShort(true), shortFlag(flag), longFlag() {}

        /** Get just the long flags from an initializer list of EitherFlags
         */
        static std::unordered_set<std::string> GetLong(std::initializer_list<EitherFlag> flags)
        {
            std::unordered_set<std::string>  longFlags;
            for (const EitherFlag &flag: flags)
            {
                if (!flag.isShort)
                {
                    longFlags.insert(flag.longFlag);
                }
            }
            return longFlags;
        }

        /** Get just the short flags from an initializer list of EitherFlags
         */
        static std::unordered_set<char> GetShort(std::initializer_list<EitherFlag> flags)
        {
            std::unordered_set<char>  shortFlags;
            for (const EitherFlag &flag: flags)
            {
                if (flag.isShort)
                {
                    shortFlags.insert(flag.shortFlag);
                }
            }
            return shortFlags;
        }

        std::string str() const
        {
            return isShort ? std::string(1, shortFlag) : longFlag;
        }

        std::string str(const std::string &shortPrefix, const std::string &longPrefix) const
        {
            return isShort ? shortPrefix + std::string(1, shortFlag) : longPrefix + longFlag;
        }
    };



    /** A class of "matchers", specifying short and flags that can possibly be
     * matched.
     *
     * This is supposed to be constructed and then passed in, not used directly
     * from user code.
     */
    class Matcher
    {
        private:
            const std::unordered_set<char> shortFlags;
            const std::unordered_set<std::string> longFlags;

        public:
            /** Specify short and long flags separately as iterators
             *
             * ex: `args::Matcher(shortFlags.begin(), shortFlags.end(), longFlags.begin(), longFlags.end())`
             */
            template <typename ShortIt, typename LongIt>
            Matcher(ShortIt shortFlagsStart, ShortIt shortFlagsEnd, LongIt longFlagsStart, LongIt longFlagsEnd) :
                shortFlags(shortFlagsStart, shortFlagsEnd),
                longFlags(longFlagsStart, longFlagsEnd)
            {
                if (shortFlags.empty() && longFlags.empty())
                {
#ifndef ARGS_NOEXCEPT
                    throw UsageError("empty Matcher");
#endif
                }
            }

#ifdef ARGS_NOEXCEPT
            /// Only for ARGS_NOEXCEPT
            Error GetError() const noexcept
            {
                return shortFlags.empty() && longFlags.empty() ? Error::Usage : Error::None;
            }
#endif

            /** Specify short and long flags separately as iterables
             *
             * ex: `args::Matcher(shortFlags, longFlags)`
             */
            template <typename Short, typename Long>
            Matcher(Short &&shortIn, Long &&longIn) :
                Matcher(std::begin(shortIn), std::end(shortIn), std::begin(longIn), std::end(longIn))
            {}

            /** Specify a mixed single initializer-list of both short and long flags
             *
             * This is the fancy one.  It takes a single initializer list of
             * any number of any mixed kinds of flags.  Chars are
             * automatically interpreted as short flags, and strings are
             * automatically interpreted as long flags:
             *
             *     args::Matcher{'a'}
             *     args::Matcher{"foo"}
             *     args::Matcher{'h', "help"}
             *     args::Matcher{"foo", 'f', 'F', "FoO"}
             */
            Matcher(std::initializer_list<EitherFlag> in) :
                Matcher(EitherFlag::GetShort(in), EitherFlag::GetLong(in)) {}

            Matcher(Matcher &&other) : shortFlags(std::move(other.shortFlags)), longFlags(std::move(other.longFlags))
            {}

            ~Matcher() {}

            /** (INTERNAL) Check if there is a match of a short flag
             */
            bool Match(const char flag) const
            {
                return shortFlags.find(flag) != shortFlags.end();
            }

            /** (INTERNAL) Check if there is a match of a long flag
             */
            bool Match(const std::string &flag) const
            {
                return longFlags.find(flag) != longFlags.end();
            }

            /** (INTERNAL) Check if there is a match of a flag
             */
            bool Match(const EitherFlag &flag) const
            {
                return flag.isShort ? Match(flag.shortFlag) : Match(flag.longFlag);
            }

            /** (INTERNAL) Get all flag strings as a vector, with the prefixes embedded
             */
            std::vector<EitherFlag> GetFlagStrings() const
            {
                std::vector<EitherFlag> flagStrings;
                flagStrings.reserve(shortFlags.size() + longFlags.size());
                for (const char flag: shortFlags)
                {
                    flagStrings.emplace_back(flag);
                }
                for (const std::string &flag: longFlags)
                {
                    flagStrings.emplace_back(flag);
                }
                return flagStrings;
            }

            /** (INTERNAL) Get long flag if it exists or any short flag
             */
            EitherFlag GetLongOrAny() const
            {
                if (!longFlags.empty())
                {
                    return *longFlags.begin();
                }

                if (!shortFlags.empty())
                {
                    return *shortFlags.begin();
                }

                // should be unreachable
                return ' ';
            }

            /** (INTERNAL) Get short flag if it exists or any long flag
             */
            EitherFlag GetShortOrAny() const
            {
                if (!shortFlags.empty())
                {
                    return *shortFlags.begin();
                }

                if (!longFlags.empty())
                {
                    return *longFlags.begin();
                }

                // should be unreachable
                return ' ';
            }
    };

    /** Attributes for flags.
     */
    enum class Options
    {
        /** Default options.
         */
        None = 0x0,

        /** Flag can't be passed multiple times.
         */
        Single = 0x01,

        /** Flag can't be omitted.
         */
        Required = 0x02,

        /** Flag is excluded from usage line.
         */
        HiddenFromUsage = 0x04,

        /** Flag is excluded from options help.
         */
        HiddenFromDescription = 0x08,

        /** Flag is global and can be used in any subcommand.
         */
        Global = 0x10,

        /** Flag stops a parser.
         */
        KickOut = 0x20,

        /** Flag is excluded from auto completion.
         */
        HiddenFromCompletion = 0x40,

        /** Flag is excluded from options help and usage line
         */
        Hidden = HiddenFromUsage | HiddenFromDescription | HiddenFromCompletion,
    };

    inline Options operator | (Options lhs, Options rhs)
    {
        return static_cast<Options>(static_cast<int>(lhs) | static_cast<int>(rhs));
    }

    inline Options operator & (Options lhs, Options rhs)
    {
        return static_cast<Options>(static_cast<int>(lhs) & static_cast<int>(rhs));
    }

    class FlagBase;
    class PositionalBase;
    class Command;
    class ArgumentParser;

    /** A simple structure of parameters for easy user-modifyable help menus
     */
    struct HelpParams
    {
        /** The width of the help menu
         */
        unsigned int width = 80;
        /** The indent of the program line
         */
        unsigned int progindent = 2;
        /** The indent of the program trailing lines for long parameters
         */
        unsigned int progtailindent = 4;
        /** The indent of the description and epilogs
         */
        unsigned int descriptionindent = 4;
        /** The indent of the flags
         */
        unsigned int flagindent = 6;
        /** The indent of the flag descriptions
         */
        unsigned int helpindent = 40;
        /** The additional indent each group adds
         */
        unsigned int eachgroupindent = 2;

        /** The minimum gutter between each flag and its help
         */
        unsigned int gutter = 1;

        /** Show the terminator when both options and positional parameters are present
         */
        bool showTerminator = true;

        /** Show the {OPTIONS} on the prog line when this is true
         */
        bool showProglineOptions = true;

        /** Show the positionals on the prog line when this is true
         */
        bool showProglinePositionals = true;

        /** The prefix for short flags
         */
        std::string shortPrefix;

        /** The prefix for long flags
         */
        std::string longPrefix;

        /** The separator for short flags
         */
        std::string shortSeparator;

        /** The separator for long flags
         */
        std::string longSeparator;

        /** The program name for help generation
         */
        std::string programName;

        /** Show command's flags
         */
        bool showCommandChildren = false;

        /** Show command's descriptions and epilog
         */
        bool showCommandFullHelp = false;

        /** The postfix for progline when showProglineOptions is true and command has any flags
         */
        std::string proglineOptions = "{OPTIONS}";

        /** The prefix for progline when command has any subcommands
         */
        std::string proglineCommand = "COMMAND";

        /** The prefix for progline value
         */
        std::string proglineValueOpen = " <";

        /** The postfix for progline value
         */
        std::string proglineValueClose = ">";

        /** The prefix for progline required argument
         */
        std::string proglineRequiredOpen = "";

        /** The postfix for progline required argument
         */
        std::string proglineRequiredClose = "";

        /** The prefix for progline non-required argument
         */
        std::string proglineNonrequiredOpen = "[";

        /** The postfix for progline non-required argument
         */
        std::string proglineNonrequiredClose = "]";

        /** Show flags in program line
         */
        bool proglineShowFlags = false;

        /** Use short flags in program lines when possible
         */
        bool proglinePreferShortFlags = false;

        /** Program line prefix
         */
        std::string usageString;

        /** String shown in help before flags descriptions
         */
        std::string optionsString = "OPTIONS:";

        /** Display value name after all the long and short flags
         */
        bool useValueNameOnce = false;

        /** Show value name
         */
        bool showValueName = true;

        /** Add newline before flag description
         */
        bool addNewlineBeforeDescription = false;

        /** The prefix for option value
         */
        std::string valueOpen = "[";

        /** The postfix for option value
         */
        std::string valueClose = "]";

        /** Add choices to argument description
         */
        bool addChoices = false;

        /** The prefix for choices
         */
        std::string choiceString = "\nOne of: ";

        /** Add default values to argument description
         */
        bool addDefault = false;

        /** The prefix for default values
         */
        std::string defaultString = "\nDefault: ";
    };

    /** A number of arguments which can be consumed by an option.
     *
     * Represents a closed interval [min, max].
     */
    struct Nargs
    {
        const size_t min;
        const size_t max;

        Nargs(size_t min_, size_t max_) : min{min_}, max{max_}
        {
#ifndef ARGS_NOEXCEPT
            if (max < min)
            {
                throw UsageError("Nargs: max > min");
            }
#endif
        }

        Nargs(size_t num_) : min{num_}, max{num_}
        {
        }

        friend bool operator == (const Nargs &lhs, const Nargs &rhs)
        {
            return lhs.min == rhs.min && lhs.max == rhs.max;
        }

        friend bool operator != (const Nargs &lhs, const Nargs &rhs)
        {
            return !(lhs == rhs);
        }
    };

    /** Base class for all match types
     */
    class Base
    {
        private:
            Options options = {};

        protected:
            bool matched = false;
            const std::string help;
#ifdef ARGS_NOEXCEPT
            /// Only for ARGS_NOEXCEPT
            mutable Error error = Error::None;
            mutable std::string errorMsg;
#endif

        public:
            Base(const std::string &help_, Options options_ = {}) : options(options_), help(help_) {}
            virtual ~Base() {}

            Options GetOptions() const noexcept
            {
                return options;
            }

            bool IsRequired() const noexcept
            {
                return (GetOptions() & Options::Required) != Options::None;
            }

            virtual bool Matched() const noexcept
            {
                return matched;
            }

            virtual void Validate(const std::string &, const std::string &) const
            {
            }

            operator bool() const noexcept
            {
                return Matched();
            }

            virtual std::vector<std::tuple<std::string, std::string, unsigned>> GetDescription(const HelpParams &, const unsigned indentLevel) const
            {
                std::tuple<std::string, std::string, unsigned> description;
                std::get<1>(description) = help;
                std::get<2>(description) = indentLevel;
                return { std::move(description) };
            }

            virtual std::vector<Command*> GetCommands()
            {
                return {};
            }

            virtual bool IsGroup() const
            {
                return false;
            }

            virtual FlagBase *Match(const EitherFlag &)
            {
                return nullptr;
            }

            virtual PositionalBase *GetNextPositional()
            {
                return nullptr;
            }

            virtual std::vector<FlagBase*> GetAllFlags()
            {
                return {};
            }

            virtual bool HasFlag() const
            {
                return false;
            }

            virtual bool HasPositional() const
            {
                return false;
            }

            virtual bool HasCommand() const
            {
                return false;
            }

            virtual std::vector<std::string> GetProgramLine(const HelpParams &) const
            {
                return {};
            }

            /// Sets a kick-out value for building subparsers
            void KickOut(bool kickout_) noexcept
            {
                if (kickout_)
                {
                    options = options | Options::KickOut;
                }
                else
                {
                    options = static_cast<Options>(static_cast<int>(options) & ~static_cast<int>(Options::KickOut));
                }
            }

            /// Gets the kick-out value for building subparsers
            bool KickOut() const noexcept
            {
                return (options & Options::KickOut) != Options::None;
            }

            virtual void Reset() noexcept
            {
                matched = false;
#ifdef ARGS_NOEXCEPT
                error = Error::None;
                errorMsg.clear();
#endif
            }

#ifdef ARGS_NOEXCEPT
            /// Only for ARGS_NOEXCEPT
            virtual Error GetError() const
            {
                return error;
            }

            /// Only for ARGS_NOEXCEPT
            std::string GetErrorMsg() const
            {
                return errorMsg;
            }
#endif
    };

    /** Base class for all match types that have a name
     */
    class NamedBase : public Base
    {
        protected:
            const std::string name;
            bool kickout = false;
            std::string defaultString;
            bool defaultStringManual = false;
            std::vector<std::string> choicesStrings;
            bool choicesStringManual = false;

            virtual std::string GetDefaultString(const HelpParams&) const { return {}; }

            virtual std::vector<std::string> GetChoicesStrings(const HelpParams&) const { return {}; }

            virtual std::string GetNameString(const HelpParams&) const { return Name(); }

            void AddDescriptionPostfix(std::string &dest, const bool isManual, const std::string &manual, bool isGenerated, const std::string &generated, const std::string &str) const
            {
                if (isManual && !manual.empty())
                {
                    dest += str;
                    dest += manual;
                }
                else if (!isManual && isGenerated && !generated.empty())
                {
                    dest += str;
                    dest += generated;
                }
            }

        public:
            NamedBase(const std::string &name_, const std::string &help_, Options options_ = {}) : Base(help_, options_), name(name_) {}
            virtual ~NamedBase() {}

            /** Sets default value string that will be added to argument description.
             *  Use empty string to disable it for this argument.
             */
            void HelpDefault(const std::string &str)
            {
                defaultStringManual = true;
                defaultString = str;
            }

            /** Gets default value string that will be added to argument description.
             */
            std::string HelpDefault(const HelpParams &params) const
            {
                return defaultStringManual ? defaultString : GetDefaultString(params);
            }

            /** Sets choices strings that will be added to argument description.
             *  Use empty vector to disable it for this argument.
             */
            void HelpChoices(const std::vector<std::string> &array)
            {
                choicesStringManual = true;
                choicesStrings = array;
            }

            /** Gets choices strings that will be added to argument description.
             */
            std::vector<std::string> HelpChoices(const HelpParams &params) const
            {
                return choicesStringManual ? choicesStrings : GetChoicesStrings(params);
            }

            virtual std::vector<std::tuple<std::string, std::string, unsigned>> GetDescription(const HelpParams &params, const unsigned indentLevel) const override
            {
                std::tuple<std::string, std::string, unsigned> description;
                std::get<0>(description) = GetNameString(params);
                std::get<1>(description) = help;
                std::get<2>(description) = indentLevel;

                AddDescriptionPostfix(std::get<1>(description), choicesStringManual, detail::Join(choicesStrings, ", "), params.addChoices, detail::Join(GetChoicesStrings(params), ", "), params.choiceString);
                AddDescriptionPostfix(std::get<1>(description), defaultStringManual, defaultString, params.addDefault, GetDefaultString(params), params.defaultString);

                return { std::move(description) };
            }

            virtual std::string Name() const
            {
                return name;
            }
    };

    namespace detail
    {
        template <typename T, typename = int>
        struct IsConvertableToString : std::false_type {};

        template <typename T>
        struct IsConvertableToString<T, decltype(std::declval<std::ostringstream&>() << std::declval<T>(), int())> : std::true_type {};

        template <typename T>
        typename std::enable_if<IsConvertableToString<T>::value, std::string>::type
        ToString(const T &value)
        {
            std::ostringstream s;
            s << value;
            return s.str();
        }

        template <typename T>
        typename std::enable_if<!IsConvertableToString<T>::value, std::string>::type
        ToString(const T &)
        {
            return {};
        }

        template <typename T>
        std::vector<std::string> MapKeysToStrings(const T &map)
        {
            std::vector<std::string> res;
            using K = typename std::decay<decltype(std::begin(map)->first)>::type;
            if (IsConvertableToString<K>::value)
            {
                for (const auto &p : map)
                {
                    res.push_back(detail::ToString(p.first));
                }

                std::sort(res.begin(), res.end());
            }
            return res;
        }
    }

    /** Base class for all flag options
     */
    class FlagBase : public NamedBase
    {
        protected:
            const Matcher matcher;

            virtual std::string GetNameString(const HelpParams &params) const override
            {
                const std::string postfix = !params.showValueName || NumberOfArguments() == 0 ? std::string() : Name();
                std::string flags;
                const auto flagStrings = matcher.GetFlagStrings();
                const bool useValueNameOnce = flagStrings.size() == 1 ? false : params.useValueNameOnce;
                for (auto it = flagStrings.begin(); it != flagStrings.end(); ++it)
                {
                    auto &flag = *it;
                    if (it != flagStrings.begin())
                    {
                        flags += ", ";
                    }

                    flags += flag.isShort ? params.shortPrefix : params.longPrefix;
                    flags += flag.str();

                    if (!postfix.empty() && (!useValueNameOnce || it + 1 == flagStrings.end()))
                    {
                        flags += flag.isShort ? params.shortSeparator : params.longSeparator;
                        flags += params.valueOpen + postfix + params.valueClose;
                    }
                }

                return flags;
            }

        public:
            FlagBase(const std::string &name_, const std::string &help_, Matcher &&matcher_, const bool extraError_ = false) : NamedBase(name_, help_, extraError_ ? Options::Single : Options()), matcher(std::move(matcher_)) {}

            FlagBase(const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_) : NamedBase(name_, help_, options_), matcher(std::move(matcher_)) {}

            virtual ~FlagBase() {}

            virtual FlagBase *Match(const EitherFlag &flag) override
            {
                if (matcher.Match(flag))
                {
                    if ((GetOptions() & Options::Single) != Options::None && matched)
                    {
                        std::ostringstream problem;
                        problem << "Flag '" << flag.str() << "' was passed multiple times, but is only allowed to be passed once";
#ifdef ARGS_NOEXCEPT
                        error = Error::Extra;
                        errorMsg = problem.str();
#else
                        throw ExtraError(problem.str());
#endif
                    }
                    matched = true;
                    return this;
                }
                return nullptr;
            }

            virtual std::vector<FlagBase*> GetAllFlags() override
            {
                return { this };
            }

            const Matcher &GetMatcher() const
            {
                return matcher;
            }

            virtual void Validate(const std::string &shortPrefix, const std::string &longPrefix) const override
            {
                if (!Matched() && IsRequired())
                {
                        std::ostringstream problem;
                        problem << "Flag '" << matcher.GetLongOrAny().str(shortPrefix, longPrefix) << "' is required";
#ifdef ARGS_NOEXCEPT
                        error = Error::Required;
                        errorMsg = problem.str();
#else
                        throw RequiredError(problem.str());
#endif
                }
            }

            virtual std::vector<std::string> GetProgramLine(const HelpParams &params) const override
            {
                if (!params.proglineShowFlags)
                {
                    return {};
                }

                const std::string postfix = NumberOfArguments() == 0 ? std::string() : Name();
                const EitherFlag flag = params.proglinePreferShortFlags ? matcher.GetShortOrAny() : matcher.GetLongOrAny();
                std::string res = flag.str(params.shortPrefix, params.longPrefix);
                if (!postfix.empty())
                {
                    res += params.proglineValueOpen + postfix + params.proglineValueClose;
                }

                return { IsRequired() ? params.proglineRequiredOpen + res + params.proglineRequiredClose
                                      : params.proglineNonrequiredOpen + res + params.proglineNonrequiredClose };
            }

            virtual bool HasFlag() const override
            {
                return true;
            }

#ifdef ARGS_NOEXCEPT
            /// Only for ARGS_NOEXCEPT
            virtual Error GetError() const override
            {
                const auto nargs = NumberOfArguments();
                if (nargs.min > nargs.max)
                {
                    return Error::Usage;
                }

                const auto matcherError = matcher.GetError();
                if (matcherError != Error::None)
                {
                    return matcherError;
                }

                return error;
            }
#endif

            /** Defines how many values can be consumed by this option.
             *
             * \return closed interval [min, max]
             */
            virtual Nargs NumberOfArguments() const noexcept = 0;

            /** Parse values of this option.
             *
             * \param value Vector of values. It's size must be in NumberOfArguments() interval.
             */
            virtual void ParseValue(const std::vector<std::string> &value) = 0;
    };

    /** Base class for value-accepting flag options
     */
    class ValueFlagBase : public FlagBase
    {
        public:
            ValueFlagBase(const std::string &name_, const std::string &help_, Matcher &&matcher_, const bool extraError_ = false) : FlagBase(name_, help_, std::move(matcher_), extraError_) {}
            ValueFlagBase(const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_) : FlagBase(name_, help_, std::move(matcher_), options_) {}
            virtual ~ValueFlagBase() {}

            virtual Nargs NumberOfArguments() const noexcept override
            {
                return 1;
            }
    };

    class CompletionFlag : public ValueFlagBase
    {
        public:
            std::vector<std::string> reply;
            size_t cword = 0;
            std::string syntax;

            template <typename GroupClass>
            CompletionFlag(GroupClass &group_, Matcher &&matcher_): ValueFlagBase("completion", "completion flag", std::move(matcher_), Options::Hidden)
            {
                group_.AddCompletion(*this);
            }

            virtual ~CompletionFlag() {}

            virtual Nargs NumberOfArguments() const noexcept override
            {
                return 2;
            }

            virtual void ParseValue(const std::vector<std::string> &value_) override
            {
                syntax = value_.at(0);
                std::istringstream(value_.at(1)) >> cword;
            }

            /** Get the completion reply
             */
            std::string Get() noexcept
            {
                return detail::Join(reply, "\n");
            }

            virtual void Reset() noexcept override
            {
                ValueFlagBase::Reset();
                cword = 0;
                syntax.clear();
                reply.clear();
            }
    };


    /** Base class for positional options
     */
    class PositionalBase : public NamedBase
    {
        protected:
            bool ready;

        public:
            PositionalBase(const std::string &name_, const std::string &help_, Options options_ = {}) : NamedBase(name_, help_, options_), ready(true) {}
            virtual ~PositionalBase() {}

            bool Ready()
            {
                return ready;
            }

            virtual void ParseValue(const std::string &value_) = 0;

            virtual void Reset() noexcept override
            {
                matched = false;
                ready = true;
#ifdef ARGS_NOEXCEPT
                error = Error::None;
                errorMsg.clear();
#endif
            }

            virtual PositionalBase *GetNextPositional() override
            {
                return Ready() ? this : nullptr;
            }

            virtual bool HasPositional() const override
            {
                return true;
            }

            virtual std::vector<std::string> GetProgramLine(const HelpParams &params) const override
            {
                return { IsRequired() ? params.proglineRequiredOpen + Name() + params.proglineRequiredClose
                                      : params.proglineNonrequiredOpen + Name() + params.proglineNonrequiredClose };
            }

            virtual void Validate(const std::string &, const std::string &) const override
            {
                if (IsRequired() && !Matched())
                {
                    std::ostringstream problem;
                    problem << "Option '" << Name() << "' is required";
#ifdef ARGS_NOEXCEPT
                    error = Error::Required;
                    errorMsg = problem.str();
#else
                    throw RequiredError(problem.str());
#endif
                }
            }
    };

    /** Class for all kinds of validating groups, including ArgumentParser
     */
    class Group : public Base
    {
        private:
            std::vector<Base*> children;
            std::function<bool(const Group &)> validator;

        public:
            /** Default validators
             */
            struct Validators
            {
                static bool Xor(const Group &group)
                {
                    return group.MatchedChildren() == 1;
                }

                static bool AtLeastOne(const Group &group)
                {
                    return group.MatchedChildren() >= 1;
                }

                static bool AtMostOne(const Group &group)
                {
                    return group.MatchedChildren() <= 1;
                }

                static bool All(const Group &group)
                {
                    return group.Children().size() == group.MatchedChildren();
                }

                static bool AllOrNone(const Group &group)
                {
                    return (All(group) || None(group));
                }

                static bool AllChildGroups(const Group &group)
                {
                    return std::none_of(std::begin(group.Children()), std::end(group.Children()), [](const Base* child) -> bool {
                            return child->IsGroup() && !child->Matched();
                            });
                }

                static bool DontCare(const Group &)
                {
                    return true;
                }

                static bool CareTooMuch(const Group &)
                {
                    return false;
                }

                static bool None(const Group &group)
                {
                    return group.MatchedChildren() == 0;
                }
            };
            /// If help is empty, this group will not be printed in help output
            Group(const std::string &help_ = std::string(), const std::function<bool(const Group &)> &validator_ = Validators::DontCare, Options options_ = {}) : Base(help_, options_), validator(validator_) {}
            /// If help is empty, this group will not be printed in help output
            Group(Group &group_, const std::string &help_ = std::string(), const std::function<bool(const Group &)> &validator_ = Validators::DontCare, Options options_ = {}) : Base(help_, options_), validator(validator_)
            {
                group_.Add(*this);
            }
            virtual ~Group() {}

            /** Append a child to this Group.
             */
            void Add(Base &child)
            {
                children.emplace_back(&child);
            }

            /** Get all this group's children
             */
            const std::vector<Base *> &Children() const
            {
                return children;
            }

            /** Return the first FlagBase that matches flag, or nullptr
             *
             * \param flag The flag with prefixes stripped
             * \return the first matching FlagBase pointer, or nullptr if there is no match
             */
            virtual FlagBase *Match(const EitherFlag &flag) override
            {
                for (Base *child: Children())
                {
                    if (FlagBase *match = child->Match(flag))
                    {
                        return match;
                    }
                }
                return nullptr;
            }

            virtual std::vector<FlagBase*> GetAllFlags() override
            {
                std::vector<FlagBase*> res;
                for (Base *child: Children())
                {
                    auto childRes = child->GetAllFlags();
                    res.insert(res.end(), childRes.begin(), childRes.end());
                }
                return res;
            }

            virtual void Validate(const std::string &shortPrefix, const std::string &longPrefix) const override
            {
                for (Base *child: Children())
                {
                    child->Validate(shortPrefix, longPrefix);
                }
            }

            /** Get the next ready positional, or nullptr if there is none
             *
             * \return the first ready PositionalBase pointer, or nullptr if there is no match
             */
            virtual PositionalBase *GetNextPositional() override
            {
                for (Base *child: Children())
                {
                    if (auto next = child->GetNextPositional())
                    {
                        return next;
                    }
                }
                return nullptr;
            }

            /** Get whether this has any FlagBase children
             *
             * \return Whether or not there are any FlagBase children
             */
            virtual bool HasFlag() const override
            {
                return std::any_of(Children().begin(), Children().end(), [](Base *child) { return child->HasFlag(); });
            }

            /** Get whether this has any PositionalBase children
             *
             * \return Whether or not there are any PositionalBase children
             */
            virtual bool HasPositional() const override
            {
                return std::any_of(Children().begin(), Children().end(), [](Base *child) { return child->HasPositional(); });
            }

            /** Get whether this has any Command children
             *
             * \return Whether or not there are any Command children
             */
            virtual bool HasCommand() const override
            {
                return std::any_of(Children().begin(), Children().end(), [](Base *child) { return child->HasCommand(); });
            }

            /** Count the number of matched children this group has
             */
            std::vector<Base *>::size_type MatchedChildren() const
            {
                return std::count_if(std::begin(Children()), std::end(Children()), [](const Base *child){return child->Matched();});
            }

            /** Whether or not this group matches validation
             */
            virtual bool Matched() const noexcept override
            {
                return validator(*this);
            }

            /** Get validation
             */
            bool Get() const
            {
                return Matched();
            }

            /** Get all the child descriptions for help generation
             */
            virtual std::vector<std::tuple<std::string, std::string, unsigned>> GetDescription(const HelpParams &params, const unsigned int indent) const override
            {
                std::vector<std::tuple<std::string, std::string, unsigned int>> descriptions;

                // Push that group description on the back if not empty
                unsigned addindent = 0;
                if (!help.empty())
                {
                    descriptions.emplace_back(help, "", indent);
                    addindent = 1;
                }

                for (Base *child: Children())
                {
                    if ((child->GetOptions() & Options::HiddenFromDescription) != Options::None)
                    {
                        continue;
                    }

                    auto groupDescriptions = child->GetDescription(params, indent + addindent);
                    descriptions.insert(
                        std::end(descriptions),
                        std::make_move_iterator(std::begin(groupDescriptions)),
                        std::make_move_iterator(std::end(groupDescriptions)));
                }
                return descriptions;
            }

            /** Get the names of positional parameters
             */
            virtual std::vector<std::string> GetProgramLine(const HelpParams &params) const override
            {
                std::vector <std::string> names;
                for (Base *child: Children())
                {
                    if ((child->GetOptions() & Options::HiddenFromUsage) != Options::None)
                    {
                        continue;
                    }

                    auto groupNames = child->GetProgramLine(params);
                    names.insert(
                        std::end(names),
                        std::make_move_iterator(std::begin(groupNames)),
                        std::make_move_iterator(std::end(groupNames)));
                }
                return names;
            }

            virtual std::vector<Command*> GetCommands() override
            {
                std::vector<Command*> res;
                for (const auto &child : Children())
                {
                    auto subparsers = child->GetCommands();
                    res.insert(std::end(res), std::begin(subparsers), std::end(subparsers));
                }
                return res;
            }

            virtual bool IsGroup() const override
            {
                return true;
            }

            virtual void Reset() noexcept override
            {
                Base::Reset();

                for (auto &child: Children())
                {
                    child->Reset();
                }
#ifdef ARGS_NOEXCEPT
                error = Error::None;
                errorMsg.clear();
#endif
            }

#ifdef ARGS_NOEXCEPT
            /// Only for ARGS_NOEXCEPT
            virtual Error GetError() const override
            {
                if (error != Error::None)
                {
                    return error;
                }

                auto it = std::find_if(Children().begin(), Children().end(), [](const Base *child){return child->GetError() != Error::None;});
                if (it == Children().end())
                {
                    return Error::None;
                } else
                {
                    return (*it)->GetError();
                }
            }
#endif

    };

    /** Class for using global options in ArgumentParser.
     */
    class GlobalOptions : public Group
    {
        public:
            GlobalOptions(Group &base, Base &options_) : Group(base, {}, Group::Validators::DontCare, Options::Global)
            {
                Add(options_);
            }
    };

    /** Utility class for building subparsers with coroutines/callbacks.
     *
     * Brief example:
     * \code
     * Command command(argumentParser, "command", "my command", [](args::Subparser &s)
     * {
     *      // your command flags/positionals
     *      s.Parse(); //required
     *      //your command code
     * });
     * \endcode
     *
     * For ARGS_NOEXCEPT mode don't forget to check `s.GetError()` after `s.Parse()`
     * and return if it isn't equals to args::Error::None.
     *
     * \sa Command
     */
    class Subparser : public Group
    {
        private:
            std::vector<std::string> args;
            std::vector<std::string> kicked;
            ArgumentParser *parser = nullptr;
            const HelpParams &helpParams;
            const Command &command;
            bool isParsed = false;

        public:
            Subparser(std::vector<std::string> args_, ArgumentParser &parser_, const Command &command_, const HelpParams &helpParams_)
                : args(std::move(args_)), parser(&parser_), helpParams(helpParams_), command(command_)
            {
            }

            Subparser(const Command &command_, const HelpParams &helpParams_) : helpParams(helpParams_), command(command_)
            {
            }

            Subparser(const Subparser&) = delete;
            Subparser(Subparser&&) = delete;
            Subparser &operator = (const Subparser&) = delete;
            Subparser &operator = (Subparser&&) = delete;

            const Command &GetCommand()
            {
                return command;
            }

            /** (INTERNAL) Determines whether Parse was called or not.
             */
            bool IsParsed() const
            {
                return isParsed;
            }

            /** Continue parsing arguments for new command.
             */
            void Parse();

            /** Returns a vector of kicked out arguments.
             *
             * \sa Base::KickOut
             */
            const std::vector<std::string> &KickedOut() const noexcept
            {
                return kicked;
            }
    };

    /** Main class for building subparsers.
     *
     * /sa Subparser
     */
    class Command : public Group
    {
        private:
            friend class Subparser;

            std::string name;
            std::string help;
            std::string description;
            std::string epilog;
            std::string proglinePostfix;

            std::function<void(Subparser&)> parserCoroutine;
            bool commandIsRequired = true;
            Command *selectedCommand = nullptr;

            mutable std::vector<std::tuple<std::string, std::string, unsigned>> subparserDescription;
            mutable std::vector<std::string> subparserProgramLine;
            mutable bool subparserHasFlag = false;
            mutable bool subparserHasPositional = false;
            mutable bool subparserHasCommand = false;
#ifdef ARGS_NOEXCEPT
            mutable Error subparserError = Error::None;
#endif
            mutable Subparser *subparser = nullptr;

        protected:

            class RaiiSubparser
            {
                public:
                    RaiiSubparser(ArgumentParser &parser_, std::vector<std::string> args_);
                    RaiiSubparser(const Command &command_, const HelpParams &params_);

                    ~RaiiSubparser()
                    {
                        command.subparser = oldSubparser;
                    }

                    Subparser &Parser()
                    {
                        return parser;
                    }

                private:
                    const Command &command;
                    Subparser parser;
                    Subparser *oldSubparser;
            };

            Command() = default;

            std::function<void(Subparser&)> &GetCoroutine()
            {
                return selectedCommand != nullptr ? selectedCommand->GetCoroutine() : parserCoroutine;
            }

            Command &SelectedCommand()
            {
                Command *res = this;
                while (res->selectedCommand != nullptr)
                {
                    res = res->selectedCommand;
                }

                return *res;
            }

            const Command &SelectedCommand() const
            {
                const Command *res = this;
                while (res->selectedCommand != nullptr)
                {
                    res = res->selectedCommand;
                }

                return *res;
            }

            void UpdateSubparserHelp(const HelpParams &params) const
            {
                if (parserCoroutine)
                {
                    RaiiSubparser coro(*this, params);
#ifndef ARGS_NOEXCEPT
                    try
                    {
                        parserCoroutine(coro.Parser());
                    }
                    catch (args::SubparserError&)
                    {
                    }
#else
                    parserCoroutine(coro.Parser());
#endif
                }
            }

        public:
            Command(Group &base_, std::string name_, std::string help_, std::function<void(Subparser&)> coroutine_ = {})
                : name(std::move(name_)), help(std::move(help_)), parserCoroutine(std::move(coroutine_))
            {
                base_.Add(*this);
            }

            /** The description that appears on the prog line after options
             */
            const std::string &ProglinePostfix() const
            { return proglinePostfix; }

            /** The description that appears on the prog line after options
             */
            void ProglinePostfix(const std::string &proglinePostfix_)
            { this->proglinePostfix = proglinePostfix_; }

            /** The description that appears above options
             */
            const std::string &Description() const
            { return description; }
            /** The description that appears above options
             */

            void Description(const std::string &description_)
            { this->description = description_; }

            /** The description that appears below options
             */
            const std::string &Epilog() const
            { return epilog; }

            /** The description that appears below options
             */
            void Epilog(const std::string &epilog_)
            { this->epilog = epilog_; }

            /** The name of command
             */
            const std::string &Name() const
            { return name; }

            /** The description of command
             */
            const std::string &Help() const
            { return help; }

            /** If value is true, parser will fail if no command was parsed.
             *
             * Default: true.
             */
            void RequireCommand(bool value)
            { commandIsRequired = value; }

            virtual bool IsGroup() const override
            { return false; }

            virtual bool Matched() const noexcept override
            { return Base::Matched(); }

            operator bool() const noexcept
            { return Matched(); }

            void Match() noexcept
            { matched = true; }

            void SelectCommand(Command *c) noexcept
            {
                selectedCommand = c;

                if (c != nullptr)
                {
                    c->Match();
                }
            }

            virtual FlagBase *Match(const EitherFlag &flag) override
            {
                if (selectedCommand != nullptr)
                {
                    if (auto *res = selectedCommand->Match(flag))
                    {
                        return res;
                    }

                    for (auto *child: Children())
                    {
                        if ((child->GetOptions() & Options::Global) != Options::None)
                        {
                            if (auto *res = child->Match(flag))
                            {
                                return res;
                            }
                        }
                    }

                    return nullptr;
                }

                if (subparser != nullptr)
                {
                    return subparser->Match(flag);
                }

                return Matched() ? Group::Match(flag) : nullptr;
            }

            virtual std::vector<FlagBase*> GetAllFlags() override
            {
                std::vector<FlagBase*> res;

                if (!Matched())
                {
                    return res;
                }

                for (auto *child: Children())
                {
                    if (selectedCommand == nullptr || (child->GetOptions() & Options::Global) != Options::None)
                    {
                        auto childFlags = child->GetAllFlags();
                        res.insert(res.end(), childFlags.begin(), childFlags.end());
                    }
                }

                if (selectedCommand != nullptr)
                {
                    auto childFlags = selectedCommand->GetAllFlags();
                    res.insert(res.end(), childFlags.begin(), childFlags.end());
                }

                if (subparser != nullptr)
                {
                    auto childFlags = subparser->GetAllFlags();
                    res.insert(res.end(), childFlags.begin(), childFlags.end());
                }

                return res;
            }

            virtual PositionalBase *GetNextPositional() override
            {
                if (selectedCommand != nullptr)
                {
                    if (auto *res = selectedCommand->GetNextPositional())
                    {
                        return res;
                    }

                    for (auto *child: Children())
                    {
                        if ((child->GetOptions() & Options::Global) != Options::None)
                        {
                            if (auto *res = child->GetNextPositional())
                            {
                                return res;
                            }
                        }
                    }

                    return nullptr;
                }

                if (subparser != nullptr)
                {
                    return subparser->GetNextPositional();
                }

                return Matched() ? Group::GetNextPositional() : nullptr;
            }

            virtual bool HasFlag() const override
            {
                return subparserHasFlag || Group::HasFlag();
            }

            virtual bool HasPositional() const override
            {
                return subparserHasPositional || Group::HasPositional();
            }

            virtual bool HasCommand() const override
            {
                return true;
            }

            std::vector<std::string> GetCommandProgramLine(const HelpParams &params) const
            {
                UpdateSubparserHelp(params);

                auto res = Group::GetProgramLine(params);
                res.insert(res.end(), subparserProgramLine.begin(), subparserProgramLine.end());

                if (!params.proglineCommand.empty() && (Group::HasCommand() || subparserHasCommand))
                {
                    res.insert(res.begin(), commandIsRequired ? params.proglineCommand : "[" + params.proglineCommand + "]");
                }

                if (!Name().empty())
                {
                    res.insert(res.begin(), Name());
                }

                if ((subparserHasFlag || Group::HasFlag()) && params.showProglineOptions && !params.proglineShowFlags)
                {
                    res.push_back(params.proglineOptions);
                }

                if (!ProglinePostfix().empty())
                {
                    std::string line;
                    for (char c : ProglinePostfix())
                    {
                        if (isspace(c))
                        {
                            if (!line.empty())
                            {
                                res.push_back(line);
                                line.clear();
                            }

                            if (c == '\n')
                            {
                                res.push_back("\n");
                            }
                        }
                        else
                        {
                            line += c;
                        }
                    }

                    if (!line.empty())
                    {
                        res.push_back(line);
                    }
                }

                return res;
            }

            virtual std::vector<std::string> GetProgramLine(const HelpParams &params) const override
            {
                if (!Matched())
                {
                    return {};
                }

                return GetCommandProgramLine(params);
            }

            virtual std::vector<Command*> GetCommands() override
            {
                if (selectedCommand != nullptr)
                {
                    return selectedCommand->GetCommands();
                }

                if (Matched())
                {
                    return Group::GetCommands();
                }

                return { this };
            }

            virtual std::vector<std::tuple<std::string, std::string, unsigned>> GetDescription(const HelpParams &params, const unsigned int indent) const override
            {
                std::vector<std::tuple<std::string, std::string, unsigned>> descriptions;
                unsigned addindent = 0;

                UpdateSubparserHelp(params);

                if (!Matched())
                {
                    if (params.showCommandFullHelp)
                    {
                        std::ostringstream s;
                        bool empty = true;
                        for (const auto &progline: GetCommandProgramLine(params))
                        {
                            if (!empty)
                            {
                                s << ' ';
                            }
                            else
                            {
                                empty = false;
                            }

                            s << progline;
                        }

                        descriptions.emplace_back(s.str(), "", indent);
                    }
                    else
                    {
                        descriptions.emplace_back(Name(), help, indent);
                    }

                    if (!params.showCommandChildren && !params.showCommandFullHelp)
                    {
                        return descriptions;
                    }

                    addindent = 1;
                }

                if (params.showCommandFullHelp && !Matched())
                {
                    descriptions.emplace_back("", "", indent + addindent);
                    descriptions.emplace_back(Description().empty() ? Help() : Description(), "", indent + addindent);
                    descriptions.emplace_back("", "", indent + addindent);
                }

                for (Base *child: Children())
                {
                    if ((child->GetOptions() & Options::HiddenFromDescription) != Options::None)
                    {
                        continue;
                    }

                    auto groupDescriptions = child->GetDescription(params, indent + addindent);
                    descriptions.insert(
                                        std::end(descriptions),
                                        std::make_move_iterator(std::begin(groupDescriptions)),
                                        std::make_move_iterator(std::end(groupDescriptions)));
                }

                for (auto childDescription: subparserDescription)
                {
                    std::get<2>(childDescription) += indent + addindent;
                    descriptions.push_back(std::move(childDescription));
                }

                if (params.showCommandFullHelp && !Matched())
                {
                    descriptions.emplace_back("", "", indent + addindent);
                    if (!Epilog().empty())
                    {
                        descriptions.emplace_back(Epilog(), "", indent + addindent);
                        descriptions.emplace_back("", "", indent + addindent);
                    }
                }

                return descriptions;
            }

            virtual void Validate(const std::string &shortprefix, const std::string &longprefix) const override
            {
                if (!Matched())
                {
                    return;
                }

                for (Base *child: Children())
                {
                    if (child->IsGroup() && !child->Matched())
                    {
                        std::ostringstream problem;
                        problem << "Group validation failed somewhere!";
#ifdef ARGS_NOEXCEPT
                        error = Error::Validation;
                        errorMsg = problem.str();
#else
                        throw ValidationError(problem.str());
#endif
                    }

                    child->Validate(shortprefix, longprefix);
                }

                if (subparser != nullptr)
                {
                    subparser->Validate(shortprefix, longprefix);
                }

                if (selectedCommand == nullptr && commandIsRequired && (Group::HasCommand() || subparserHasCommand))
                {
                    std::ostringstream problem;
                    problem << "Command is required";
#ifdef ARGS_NOEXCEPT
                    error = Error::Validation;
                    errorMsg = problem.str();
#else
                    throw ValidationError(problem.str());
#endif
                }
            }

            virtual void Reset() noexcept override
            {
                Group::Reset();
                selectedCommand = nullptr;
                subparserProgramLine.clear();
                subparserDescription.clear();
                subparserHasFlag = false;
                subparserHasPositional = false;
                subparserHasCommand = false;
#ifdef ARGS_NOEXCEPT
                subparserError = Error::None;
#endif
            }

#ifdef ARGS_NOEXCEPT
            /// Only for ARGS_NOEXCEPT
            virtual Error GetError() const override
            {
                if (!Matched())
                {
                    return Error::None;
                }

                if (error != Error::None)
                {
                    return error;
                }

                if (subparserError != Error::None)
                {
                    return subparserError;
                }

                return Group::GetError();
            }
#endif
    };

    /** The main user facing command line argument parser class
     */
    class ArgumentParser : public Command
    {
        friend class Subparser;

        private:
            std::string longprefix;
            std::string shortprefix;

            std::string longseparator;

            std::string terminator;

            bool allowJoinedShortValue = true;
            bool allowJoinedLongValue = true;
            bool allowSeparateShortValue = true;
            bool allowSeparateLongValue = true;

            CompletionFlag *completion = nullptr;
            bool readCompletion = false;

        protected:
            enum class OptionType
            {
                LongFlag,
                ShortFlag,
                Positional
            };

            OptionType ParseOption(const std::string &s, bool allowEmpty = false)
            {
                if (s.find(longprefix) == 0 && (allowEmpty || s.length() > longprefix.length()))
                {
                    return OptionType::LongFlag;
                }

                if (s.find(shortprefix) == 0 && (allowEmpty || s.length() > shortprefix.length()))
                {
                    return OptionType::ShortFlag;
                }

                return OptionType::Positional;
            }

            template <typename It>
            bool Complete(FlagBase &flag, It it, It end)
            {
                auto nextIt = it;
                if (!readCompletion || (++nextIt != end))
                {
                    return false;
                }

                const auto &chunk = *it;
                for (auto &choice : flag.HelpChoices(helpParams))
                {
                    AddCompletionReply(chunk, choice);
                }

#ifndef ARGS_NOEXCEPT
                throw Completion(completion->Get());
#else
                return true;
#endif
            }

            /** (INTERNAL) Parse flag's values
             *
             * \param arg The string to display in error message as a flag name
             * \param[in, out] it The iterator to first value. It will point to the last value
             * \param end The end iterator
             * \param joinedArg Joined value (e.g. bar in --foo=bar)
             * \param canDiscardJoined If true joined value can be parsed as flag not as a value (as in -abcd)
             * \param[out] values The vector to store parsed arg's values
             */
            template <typename It>
            std::string ParseArgsValues(FlagBase &flag, const std::string &arg, It &it, It end,
                                        const bool allowSeparate, const bool allowJoined,
                                        const bool hasJoined, const std::string &joinedArg,
                                        const bool canDiscardJoined, std::vector<std::string> &values)
            {
                values.clear();

                Nargs nargs = flag.NumberOfArguments();

                if (hasJoined && !allowJoined && nargs.min != 0)
                {
                    return "Flag '" + arg + "' was passed a joined argument, but these are disallowed";
                }

                if (hasJoined)
                {
                    if (!canDiscardJoined || nargs.max != 0)
                    {
                        values.push_back(joinedArg);
                    }
                } else if (!allowSeparate)
                {
                    if (nargs.min != 0)
                    {
                        return "Flag '" + arg + "' was passed a separate argument, but these are disallowed";
                    }
                } else
                {
                    auto valueIt = it;
                    ++valueIt;

                    while (valueIt != end &&
                           values.size() < nargs.max &&
                           (nargs.min == nargs.max || ParseOption(*valueIt) == OptionType::Positional))
                    {
                        if (Complete(flag, valueIt, end))
                        {
                            it = end;
                            return "";
                        }

                        values.push_back(*valueIt);
                        ++it;
                        ++valueIt;
                    }
                }

                if (values.size() > nargs.max)
                {
                    return "Passed an argument into a non-argument flag: " + arg;
                } else if (values.size() < nargs.min)
                {
                    if (nargs.min == 1 && nargs.max == 1)
                    {
                        return "Flag '" + arg + "' requires an argument but received none";
                    } else if (nargs.min == 1)
                    {
                        return "Flag '" + arg + "' requires at least one argument but received none";
                    } else if (nargs.min != nargs.max)
                    {
                        return "Flag '" + arg + "' requires at least " + std::to_string(nargs.min) +
                               " arguments but received " + std::to_string(values.size());
                    } else
                    {
                        return "Flag '" + arg + "' requires " + std::to_string(nargs.min) +
                               " arguments but received " + std::to_string(values.size());
                    }
                }

                return {};
            }

            template <typename It>
            bool ParseLong(It &it, It end)
            {
                const auto &chunk = *it;
                const auto argchunk = chunk.substr(longprefix.size());
                // Try to separate it, in case of a separator:
                const auto separator = longseparator.empty() ? argchunk.npos : argchunk.find(longseparator);
                // If the separator is in the argument, separate it.
                const auto arg = (separator != argchunk.npos ?
                    std::string(argchunk, 0, separator)
                    : argchunk);
                const auto joined = (separator != argchunk.npos ?
                    argchunk.substr(separator + longseparator.size())
                    : std::string());

                if (auto flag = Match(arg))
                {
                    std::vector<std::string> values;
                    const std::string errorMessage = ParseArgsValues(*flag, arg, it, end, allowSeparateLongValue, allowJoinedLongValue,
                                                                     separator != argchunk.npos, joined, false, values);
                    if (!errorMessage.empty())
                    {
#ifndef ARGS_NOEXCEPT
                        throw ParseError(errorMessage);
#else
                        error = Error::Parse;
                        errorMsg = errorMessage;
                        return false;
#endif
                    }

                    if (!readCompletion)
                    {
                        flag->ParseValue(values);
                    }

                    if (flag->KickOut())
                    {
                        ++it;
                        return false;
                    }
                } else
                {
                    const std::string errorMessage("Flag could not be matched: " + arg);
#ifndef ARGS_NOEXCEPT
                    throw ParseError(errorMessage);
#else
                    error = Error::Parse;
                    errorMsg = errorMessage;
                    return false;
#endif
                }

                return true;
            }

            template <typename It>
            bool ParseShort(It &it, It end)
            {
                const auto &chunk = *it;
                const auto argchunk = chunk.substr(shortprefix.size());
                for (auto argit = std::begin(argchunk); argit != std::end(argchunk); ++argit)
                {
                    const auto arg = *argit;

                    if (auto flag = Match(arg))
                    {
                        const std::string value(argit + 1, std::end(argchunk));
                        std::vector<std::string> values;
                        const std::string errorMessage = ParseArgsValues(*flag, std::string(1, arg), it, end,
                                                                         allowSeparateShortValue, allowJoinedShortValue,
                                                                         !value.empty(), value, !value.empty(), values);

                        if (!errorMessage.empty())
                        {
#ifndef ARGS_NOEXCEPT
                            throw ParseError(errorMessage);
#else
                            error = Error::Parse;
                            errorMsg = errorMessage;
                            return false;
#endif
                        }

                        if (!readCompletion)
                        {
                            flag->ParseValue(values);
                        }

                        if (flag->KickOut())
                        {
                            ++it;
                            return false;
                        }

                        if (!values.empty())
                        {
                            break;
                        }
                    } else
                    {
                        const std::string errorMessage("Flag could not be matched: '" + std::string(1, arg) + "'");
#ifndef ARGS_NOEXCEPT
                        throw ParseError(errorMessage);
#else
                        error = Error::Parse;
                        errorMsg = errorMessage;
                        return false;
#endif
                    }
                }

                return true;
            }

            bool AddCompletionReply(const std::string &cur, const std::string &choice)
            {
                if (cur.empty() || choice.find(cur) == 0)
                {
                    if (completion->syntax == "bash" && ParseOption(choice) == OptionType::LongFlag && choice.find(longseparator) != std::string::npos)
                    {
                        completion->reply.push_back(choice.substr(choice.find(longseparator) + 1));
                    } else
                    {
                        completion->reply.push_back(choice);
                    }
                    return true;
                }

                return false;
            }

            template <typename It>
            bool Complete(It it, It end)
            {
                auto nextIt = it;
                if (!readCompletion || (++nextIt != end))
                {
                    return false;
                }

                const auto &chunk = *it;
                auto pos = GetNextPositional();
                std::vector<Command *> commands = GetCommands();
                const auto optionType = ParseOption(chunk, true);

                if (!commands.empty() && (chunk.empty() || optionType == OptionType::Positional))
                {
                    for (auto &cmd : commands)
                    {
                        if ((cmd->GetOptions() & Options::HiddenFromCompletion) == Options::None)
                        {
                            AddCompletionReply(chunk, cmd->Name());
                        }
                    }
                } else
                {
                    bool hasPositionalCompletion = true;

                    if (!commands.empty())
                    {
                        for (auto &cmd : commands)
                        {
                            if ((cmd->GetOptions() & Options::HiddenFromCompletion) == Options::None)
                            {
                                AddCompletionReply(chunk, cmd->Name());
                            }
                        }
                    } else if (pos)
                    {
                        if ((pos->GetOptions() & Options::HiddenFromCompletion) == Options::None)
                        {
                            auto choices = pos->HelpChoices(helpParams);
                            hasPositionalCompletion = !choices.empty() || optionType != OptionType::Positional;
                            for (auto &choice : choices)
                            {
                                AddCompletionReply(chunk, choice);
                            }
                        }
                    }

                    if (hasPositionalCompletion)
                    {
                        auto flags = GetAllFlags();
                        for (auto flag : flags)
                        {
                            if ((flag->GetOptions() & Options::HiddenFromCompletion) != Options::None)
                            {
                                continue;
                            }

                            auto &matcher = flag->GetMatcher();
                            if (!AddCompletionReply(chunk, matcher.GetShortOrAny().str(shortprefix, longprefix)))
                            {
                                for (auto &flagName : matcher.GetFlagStrings())
                                {
                                    if (AddCompletionReply(chunk, flagName.str(shortprefix, longprefix)))
                                    {
                                        break;
                                    }
                                }
                            }
                        }

                        if (optionType == OptionType::LongFlag && allowJoinedLongValue)
                        {
                            const auto separator = longseparator.empty() ? chunk.npos : chunk.find(longseparator);
                            if (separator != chunk.npos)
                            {
                                std::string arg(chunk, 0, separator);
                                if (auto flag = this->Match(arg.substr(longprefix.size())))
                                {
                                    for (auto &choice : flag->HelpChoices(helpParams))
                                    {
                                        AddCompletionReply(chunk, arg + longseparator + choice);
                                    }
                                }
                            }
                        } else if (optionType == OptionType::ShortFlag && allowJoinedShortValue)
                        {
                            if (chunk.size() > shortprefix.size() + 1)
                            {
                                auto arg = chunk.at(shortprefix.size());
                                //TODO: support -abcVALUE where a and b take no value
                                if (auto flag = this->Match(arg))
                                {
                                    for (auto &choice : flag->HelpChoices(helpParams))
                                    {
                                        AddCompletionReply(chunk, shortprefix + arg + choice);
                                    }
                                }
                            }
                        }
                    }
                }

#ifndef ARGS_NOEXCEPT
                throw Completion(completion->Get());
#else
                return true;
#endif
            }

            template <typename It>
            It Parse(It begin, It end)
            {
                bool terminated = false;
                std::vector<Command *> commands = GetCommands();

                // Check all arg chunks
                for (auto it = begin; it != end; ++it)
                {
                    if (Complete(it, end))
                    {
                        return end;
                    }

                    const auto &chunk = *it;

                    if (!terminated && chunk == terminator)
                    {
                        terminated = true;
                    } else if (!terminated && ParseOption(chunk) == OptionType::LongFlag)
                    {
                        if (!ParseLong(it, end))
                        {
                            return it;
                        }
                    } else if (!terminated && ParseOption(chunk) == OptionType::ShortFlag)
                    {
                        if (!ParseShort(it, end))
                        {
                            return it;
                        }
                    } else if (!terminated && !commands.empty())
                    {
                        auto itCommand = std::find_if(commands.begin(), commands.end(), [&chunk](Command *c) { return c->Name() == chunk; });
                        if (itCommand == commands.end())
                        {
                            const std::string errorMessage("Unknown command: " + chunk);
#ifndef ARGS_NOEXCEPT
                            throw ParseError(errorMessage);
#else
                            error = Error::Parse;
                            errorMsg = errorMessage;
                            return it;
#endif
                        }

                        SelectCommand(*itCommand);

                        if (const auto &coroutine = GetCoroutine())
                        {
                            ++it;
                            RaiiSubparser coro(*this, std::vector<std::string>(it, end));
                            coroutine(coro.Parser());
#ifdef ARGS_NOEXCEPT
                            error = GetError();
                            if (error != Error::None)
                            {
                                return end;
                            }

                            if (!coro.Parser().IsParsed())
                            {
                                error = Error::Usage;
                                return end;
                            }
#else
                            if (!coro.Parser().IsParsed())
                            {
                                throw UsageError("Subparser::Parse was not called");
                            }
#endif

                            break;
                        }

                        commands = GetCommands();
                    } else
                    {
                        auto pos = GetNextPositional();
                        if (pos)
                        {
                            pos->ParseValue(chunk);

                            if (pos->KickOut())
                            {
                                return ++it;
                            }
                        } else
                        {
                            const std::string errorMessage("Passed in argument, but no positional arguments were ready to receive it: " + chunk);
#ifndef ARGS_NOEXCEPT
                            throw ParseError(errorMessage);
#else
                            error = Error::Parse;
                            errorMsg = errorMessage;
                            return it;
#endif
                        }
                    }

                    if (!readCompletion && completion != nullptr && completion->Matched())
                    {
#ifdef ARGS_NOEXCEPT
                        error = Error::Completion;
#endif
                        readCompletion = true;
                        ++it;
                        size_t argsLeft = std::distance(it, end);
                        if (completion->cword == 0 || argsLeft <= 1 || completion->cword >= argsLeft)
                        {
#ifndef ARGS_NOEXCEPT
                            throw Completion("");
#endif
                        }

                        std::vector<std::string> curArgs(++it, end);
                        curArgs.resize(completion->cword);

                        if (completion->syntax == "bash")
                        {
                            // bash tokenizes --flag=value as --flag=value
                            for (size_t idx = 0; idx < curArgs.size(); )
                            {
                                if (idx > 0 && curArgs[idx] == "=")
                                {
                                    curArgs[idx - 1] += "=";
                                    if (idx + 1 < curArgs.size())
                                    {
                                        curArgs[idx - 1] += curArgs[idx + 1];
                                        curArgs.erase(curArgs.begin() + idx, curArgs.begin() + idx + 2);
                                    } else
                                    {
                                        curArgs.erase(curArgs.begin() + idx);
                                    }
                                } else
                                {
                                    ++idx;
                                }
                            }

                        }
#ifndef ARGS_NOEXCEPT
                        try
                        {
                            Parse(curArgs.begin(), curArgs.end());
                            throw Completion("");
                        }
                        catch (Completion &)
                        {
                            throw;
                        }
                        catch (args::Error&)
                        {
                            throw Completion("");
                        }
#else
                        return Parse(curArgs.begin(), curArgs.end());
#endif
                    }
                }

                Validate(shortprefix, longprefix);
                return end;
            }

        public:
            HelpParams helpParams;

            ArgumentParser(const std::string &description_, const std::string &epilog_ = std::string())
            {
                Description(description_);
                Epilog(epilog_);
                LongPrefix("--");
                ShortPrefix("-");
                LongSeparator("=");
                Terminator("--");
                SetArgumentSeparations(true, true, true, true);
                matched = true;
            }

            void AddCompletion(CompletionFlag &completionFlag)
            {
                completion = &completionFlag;
                Add(completionFlag);
            }

            /** The program name for help generation
             */
            const std::string &Prog() const
            { return helpParams.programName; }
            /** The program name for help generation
             */
            void Prog(const std::string &prog_)
            { this->helpParams.programName = prog_; }

            /** The prefix for long flags
             */
            const std::string &LongPrefix() const
            { return longprefix; }
            /** The prefix for long flags
             */
            void LongPrefix(const std::string &longprefix_)
            {
                this->longprefix = longprefix_;
                this->helpParams.longPrefix = longprefix_;
            }

            /** The prefix for short flags
             */
            const std::string &ShortPrefix() const
            { return shortprefix; }
            /** The prefix for short flags
             */
            void ShortPrefix(const std::string &shortprefix_)
            {
                this->shortprefix = shortprefix_;
                this->helpParams.shortPrefix = shortprefix_;
            }

            /** The separator for long flags
             */
            const std::string &LongSeparator() const
            { return longseparator; }
            /** The separator for long flags
             */
            void LongSeparator(const std::string &longseparator_)
            {
                if (longseparator_.empty())
                {
                    const std::string errorMessage("longseparator can not be set to empty");
#ifdef ARGS_NOEXCEPT
                    error = Error::Usage;
                    errorMsg = errorMessage;
#else
                    throw UsageError(errorMessage);
#endif
                } else
                {
                    this->longseparator = longseparator_;
                    this->helpParams.longSeparator = allowJoinedLongValue ? longseparator_ : " ";
                }
            }

            /** The terminator that forcibly separates flags from positionals
             */
            const std::string &Terminator() const
            { return terminator; }
            /** The terminator that forcibly separates flags from positionals
             */
            void Terminator(const std::string &terminator_)
            { this->terminator = terminator_; }

            /** Get the current argument separation parameters.
             *
             * See SetArgumentSeparations for details on what each one means.
             */
            void GetArgumentSeparations(
                bool &allowJoinedShortValue_,
                bool &allowJoinedLongValue_,
                bool &allowSeparateShortValue_,
                bool &allowSeparateLongValue_) const
            {
                allowJoinedShortValue_ = this->allowJoinedShortValue;
                allowJoinedLongValue_ = this->allowJoinedLongValue;
                allowSeparateShortValue_ = this->allowSeparateShortValue;
                allowSeparateLongValue_ = this->allowSeparateLongValue;
            }

            /** Change allowed option separation.
             *
             * \param allowJoinedShortValue_ Allow a short flag that accepts an argument to be passed its argument immediately next to it (ie. in the same argv field)
             * \param allowJoinedLongValue_ Allow a long flag that accepts an argument to be passed its argument separated by the longseparator (ie. in the same argv field)
             * \param allowSeparateShortValue_ Allow a short flag that accepts an argument to be passed its argument separated by whitespace (ie. in the next argv field)
             * \param allowSeparateLongValue_ Allow a long flag that accepts an argument to be passed its argument separated by whitespace (ie. in the next argv field)
             */
            void SetArgumentSeparations(
                const bool allowJoinedShortValue_,
                const bool allowJoinedLongValue_,
                const bool allowSeparateShortValue_,
                const bool allowSeparateLongValue_)
            {
                this->allowJoinedShortValue = allowJoinedShortValue_;
                this->allowJoinedLongValue = allowJoinedLongValue_;
                this->allowSeparateShortValue = allowSeparateShortValue_;
                this->allowSeparateLongValue = allowSeparateLongValue_;

                this->helpParams.longSeparator = allowJoinedLongValue ? longseparator : " ";
                this->helpParams.shortSeparator = allowJoinedShortValue ? "" : " ";
            }

            /** Pass the help menu into an ostream
             */
            void Help(std::ostream &help_) const
            {
                auto &command = SelectedCommand();
                const auto &commandDescription = command.Description().empty() ? command.Help() : command.Description();
                const auto description_text = Wrap(commandDescription, helpParams.width - helpParams.descriptionindent);
                const auto epilog_text = Wrap(command.Epilog(), helpParams.width - helpParams.descriptionindent);

                const bool hasoptions = command.HasFlag();
                const bool hasarguments = command.HasPositional();

                std::vector<std::string> prognameline;
                prognameline.push_back(helpParams.usageString);
                prognameline.push_back(Prog());
                auto commandProgLine = command.GetProgramLine(helpParams);
                prognameline.insert(prognameline.end(), commandProgLine.begin(), commandProgLine.end());

                const auto proglines = Wrap(prognameline.begin(), prognameline.end(),
                                            helpParams.width - (helpParams.progindent + helpParams.progtailindent),
                                            helpParams.width - helpParams.progindent);
                auto progit = std::begin(proglines);
                if (progit != std::end(proglines))
                {
                    help_ << std::string(helpParams.progindent, ' ') << *progit << '\n';
                    ++progit;
                }
                for (; progit != std::end(proglines); ++progit)
                {
                    help_ << std::string(helpParams.progtailindent, ' ') << *progit << '\n';
                }

                help_ << '\n';

                if (!description_text.empty())
                {
                    for (const auto &line: description_text)
                    {
                        help_ << std::string(helpParams.descriptionindent, ' ') << line << "\n";
                    }
                    help_ << "\n";
                }

                bool lastDescriptionIsNewline = false;

                if (!helpParams.optionsString.empty())
                {
                    help_ << std::string(helpParams.progindent, ' ') << helpParams.optionsString << "\n\n";
                }

                for (const auto &desc: command.GetDescription(helpParams, 0))
                {
                    lastDescriptionIsNewline = std::get<0>(desc).empty() && std::get<1>(desc).empty();
                    const auto groupindent = std::get<2>(desc) * helpParams.eachgroupindent;
                    const auto flags = Wrap(std::get<0>(desc), helpParams.width - (helpParams.flagindent + helpParams.helpindent + helpParams.gutter));
                    const auto info = Wrap(std::get<1>(desc), helpParams.width - (helpParams.helpindent + groupindent));

                    std::string::size_type flagssize = 0;
                    for (auto flagsit = std::begin(flags); flagsit != std::end(flags); ++flagsit)
                    {
                        if (flagsit != std::begin(flags))
                        {
                            help_ << '\n';
                        }
                        help_ << std::string(groupindent + helpParams.flagindent, ' ') << *flagsit;
                        flagssize = Glyphs(*flagsit);
                    }

                    auto infoit = std::begin(info);
                    // groupindent is on both sides of this inequality, and therefore can be removed
                    if ((helpParams.flagindent + flagssize + helpParams.gutter) > helpParams.helpindent || infoit == std::end(info) || helpParams.addNewlineBeforeDescription)
                    {
                        help_ << '\n';
                    } else
                    {
                        // groupindent is on both sides of the minus sign, and therefore doesn't actually need to be in here
                        help_ << std::string(helpParams.helpindent - (helpParams.flagindent + flagssize), ' ') << *infoit << '\n';
                        ++infoit;
                    }
                    for (; infoit != std::end(info); ++infoit)
                    {
                        help_ << std::string(groupindent + helpParams.helpindent, ' ') << *infoit << '\n';
                    }
                }
                if (hasoptions && hasarguments && helpParams.showTerminator)
                {
                    lastDescriptionIsNewline = false;
                    for (const auto &item: Wrap(std::string("\"") + terminator + "\" can be used to terminate flag options and force all following arguments to be treated as positional options", helpParams.width - helpParams.flagindent))
                    {
                        help_ << std::string(helpParams.flagindent, ' ') << item << '\n';
                    }
                }

                if (!lastDescriptionIsNewline)
                {
                    help_ << "\n";
                }

                for (const auto &line: epilog_text)
                {
                    help_ << std::string(helpParams.descriptionindent, ' ') << line << "\n";
                }
            }

            /** Generate a help menu as a string.
             *
             * \return the help text as a single string
             */
            std::string Help() const
            {
                std::ostringstream help_;
                Help(help_);
                return help_.str();
            }

            virtual void Reset() noexcept override
            {
                Command::Reset();
                matched = true;
                readCompletion = false;
            }

            /** Parse all arguments.
             *
             * \param begin an iterator to the beginning of the argument list
             * \param end an iterator to the past-the-end element of the argument list
             * \return the iterator after the last parsed value.  Only useful for kick-out
             */
            template <typename It>
            It ParseArgs(It begin, It end)
            {
                // Reset all Matched statuses and errors
                Reset();
#ifdef ARGS_NOEXCEPT
                error = GetError();
                if (error != Error::None)
                {
                    return end;
                }
#endif
                return Parse(begin, end);
            }

            /** Parse all arguments.
             *
             * \param args an iterable of the arguments
             * \return the iterator after the last parsed value.  Only useful for kick-out
             */
            template <typename T>
            auto ParseArgs(const T &args) -> decltype(std::begin(args))
            {
                return ParseArgs(std::begin(args), std::end(args));
            }

            /** Convenience function to parse the CLI from argc and argv
             *
             * Just assigns the program name and vectorizes arguments for passing into ParseArgs()
             *
             * \return whether or not all arguments were parsed.  This works for detecting kick-out, but is generally useless as it can't do anything with it.
             */
            bool ParseCLI(const int argc, const char * const * argv)
            {
                if (Prog().empty())
                {
                    Prog(argv[0]);
                }
                const std::vector<std::string> args(argv + 1, argv + argc);
                return ParseArgs(args) == std::end(args);
            }
            
            template <typename T>
            bool ParseCLI(const T &args)
            {
                return ParseArgs(args) == std::end(args);
            }
    };

    inline Command::RaiiSubparser::RaiiSubparser(ArgumentParser &parser_, std::vector<std::string> args_)
        : command(parser_.SelectedCommand()), parser(std::move(args_), parser_, command, parser_.helpParams), oldSubparser(command.subparser)
    {
        command.subparser = &parser;
    }

    inline Command::RaiiSubparser::RaiiSubparser(const Command &command_, const HelpParams &params_): command(command_), parser(command, params_), oldSubparser(command.subparser)
    {
        command.subparser = &parser;
    }

    inline void Subparser::Parse()
    {
        isParsed = true;
        Reset();
        command.subparserDescription = GetDescription(helpParams, 0);
        command.subparserHasFlag = HasFlag();
        command.subparserHasPositional = HasPositional();
        command.subparserHasCommand = HasCommand();
        command.subparserProgramLine = GetProgramLine(helpParams);
        if (parser == nullptr)
        {
#ifndef ARGS_NOEXCEPT
            throw args::SubparserError();
#else
            error = Error::Subparser;
            return;
#endif
        }

        auto it = parser->Parse(args.begin(), args.end());
        command.Validate(parser->ShortPrefix(), parser->LongPrefix());
        kicked.assign(it, args.end());

#ifdef ARGS_NOEXCEPT
        command.subparserError = GetError();
#endif
    }

    inline std::ostream &operator<<(std::ostream &os, const ArgumentParser &parser)
    {
        parser.Help(os);
        return os;
    }

    /** Boolean argument matcher
     */
    class Flag : public FlagBase
    {
        public:
            Flag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_): FlagBase(name_, help_, std::move(matcher_), options_)
            {
                group_.Add(*this);
            }

            Flag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const bool extraError_ = false): Flag(group_, name_, help_, std::move(matcher_), extraError_ ? Options::Single : Options::None)
            {
            }

            virtual ~Flag() {}

            /** Get whether this was matched
             */
            bool Get() const
            {
                return Matched();
            }

            virtual Nargs NumberOfArguments() const noexcept override
            {
                return 0;
            }

            virtual void ParseValue(const std::vector<std::string>&) override
            {
            }
    };

    /** Help flag class
     *
     * Works like a regular flag, but throws an instance of Help when it is matched
     */
    class HelpFlag : public Flag
    {
        public:
            HelpFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_ = {}): Flag(group_, name_, help_, std::move(matcher_), options_) {}

            virtual ~HelpFlag() {}

            virtual void ParseValue(const std::vector<std::string> &)
            {
#ifdef ARGS_NOEXCEPT
                    error = Error::Help;
                    errorMsg = Name();
#else
                    throw Help(Name());
#endif
            }

            /** Get whether this was matched
             */
            bool Get() const noexcept
            {
                return Matched();
            }
    };

    /** A flag class that simply counts the number of times it's matched
     */
    class CounterFlag : public Flag
    {
        private:
            const int startcount;
            int count;

        public:
            CounterFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const int startcount_ = 0, Options options_ = {}):
                Flag(group_, name_, help_, std::move(matcher_), options_), startcount(startcount_), count(startcount_) {}

            virtual ~CounterFlag() {}

            virtual FlagBase *Match(const EitherFlag &arg) override
            {
                auto me = FlagBase::Match(arg);
                if (me)
                {
                    ++count;
                }
                return me;
            }

            /** Get the count
             */
            int &Get() noexcept
            {
                return count;
            }

            virtual void Reset() noexcept override
            {
                FlagBase::Reset();
                count = startcount;
            }
    };

    /** A flag class that calls a function when it's matched
     */
    class ActionFlag : public FlagBase
    {
        private:
            std::function<void(const std::vector<std::string> &)> action;
            Nargs nargs;

        public:
            ActionFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, Nargs nargs_, std::function<void(const std::vector<std::string> &)> action_, Options options_ = {}):
                FlagBase(name_, help_, std::move(matcher_), options_), action(std::move(action_)), nargs(nargs_)
            {
                group_.Add(*this);
            }

            ActionFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, std::function<void(const std::string &)> action_, Options options_ = {}):
                FlagBase(name_, help_, std::move(matcher_), options_), nargs(1)
            {
                group_.Add(*this);
                action = [action_](const std::vector<std::string> &a) { return action_(a.at(0)); };
            }

            ActionFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, std::function<void()> action_, Options options_ = {}):
                FlagBase(name_, help_, std::move(matcher_), options_), nargs(0)
            {
                group_.Add(*this);
                action = [action_](const std::vector<std::string> &) { return action_(); };
            }

            virtual Nargs NumberOfArguments() const noexcept override
            { return nargs; }

            virtual void ParseValue(const std::vector<std::string> &value) override
            { action(value); }
    };

    /** A default Reader class for argument classes
     *
     * If destination type is assignable to std::string it uses an assignment to std::string.
     * Otherwise ValueReader simply uses a std::istringstream to read into the destination type, and
     * raises a ParseError if there are any characters left.
     */
    struct ValueReader
    {
        template <typename T>
        typename std::enable_if<!std::is_assignable<T, std::string>::value, bool>::type
        operator ()(const std::string &name, const std::string &value, T &destination)
        {
            std::istringstream ss(value);
            ss >> destination >> std::ws;

            if (ss.rdbuf()->in_avail() > 0)
            {
#ifdef ARGS_NOEXCEPT
                (void)name;
                return false;
#else
                std::ostringstream problem;
                problem << "Argument '" << name << "' received invalid value type '" << value << "'";
                throw ParseError(problem.str());
#endif
            }
            return true;
        }

        template <typename T>
        typename std::enable_if<std::is_assignable<T, std::string>::value, bool>::type
        operator()(const std::string &, const std::string &value, T &destination)
        {
            destination = value;
            return true;
        }
    };

    /** An argument-accepting flag class
     * 
     * \tparam T the type to extract the argument as
     * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined)
     */
    template <
        typename T,
        typename Reader = ValueReader>
    class ValueFlag : public ValueFlagBase
    {
        protected:
            T value;
            T defaultValue;

            virtual std::string GetDefaultString(const HelpParams&) const override
            {
                return detail::ToString(defaultValue);
            }

        private:
            Reader reader;

        public:

            ValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const T &defaultValue_, Options options_): ValueFlagBase(name_, help_, std::move(matcher_), options_), value(defaultValue_), defaultValue(defaultValue_)
            {
                group_.Add(*this);
            }

            ValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const T &defaultValue_ = T(), const bool extraError_ = false): ValueFlag(group_, name_, help_, std::move(matcher_), defaultValue_, extraError_ ? Options::Single : Options::None)
            {
            }

            ValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_): ValueFlag(group_, name_, help_, std::move(matcher_), T(), options_)
            {
            }

            virtual ~ValueFlag() {}

            virtual void ParseValue(const std::vector<std::string> &values_) override
            {
                const std::string &value_ = values_.at(0);

#ifdef ARGS_NOEXCEPT
                if (!reader(name, value_, this->value))
                {
                    error = Error::Parse;
                }
#else
                reader(name, value_, this->value);
#endif
            }

            virtual void Reset() noexcept override
            {
                ValueFlagBase::Reset();
                value = defaultValue;
            }

            /** Get the value
             */
            T &Get() noexcept
            {
                return value;
            }

            /** Get the default value
             */
            const T &GetDefault() noexcept
            {
                return defaultValue;
            }
    };

    /** An optional argument-accepting flag class
     *
     * \tparam T the type to extract the argument as
     * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined)
     */
    template <
        typename T,
        typename Reader = ValueReader>
    class ImplicitValueFlag : public ValueFlag<T, Reader>
    {
        protected:
            T implicitValue;

        public:

            ImplicitValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const T &implicitValue_, const T &defaultValue_ = T(), Options options_ = {})
                : ValueFlag<T, Reader>(group_, name_, help_, std::move(matcher_), defaultValue_, options_), implicitValue(implicitValue_)
            {
            }

            ImplicitValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const T &defaultValue_ = T(), Options options_ = {})
                : ValueFlag<T, Reader>(group_, name_, help_, std::move(matcher_), defaultValue_, options_), implicitValue(defaultValue_)
            {
            }

            ImplicitValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_)
                : ValueFlag<T, Reader>(group_, name_, help_, std::move(matcher_), {}, options_), implicitValue()
            {
            }

            virtual ~ImplicitValueFlag() {}

            virtual Nargs NumberOfArguments() const noexcept override
            {
                return {0, 1};
            }

            virtual void ParseValue(const std::vector<std::string> &value_) override
            {
                if (value_.empty())
                {
                    this->value = implicitValue;
                } else
                {
                    ValueFlag<T, Reader>::ParseValue(value_);
                }
            }
    };

    /** A variadic arguments accepting flag class
     *
     * \tparam T the type to extract the argument as
     * \tparam List the list type that houses the values
     * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined)
     */
    template <
        typename T,
        template <typename...> class List = std::vector,
        typename Reader = ValueReader>
    class NargsValueFlag : public FlagBase
    {
        protected:

            List<T> values;
            const List<T> defaultValues;
            Nargs nargs;
            Reader reader;

        public:

            typedef List<T> Container;
            typedef T value_type;
            typedef typename Container::allocator_type allocator_type;
            typedef typename Container::pointer pointer;
            typedef typename Container::const_pointer const_pointer;
            typedef T& reference;
            typedef const T& const_reference;
            typedef typename Container::size_type size_type;
            typedef typename Container::difference_type difference_type;
            typedef typename Container::iterator iterator;
            typedef typename Container::const_iterator const_iterator;
            typedef std::reverse_iterator<iterator> reverse_iterator;
            typedef std::reverse_iterator<const_iterator> const_reverse_iterator;

            NargsValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, Nargs nargs_, const List<T> &defaultValues_ = {}, Options options_ = {})
                : FlagBase(name_, help_, std::move(matcher_), options_), values(defaultValues_), defaultValues(defaultValues_),nargs(nargs_)
            {
                group_.Add(*this);
            }

            virtual ~NargsValueFlag() {}

            virtual Nargs NumberOfArguments() const noexcept override
            {
                return nargs;
            }

            virtual void ParseValue(const std::vector<std::string> &values_) override
            {
                values.clear();

                for (const std::string &value : values_)
                {
                    T v;
#ifdef ARGS_NOEXCEPT
                    if (!reader(name, value, v))
                    {
                        error = Error::Parse;
                    }
#else
                    reader(name, value, v);
#endif
                    values.insert(std::end(values), v);
                }
            }

            List<T> &Get() noexcept
            {
                return values;
            }

            iterator begin() noexcept
            {
                return values.begin();
            }

            const_iterator begin() const noexcept
            {
                return values.begin();
            }

            const_iterator cbegin() const noexcept
            {
                return values.cbegin();
            }

            iterator end() noexcept
            {
                return values.end();
            }

            const_iterator end() const noexcept 
            {
                return values.end();
            }

            const_iterator cend() const noexcept
            {
                return values.cend();
            }

            virtual void Reset() noexcept override
            {
                FlagBase::Reset();
                values = defaultValues;
            }

            virtual FlagBase *Match(const EitherFlag &arg) override
            {
                const bool wasMatched = Matched();
                auto me = FlagBase::Match(arg);
                if (me && !wasMatched)
                {
                    values.clear();
                }
                return me;
            }
    };

    /** An argument-accepting flag class that pushes the found values into a list
     * 
     * \tparam T the type to extract the argument as
     * \tparam List the list type that houses the values
     * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined)
     */
    template <
        typename T,
        template <typename...> class List = std::vector,
        typename Reader = ValueReader>
    class ValueFlagList : public ValueFlagBase
    {
        private:
            using Container = List<T>;
            Container values;
            const Container defaultValues;
            Reader reader;

        public:

            typedef T value_type;
            typedef typename Container::allocator_type allocator_type;
            typedef typename Container::pointer pointer;
            typedef typename Container::const_pointer const_pointer;
            typedef T& reference;
            typedef const T& const_reference;
            typedef typename Container::size_type size_type;
            typedef typename Container::difference_type difference_type;
            typedef typename Container::iterator iterator;
            typedef typename Container::const_iterator const_iterator;
            typedef std::reverse_iterator<iterator> reverse_iterator;
            typedef std::reverse_iterator<const_iterator> const_reverse_iterator;

            ValueFlagList(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const Container &defaultValues_ = Container(), Options options_ = {}):
                ValueFlagBase(name_, help_, std::move(matcher_), options_), values(defaultValues_), defaultValues(defaultValues_)
            {
                group_.Add(*this);
            }

            virtual ~ValueFlagList() {}

            virtual void ParseValue(const std::vector<std::string> &values_) override
            {
                const std::string &value_ = values_.at(0);

                T v;
#ifdef ARGS_NOEXCEPT
                if (!reader(name, value_, v))
                {
                    error = Error::Parse;
                }
#else
                reader(name, value_, v);
#endif
                values.insert(std::end(values), v);
            }

            /** Get the values
             */
            Container &Get() noexcept
            {
                return values;
            }

            virtual std::string Name() const override
            {
                return name + std::string("...");
            }

            virtual void Reset() noexcept override
            {
                ValueFlagBase::Reset();
                values = defaultValues;
            }

            virtual FlagBase *Match(const EitherFlag &arg) override
            {
                const bool wasMatched = Matched();
                auto me = FlagBase::Match(arg);
                if (me && !wasMatched)
                {
                    values.clear();
                }
                return me;
            }

            iterator begin() noexcept
            {
                return values.begin();
            }

            const_iterator begin() const noexcept
            {
                return values.begin();
            }

            const_iterator cbegin() const noexcept
            {
                return values.cbegin();
            }

            iterator end() noexcept
            {
                return values.end();
            }

            const_iterator end() const noexcept 
            {
                return values.end();
            }

            const_iterator cend() const noexcept
            {
                return values.cend();
            }
    };

    /** A mapping value flag class
     * 
     * \tparam K the type to extract the argument as
     * \tparam T the type to store the result as
     * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined)
     * \tparam Map The Map type.  Should operate like std::map or std::unordered_map
     */
    template <
        typename K,
        typename T,
        typename Reader = ValueReader,
        template <typename...> class Map = std::unordered_map>
    class MapFlag : public ValueFlagBase
    {
        private:
            const Map<K, T> map;
            T value;
            const T defaultValue;
            Reader reader;

        protected:
            virtual std::vector<std::string> GetChoicesStrings(const HelpParams &) const override
            {
                return detail::MapKeysToStrings(map);
            }

        public:

            MapFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const Map<K, T> &map_, const T &defaultValue_, Options options_): ValueFlagBase(name_, help_, std::move(matcher_), options_), map(map_), value(defaultValue_), defaultValue(defaultValue_)
            {
                group_.Add(*this);
            }

            MapFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const Map<K, T> &map_, const T &defaultValue_ = T(), const bool extraError_ = false): MapFlag(group_, name_, help_, std::move(matcher_), map_, defaultValue_, extraError_ ? Options::Single : Options::None)
            {
            }

            MapFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const Map<K, T> &map_, Options options_): MapFlag(group_, name_, help_, std::move(matcher_), map_, T(), options_)
            {
            }

            virtual ~MapFlag() {}

            virtual void ParseValue(const std::vector<std::string> &values_) override
            {
                const std::string &value_ = values_.at(0);

                K key;
#ifdef ARGS_NOEXCEPT
                if (!reader(name, value_, key))
                {
                    error = Error::Parse;
                }
#else
                reader(name, value_, key);
#endif
                auto it = map.find(key);
                if (it == std::end(map))
                {
                    std::ostringstream problem;
                    problem << "Could not find key '" << key << "' in map for arg '" << name << "'";
#ifdef ARGS_NOEXCEPT
                    error = Error::Map;
                    errorMsg = problem.str();
#else
                    throw MapError(problem.str());
#endif
                } else
                {
                    this->value = it->second;
                }
            }

            /** Get the value
             */
            T &Get() noexcept
            {
                return value;
            }

            virtual void Reset() noexcept override
            {
                ValueFlagBase::Reset();
                value = defaultValue;
            }
    };

    /** A mapping value flag list class
     * 
     * \tparam K the type to extract the argument as
     * \tparam T the type to store the result as
     * \tparam List the list type that houses the values
     * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined)
     * \tparam Map The Map type.  Should operate like std::map or std::unordered_map
     */
    template <
        typename K,
        typename T,
        template <typename...> class List = std::vector,
        typename Reader = ValueReader,
        template <typename...> class Map = std::unordered_map>
    class MapFlagList : public ValueFlagBase
    {
        private:
            using Container = List<T>;
            const Map<K, T> map;
            Container values;
            const Container defaultValues;
            Reader reader;

        protected:
            virtual std::vector<std::string> GetChoicesStrings(const HelpParams &) const override
            {
                return detail::MapKeysToStrings(map);
            }

        public:
            typedef T value_type;
            typedef typename Container::allocator_type allocator_type;
            typedef typename Container::pointer pointer;
            typedef typename Container::const_pointer const_pointer;
            typedef T& reference;
            typedef const T& const_reference;
            typedef typename Container::size_type size_type;
            typedef typename Container::difference_type difference_type;
            typedef typename Container::iterator iterator;
            typedef typename Container::const_iterator const_iterator;
            typedef std::reverse_iterator<iterator> reverse_iterator;
            typedef std::reverse_iterator<const_iterator> const_reverse_iterator;

            MapFlagList(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const Map<K, T> &map_, const Container &defaultValues_ = Container()): ValueFlagBase(name_, help_, std::move(matcher_)), map(map_), values(defaultValues_), defaultValues(defaultValues_)
            {
                group_.Add(*this);
            }

            virtual ~MapFlagList() {}

            virtual void ParseValue(const std::vector<std::string> &values_) override
            {
                const std::string &value = values_.at(0);

                K key;
#ifdef ARGS_NOEXCEPT
                if (!reader(name, value, key))
                {
                    error = Error::Parse;
                }
#else
                reader(name, value, key);
#endif
                auto it = map.find(key);
                if (it == std::end(map))
                {
                    std::ostringstream problem;
                    problem << "Could not find key '" << key << "' in map for arg '" << name << "'";
#ifdef ARGS_NOEXCEPT
                    error = Error::Map;
                    errorMsg = problem.str();
#else
                    throw MapError(problem.str());
#endif
                } else
                {
                    this->values.emplace_back(it->second);
                }
            }

            /** Get the value
             */
            Container &Get() noexcept
            {
                return values;
            }

            virtual std::string Name() const override
            {
                return name + std::string("...");
            }

            virtual void Reset() noexcept override
            {
                ValueFlagBase::Reset();
                values = defaultValues;
            }

            virtual FlagBase *Match(const EitherFlag &arg) override
            {
                const bool wasMatched = Matched();
                auto me = FlagBase::Match(arg);
                if (me && !wasMatched)
                {
                    values.clear();
                }
                return me;
            }

            iterator begin() noexcept
            {
                return values.begin();
            }

            const_iterator begin() const noexcept
            {
                return values.begin();
            }

            const_iterator cbegin() const noexcept
            {
                return values.cbegin();
            }

            iterator end() noexcept
            {
                return values.end();
            }

            const_iterator end() const noexcept 
            {
                return values.end();
            }

            const_iterator cend() const noexcept
            {
                return values.cend();
            }
    };

    /** A positional argument class
     *
     * \tparam T the type to extract the argument as
     * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined)
     */
    template <
        typename T,
        typename Reader = ValueReader>
    class Positional : public PositionalBase
    {
        private:
            T value;
            const T defaultValue;
            Reader reader;
        public:
            Positional(Group &group_, const std::string &name_, const std::string &help_, const T &defaultValue_ = T(), Options options_ = {}): PositionalBase(name_, help_, options_), value(defaultValue_), defaultValue(defaultValue_)
            {
                group_.Add(*this);
            }

            Positional(Group &group_, const std::string &name_, const std::string &help_, Options options_): Positional(group_, name_, help_, T(), options_)
            {
            }

            virtual ~Positional() {}

            virtual void ParseValue(const std::string &value_) override
            {
#ifdef ARGS_NOEXCEPT
                if (!reader(name, value_, this->value))
                {
                    error = Error::Parse;
                }
#else
                reader(name, value_, this->value);
#endif
                ready = false;
                matched = true;
            }

            /** Get the value
             */
            T &Get() noexcept
            {
                return value;
            }

            virtual void Reset() noexcept override
            {
                PositionalBase::Reset();
                value = defaultValue;
            }
    };

    /** A positional argument class that pushes the found values into a list
     * 
     * \tparam T the type to extract the argument as
     * \tparam List the list type that houses the values
     * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined)
     */
    template <
        typename T,
        template <typename...> class List = std::vector,
        typename Reader = ValueReader>
    class PositionalList : public PositionalBase
    {
        private:
            using Container = List<T>;
            Container values;
            const Container defaultValues;
            Reader reader;

        public:
            typedef T value_type;
            typedef typename Container::allocator_type allocator_type;
            typedef typename Container::pointer pointer;
            typedef typename Container::const_pointer const_pointer;
            typedef T& reference;
            typedef const T& const_reference;
            typedef typename Container::size_type size_type;
            typedef typename Container::difference_type difference_type;
            typedef typename Container::iterator iterator;
            typedef typename Container::const_iterator const_iterator;
            typedef std::reverse_iterator<iterator> reverse_iterator;
            typedef std::reverse_iterator<const_iterator> const_reverse_iterator;

            PositionalList(Group &group_, const std::string &name_, const std::string &help_, const Container &defaultValues_ = Container(), Options options_ = {}): PositionalBase(name_, help_, options_), values(defaultValues_), defaultValues(defaultValues_)
            {
                group_.Add(*this);
            }

            PositionalList(Group &group_, const std::string &name_, const std::string &help_, Options options_): PositionalList(group_, name_, help_, {}, options_)
            {
            }

            virtual ~PositionalList() {}

            virtual void ParseValue(const std::string &value_) override
            {
                T v;
#ifdef ARGS_NOEXCEPT
                if (!reader(name, value_, v))
                {
                    error = Error::Parse;
                }
#else
                reader(name, value_, v);
#endif
                values.insert(std::end(values), v);
                matched = true;
            }

            virtual std::string Name() const override
            {
                return name + std::string("...");
            }

            /** Get the values
             */
            Container &Get() noexcept
            {
                return values;
            }

            virtual void Reset() noexcept override
            {
                PositionalBase::Reset();
                values = defaultValues;
            }

            virtual PositionalBase *GetNextPositional() override
            {
                const bool wasMatched = Matched();
                auto me = PositionalBase::GetNextPositional();
                if (me && !wasMatched)
                {
                    values.clear();
                }
                return me;
            }

            iterator begin() noexcept
            {
                return values.begin();
            }

            const_iterator begin() const noexcept
            {
                return values.begin();
            }

            const_iterator cbegin() const noexcept
            {
                return values.cbegin();
            }

            iterator end() noexcept
            {
                return values.end();
            }

            const_iterator end() const noexcept 
            {
                return values.end();
            }

            const_iterator cend() const noexcept
            {
                return values.cend();
            }
    };

    /** A positional argument mapping class
     * 
     * \tparam K the type to extract the argument as
     * \tparam T the type to store the result as
     * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined)
     * \tparam Map The Map type.  Should operate like std::map or std::unordered_map
     */
    template <
        typename K,
        typename T,
        typename Reader = ValueReader,
        template <typename...> class Map = std::unordered_map>
    class MapPositional : public PositionalBase
    {
        private:
            const Map<K, T> map;
            T value;
            const T defaultValue;
            Reader reader;

        protected:
            virtual std::vector<std::string> GetChoicesStrings(const HelpParams &) const override
            {
                return detail::MapKeysToStrings(map);
            }

        public:

            MapPositional(Group &group_, const std::string &name_, const std::string &help_, const Map<K, T> &map_, const T &defaultValue_ = T(), Options options_ = {}):
                PositionalBase(name_, help_, options_), map(map_), value(defaultValue_), defaultValue(defaultValue_)
            {
                group_.Add(*this);
            }

            virtual ~MapPositional() {}

            virtual void ParseValue(const std::string &value_) override
            {
                K key;
#ifdef ARGS_NOEXCEPT
                if (!reader(name, value_, key))
                {
                    error = Error::Parse;
                }
#else
                reader(name, value_, key);
#endif
                auto it = map.find(key);
                if (it == std::end(map))
                {
                    std::ostringstream problem;
                    problem << "Could not find key '" << key << "' in map for arg '" << name << "'";
#ifdef ARGS_NOEXCEPT
                    error = Error::Map;
                    errorMsg = problem.str();
#else
                    throw MapError(problem.str());
#endif
                } else
                {
                    this->value = it->second;
                    ready = false;
                    matched = true;
                }
            }

            /** Get the value
             */
            T &Get() noexcept
            {
                return value;
            }

            virtual void Reset() noexcept override
            {
                PositionalBase::Reset();
                value = defaultValue;
            }
    };

    /** A positional argument mapping list class
     * 
     * \tparam K the type to extract the argument as
     * \tparam T the type to store the result as
     * \tparam List the list type that houses the values
     * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined)
     * \tparam Map The Map type.  Should operate like std::map or std::unordered_map
     */
    template <
        typename K,
        typename T,
        template <typename...> class List = std::vector,
        typename Reader = ValueReader,
        template <typename...> class Map = std::unordered_map>
    class MapPositionalList : public PositionalBase
    {
        private:
            using Container = List<T>;

            const Map<K, T> map;
            Container values;
            const Container defaultValues;
            Reader reader;

        protected:
            virtual std::vector<std::string> GetChoicesStrings(const HelpParams &) const override
            {
                return detail::MapKeysToStrings(map);
            }

        public:
            typedef T value_type;
            typedef typename Container::allocator_type allocator_type;
            typedef typename Container::pointer pointer;
            typedef typename Container::const_pointer const_pointer;
            typedef T& reference;
            typedef const T& const_reference;
            typedef typename Container::size_type size_type;
            typedef typename Container::difference_type difference_type;
            typedef typename Container::iterator iterator;
            typedef typename Container::const_iterator const_iterator;
            typedef std::reverse_iterator<iterator> reverse_iterator;
            typedef std::reverse_iterator<const_iterator> const_reverse_iterator;

            MapPositionalList(Group &group_, const std::string &name_, const std::string &help_, const Map<K, T> &map_, const Container &defaultValues_ = Container(), Options options_ = {}):
                PositionalBase(name_, help_, options_), map(map_), values(defaultValues_), defaultValues(defaultValues_)
            {
                group_.Add(*this);
            }

            virtual ~MapPositionalList() {}

            virtual void ParseValue(const std::string &value_) override
            {
                K key;
#ifdef ARGS_NOEXCEPT
                if (!reader(name, value_, key))
                {
                    error = Error::Parse;
                }
#else
                reader(name, value_, key);
#endif
                auto it = map.find(key);
                if (it == std::end(map))
                {
                    std::ostringstream problem;
                    problem << "Could not find key '" << key << "' in map for arg '" << name << "'";
#ifdef ARGS_NOEXCEPT
                    error = Error::Map;
                    errorMsg = problem.str();
#else
                    throw MapError(problem.str());
#endif
                } else
                {
                    this->values.emplace_back(it->second);
                    matched = true;
                }
            }

            /** Get the value
             */
            Container &Get() noexcept
            {
                return values;
            }

            virtual std::string Name() const override
            {
                return name + std::string("...");
            }

            virtual void Reset() noexcept override
            {
                PositionalBase::Reset();
                values = defaultValues;
            }

            virtual PositionalBase *GetNextPositional() override
            {
                const bool wasMatched = Matched();
                auto me = PositionalBase::GetNextPositional();
                if (me && !wasMatched)
                {
                    values.clear();
                }
                return me;
            }

            iterator begin() noexcept
            {
                return values.begin();
            }

            const_iterator begin() const noexcept
            {
                return values.begin();
            }

            const_iterator cbegin() const noexcept
            {
                return values.cbegin();
            }

            iterator end() noexcept
            {
                return values.end();
            }

            const_iterator end() const noexcept 
            {
                return values.end();
            }

            const_iterator cend() const noexcept
            {
                return values.cend();
            }
    };
}

#endif
