/*****************************************************************************
 *  ___  _    _   ___ ___
 * |  _|| |  | | | _ \ _ \   CLIPP - command line interfaces for modern C++
 * | |_ | |_ | | |  _/  _/   version 1.2.4
 * |___||___||_| |_| |_|     https://github.com/muellan/clipp
 *
 * Licensed under the MIT License <http://opensource.org/licenses/MIT>.
 * Copyright (c) 2017-2020 André Müller <foss@andremueller-online.de>
 *
 * ---------------------------------------------------------------------------
 * 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.
 *
 *****************************************************************************/

#ifndef AM_CLIPP_H__
#define AM_CLIPP_H__

#include <cstring>
#include <string>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <memory>
#include <vector>
#include <limits>
#include <stack>
#include <algorithm>
#include <sstream>
#include <utility>
#include <iterator>
#include <functional>


/*************************************************************************//**
 *
 * @brief primary namespace
 *
 *****************************************************************************/
namespace clipp {



/*****************************************************************************
 *
 * basic constants and datatype definitions
 *
 *****************************************************************************/
using arg_index = int;

using arg_string = std::string;
using doc_string = std::string;

using arg_list  = std::vector<arg_string>;



/*************************************************************************//**
 *
 * @brief tristate
 *
 *****************************************************************************/
enum class tri : char { no, yes, either };

inline constexpr bool operator == (tri t, bool b) noexcept {
    return b ? t != tri::no : t != tri::yes;
}
inline constexpr bool operator == (bool b, tri t) noexcept { return  (t == b); }
inline constexpr bool operator != (tri t, bool b) noexcept { return !(t == b); }
inline constexpr bool operator != (bool b, tri t) noexcept { return !(t == b); }



/*************************************************************************//**
 *
 * @brief (start,size) index range
 *
 *****************************************************************************/
class subrange {
public:
    using size_type = arg_string::size_type;

    /** @brief default: no match */
    explicit constexpr
    subrange() noexcept :
        at_{arg_string::npos}, length_{0}
    {}

    /** @brief match length & position within subject string */
    explicit constexpr
    subrange(size_type pos, size_type len) noexcept :
        at_{pos}, length_{len}
    {}

    /** @brief position of the match within the subject string */
    constexpr size_type at()     const noexcept { return at_; }
    /** @brief length of the matching subsequence */
    constexpr size_type length() const noexcept { return length_; }

    /** @brief returns true, if query string is a prefix of the subject string */
    constexpr bool prefix() const noexcept {
        return at_ == 0;
    }

    /** @brief returns true, if query is a substring of the query string */
    constexpr explicit operator bool () const noexcept {
        return at_ != arg_string::npos;
    }

private:
    size_type at_;
    size_type length_;
};



/*************************************************************************//**
 *
 * @brief match predicates
 *
 *****************************************************************************/
using match_predicate = std::function<bool(const arg_string&)>;
using match_function  = std::function<subrange(const arg_string&)>;






/*************************************************************************//**
 *
 * @brief type traits (NOT FOR DIRECT USE IN CLIENT CODE!)
 *        no interface guarantees; might be changed or removed in the future
 *
 *****************************************************************************/
namespace traits {

/*************************************************************************//**
 *
 * @brief function (class) signature type trait
 *
 *****************************************************************************/
template<class Fn, class Ret, class... Args>
constexpr auto
check_is_callable(int) -> decltype(
    std::declval<Fn>()(std::declval<Args>()...),
    std::integral_constant<bool,
        std::is_same<Ret,typename std::result_of<Fn(Args...)>::type>::value>{} );

template<class,class,class...>
constexpr auto
check_is_callable(long) -> std::false_type;

template<class Fn, class Ret>
constexpr auto
check_is_callable_without_arg(int) -> decltype(
    std::declval<Fn>()(),
    std::integral_constant<bool,
        std::is_same<Ret,typename std::result_of<Fn()>::type>::value>{} );

template<class,class>
constexpr auto
check_is_callable_without_arg(long) -> std::false_type;



template<class Fn, class... Args>
constexpr auto
check_is_void_callable(int) -> decltype(
    std::declval<Fn>()(std::declval<Args>()...), std::true_type{});

template<class,class,class...>
constexpr auto
check_is_void_callable(long) -> std::false_type;

template<class Fn>
constexpr auto
check_is_void_callable_without_arg(int) -> decltype(
    std::declval<Fn>()(), std::true_type{});

template<class>
constexpr auto
check_is_void_callable_without_arg(long) -> std::false_type;



template<class Fn, class Ret>
struct is_callable;


template<class Fn, class Ret, class... Args>
struct is_callable<Fn, Ret(Args...)> :
    decltype(check_is_callable<Fn,Ret,Args...>(0))
{};

template<class Fn, class Ret>
struct is_callable<Fn,Ret()> :
    decltype(check_is_callable_without_arg<Fn,Ret>(0))
{};


template<class Fn, class... Args>
struct is_callable<Fn, void(Args...)> :
    decltype(check_is_void_callable<Fn,Args...>(0))
{};

template<class Fn>
struct is_callable<Fn,void()> :
    decltype(check_is_void_callable_without_arg<Fn>(0))
{};



/*************************************************************************//**
 *
 * @brief input range type trait
 *
 *****************************************************************************/
template<class T>
constexpr auto
check_is_input_range(int) -> decltype(
    begin(std::declval<T>()), end(std::declval<T>()),
    std::true_type{});

template<class T>
constexpr auto
check_is_input_range(char) -> decltype(
    std::begin(std::declval<T>()), std::end(std::declval<T>()),
    std::true_type{});

template<class>
constexpr auto
check_is_input_range(long) -> std::false_type;

template<class T>
struct is_input_range :
    decltype(check_is_input_range<T>(0))
{};



/*************************************************************************//**
 *
 * @brief size() member type trait
 *
 *****************************************************************************/
template<class T>
constexpr auto
check_has_size_getter(int) ->
    decltype(std::declval<T>().size(), std::true_type{});

template<class>
constexpr auto
check_has_size_getter(long) -> std::false_type;

template<class T>
struct has_size_getter :
    decltype(check_has_size_getter<T>(0))
{};

} // namespace traits






/*************************************************************************//**
 *
 * @brief helpers (NOT FOR DIRECT USE IN CLIENT CODE!)
 *        no interface guarantees; might be changed or removed in the future
 *
 *****************************************************************************/
namespace detail {


/*************************************************************************//**
 * @brief forwards string to first non-whitespace char;
 *        std string -> unsigned conv yields max value, but we want 0;
 *        also checks for nullptr
 *****************************************************************************/
inline bool
fwd_to_unsigned_int(const char*& s)
{
    if(!s) return false;
    for(; std::isspace(*s); ++s);
    if(!s[0] || s[0] == '-') return false;
    if(s[0] == '-') return false;
    return true;
}


/*************************************************************************//**
 *
 * @brief value limits clamping
 *
 *****************************************************************************/
template<class T, class V, bool = (sizeof(V) > sizeof(T))>
struct limits_clamped {
    static T from(const V& v) {
        if(v >= V(std::numeric_limits<T>::max())) {
            return std::numeric_limits<T>::max();
        }
        if(v <= V(std::numeric_limits<T>::lowest())) {
            return std::numeric_limits<T>::lowest();
        }
        return T(v);
    }
};

template<class T, class V>
struct limits_clamped<T,V,false> {
    static T from(const V& v) { return T(v); }
};


/*************************************************************************//**
 *
 * @brief returns value of v as a T, clamped at T's maximum
 *
 *****************************************************************************/
template<class T, class V>
inline T clamped_on_limits(const V& v) {
    return limits_clamped<T,V>::from(v);
}




/*************************************************************************//**
 *
 * @brief type conversion helpers
 *
 *****************************************************************************/
template<class T>
struct make {
    static inline T from(const char* s) {
        if(!s) return false;
        //a conversion from const char* to / must exist
        return static_cast<T>(s);
    }
};

template<>
struct make<bool> {
    static inline bool from(const char* s) {
        if(!s) return false;
        return static_cast<bool>(s);
    }
};

template<>
struct make<unsigned char> {
    static inline unsigned char from(const char* s) {
        if(!fwd_to_unsigned_int(s)) return (0);
        return clamped_on_limits<unsigned char>(std::strtoull(s,nullptr,10));
    }
};

template<>
struct make<unsigned short int> {
    static inline unsigned short int from(const char* s) {
        if(!fwd_to_unsigned_int(s)) return (0);
        return clamped_on_limits<unsigned short int>(std::strtoull(s,nullptr,10));
    }
};

template<>
struct make<unsigned int> {
    static inline unsigned int from(const char* s) {
        if(!fwd_to_unsigned_int(s)) return (0);
        return clamped_on_limits<unsigned int>(std::strtoull(s,nullptr,10));
    }
};

template<>
struct make<unsigned long int> {
    static inline unsigned long int from(const char* s) {
        if(!fwd_to_unsigned_int(s)) return (0);
        return clamped_on_limits<unsigned long int>(std::strtoull(s,nullptr,10));
    }
};

template<>
struct make<unsigned long long int> {
    static inline unsigned long long int from(const char* s) {
        if(!fwd_to_unsigned_int(s)) return (0);
        return clamped_on_limits<unsigned long long int>(std::strtoull(s,nullptr,10));
    }
};

template<>
struct make<char> {
    static inline char from(const char* s) {
        //parse as single character?
        const auto n = std::strlen(s);
        if(n == 1) return s[0];
        //parse as integer
        return clamped_on_limits<char>(std::strtoll(s,nullptr,10));
    }
};

template<>
struct make<short int> {
    static inline short int from(const char* s) {
        return clamped_on_limits<short int>(std::strtoll(s,nullptr,10));
    }
};

template<>
struct make<int> {
    static inline int from(const char* s) {
        return clamped_on_limits<int>(std::strtoll(s,nullptr,10));
    }
};

template<>
struct make<long int> {
    static inline long int from(const char* s) {
        return clamped_on_limits<long int>(std::strtoll(s,nullptr,10));
    }
};

template<>
struct make<long long int> {
    static inline long long int from(const char* s) {
        return (std::strtoll(s,nullptr,10));
    }
};

template<>
struct make<float> {
    static inline float from(const char* s) {
        return (std::strtof(s,nullptr));
    }
};

template<>
struct make<double> {
    static inline double from(const char* s) {
        return (std::strtod(s,nullptr));
    }
};

template<>
struct make<long double> {
    static inline long double from(const char* s) {
        return (std::strtold(s,nullptr));
    }
};

template<>
struct make<std::string> {
    static inline std::string from(const char* s) {
        return std::string(s);
    }
};



/*************************************************************************//**
 *
 * @brief assigns boolean constant to one or multiple target objects
 *
 *****************************************************************************/
template<class T, class V = T>
class assign_value
{
public:
    template<class X>
    explicit constexpr
    assign_value(T& target, X&& value) noexcept :
        t_{std::addressof(target)}, v_{std::forward<X>(value)}
    {}

    void operator () () const {
        if(t_) *t_ = v_;
    }

private:
    T* t_;
    V v_;
};



/*************************************************************************//**
 *
 * @brief flips bools
 *
 *****************************************************************************/
class flip_bool
{
public:
    explicit constexpr
    flip_bool(bool& target) noexcept :
        b_{&target}
    {}

    void operator () () const {
        if(b_) *b_ = !*b_;
    }

private:
    bool* b_;
};



/*************************************************************************//**
 *
 * @brief increments using operator ++
 *
 *****************************************************************************/
template<class T>
class increment
{
public:
    explicit constexpr
    increment(T& target) noexcept : t_{std::addressof(target)} {}

    void operator () () const {
        if(t_) ++(*t_);
    }

private:
    T* t_;
};



/*************************************************************************//**
 *
 * @brief decrements using operator --
 *
 *****************************************************************************/
template<class T>
class decrement
{
public:
    explicit constexpr
    decrement(T& target) noexcept : t_{std::addressof(target)} {}

    void operator () () const {
        if(t_) --(*t_);
    }

private:
    T* t_;
};



/*************************************************************************//**
 *
 * @brief increments by a fixed amount using operator +=
 *
 *****************************************************************************/
template<class T>
class increment_by
{
public:
    explicit constexpr
    increment_by(T& target, T by) noexcept :
        t_{std::addressof(target)}, by_{std::move(by)}
    {}

    void operator () () const {
        if(t_) (*t_) += by_;
    }

private:
    T* t_;
    T by_;
};




/*************************************************************************//**
 *
 * @brief makes a value from a string and assigns it to an object
 *
 *****************************************************************************/
template<class T>
class map_arg_to
{
public:
    explicit constexpr
    map_arg_to(T& target) noexcept : t_{std::addressof(target)} {}

    void operator () (const char* s) const {
        if(t_ && s) *t_ = detail::make<T>::from(s);
    }

private:
    T* t_;
};


//-------------------------------------------------------------------
/**
 * @brief specialization for vectors: append element
 */
template<class T>
class map_arg_to<std::vector<T>>
{
public:
    map_arg_to(std::vector<T>& target): t_{std::addressof(target)} {}

    void operator () (const char* s) const {
        if(t_ && s) t_->push_back(detail::make<T>::from(s));
    }

private:
    std::vector<T>* t_;
};


//-------------------------------------------------------------------
/**
 * @brief specialization for bools:
 *        set to true regardless of string content
 */
template<>
class map_arg_to<bool>
{
public:
    map_arg_to(bool& target): t_{&target} {}

    void operator () (const char* s) const {
        if(t_ && s) *t_ = true;
    }

private:
    bool* t_;
};


} // namespace detail






/*************************************************************************//**
 *
 * @brief string matching and processing tools
 *
 *****************************************************************************/

namespace str {


/*************************************************************************//**
 *
 * @brief converts string to value of target type 'T'
 *
 *****************************************************************************/
template<class T>
T make(const arg_string& s)
{
    return detail::make<T>::from(s);
}



/*************************************************************************//**
 *
 * @brief removes trailing whitespace from string
 *
 *****************************************************************************/
template<class C, class T, class A>
inline void
trimr(std::basic_string<C,T,A>& s)
{
    if(s.empty()) return;

    s.erase(
        std::find_if_not(s.rbegin(), s.rend(),
                         [](char c) { return std::isspace(c);} ).base(),
        s.end() );
}


/*************************************************************************//**
 *
 * @brief removes leading whitespace from string
 *
 *****************************************************************************/
template<class C, class T, class A>
inline void
triml(std::basic_string<C,T,A>& s)
{
    if(s.empty()) return;

    s.erase(
        s.begin(),
        std::find_if_not(s.begin(), s.end(),
                         [](char c) { return std::isspace(c);})
    );
}


/*************************************************************************//**
 *
 * @brief removes leading and trailing whitespace from string
 *
 *****************************************************************************/
template<class C, class T, class A>
inline void
trim(std::basic_string<C,T,A>& s)
{
    triml(s);
    trimr(s);
}


/*************************************************************************//**
 *
 * @brief removes all whitespaces from string
 *
 *****************************************************************************/
template<class C, class T, class A>
inline void
remove_ws(std::basic_string<C,T,A>& s)
{
    if(s.empty()) return;

    s.erase(std::remove_if(s.begin(), s.end(),
                           [](char c) { return std::isspace(c); }),
            s.end() );
}


/*************************************************************************//**
 *
 * @brief returns true, if the 'prefix' argument
 *        is a prefix of the 'subject' argument
 *
 *****************************************************************************/
template<class C, class T, class A>
inline bool
has_prefix(const std::basic_string<C,T,A>& subject,
           const std::basic_string<C,T,A>& prefix)
{
    if(prefix.size() > subject.size()) return false;
    return subject.find(prefix) == 0;
}


/*************************************************************************//**
 *
 * @brief returns true, if the 'postfix' argument
 *        is a postfix of the 'subject' argument
 *
 *****************************************************************************/
template<class C, class T, class A>
inline bool
has_postfix(const std::basic_string<C,T,A>& subject,
            const std::basic_string<C,T,A>& postfix)
{
    if(postfix.size() > subject.size()) return false;
    return (subject.size() - postfix.size()) == subject.find(postfix);
}



/*************************************************************************//**
*
* @brief   returns longest common prefix of several
*          sequential random access containers
*
* @details InputRange require begin and end (member functions or overloads)
*          the elements of InputRange require a size() member
*
*****************************************************************************/
template<class InputRange>
auto
longest_common_prefix(const InputRange& strs)
    -> typename std::decay<decltype(*begin(strs))>::type
{
    static_assert(traits::is_input_range<InputRange>(),
        "parameter must satisfy the InputRange concept");

    static_assert(traits::has_size_getter<
        typename std::decay<decltype(*begin(strs))>::type>(),
        "elements of input range must have a ::size() member function");

    using std::begin;
    using std::end;

    using item_t = typename std::decay<decltype(*begin(strs))>::type;
    using str_size_t = typename std::decay<decltype(begin(strs)->size())>::type;

    const auto n = size_t(distance(begin(strs), end(strs)));
    if(n < 1) return item_t("");
    if(n == 1) return *begin(strs);

    //length of shortest string
    auto m = std::min_element(begin(strs), end(strs),
                [](const item_t& a, const item_t& b) {
                    return a.size() < b.size(); })->size();

    //check each character until we find a mismatch
    for(str_size_t i = 0; i < m; ++i) {
        for(str_size_t j = 1; j < n; ++j) {
            if(strs[j][i] != strs[j-1][i])
                return strs[0].substr(0, i);
        }
    }
    return strs[0].substr(0, m);
}



/*************************************************************************//**
 *
 * @brief  returns longest substring range that could be found in 'arg'
 *
 * @param  arg         string to be searched in
 * @param  substrings  range of candidate substrings
 *
 *****************************************************************************/
template<class C, class T, class A, class InputRange>
subrange
longest_substring_match(const std::basic_string<C,T,A>& arg,
                        const InputRange& substrings)
{
    using string_t = std::basic_string<C,T,A>;

    static_assert(traits::is_input_range<InputRange>(),
        "parameter must satisfy the InputRange concept");

    static_assert(std::is_same<string_t,
        typename std::decay<decltype(*begin(substrings))>::type>(),
        "substrings must have same type as 'arg'");

    auto i = string_t::npos;
    auto n = string_t::size_type(0);
    for(const auto& s : substrings) {
        auto j = arg.find(s);
        if(j != string_t::npos && s.size() > n) {
            i = j;
            n = s.size();
        }
    }
    return subrange{i,n};
}



/*************************************************************************//**
 *
 * @brief  returns longest prefix range that could be found in 'arg'
 *
 * @param  arg       string to be searched in
 * @param  prefixes  range of candidate prefix strings
 *
 *****************************************************************************/
template<class C, class T, class A, class InputRange>
subrange
longest_prefix_match(const std::basic_string<C,T,A>& arg,
                     const InputRange& prefixes)
{
    using string_t = std::basic_string<C,T,A>;
    using s_size_t = typename string_t::size_type;

    static_assert(traits::is_input_range<InputRange>(),
        "parameter must satisfy the InputRange concept");

    static_assert(std::is_same<string_t,
        typename std::decay<decltype(*begin(prefixes))>::type>(),
        "prefixes must have same type as 'arg'");

    auto i = string_t::npos;
    auto n = s_size_t(0);
    for(const auto& s : prefixes) {
        auto j = arg.find(s);
        if(j == 0 && s.size() > n) {
            i = 0;
            n = s.size();
        }
    }
    return subrange{i,n};
}



/*************************************************************************//**
 *
 * @brief returns the first occurrence of 'query' within 'subject'
 *
 *****************************************************************************/
template<class C, class T, class A>
inline subrange
substring_match(const std::basic_string<C,T,A>& subject,
                const std::basic_string<C,T,A>& query)
{
    if(subject.empty() && query.empty()) return subrange(0,0);
    if(subject.empty() || query.empty()) return subrange{};
    auto i = subject.find(query);
    if(i == std::basic_string<C,T,A>::npos) return subrange{};
    return subrange{i,query.size()};
}



/*************************************************************************//**
 *
 * @brief returns first substring match (pos,len) within the input string
 *        that represents a number
 *        (with at maximum one decimal point and digit separators)
 *
 *****************************************************************************/
template<class C, class T, class A>
subrange
first_number_match(std::basic_string<C,T,A> s,
                   C digitSeparator = C(','),
                   C decimalPoint = C('.'),
                   C exponential = C('e'))
{
    using string_t = std::basic_string<C,T,A>;

    str::trim(s);
    if(s.empty()) return subrange{};

    auto i = s.find_first_of("0123456789+-");
    if(i == string_t::npos) {
        i = s.find(decimalPoint);
        if(i == string_t::npos) return subrange{};
    }

    bool point = false;
    bool sep = false;
    auto exp = string_t::npos;
    auto j = i + 1;
    for(; j < s.size(); ++j) {
        if(s[j] == digitSeparator) {
            if(!sep) sep = true; else break;
        }
        else {
            sep = false;
            if(s[j] == decimalPoint) {
                //only one decimal point before exponent allowed
                if(!point && exp == string_t::npos) point = true; else break;
            }
            else if(std::tolower(s[j]) == std::tolower(exponential)) {
                //only one exponent separator allowed
                if(exp == string_t::npos) exp = j; else break;
            }
            else if(exp != string_t::npos && (exp+1) == j) {
                //only sign or digit after exponent separator
                if(s[j] != '+' && s[j] != '-' && !std::isdigit(s[j])) break;
            }
            else if(!std::isdigit(s[j])) {
                break;
            }
        }
    }

    //if length == 1 then must be a digit
    if(j-i == 1 && !std::isdigit(s[i])) return subrange{};

    return subrange{i,j-i};
}



/*************************************************************************//**
 *
 * @brief returns first substring match (pos,len)
 *        that represents an integer (with optional digit separators)
 *
 *****************************************************************************/
template<class C, class T, class A>
subrange
first_integer_match(std::basic_string<C,T,A> s,
                    C digitSeparator = C(','))
{
    using string_t = std::basic_string<C,T,A>;

    str::trim(s);
    if(s.empty()) return subrange{};

    auto i = s.find_first_of("0123456789+-");
    if(i == string_t::npos) return subrange{};

    bool sep = false;
    auto j = i + 1;
    for(; j < s.size(); ++j) {
        if(s[j] == digitSeparator) {
            if(!sep) sep = true; else break;
        }
        else {
            sep = false;
            if(!std::isdigit(s[j])) break;
        }
    }

    //if length == 1 then must be a digit
    if(j-i == 1 && !std::isdigit(s[i])) return subrange{};

    return subrange{i,j-i};
}



/*************************************************************************//**
 *
 * @brief returns true if candidate string represents a number
 *
 *****************************************************************************/
template<class C, class T, class A>
bool represents_number(const std::basic_string<C,T,A>& candidate,
                   C digitSeparator = C(','),
                   C decimalPoint = C('.'),
                   C exponential = C('e'))
{
    const auto match = str::first_number_match(candidate, digitSeparator,
                                               decimalPoint, exponential);

    return (match && match.length() == candidate.size());
}



/*************************************************************************//**
 *
 * @brief returns true if candidate string represents an integer
 *
 *****************************************************************************/
template<class C, class T, class A>
bool represents_integer(const std::basic_string<C,T,A>& candidate,
                        C digitSeparator = C(','))
{
    const auto match = str::first_integer_match(candidate, digitSeparator);
    return (match && match.length() == candidate.size());
}

} // namespace str






/*************************************************************************//**
 *
 * @brief makes function object with a const char* parameter
 *        that assigns a value to a ref-captured object
 *
 *****************************************************************************/
template<class T, class V>
inline detail::assign_value<T,V>
set(T& target, V value) {
    return detail::assign_value<T>{target, std::move(value)};
}



/*************************************************************************//**
 *
 * @brief makes parameter-less function object
 *        that assigns value(s) to a ref-captured object;
 *        value(s) are obtained by converting the const char* argument to
 *        the captured object types;
 *        bools are always set to true if the argument is not nullptr
 *
 *****************************************************************************/
template<class T>
inline detail::map_arg_to<T>
set(T& target) {
    return detail::map_arg_to<T>{target};
}



/*************************************************************************//**
 *
 * @brief makes function object that sets a bool to true
 *
 *****************************************************************************/
inline detail::assign_value<bool>
set(bool& target) {
    return detail::assign_value<bool>{target,true};
}

/*************************************************************************//**
 *
 * @brief makes function object that sets a bool to false
 *
 *****************************************************************************/
inline detail::assign_value<bool>
unset(bool& target) {
    return detail::assign_value<bool>{target,false};
}

/*************************************************************************//**
 *
 * @brief makes function object that flips the value of a ref-captured bool
 *
 *****************************************************************************/
inline detail::flip_bool
flip(bool& b) {
    return detail::flip_bool(b);
}





/*************************************************************************//**
 *
 * @brief makes function object that increments using operator ++
 *
 *****************************************************************************/
template<class T>
inline detail::increment<T>
increment(T& target) {
    return detail::increment<T>{target};
}

/*************************************************************************//**
 *
 * @brief makes function object that decrements using operator --
 *
 *****************************************************************************/
template<class T>
inline detail::increment_by<T>
increment(T& target, T by) {
    return detail::increment_by<T>{target, std::move(by)};
}

/*************************************************************************//**
 *
 * @brief makes function object that increments by a fixed amount using operator +=
 *
 *****************************************************************************/
template<class T>
inline detail::decrement<T>
decrement(T& target) {
    return detail::decrement<T>{target};
}






/*************************************************************************//**
 *
 * @brief helpers (NOT FOR DIRECT USE IN CLIENT CODE!)
 *
 *****************************************************************************/
namespace detail {


/*************************************************************************//**
 *
 * @brief mixin that provides action definition and execution
 *
 *****************************************************************************/
template<class Derived>
class action_provider
{
private:
    //---------------------------------------------------------------
    using simple_action = std::function<void()>;
    using arg_action    = std::function<void(const char*)>;
    using index_action  = std::function<void(int)>;

    //-----------------------------------------------------
    class simple_action_adapter {
    public:
        simple_action_adapter() = default;
        simple_action_adapter(const simple_action& a): action_(a) {}
        simple_action_adapter(simple_action&& a): action_(std::move(a)) {}
        void operator() (const char*) const { action_(); }
        void operator() (int) const { action_(); }
    private:
        simple_action action_;
    };


public:
    //---------------------------------------------------------------
    /** @brief adds an action that has an operator() that is callable
     *         with a 'const char*' argument */
    Derived&
    call(arg_action a) {
        argActions_.push_back(std::move(a));
        return *static_cast<Derived*>(this);
    }

    /** @brief adds an action that has an operator()() */
    Derived&
    call(simple_action a) {
        argActions_.push_back(simple_action_adapter(std::move(a)));
        return *static_cast<Derived*>(this);
    }

    /** @brief adds an action that has an operator() that is callable
     *         with a 'const char*' argument */
    Derived& operator () (arg_action a)    { return call(std::move(a)); }

    /** @brief adds an action that has an operator()() */
    Derived& operator () (simple_action a) { return call(std::move(a)); }


    //---------------------------------------------------------------
    /** @brief adds an action that will set the value of 't' from
     *         a 'const char*' arg */
    template<class Target>
    Derived&
    set(Target& t) {
        static_assert(!std::is_pointer<Target>::value,
                      "parameter target type must not be a pointer");

        return call(clipp::set(t));
    }

    /** @brief adds an action that will set the value of 't' to 'v' */
    template<class Target, class Value>
    Derived&
    set(Target& t, Value&& v) {
        return call(clipp::set(t, std::forward<Value>(v)));
    }


    //---------------------------------------------------------------
    /** @brief adds an action that will be called if a parameter
     *         matches an argument for the 2nd, 3rd, 4th, ... time
     */
    Derived&
    if_repeated(simple_action a) {
        repeatActions_.push_back(simple_action_adapter{std::move(a)});
        return *static_cast<Derived*>(this);
    }
    /** @brief adds an action that will be called with the argument's
     *         index if a parameter matches an argument for
     *         the 2nd, 3rd, 4th, ... time
     */
    Derived&
    if_repeated(index_action a) {
        repeatActions_.push_back(std::move(a));
        return *static_cast<Derived*>(this);
    }


    //---------------------------------------------------------------
    /** @brief adds an action that will be called if a required parameter
     *         is missing
     */
    Derived&
    if_missing(simple_action a) {
        missingActions_.push_back(simple_action_adapter{std::move(a)});
        return *static_cast<Derived*>(this);
    }
    /** @brief adds an action that will be called if a required parameter
     *         is missing; the action will get called with the index of
     *         the command line argument where the missing event occurred first
     */
    Derived&
    if_missing(index_action a) {
        missingActions_.push_back(std::move(a));
        return *static_cast<Derived*>(this);
    }


    //---------------------------------------------------------------
    /** @brief adds an action that will be called if a parameter
     *         was matched, but was unreachable in the current scope
     */
    Derived&
    if_blocked(simple_action a) {
        blockedActions_.push_back(simple_action_adapter{std::move(a)});
        return *static_cast<Derived*>(this);
    }
    /** @brief adds an action that will be called if a parameter
     *         was matched, but was unreachable in the current scope;
     *         the action will be called with the index of
     *         the command line argument where the problem occurred
     */
    Derived&
    if_blocked(index_action a) {
        blockedActions_.push_back(std::move(a));
        return *static_cast<Derived*>(this);
    }


    //---------------------------------------------------------------
    /** @brief adds an action that will be called if a parameter match
     *         was in conflict with a different alternative parameter
     */
    Derived&
    if_conflicted(simple_action a) {
        conflictActions_.push_back(simple_action_adapter{std::move(a)});
        return *static_cast<Derived*>(this);
    }
    /** @brief adds an action that will be called if a parameter match
     *         was in conflict with a different alternative parameter;
     *         the action will be called with the index of
     *         the command line argument where the problem occurred
     */
    Derived&
    if_conflicted(index_action a) {
        conflictActions_.push_back(std::move(a));
        return *static_cast<Derived*>(this);
    }


    //---------------------------------------------------------------
    /** @brief adds targets = either objects whose values should be
     *         set by command line arguments or actions that should
     *         be called in case of a match */
    template<class T, class... Ts>
    Derived&
    target(T&& t, Ts&&... ts) {
        target(std::forward<T>(t));
        target(std::forward<Ts>(ts)...);
        return *static_cast<Derived*>(this);
    }

    /** @brief adds action that should be called in case of a match */
    template<class T, class = typename std::enable_if<
            !std::is_fundamental<typename std::decay<T>::type>() &&
            (traits::is_callable<T,void()>() ||
             traits::is_callable<T,void(const char*)>() )
        >::type>
    Derived&
    target(T&& t) {
        call(std::forward<T>(t));
        return *static_cast<Derived*>(this);
    }

    /** @brief adds object whose value should be set by command line arguments
     */
    template<class T, class = typename std::enable_if<
            std::is_fundamental<typename std::decay<T>::type>() ||
            (!traits::is_callable<T,void()>() &&
             !traits::is_callable<T,void(const char*)>() )
        >::type>
    Derived&
    target(T& t) {
        set(t);
        return *static_cast<Derived*>(this);
    }

    //TODO remove ugly empty param list overload
    Derived&
    target() {
        return *static_cast<Derived*>(this);
    }


    //---------------------------------------------------------------
    /** @brief adds target, see member function 'target' */
    template<class Target>
    inline friend Derived&
    operator << (Target&& t, Derived& p) {
        p.target(std::forward<Target>(t));
        return p;
    }
    /** @brief adds target, see member function 'target' */
    template<class Target>
    inline friend Derived&&
    operator << (Target&& t, Derived&& p) {
        p.target(std::forward<Target>(t));
        return std::move(p);
    }

    //-----------------------------------------------------
    /** @brief adds target, see member function 'target' */
    template<class Target>
    inline friend Derived&
    operator >> (Derived& p, Target&& t) {
        p.target(std::forward<Target>(t));
        return p;
    }
    /** @brief adds target, see member function 'target' */
    template<class Target>
    inline friend Derived&&
    operator >> (Derived&& p, Target&& t) {
        p.target(std::forward<Target>(t));
        return std::move(p);
    }


    //---------------------------------------------------------------
    /** @brief executes all argument actions */
    void execute_actions(const arg_string& arg) const {
        int i = 0;
        for(const auto& a : argActions_) {
            ++i;
            a(arg.c_str());
        }
    }

    /** @brief executes repeat actions */
    void notify_repeated(arg_index idx) const {
        for(const auto& a : repeatActions_) a(idx);
    }
    /** @brief executes missing error actions */
    void notify_missing(arg_index idx) const {
        for(const auto& a : missingActions_) a(idx);
    }
    /** @brief executes blocked error actions */
    void notify_blocked(arg_index idx) const {
        for(const auto& a : blockedActions_) a(idx);
    }
    /** @brief executes conflict error actions */
    void notify_conflict(arg_index idx) const {
        for(const auto& a : conflictActions_) a(idx);
    }

private:
    //---------------------------------------------------------------
    std::vector<arg_action> argActions_;
    std::vector<index_action> repeatActions_;
    std::vector<index_action> missingActions_;
    std::vector<index_action> blockedActions_;
    std::vector<index_action> conflictActions_;
};






/*************************************************************************//**
 *
 * @brief mixin that provides basic common settings of parameters and groups
 *
 *****************************************************************************/
template<class Derived>
class token
{
public:
    //---------------------------------------------------------------
    using doc_string = clipp::doc_string;


    //---------------------------------------------------------------
    /** @brief returns documentation string */
    const doc_string& doc() const noexcept {
        return doc_;
    }

    /** @brief sets documentations string */
    Derived& doc(const doc_string& txt) {
        doc_ = txt;
        return *static_cast<Derived*>(this);
    }

    /** @brief sets documentations string */
    Derived& doc(doc_string&& txt) {
        doc_ = std::move(txt);
        return *static_cast<Derived*>(this);
    }


    //---------------------------------------------------------------
    /** @brief returns if a group/parameter is repeatable */
    bool repeatable() const noexcept {
        return repeatable_;
    }

    /** @brief sets repeatability of group/parameter */
    Derived& repeatable(bool yes) noexcept {
        repeatable_ = yes;
        return *static_cast<Derived*>(this);
    }


    //---------------------------------------------------------------
    /** @brief returns if a group/parameter is blocking/positional */
    bool blocking() const noexcept {
        return blocking_;
    }

    /** @brief determines, if a group/parameter is blocking/positional */
    Derived& blocking(bool yes) noexcept {
        blocking_ = yes;
        return *static_cast<Derived*>(this);
    }


private:
    //---------------------------------------------------------------
    doc_string doc_;
    bool repeatable_ = false;
    bool blocking_ = false;
};




/*************************************************************************//**
 *
 * @brief sets documentation strings on a token
 *
 *****************************************************************************/
template<class T>
inline T&
operator % (doc_string docstr, token<T>& p)
{
    return p.doc(std::move(docstr));
}
//---------------------------------------------------------
template<class T>
inline T&&
operator % (doc_string docstr, token<T>&& p)
{
    return std::move(p.doc(std::move(docstr)));
}

//---------------------------------------------------------
template<class T>
inline T&
operator % (token<T>& p, doc_string docstr)
{
    return p.doc(std::move(docstr));
}
//---------------------------------------------------------
template<class T>
inline T&&
operator % (token<T>&& p, doc_string docstr)
{
    return std::move(p.doc(std::move(docstr)));
}




/*************************************************************************//**
 *
 * @brief sets documentation strings on a token
 *
 *****************************************************************************/
template<class T>
inline T&
doc(doc_string docstr, token<T>& p)
{
    return p.doc(std::move(docstr));
}
//---------------------------------------------------------
template<class T>
inline T&&
doc(doc_string docstr, token<T>&& p)
{
    return std::move(p.doc(std::move(docstr)));
}



} // namespace detail



/*************************************************************************//**
 *
 * @brief contains parameter matching functions and function classes
 *
 *****************************************************************************/
namespace match {


/*************************************************************************//**
 *
 * @brief predicate that is always true
 *
 *****************************************************************************/
inline bool
any(const arg_string&) { return true; }

/*************************************************************************//**
 *
 * @brief predicate that is always false
 *
 *****************************************************************************/
inline bool
none(const arg_string&) { return false; }



/*************************************************************************//**
 *
 * @brief predicate that returns true if the argument string is non-empty string
 *
 *****************************************************************************/
inline bool
nonempty(const arg_string& s) {
    return !s.empty();
}



/*************************************************************************//**
 *
 * @brief predicate that returns true if the argument is a non-empty
 *        string that consists only of alphanumeric characters
 *
 *****************************************************************************/
inline bool
alphanumeric(const arg_string& s) {
    if(s.empty()) return false;
    return std::all_of(s.begin(), s.end(), [](char c) {return std::isalnum(c); });
}



/*************************************************************************//**
 *
 * @brief predicate that returns true if the argument is a non-empty
 *        string that consists only of alphabetic characters
 *
 *****************************************************************************/
inline bool
alphabetic(const arg_string& s) {
    return std::all_of(s.begin(), s.end(), [](char c) {return std::isalpha(c); });
}



/*************************************************************************//**
 *
 * @brief predicate that returns false if the argument string is
 *        equal to any string from the exclusion list
 *
 *****************************************************************************/
class none_of
{
public:
    none_of(arg_list strs):
        excluded_{std::move(strs)}
    {}

    template<class... Strings>
    none_of(arg_string str, Strings&&... strs):
        excluded_{std::move(str), std::forward<Strings>(strs)...}
    {}

    template<class... Strings>
    none_of(const char* str, Strings&&... strs):
        excluded_{arg_string(str), std::forward<Strings>(strs)...}
    {}

    bool operator () (const arg_string& arg) const {
        return (std::find(begin(excluded_), end(excluded_), arg)
                == end(excluded_));
    }

private:
    arg_list excluded_;
};



/*************************************************************************//**
 *
 * @brief predicate that returns the first substring match within the input
 *        string that rmeepresents a number
 *        (with at maximum one decimal point and digit separators)
 *
 *****************************************************************************/
class numbers
{
public:
    explicit
    numbers(char decimalPoint = '.',
            char digitSeparator = ' ',
            char exponentSeparator = 'e')
    :
        decpoint_{decimalPoint}, separator_{digitSeparator},
        exp_{exponentSeparator}
    {}

    subrange operator () (const arg_string& s) const {
        return str::first_number_match(s, separator_, decpoint_, exp_);
    }

private:
    char decpoint_;
    char separator_;
    char exp_;
};



/*************************************************************************//**
 *
 * @brief predicate that returns true if the input string represents an integer
 *        (with optional digit separators)
 *
 *****************************************************************************/
class integers {
public:
    explicit
    integers(char digitSeparator = ' '): separator_{digitSeparator} {}

    subrange operator () (const arg_string& s) const {
        return str::first_integer_match(s, separator_);
    }

private:
    char separator_;
};



/*************************************************************************//**
 *
 * @brief predicate that returns true if the input string represents
 *        a non-negative integer (with optional digit separators)
 *
 *****************************************************************************/
class positive_integers {
public:
    explicit
    positive_integers(char digitSeparator = ' '): separator_{digitSeparator} {}

    subrange operator () (const arg_string& s) const {
        auto match = str::first_integer_match(s, separator_);
        if(!match) return subrange{};
        if(s[match.at()] == '-') return subrange{};
        return match;
    }

private:
    char separator_;
};



/*************************************************************************//**
 *
 * @brief predicate that returns true if the input string
 *        contains a given substring
 *
 *****************************************************************************/
class substring
{
public:
    explicit
    substring(arg_string str): str_{std::move(str)} {}

    subrange operator () (const arg_string& s) const {
        return str::substring_match(s, str_);
    }

private:
    arg_string str_;
};



/*************************************************************************//**
 *
 * @brief predicate that returns true if the input string starts
 *        with a given prefix
 *
 *****************************************************************************/
class prefix {
public:
    explicit
    prefix(arg_string p): prefix_{std::move(p)} {}

    bool operator () (const arg_string& s) const {
        return s.find(prefix_) == 0;
    }

private:
    arg_string prefix_;
};



/*************************************************************************//**
 *
 * @brief predicate that returns true if the input string does not start
 *        with a given prefix
 *
 *****************************************************************************/
class prefix_not {
public:
    explicit
    prefix_not(arg_string p): prefix_{std::move(p)} {}

    bool operator () (const arg_string& s) const {
        return s.find(prefix_) != 0;
    }

private:
    arg_string prefix_;
};


/** @brief alias for prefix_not */
using noprefix = prefix_not;



/*************************************************************************//**
 *
 * @brief predicate that returns true if the length of the input string
 *        is wihtin a given interval
 *
 *****************************************************************************/
class length {
public:
    explicit
    length(std::size_t exact):
        min_{exact}, max_{exact}
    {}

    explicit
    length(std::size_t min, std::size_t max):
        min_{min}, max_{max}
    {}

    bool operator () (const arg_string& s) const {
        return s.size() >= min_ && s.size() <= max_;
    }

private:
    std::size_t min_;
    std::size_t max_;
};


/*************************************************************************//**
 *
 * @brief makes function object that returns true if the input string has a
 *        given minimum length
 *
 *****************************************************************************/
inline length min_length(std::size_t min)
{
    return length{min, arg_string::npos-1};
}

/*************************************************************************//**
 *
 * @brief makes function object that returns true if the input string is
 *        not longer than a given maximum length
 *
 *****************************************************************************/
inline length max_length(std::size_t max)
{
    return length{0, max};
}


} // namespace match





/*************************************************************************//**
 *
 * @brief command line parameter that can match one or many arguments.
 *
 *****************************************************************************/
class parameter :
    public detail::token<parameter>,
    public detail::action_provider<parameter>
{
    /** @brief adapts a 'match_predicate' to the 'match_function' interface */
    class predicate_adapter {
    public:
        explicit
        predicate_adapter(match_predicate pred): match_{std::move(pred)} {}

        subrange operator () (const arg_string& arg) const {
            return match_(arg) ? subrange{0,arg.size()} : subrange{};
        }

    private:
        match_predicate match_;
    };

public:
    //---------------------------------------------------------------
    /** @brief makes default parameter, that will match nothing */
    parameter():
        flags_{},
        matcher_{predicate_adapter{match::none}},
        label_{}, required_{false}, greedy_{false}
    {}

    /** @brief makes "flag" parameter */
    template<class... Strings>
    explicit
    parameter(arg_string str, Strings&&... strs):
        flags_{},
        matcher_{predicate_adapter{match::none}},
        label_{}, required_{false}, greedy_{false}
    {
        add_flags(std::move(str), std::forward<Strings>(strs)...);
    }

    /** @brief makes "flag" parameter from range of strings */
    explicit
    parameter(const arg_list& flaglist):
        flags_{},
        matcher_{predicate_adapter{match::none}},
        label_{}, required_{false}, greedy_{false}
    {
        add_flags(flaglist);
    }

    //-----------------------------------------------------
    /** @brief makes "value" parameter with custom match predicate
     *         (= yes/no matcher)
     */
    explicit
    parameter(match_predicate filter):
        flags_{},
        matcher_{predicate_adapter{std::move(filter)}},
        label_{}, required_{false}, greedy_{false}
    {}

    /** @brief makes "value" parameter with custom match function
     *         (= partial matcher)
     */
    explicit
    parameter(match_function filter):
        flags_{},
        matcher_{std::move(filter)},
        label_{}, required_{false}, greedy_{false}
    {}


    //---------------------------------------------------------------
    /** @brief returns if a parameter is required */
    bool
    required() const noexcept {
        return required_;
    }

    /** @brief determines if a parameter is required */
    parameter&
    required(bool yes) noexcept {
        required_ = yes;
        return *this;
    }


    //---------------------------------------------------------------
    /** @brief returns if a parameter should match greedily */
    bool
    greedy() const noexcept {
        return greedy_;
    }

    /** @brief determines if a parameter should match greedily */
    parameter&
    greedy(bool yes) noexcept {
        greedy_ = yes;
        return *this;
    }


    //---------------------------------------------------------------
    /** @brief returns parameter label;
     *         will be used for documentation, if flags are empty
     */
    const doc_string&
    label() const {
        return label_;
    }

    /** @brief sets parameter label;
     *         will be used for documentation, if flags are empty
     */
    parameter&
    label(const doc_string& lbl) {
        label_ = lbl;
        return *this;
    }

    /** @brief sets parameter label;
     *         will be used for documentation, if flags are empty
     */
    parameter&
    label(doc_string&& lbl) {
        label_ = lbl;
        return *this;
    }


    //---------------------------------------------------------------
    /** @brief returns either longest matching prefix of 'arg' in any
     *         of the flags or the result of the custom match operation
     */
    subrange
    match(const arg_string& arg) const
    {
        if(flags_.empty()) {
            return matcher_(arg);
        }
        else {
            //empty flags are not allowed
            if(arg.empty()) return subrange{};

            if(std::find(flags_.begin(), flags_.end(), arg) != flags_.end()) {
                return subrange{0,arg.size()};
            }
            return str::longest_prefix_match(arg, flags_);
        }
    }


    //---------------------------------------------------------------
    /** @brief access range of flag strings */
    const arg_list&
    flags() const noexcept {
        return flags_;
    }

    /** @brief access custom match operation */
    const match_function&
    matcher() const noexcept {
        return matcher_;
    }


    //---------------------------------------------------------------
    /** @brief prepend prefix to each flag */
    inline friend parameter&
    with_prefix(const arg_string& prefix, parameter& p)
    {
        if(prefix.empty() || p.flags().empty()) return p;

        for(auto& f : p.flags_) {
            if(f.find(prefix) != 0) f.insert(0, prefix);
        }
        return p;
    }


    /** @brief prepend prefix to each flag
     */
    inline friend parameter&
    with_prefixes_short_long(
        const arg_string& shortpfx, const arg_string& longpfx,
        parameter& p)
    {
        if(shortpfx.empty() && longpfx.empty()) return p;
        if(p.flags().empty()) return p;

        for(auto& f : p.flags_) {
            if(f.size() == 1) {
                if(f.find(shortpfx) != 0) f.insert(0, shortpfx);
            } else {
                if(f.find(longpfx) != 0) f.insert(0, longpfx);
            }
        }
        return p;
    }


    //---------------------------------------------------------------
    /** @brief prepend suffix to each flag */
    inline friend parameter&
    with_suffix(const arg_string& suffix, parameter& p)
    {
        if(suffix.empty() || p.flags().empty()) return p;

        for(auto& f : p.flags_) {
            if(f.find(suffix) + suffix.size() != f.size()) {
                f.insert(f.end(), suffix.begin(), suffix.end());
            }
        }
        return p;
    }


    /** @brief prepend suffix to each flag
     */
    inline friend parameter&
    with_suffixes_short_long(
        const arg_string& shortsfx, const arg_string& longsfx,
        parameter& p)
    {
        if(shortsfx.empty() && longsfx.empty()) return p;
        if(p.flags().empty()) return p;

        for(auto& f : p.flags_) {
            if(f.size() == 1) {
                if(f.find(shortsfx) + shortsfx.size() != f.size()) {
                    f.insert(f.end(), shortsfx.begin(), shortsfx.end());
                }
            } else {
                if(f.find(longsfx) + longsfx.size() != f.size()) {
                    f.insert(f.end(), longsfx.begin(), longsfx.end());
                }
            }
        }
        return p;
    }

private:
    //---------------------------------------------------------------
    void add_flags(arg_string str) {
        //empty flags are not allowed
        str::remove_ws(str);
        if(!str.empty()) flags_.push_back(std::move(str));
    }

    //---------------------------------------------------------------
    void add_flags(const arg_list& strs) {
        if(strs.empty()) return;
        flags_.reserve(flags_.size() + strs.size());
        for(const auto& s : strs) add_flags(s);
    }

    template<class String1, class String2, class... Strings>
    void
    add_flags(String1&& s1, String2&& s2, Strings&&... ss) {
        flags_.reserve(2 + sizeof...(ss));
        add_flags(std::forward<String1>(s1));
        add_flags(std::forward<String2>(s2), std::forward<Strings>(ss)...);
    }

    arg_list flags_;
    match_function matcher_;
    doc_string label_;
    bool required_ = false;
    bool greedy_ = false;
};




/*************************************************************************//**
 *
 * @brief makes required non-blocking exact match parameter
 *
 *****************************************************************************/
template<class String, class... Strings>
inline parameter
command(String&& flag, Strings&&... flags)
{
    return parameter{std::forward<String>(flag), std::forward<Strings>(flags)...}
        .required(true).blocking(true).repeatable(false);
}



/*************************************************************************//**
 *
 * @brief makes required non-blocking exact match parameter
 *
 *****************************************************************************/
template<class String, class... Strings>
inline parameter
required(String&& flag, Strings&&... flags)
{
    return parameter{std::forward<String>(flag), std::forward<Strings>(flags)...}
        .required(true).blocking(false).repeatable(false);
}



/*************************************************************************//**
 *
 * @brief makes optional, non-blocking exact match parameter
 *
 *****************************************************************************/
template<class String, class... Strings>
inline parameter
option(String&& flag, Strings&&... flags)
{
    return parameter{std::forward<String>(flag), std::forward<Strings>(flags)...}
        .required(false).blocking(false).repeatable(false);
}



/*************************************************************************//**
 *
 * @brief makes required, blocking, repeatable value parameter;
 *        matches any non-empty string
 *
 *****************************************************************************/
template<class... Targets>
inline parameter
value(const doc_string& label, Targets&&... tgts)
{
    return parameter{match::nonempty}
        .label(label)
        .target(std::forward<Targets>(tgts)...)
        .required(true).blocking(true).repeatable(false);
}

template<class Filter, class... Targets, class = typename std::enable_if<
    traits::is_callable<Filter,bool(const char*)>::value ||
    traits::is_callable<Filter,subrange(const char*)>::value>::type>
inline parameter
value(Filter&& filter, doc_string label, Targets&&... tgts)
{
    return parameter{std::forward<Filter>(filter)}
        .label(label)
        .target(std::forward<Targets>(tgts)...)
        .required(true).blocking(true).repeatable(false);
}



/*************************************************************************//**
 *
 * @brief makes required, blocking, repeatable value parameter;
 *        matches any non-empty string
 *
 *****************************************************************************/
template<class... Targets>
inline parameter
values(const doc_string& label, Targets&&... tgts)
{
    return parameter{match::nonempty}
        .label(label)
        .target(std::forward<Targets>(tgts)...)
        .required(true).blocking(true).repeatable(true);
}

template<class Filter, class... Targets, class = typename std::enable_if<
    traits::is_callable<Filter,bool(const char*)>::value ||
    traits::is_callable<Filter,subrange(const char*)>::value>::type>
inline parameter
values(Filter&& filter, doc_string label, Targets&&... tgts)
{
    return parameter{std::forward<Filter>(filter)}
        .label(label)
        .target(std::forward<Targets>(tgts)...)
        .required(true).blocking(true).repeatable(true);
}



/*************************************************************************//**
 *
 * @brief makes optional, blocking value parameter;
 *        matches any non-empty string
 *
 *****************************************************************************/
template<class... Targets>
inline parameter
opt_value(const doc_string& label, Targets&&... tgts)
{
    return parameter{match::nonempty}
        .label(label)
        .target(std::forward<Targets>(tgts)...)
        .required(false).blocking(false).repeatable(false);
}

template<class Filter, class... Targets, class = typename std::enable_if<
    traits::is_callable<Filter,bool(const char*)>::value ||
    traits::is_callable<Filter,subrange(const char*)>::value>::type>
inline parameter
opt_value(Filter&& filter, doc_string label, Targets&&... tgts)
{
    return parameter{std::forward<Filter>(filter)}
        .label(label)
        .target(std::forward<Targets>(tgts)...)
        .required(false).blocking(false).repeatable(false);
}



/*************************************************************************//**
 *
 * @brief makes optional, blocking, repeatable value parameter;
 *        matches any non-empty string
 *
 *****************************************************************************/
template<class... Targets>
inline parameter
opt_values(const doc_string& label, Targets&&... tgts)
{
    return parameter{match::nonempty}
        .label(label)
        .target(std::forward<Targets>(tgts)...)
        .required(false).blocking(false).repeatable(true);
}

template<class Filter, class... Targets, class = typename std::enable_if<
    traits::is_callable<Filter,bool(const char*)>::value ||
    traits::is_callable<Filter,subrange(const char*)>::value>::type>
inline parameter
opt_values(Filter&& filter, doc_string label, Targets&&... tgts)
{
    return parameter{std::forward<Filter>(filter)}
        .label(label)
        .target(std::forward<Targets>(tgts)...)
        .required(false).blocking(false).repeatable(true);
}



/*************************************************************************//**
 *
 * @brief makes required, blocking value parameter;
 *        matches any string consisting of alphanumeric characters
 *
 *****************************************************************************/
template<class... Targets>
inline parameter
word(const doc_string& label, Targets&&... tgts)
{
    return parameter{match::alphanumeric}
        .label(label)
        .target(std::forward<Targets>(tgts)...)
        .required(true).blocking(true).repeatable(false);
}



/*************************************************************************//**
 *
 * @brief makes required, blocking, repeatable value parameter;
 *        matches any string consisting of alphanumeric characters
 *
 *****************************************************************************/
template<class... Targets>
inline parameter
words(const doc_string& label, Targets&&... tgts)
{
    return parameter{match::alphanumeric}
        .label(label)
        .target(std::forward<Targets>(tgts)...)
        .required(true).blocking(true).repeatable(true);
}



/*************************************************************************//**
 *
 * @brief makes optional, blocking value parameter;
 *        matches any string consisting of alphanumeric characters
 *
 *****************************************************************************/
template<class... Targets>
inline parameter
opt_word(const doc_string& label, Targets&&... tgts)
{
    return parameter{match::alphanumeric}
        .label(label)
        .target(std::forward<Targets>(tgts)...)
        .required(false).blocking(false).repeatable(false);
}



/*************************************************************************//**
 *
 * @brief makes optional, blocking, repeatable value parameter;
 *        matches any string consisting of alphanumeric characters
 *
 *****************************************************************************/
template<class... Targets>
inline parameter
opt_words(const doc_string& label, Targets&&... tgts)
{
    return parameter{match::alphanumeric}
        .label(label)
        .target(std::forward<Targets>(tgts)...)
        .required(false).blocking(false).repeatable(true);
}



/*************************************************************************//**
 *
 * @brief makes required, blocking value parameter;
 *        matches any string that represents a number
 *
 *****************************************************************************/
template<class... Targets>
inline parameter
number(const doc_string& label, Targets&&... tgts)
{
    return parameter{match::numbers{}}
        .label(label)
        .target(std::forward<Targets>(tgts)...)
        .required(true).blocking(true).repeatable(false);
}



/*************************************************************************//**
 *
 * @brief makes required, blocking, repeatable value parameter;
 *        matches any string that represents a number
 *
 *****************************************************************************/
template<class... Targets>
inline parameter
numbers(const doc_string& label, Targets&&... tgts)
{
    return parameter{match::numbers{}}
        .label(label)
        .target(std::forward<Targets>(tgts)...)
        .required(true).blocking(true).repeatable(true);
}



/*************************************************************************//**
 *
 * @brief makes optional, blocking value parameter;
 *        matches any string that represents a number
 *
 *****************************************************************************/
template<class... Targets>
inline parameter
opt_number(const doc_string& label, Targets&&... tgts)
{
    return parameter{match::numbers{}}
        .label(label)
        .target(std::forward<Targets>(tgts)...)
        .required(false).blocking(false).repeatable(false);
}



/*************************************************************************//**
 *
 * @brief makes optional, blocking, repeatable value parameter;
 *        matches any string that represents a number
 *
 *****************************************************************************/
template<class... Targets>
inline parameter
opt_numbers(const doc_string& label, Targets&&... tgts)
{
    return parameter{match::numbers{}}
        .label(label)
        .target(std::forward<Targets>(tgts)...)
        .required(false).blocking(false).repeatable(true);
}



/*************************************************************************//**
 *
 * @brief makes required, blocking value parameter;
 *        matches any string that represents an integer
 *
 *****************************************************************************/
template<class... Targets>
inline parameter
integer(const doc_string& label, Targets&&... tgts)
{
    return parameter{match::integers{}}
        .label(label)
        .target(std::forward<Targets>(tgts)...)
        .required(true).blocking(true).repeatable(false);
}



/*************************************************************************//**
 *
 * @brief makes required, blocking, repeatable value parameter;
 *        matches any string that represents an integer
 *
 *****************************************************************************/
template<class... Targets>
inline parameter
integers(const doc_string& label, Targets&&... tgts)
{
    return parameter{match::integers{}}
        .label(label)
        .target(std::forward<Targets>(tgts)...)
        .required(true).blocking(true).repeatable(true);
}



/*************************************************************************//**
 *
 * @brief makes optional, blocking value parameter;
 *        matches any string that represents an integer
 *
 *****************************************************************************/
template<class... Targets>
inline parameter
opt_integer(const doc_string& label, Targets&&... tgts)
{
    return parameter{match::integers{}}
        .label(label)
        .target(std::forward<Targets>(tgts)...)
        .required(false).blocking(false).repeatable(false);
}



/*************************************************************************//**
 *
 * @brief makes optional, blocking, repeatable value parameter;
 *        matches any string that represents an integer
 *
 *****************************************************************************/
template<class... Targets>
inline parameter
opt_integers(const doc_string& label, Targets&&... tgts)
{
    return parameter{match::integers{}}
        .label(label)
        .target(std::forward<Targets>(tgts)...)
        .required(false).blocking(false).repeatable(true);
}



/*************************************************************************//**
 *
 * @brief makes catch-all value parameter
 *
 *****************************************************************************/
template<class... Targets>
inline parameter
any_other(Targets&&... tgts)
{
    return parameter{match::any}
        .target(std::forward<Targets>(tgts)...)
        .required(false).blocking(false).repeatable(true);
}



/*************************************************************************//**
 *
 * @brief makes catch-all value parameter with custom filter
 *
 *****************************************************************************/
template<class Filter, class... Targets, class = typename std::enable_if<
    traits::is_callable<Filter,bool(const char*)>::value ||
    traits::is_callable<Filter,subrange(const char*)>::value>::type>
inline parameter
any(Filter&& filter, Targets&&... tgts)
{
    return parameter{std::forward<Filter>(filter)}
        .target(std::forward<Targets>(tgts)...)
        .required(false).blocking(false).repeatable(true);
}




/*************************************************************************//**
 *
 * @brief group of parameters and/or other groups;
 *        can be configured to act as a group of alternatives (exclusive match)
 *
 *****************************************************************************/
class group :
    public detail::token<group>
{
    //---------------------------------------------------------------
    /**
        * @brief tagged union type that either stores a parameter or a group
        *        and provides a common interface to them
        *        could be replaced by std::variant in the future
        *
        *        Note to future self: do NOT try again to do this with
        *        dynamic polymorphism; there are a couple of
        *        nasty problems associated with it and the implementation
        *        becomes bloated and needlessly complicated.
        */
    template<class Param, class Group>
    struct child_t {
        enum class type : char {param, group};
    public:

        explicit
        child_t(const Param&  v)          : m_{v},            type_{type::param} {}
        child_t(      Param&& v) noexcept : m_{std::move(v)}, type_{type::param} {}

        explicit
        child_t(const Group&  g)          : m_{g},            type_{type::group} {}
        child_t(      Group&& g) noexcept : m_{std::move(g)}, type_{type::group} {}

        child_t(const child_t& src): type_{src.type_} {
            switch(type_) {
                default:
                case type::param: new(&m_)data{src.m_.param}; break;
                case type::group: new(&m_)data{src.m_.group}; break;
            }
        }

        child_t(child_t&& src) noexcept : type_{src.type_} {
            switch(type_) {
                default:
                case type::param: new(&m_)data{std::move(src.m_.param)}; break;
                case type::group: new(&m_)data{std::move(src.m_.group)}; break;
            }
        }

        child_t& operator = (const child_t& src) {
            destroy_content();
            type_ = src.type_;
            switch(type_) {
                default:
                case type::param: new(&m_)data{src.m_.param}; break;
                case type::group: new(&m_)data{src.m_.group}; break;
            }
            return *this;
        }

        child_t& operator = (child_t&& src) noexcept {
            destroy_content();
            type_ = src.type_;
            switch(type_) {
                default:
                case type::param: new(&m_)data{std::move(src.m_.param)}; break;
                case type::group: new(&m_)data{std::move(src.m_.group)}; break;
            }
            return *this;
        }

        ~child_t() {
            destroy_content();
        }

        const doc_string&
        doc() const noexcept {
            switch(type_) {
                default:
                case type::param: return m_.param.doc();
                case type::group: return m_.group.doc();
            }
        }

        bool blocking() const noexcept {
            switch(type_) {
                case type::param: return m_.param.blocking();
                case type::group: return m_.group.blocking();
                default: return false;
            }
        }
        bool repeatable() const noexcept {
            switch(type_) {
                case type::param: return m_.param.repeatable();
                case type::group: return m_.group.repeatable();
                default: return false;
            }
        }
        bool required() const noexcept {
            switch(type_) {
                case type::param: return m_.param.required();
                case type::group:
                    return (m_.group.exclusive() && m_.group.all_required() ) ||
                          (!m_.group.exclusive() && m_.group.any_required()  );
                default: return false;
            }
        }
        bool exclusive() const noexcept {
            switch(type_) {
                case type::group: return m_.group.exclusive();
                case type::param:
                default: return false;
            }
        }
        std::size_t param_count() const noexcept {
            switch(type_) {
                case type::group: return m_.group.param_count();
                case type::param:
                default: return std::size_t(1);
            }
        }
        std::size_t depth() const noexcept {
            switch(type_) {
                case type::group: return m_.group.depth();
                case type::param:
                default: return std::size_t(0);
            }
        }

        void execute_actions(const arg_string& arg) const {
            switch(type_) {
                default:
                case type::group: return;
                case type::param: m_.param.execute_actions(arg); break;
            }

        }

        void notify_repeated(arg_index idx) const {
            switch(type_) {
                default:
                case type::group: return;
                case type::param: m_.param.notify_repeated(idx); break;
            }
        }
        void notify_missing(arg_index idx) const {
            switch(type_) {
                default:
                case type::group: return;
                case type::param: m_.param.notify_missing(idx); break;
            }
        }
        void notify_blocked(arg_index idx) const {
            switch(type_) {
                default:
                case type::group: return;
                case type::param: m_.param.notify_blocked(idx); break;
            }
        }
        void notify_conflict(arg_index idx) const {
            switch(type_) {
                default:
                case type::group: return;
                case type::param: m_.param.notify_conflict(idx); break;
            }
        }

        bool is_param() const noexcept { return type_ == type::param; }
        bool is_group() const noexcept { return type_ == type::group; }

        Param& as_param() noexcept { return m_.param; }
        Group& as_group() noexcept { return m_.group; }

        const Param& as_param() const noexcept { return m_.param; }
        const Group& as_group() const noexcept { return m_.group; }

    private:
        void destroy_content() {
            switch(type_) {
                default:
                case type::param: m_.param.~Param(); break;
                case type::group: m_.group.~Group(); break;
            }
        }

        union data {
            data() {}

            data(const Param&  v)          : param{v} {}
            data(      Param&& v) noexcept : param{std::move(v)} {}

            data(const Group&  g)          : group{g} {}
            data(      Group&& g) noexcept : group{std::move(g)} {}
            ~data() {}

            Param param;
            Group group;
        };

        data m_;
        type type_;
    };


public:
    //---------------------------------------------------------------
    using child      = child_t<parameter,group>;
    using value_type = child;

private:
    using children_store = std::vector<child>;

public:
    using const_iterator = children_store::const_iterator;
    using iterator       = children_store::iterator;
    using size_type      = children_store::size_type;


    //---------------------------------------------------------------
    /**
     * @brief recursively iterates over all nodes
     */
    class depth_first_traverser
    {
    public:
        //-----------------------------------------------------
        struct context {
            context() = default;
            context(const group& p):
                parent{&p}, cur{p.begin()}, end{p.end()}
            {}
            const group* parent = nullptr;
            const_iterator cur;
            const_iterator end;
        };
        using context_list = std::vector<context>;

        //-----------------------------------------------------
        class memento {
            friend class depth_first_traverser;
            int level_;
            context context_;
        public:
            int level() const noexcept { return level_; }
            const child* param() const noexcept { return &(*context_.cur); }
        };

        depth_first_traverser() = default;

        explicit
        depth_first_traverser(const group& cur): stack_{} {
            if(!cur.empty()) stack_.emplace_back(cur);
        }

        explicit operator bool() const noexcept {
            return !stack_.empty();
        }

        int level() const noexcept {
            return int(stack_.size());
        }

        bool is_first_in_parent() const noexcept {
            if(stack_.empty()) return false;
            return (stack_.back().cur == stack_.back().parent->begin());
        }

        bool is_last_in_parent() const noexcept {
            if(stack_.empty()) return false;
            return (stack_.back().cur+1 == stack_.back().end);
        }

        bool is_last_in_path() const noexcept {
            if(stack_.empty()) return false;
            for(const auto& t : stack_) {
                if(t.cur+1 != t.end) return false;
            }
            const auto& top = stack_.back();
            //if we have to descend into group on next ++ => not last in path
            if(top.cur->is_group()) return false;
            return true;
        }

        /** @brief inside a group of alternatives >= minlevel */
        bool is_alternative(int minlevel = 0) const noexcept {
            if(stack_.empty()) return false;
            if(minlevel > 0) minlevel -= 1;
            if(minlevel >= int(stack_.size())) return false;
            return std::any_of(stack_.begin() + minlevel, stack_.end(),
                [](const context& c) { return c.parent->exclusive(); });
        }

        /** @brief repeatable or inside a repeatable group >= minlevel */
        bool is_repeatable(int minlevel = 0) const noexcept {
            if(stack_.empty()) return false;
            if(stack_.back().cur->repeatable()) return true;
            if(minlevel > 0) minlevel -= 1;
            if(minlevel >= int(stack_.size())) return false;
            return std::any_of(stack_.begin() + minlevel, stack_.end(),
                [](const context& c) { return c.parent->repeatable(); });
        }

        /** @brief inside a particular group */
        bool is_inside(const group* g) const noexcept {
            if(!g) return false;
            return std::any_of(stack_.begin(), stack_.end(),
                [g](const context& c) { return c.parent == g; });
        }

        /** @brief inside group with joinable flags */
        bool joinable() const noexcept {
            if(stack_.empty()) return false;
            return std::any_of(stack_.begin(), stack_.end(),
                [](const context& c) { return c.parent->joinable(); });
        }

        const context_list&
        stack() const {
            return stack_;
        }

        /** @brief innermost repeat group */
        const group*
        innermost_repeat_group() const noexcept {
            auto i = std::find_if(stack_.rbegin(), stack_.rend(),
                [](const context& c) { return c.parent->repeatable(); });
            return i != stack_.rend() ? i->parent : nullptr;
        }

        /** @brief innermost exclusive (alternatives) group */
        const group*
        innermost_exclusive_group() const noexcept {
            auto i = std::find_if(stack_.rbegin(), stack_.rend(),
                [](const context& c) { return c.parent->exclusive(); });
            return i != stack_.rend() ? i->parent : nullptr;
        }

        /** @brief innermost blocking group */
        const group*
        innermost_blocking_group() const noexcept {
            auto i = std::find_if(stack_.rbegin(), stack_.rend(),
                [](const context& c) { return c.parent->blocking(); });
            return i != stack_.rend() ? i->parent : nullptr;
        }

        /** @brief returns the outermost group that will be left on next ++*/
        const group*
        outermost_blocking_group_fully_explored() const noexcept {
            if(stack_.empty()) return nullptr;

            const group* g = nullptr;
            for(auto i = stack_.rbegin(); i != stack_.rend(); ++i) {
                if(i->cur+1 == i->end) {
                    if(i->parent->blocking()) g = i->parent;
                } else {
                    return g;
                }
            }
            return g;
        }

        /** @brief outermost join group */
        const group*
        outermost_join_group() const noexcept {
            auto i = std::find_if(stack_.begin(), stack_.end(),
                [](const context& c) { return c.parent->joinable(); });
            return i != stack_.end() ? i->parent : nullptr;
        }

        const group* root() const noexcept {
            return stack_.empty() ? nullptr : stack_.front().parent;
        }

        /** @brief common flag prefix of all flags in current group */
        arg_string common_flag_prefix() const noexcept {
            if(stack_.empty()) return "";
            auto g = outermost_join_group();
            return g ? g->common_flag_prefix() : arg_string("");
        }

        const child&
        operator * () const noexcept {
            return *stack_.back().cur;
        }

        const child*
        operator -> () const noexcept {
            return &(*stack_.back().cur);
        }

        const group&
        parent() const noexcept {
            return *(stack_.back().parent);
        }


        /** @brief go to next element of depth first search */
        depth_first_traverser&
        operator ++ () {
            if(stack_.empty()) return *this;
            //at non-empty group -> decend into group
            const auto& top = stack_.back();
            if(top.cur->is_group() && !top.cur->as_group().empty()) {
                stack_.emplace_back(stack_.back().cur->as_group());
            }
            else {
                next_sibling();
            }
            return *this;
        }

        /** @brief go to next sibling of current */
        depth_first_traverser&
        next_sibling() {
            if(stack_.empty()) return *this;
            ++stack_.back().cur;
            //at the end of current group?
            while(stack_.back().cur == stack_.back().end) {
                //go to parent
                stack_.pop_back();
                if(stack_.empty()) return *this;
                //go to next sibling in parent
                ++stack_.back().cur;
            }
            return *this;
        }

        /** @brief go to next position after siblings of current */
        depth_first_traverser&
        next_after_siblings() {
            if(stack_.empty()) return *this;
            stack_.back().cur = stack_.back().end-1;
            next_sibling();
            return *this;
        }

        /**
         * @brief
         */
        depth_first_traverser&
        back_to_ancestor(const group* g) {
            if(!g) return *this;
            while(!stack_.empty()) {
                const auto& top = stack_.back().cur;
                if(top->is_group() && &(top->as_group()) == g) return *this;
                stack_.pop_back();
            }
            return *this;
        }

        /** @brief don't visit next siblings, go back to parent on next ++
         *         note: renders siblings unreachable for *this
         **/
        depth_first_traverser&
        skip_siblings() {
            if(stack_.empty()) return *this;
            //future increments won't visit subsequent siblings:
            stack_.back().end = stack_.back().cur+1;
            return *this;
        }

        /** @brief skips all other alternatives in surrounding exclusive groups
         *         on next ++
         *         note: renders alternatives unreachable for *this
        */
        depth_first_traverser&
        skip_alternatives() {
            if(stack_.empty()) return *this;

            //exclude all other alternatives in surrounding groups
            //by making their current position the last one
            for(auto& c : stack_) {
                if(c.parent && c.parent->exclusive() && c.cur < c.end)
                    c.end = c.cur+1;
            }

            return *this;
        }

        void invalidate() {
            stack_.clear();
        }

        inline friend bool operator == (const depth_first_traverser& a,
                                        const depth_first_traverser& b)
        {
            if(a.stack_.empty() || b.stack_.empty()) return false;

            //parents not the same -> different position
            if(a.stack_.back().parent != b.stack_.back().parent) return false;

            bool aEnd = a.stack_.back().cur == a.stack_.back().end;
            bool bEnd = b.stack_.back().cur == b.stack_.back().end;
            //either both at the end of the same parent => same position
            if(aEnd && bEnd) return true;
            //or only one at the end => not at the same position
            if(aEnd || bEnd) return false;
            return std::addressof(*a.stack_.back().cur) ==
                   std::addressof(*b.stack_.back().cur);
        }
        inline friend bool operator != (const depth_first_traverser& a,
                                        const depth_first_traverser& b)
        {
            return !(a == b);
        }

        memento
        undo_point() const {
            memento m;
            m.level_ = int(stack_.size());
            if(!stack_.empty()) m.context_ = stack_.back();
            return m;
        }

        void undo(const memento& m) {
            if(m.level_ < 1) return;
            if(m.level_ <= int(stack_.size())) {
                stack_.erase(stack_.begin() + m.level_, stack_.end());
                stack_.back() = m.context_;
            }
            else if(stack_.empty() && m.level_ == 1) {
                stack_.push_back(m.context_);
            }
        }

    private:
        context_list stack_;
    };


    //---------------------------------------------------------------
    group() = default;

    template<class Param, class... Params>
    explicit
    group(doc_string docstr, Param param, Params... params):
        children_{}, exclusive_{false}, joinable_{false}, scoped_{true}
    {
        doc(std::move(docstr));
        push_back(std::move(param), std::move(params)...);
    }

    template<class... Params>
    explicit
    group(parameter param, Params... params):
        children_{}, exclusive_{false}, joinable_{false}, scoped_{true}
    {
        push_back(std::move(param), std::move(params)...);
    }

    template<class P2, class... Ps>
    explicit
    group(group p1, P2 p2, Ps... ps):
        children_{}, exclusive_{false}, joinable_{false}, scoped_{true}
    {
        push_back(std::move(p1), std::move(p2), std::move(ps)...);
    }


    //-----------------------------------------------------
    group(const group&) = default;
    group(group&&) = default;


    //---------------------------------------------------------------
    group& operator = (const group&) = default;
    group& operator = (group&&) = default;


    //---------------------------------------------------------------
    /** @brief determines if a command line argument can be matched by a
     *         combination of (partial) matches through any number of children
     */
    group& joinable(bool yes) {
        joinable_ = yes;
        return *this;
    }

    /** @brief returns if a command line argument can be matched by a
     *         combination of (partial) matches through any number of children
     */
    bool joinable() const noexcept {
        return joinable_;
    }


    //---------------------------------------------------------------
    /** @brief turns explicit scoping on or off
     *         operators , & | and other combinating functions will
     *         not merge groups that are marked as scoped
     */
    group& scoped(bool yes) {
        scoped_ = yes;
        return *this;
    }

    /** @brief returns true if operators , & | and other combinating functions
     *         will merge groups and false otherwise
     */
    bool scoped() const noexcept
    {
        return scoped_;
    }


    //---------------------------------------------------------------
    /** @brief determines if children are mutually exclusive alternatives */
    group& exclusive(bool yes) {
        exclusive_ = yes;
        return *this;
    }
    /** @brief returns if children are mutually exclusive alternatives */
    bool exclusive() const noexcept {
        return exclusive_;
    }


    //---------------------------------------------------------------
    /** @brief returns true, if any child is required to match */
    bool any_required() const
    {
        return std::any_of(children_.begin(), children_.end(),
            [](const child& n){ return n.required(); });
    }
    /** @brief returns true, if all children are required to match */
    bool all_required() const
    {
        return std::all_of(children_.begin(), children_.end(),
            [](const child& n){ return n.required(); });
    }


    //---------------------------------------------------------------
    /** @brief returns true if any child is optional (=non-required) */
    bool any_optional() const {
        return !all_required();
    }
    /** @brief returns true if all children are optional (=non-required) */
    bool all_optional() const {
        return !any_required();
    }


    //---------------------------------------------------------------
    /** @brief returns whether the entire group is blocking / positional */
    bool blocking() const noexcept {
        return token<group>::blocking() || (exclusive() && all_blocking());
    }
    //-----------------------------------------------------
    /** @brief determines whether the entire group is blocking / positional */
    group& blocking(bool yes) {
        return token<group>::blocking(yes);
    }

    //---------------------------------------------------------------
    /** @brief returns true if any child is blocking */
    bool any_blocking() const
    {
        return std::any_of(children_.begin(), children_.end(),
            [](const child& n){ return n.blocking(); });
    }
    //---------------------------------------------------------------
    /** @brief returns true if all children is blocking */
    bool all_blocking() const
    {
        return std::all_of(children_.begin(), children_.end(),
            [](const child& n){ return n.blocking(); });
    }


    //---------------------------------------------------------------
    /** @brief returns if any child is a value parameter (recursive) */
    bool any_flagless() const
    {
        return std::any_of(children_.begin(), children_.end(),
            [](const child& p){
                return p.is_param() && p.as_param().flags().empty();
            });
    }
    /** @brief returns if all children are value parameters (recursive) */
    bool all_flagless() const
    {
        return std::all_of(children_.begin(), children_.end(),
            [](const child& p){
                return p.is_param() && p.as_param().flags().empty();
            });
    }


    //---------------------------------------------------------------
    /** @brief adds child parameter at the end */
    group&
    push_back(const parameter& v) {
        children_.emplace_back(v);
        return *this;
    }
    //-----------------------------------------------------
    /** @brief adds child parameter at the end */
    group&
    push_back(parameter&& v) {
        children_.emplace_back(std::move(v));
        return *this;
    }
    //-----------------------------------------------------
    /** @brief adds child group at the end */
    group&
    push_back(const group& g) {
        children_.emplace_back(g);
        return *this;
    }
    //-----------------------------------------------------
    /** @brief adds child group at the end */
    group&
    push_back(group&& g) {
        children_.emplace_back(std::move(g));
        return *this;
    }


    //-----------------------------------------------------
    /** @brief adds children (groups and/or parameters) */
    template<class Param1, class Param2, class... Params>
    group&
    push_back(Param1&& param1, Param2&& param2, Params&&... params)
    {
        children_.reserve(children_.size() + 2 + sizeof...(params));
        push_back(std::forward<Param1>(param1));
        push_back(std::forward<Param2>(param2), std::forward<Params>(params)...);
        return *this;
    }


    //---------------------------------------------------------------
    /** @brief adds child parameter at the beginning */
    group&
    push_front(const parameter& v) {
        children_.emplace(children_.begin(), v);
        return *this;
    }
    //-----------------------------------------------------
    /** @brief adds child parameter at the beginning */
    group&
    push_front(parameter&& v) {
        children_.emplace(children_.begin(), std::move(v));
        return *this;
    }
    //-----------------------------------------------------
    /** @brief adds child group at the beginning */
    group&
    push_front(const group& g) {
        children_.emplace(children_.begin(), g);
        return *this;
    }
    //-----------------------------------------------------
    /** @brief adds child group at the beginning */
    group&
    push_front(group&& g) {
        children_.emplace(children_.begin(), std::move(g));
        return *this;
    }


    //---------------------------------------------------------------
    /** @brief adds all children of other group at the end */
    group&
    merge(group&& g)
    {
        children_.insert(children_.end(),
                      std::make_move_iterator(g.begin()),
                      std::make_move_iterator(g.end()));
        return *this;
    }
    //-----------------------------------------------------
    /** @brief adds all children of several other groups at the end */
    template<class... Groups>
    group&
    merge(group&& g1, group&& g2, Groups&&... gs)
    {
        merge(std::move(g1));
        merge(std::move(g2), std::forward<Groups>(gs)...);
        return *this;
    }


    //---------------------------------------------------------------
    /** @brief indexed, nutable access to child */
    child& operator [] (size_type index) noexcept {
        return children_[index];
    }
    /** @brief indexed, non-nutable access to child */
    const child& operator [] (size_type index) const noexcept {
        return children_[index];
    }

    //---------------------------------------------------------------
    /** @brief mutable access to first child */
          child& front()       noexcept { return children_.front(); }
    /** @brief non-mutable access to first child */
    const child& front() const noexcept { return children_.front(); }
    //-----------------------------------------------------
    /** @brief mutable access to last child */
          child& back()       noexcept { return children_.back(); }
    /** @brief non-mutable access to last child */
    const child& back() const noexcept { return children_.back(); }


    //---------------------------------------------------------------
    /** @brief returns true, if group has no children, false otherwise */
    bool empty() const noexcept     { return children_.empty(); }

    /** @brief returns number of children */
    size_type size() const noexcept { return children_.size(); }

    /** @brief returns number of nested levels; 1 for a flat group */
    size_type depth() const {
        size_type n = 0;
        for(const auto& c : children_) {
            auto l = 1 + c.depth();
            if(l > n) n = l;
        }
        return n;
    }


    //---------------------------------------------------------------
    /** @brief returns mutating iterator to position of first element */
          iterator  begin()       noexcept { return children_.begin(); }
    /** @brief returns non-mutating iterator to position of first element */
    const_iterator  begin() const noexcept { return children_.begin(); }
    /** @brief returns non-mutating iterator to position of first element */
    const_iterator cbegin() const noexcept { return children_.begin(); }

    /** @brief returns mutating iterator to position one past the last element */
          iterator  end()         noexcept { return children_.end(); }
    /** @brief returns non-mutating iterator to position one past the last element */
    const_iterator  end()   const noexcept { return children_.end(); }
    /** @brief returns non-mutating iterator to position one past the last element */
    const_iterator cend()   const noexcept { return children_.end(); }


    //---------------------------------------------------------------
    /** @brief returns augmented iterator for depth first searches
     *  @details traverser knows end of iteration and can skip over children
     */
    depth_first_traverser
    begin_dfs() const noexcept {
        return depth_first_traverser{*this};
    }


    //---------------------------------------------------------------
    /** @brief returns recursive parameter count */
    size_type param_count() const {
        size_type c = 0;
        for(const auto& n : children_) {
            c += n.param_count();
        }
        return c;
    }


    //---------------------------------------------------------------
    /** @brief returns range of all flags (recursive) */
    arg_list all_flags() const
    {
        std::vector<arg_string> all;
        gather_flags(children_, all);
        return all;
    }

    /** @brief returns true, if no flag occurs as true
     *         prefix of any other flag (identical flags will be ignored) */
    bool flags_are_prefix_free() const
    {
        const auto fs = all_flags();

        using std::begin; using std::end;
        for(auto i = begin(fs), e = end(fs); i != e; ++i) {
            if(!i->empty()) {
                for(auto j = i+1; j != e; ++j) {
                    if(!j->empty() && *i != *j) {
                        if(i->find(*j) == 0) return false;
                        if(j->find(*i) == 0) return false;
                    }
                }
            }
        }

        return true;
    }


    //---------------------------------------------------------------
    /** @brief returns longest common prefix of all flags */
    arg_string common_flag_prefix() const
    {
        arg_list prefixes;
        gather_prefixes(children_, prefixes);
        return str::longest_common_prefix(prefixes);
    }


private:
    //---------------------------------------------------------------
    static void
    gather_flags(const children_store& nodes, arg_list& all)
    {
        for(const auto& p : nodes) {
            if(p.is_group()) {
                gather_flags(p.as_group().children_, all);
            }
            else {
                const auto& pf = p.as_param().flags();
                using std::begin;
                using std::end;
                if(!pf.empty()) all.insert(end(all), begin(pf), end(pf));
            }
        }
    }
    //---------------------------------------------------------------
    static void
    gather_prefixes(const children_store& nodes, arg_list& all)
    {
        for(const auto& p : nodes) {
            if(p.is_group()) {
                gather_prefixes(p.as_group().children_, all);
            }
            else if(!p.as_param().flags().empty()) {
                auto pfx = str::longest_common_prefix(p.as_param().flags());
                if(!pfx.empty()) all.push_back(std::move(pfx));
            }
        }
    }

    //---------------------------------------------------------------
    children_store children_;
    bool exclusive_ = false;
    bool joinable_ = false;
    bool scoped_ = false;
};



/*************************************************************************//**
 *
 * @brief group or parameter
 *
 *****************************************************************************/
using pattern = group::child;



/*************************************************************************//**
 *
 * @brief apply an action to all parameters in a group
 *
 *****************************************************************************/
template<class Action>
void for_all_params(group& g, Action&& action)
{
    for(auto& p : g) {
        if(p.is_group()) {
            for_all_params(p.as_group(), action);
        }
        else {
            action(p.as_param());
        }
    }
}

template<class Action>
void for_all_params(const group& g, Action&& action)
{
    for(auto& p : g) {
        if(p.is_group()) {
            for_all_params(p.as_group(), action);
        }
        else {
            action(p.as_param());
        }
    }
}



/*************************************************************************//**
 *
 * @brief makes a group of parameters and/or groups
 *
 *****************************************************************************/
inline group
operator , (parameter a, parameter b)
{
    return group{std::move(a), std::move(b)}.scoped(false);
}

//---------------------------------------------------------
inline group
operator , (parameter a, group b)
{
    return !b.scoped() && !b.blocking() && !b.exclusive() && !b.repeatable()
        && !b.joinable() && (b.doc().empty() || b.doc() == a.doc())
       ? b.push_front(std::move(a))
       : group{std::move(a), std::move(b)}.scoped(false);
}

//---------------------------------------------------------
inline group
operator , (group a, parameter b)
{
    return !a.scoped() && !a.blocking() && !a.exclusive() && !a.repeatable()
        && !a.joinable() && (a.doc().empty() || a.doc() == b.doc())
       ? a.push_back(std::move(b))
       : group{std::move(a), std::move(b)}.scoped(false);
}

//---------------------------------------------------------
inline group
operator , (group a, group b)
{
    return !a.scoped() && !a.blocking() && !a.exclusive() && !a.repeatable()
        && !a.joinable() && (a.doc().empty() || a.doc() == b.doc())
       ? a.push_back(std::move(b))
       : group{std::move(a), std::move(b)}.scoped(false);
}



/*************************************************************************//**
 *
 * @brief makes a group of alternative parameters or groups
 *
 *****************************************************************************/
template<class Param, class... Params>
inline group
one_of(Param param, Params... params)
{
    return group{std::move(param), std::move(params)...}.exclusive(true);
}


/*************************************************************************//**
 *
 * @brief makes a group of alternative parameters or groups
 *
 *****************************************************************************/
inline group
operator | (parameter a, parameter b)
{
    return group{std::move(a), std::move(b)}.scoped(false).exclusive(true);
}

//-------------------------------------------------------------------
inline group
operator | (parameter a, group b)
{
    return !b.scoped() && b.exclusive() && !b.repeatable() 
        // && !b.blocking()
        && !b.joinable()
        && (b.doc().empty() || b.doc() == a.doc())
        ? b.push_front(std::move(a))
        : group{std::move(a), std::move(b)}.scoped(false).exclusive(true);
}

//-------------------------------------------------------------------
inline group
operator | (group a, parameter b)
{
    return !a.scoped() && a.exclusive() && !a.repeatable() && !a.joinable()
        && a.blocking() == b.blocking()
        && (a.doc().empty() || a.doc() == b.doc())
        ? a.push_back(std::move(b))
        : group{std::move(a), std::move(b)}.scoped(false).exclusive(true);
}

inline group
operator | (group a, group b)
{
    return !a.scoped() && a.exclusive() && !a.repeatable() && !a.joinable()
        && a.blocking() == b.blocking()
        && (a.doc().empty() || a.doc() == b.doc())
        ? a.push_back(std::move(b))
        : group{std::move(a), std::move(b)}.scoped(false).exclusive(true);
}



/*************************************************************************//**
 *
 * @brief helpers (NOT FOR DIRECT USE IN CLIENT CODE!)
 *        no interface guarantees; might be changed or removed in the future
 *
 *****************************************************************************/
namespace detail {

inline void set_blocking(bool) {}

template<class P, class... Ps>
void set_blocking(bool yes, P& p, Ps&... ps) {
    p.blocking(yes);
    set_blocking(yes, ps...);
}

} // namespace detail


/*************************************************************************//**
 *
 * @brief makes a parameter/group sequence by making all input objects blocking
 *
 *****************************************************************************/
template<class Param, class... Params>
inline group
in_sequence(Param param, Params... params)
{
    detail::set_blocking(true, param, params...);
    return group{std::move(param), std::move(params)...}.scoped(true);
}


/*************************************************************************//**
 *
 * @brief makes a parameter/group sequence by making all input objects blocking
 *
 *****************************************************************************/
inline group
operator & (parameter a, parameter b)
{
    a.blocking(true);
    b.blocking(true);
    return group{std::move(a), std::move(b)}.scoped(true);
}

//---------------------------------------------------------
inline group
operator & (parameter a, group b)
{
    a.blocking(true);
    return group{std::move(a), std::move(b)}.scoped(true);
}

//---------------------------------------------------------
inline group
operator & (group a, parameter b)
{
    b.blocking(true);
    if(a.all_blocking() && !a.exclusive() && !a.repeatable() && !a.joinable()
        && (a.doc().empty() || a.doc() == b.doc()))
    {
        return a.push_back(std::move(b));
    }
    else {
        if(!a.all_blocking()) a.blocking(true);
        return group{std::move(a), std::move(b)}.scoped(true);
    }
}

inline group
operator & (group a, group b)
{
    if(!b.all_blocking()) b.blocking(true);
    if(a.all_blocking() && !a.exclusive() && !a.repeatable()
        && !a.joinable() && (a.doc().empty() || a.doc() == b.doc()))
    {
        return a.push_back(std::move(b));
    }
    else {
        if(!a.all_blocking()) a.blocking(true);
        return group{std::move(a), std::move(b)}.scoped(true);
    }
}



/*************************************************************************//**
 *
 * @brief makes a group of parameters and/or groups
 *        where all single char flag params ("-a", "b", ...) are joinable
 *
 *****************************************************************************/
inline group
joinable(group g) {
    return g.joinable(true);
}

//-------------------------------------------------------------------
template<class... Params>
inline group
joinable(parameter param, Params... params)
{
    return group{std::move(param), std::move(params)...}.joinable(true);
}

template<class P2, class... Ps>
inline group
joinable(group p1, P2 p2, Ps... ps)
{
    return group{std::move(p1), std::move(p2), std::move(ps)...}.joinable(true);
}

template<class Param, class... Params>
inline group
joinable(doc_string docstr, Param param, Params... params)
{
    return group{std::move(param), std::move(params)...}
                .joinable(true).doc(std::move(docstr));
}



/*************************************************************************//**
 *
 * @brief makes a repeatable copy of a parameter
 *
 *****************************************************************************/
inline parameter
repeatable(parameter p) {
    return p.repeatable(true);
}

/*************************************************************************//**
 *
 * @brief makes a repeatable copy of a group
 *
 *****************************************************************************/
inline group
repeatable(group g) {
    return g.repeatable(true);
}



/*************************************************************************//**
 *
 * @brief makes a group of parameters and/or groups
 *        that is repeatable as a whole
 *        Note that a repeatable group consisting entirely of non-blocking
 *        children is equivalent to a non-repeatable group of
 *        repeatable children.
 *
 *****************************************************************************/
template<class P2, class... Ps>
inline group
repeatable(parameter p1, P2 p2, Ps... ps)
{
    return group{std::move(p1), std::move(p2),
                 std::move(ps)...}.repeatable(true);
}

template<class P2, class... Ps>
inline group
repeatable(group p1, P2 p2, Ps... ps)
{
    return group{std::move(p1), std::move(p2),
                 std::move(ps)...}.repeatable(true);
}



/*************************************************************************//**
 *
 * @brief makes a parameter greedy (match with top priority)
 *
 *****************************************************************************/
inline parameter
greedy(parameter p) {
    return p.greedy(true);
}

inline parameter
operator ! (parameter p) {
    return greedy(p);
}



/*************************************************************************//**
 *
 * @brief recursively prepends a prefix to all flags
 *
 *****************************************************************************/
inline parameter&&
with_prefix(const arg_string& prefix, parameter&& p) {
    return std::move(with_prefix(prefix, p));
}


//-------------------------------------------------------------------
inline group&
with_prefix(const arg_string& prefix, group& g)
{
    for(auto& p : g) {
        if(p.is_group()) {
            with_prefix(prefix, p.as_group());
        } else {
            with_prefix(prefix, p.as_param());
        }
    }
    return g;
}


inline group&&
with_prefix(const arg_string& prefix, group&& params)
{
    return std::move(with_prefix(prefix, params));
}


template<class Param, class... Params>
inline group
with_prefix(arg_string prefix, Param&& param, Params&&... params)
{
    return with_prefix(prefix, group{std::forward<Param>(param),
                                     std::forward<Params>(params)...});
}



/*************************************************************************//**
 *
 * @brief recursively prepends a prefix to all flags
 *
 * @param shortpfx : used for single-letter flags
 * @param longpfx  : used for flags with length > 1
 *
 *****************************************************************************/
inline parameter&&
with_prefixes_short_long(const arg_string& shortpfx, const arg_string& longpfx,
                         parameter&& p)
{
    return std::move(with_prefixes_short_long(shortpfx, longpfx, p));
}


//-------------------------------------------------------------------
inline group&
with_prefixes_short_long(const arg_string& shortFlagPrefix,
                         const arg_string& longFlagPrefix,
                         group& g)
{
    for(auto& p : g) {
        if(p.is_group()) {
            with_prefixes_short_long(shortFlagPrefix, longFlagPrefix, p.as_group());
        } else {
            with_prefixes_short_long(shortFlagPrefix, longFlagPrefix, p.as_param());
        }
    }
    return g;
}


inline group&&
with_prefixes_short_long(const arg_string& shortFlagPrefix,
                         const arg_string& longFlagPrefix,
                         group&& params)
{
    return std::move(with_prefixes_short_long(shortFlagPrefix, longFlagPrefix,
                                              params));
}


template<class Param, class... Params>
inline group
with_prefixes_short_long(const arg_string& shortFlagPrefix,
                         const arg_string& longFlagPrefix,
                         Param&& param, Params&&... params)
{
    return with_prefixes_short_long(shortFlagPrefix, longFlagPrefix,
                                    group{std::forward<Param>(param),
                                          std::forward<Params>(params)...});
}



/*************************************************************************//**
 *
 * @brief recursively prepends a suffix to all flags
 *
 *****************************************************************************/
inline parameter&&
with_suffix(const arg_string& suffix, parameter&& p) {
    return std::move(with_suffix(suffix, p));
}


//-------------------------------------------------------------------
inline group&
with_suffix(const arg_string& suffix, group& g)
{
    for(auto& p : g) {
        if(p.is_group()) {
            with_suffix(suffix, p.as_group());
        } else {
            with_suffix(suffix, p.as_param());
        }
    }
    return g;
}


inline group&&
with_suffix(const arg_string& suffix, group&& params)
{
    return std::move(with_suffix(suffix, params));
}


template<class Param, class... Params>
inline group
with_suffix(arg_string suffix, Param&& param, Params&&... params)
{
    return with_suffix(suffix, group{std::forward<Param>(param),
                                     std::forward<Params>(params)...});
}



/*************************************************************************//**
 *
 * @brief recursively prepends a suffix to all flags
 *
 * @param shortsfx : used for single-letter flags
 * @param longsfx  : used for flags with length > 1
 *
 *****************************************************************************/
inline parameter&&
with_suffixes_short_long(const arg_string& shortsfx, const arg_string& longsfx,
                         parameter&& p)
{
    return std::move(with_suffixes_short_long(shortsfx, longsfx, p));
}


//-------------------------------------------------------------------
inline group&
with_suffixes_short_long(const arg_string& shortFlagSuffix,
                         const arg_string& longFlagSuffix,
                         group& g)
{
    for(auto& p : g) {
        if(p.is_group()) {
            with_suffixes_short_long(shortFlagSuffix, longFlagSuffix, p.as_group());
        } else {
            with_suffixes_short_long(shortFlagSuffix, longFlagSuffix, p.as_param());
        }
    }
    return g;
}


inline group&&
with_suffixes_short_long(const arg_string& shortFlagSuffix,
                         const arg_string& longFlagSuffix,
                         group&& params)
{
    return std::move(with_suffixes_short_long(shortFlagSuffix, longFlagSuffix,
                                              params));
}


template<class Param, class... Params>
inline group
with_suffixes_short_long(const arg_string& shortFlagSuffix,
                         const arg_string& longFlagSuffix,
                         Param&& param, Params&&... params)
{
    return with_suffixes_short_long(shortFlagSuffix, longFlagSuffix,
                                    group{std::forward<Param>(param),
                                          std::forward<Params>(params)...});
}








/*************************************************************************//**
 *
 * @brief parsing implementation details
 *
 *****************************************************************************/

namespace detail {


/*************************************************************************//**
 *
 * @brief DFS traverser that keeps track of 'scopes'
 *        scope = all parameters that are either bounded by
 *        two blocking parameters on the same depth level
 *        or the beginning/end of the outermost group
 *
 *****************************************************************************/
class scoped_dfs_traverser
{
public:
    using dfs_traverser = group::depth_first_traverser;

    scoped_dfs_traverser() = default;

    explicit
    scoped_dfs_traverser(const group& g):
        pos_{g}, lastMatch_{}, posAfterLastMatch_{}, scopes_{},
        ignoreBlocks_{false},
        repeatGroupStarted_{false}, repeatGroupContinues_{false}
    {}

    const dfs_traverser& base() const noexcept { return pos_; }
    const dfs_traverser& last_match() const noexcept { return lastMatch_; }

    const group& parent() const noexcept { return pos_.parent(); }

    const group* innermost_repeat_group() const noexcept {
        return pos_.innermost_repeat_group();
    }
    const group* outermost_join_group() const noexcept {
        return pos_.outermost_join_group();
    }
    const group* innermost_blocking_group() const noexcept {
        return pos_.innermost_blocking_group();
    }
    const group* innermost_exclusive_group() const noexcept {
        return pos_.innermost_exclusive_group();
    }

    const pattern* operator ->() const noexcept { return pos_.operator->(); }
    const pattern& operator *() const noexcept { return *pos_; }

    const pattern* ptr() const noexcept { return pos_.operator->(); }

    explicit operator bool() const noexcept { return bool(pos_); }

    bool joinable() const noexcept { return pos_.joinable(); }
    arg_string common_flag_prefix() const { return pos_.common_flag_prefix(); }

    void ignore_blocking(bool yes) { ignoreBlocks_ = yes; }

    void invalidate() {
        pos_.invalidate();
    }

    bool matched() const noexcept {
        return (pos_ == lastMatch_);
    }

    bool start_of_repeat_group() const noexcept { return repeatGroupStarted_; }

    //-----------------------------------------------------
    scoped_dfs_traverser&
    next_sibling() { pos_.next_sibling(); return *this; }

    scoped_dfs_traverser&
    next_after_siblings() { pos_.next_after_siblings(); return *this; }


    //-----------------------------------------------------
    scoped_dfs_traverser&
    operator ++ ()
    {
        if(!pos_) return *this;

        if(pos_.is_last_in_path()) {
            return_to_outermost_scope();
            return *this;
        }

        //current pattern can block if it didn't match already
        if(ignoreBlocks_ || matched()) {
            ++pos_;
        }
        else if(!pos_->is_group()) {
            //current group can block if we didn't have any match in it
            const group* g = pos_.outermost_blocking_group_fully_explored();
            //no match in 'g' before -> skip to after its siblings
            if(g && !lastMatch_.is_inside(g)) {
                pos_.back_to_ancestor(g).next_after_siblings();
                if(!pos_) return_to_outermost_scope();
            }
            else if(pos_->blocking()) {
                if(pos_.parent().exclusive()) {
                    pos_.next_sibling();
                } else {
                    //no match => skip siblings of blocking param
                    pos_.next_after_siblings();
                }
                if(!pos_) return_to_outermost_scope();
            } else {
                ++pos_;
            }
        } else {
            ++pos_;
        }
        check_if_left_scope();
        return *this;
    }

    //-----------------------------------------------------
    void next_after_match(scoped_dfs_traverser match)
    {
        if(!match || ignoreBlocks_) return;

        check_repeat_group_start(match);

        lastMatch_ = match.base();

        // if there is a blocking ancestor -> go back to it
        if(!match->blocking()) {
            match.pos_.back_to_ancestor(match.innermost_blocking_group());
        }

        //if match is not in current position & current position is blocking
        //=> current position has to be advanced by one so that it is
        //no longer reachable within current scope
        //(can happen for repeatable, blocking parameters)
        if(match.base() != pos_ && pos_->blocking()) pos_.next_sibling();

        if(match->blocking()) {
            if(match.pos_.is_alternative()) {
                //discard other alternatives
                match.pos_.skip_alternatives();
            }

            if(is_last_in_current_scope(match.pos_)) {
                //if current param is not repeatable -> back to previous scope
                if(!match->repeatable() && !match->is_group()) {
                    pos_ = std::move(match.pos_);
                    if(!scopes_.empty()) pos_.undo(scopes_.top());
                }
                else { //stay at match position
                    pos_ = std::move(match.pos_);
                }
            }
            else { //not last in current group
                //if current param is not repeatable, go directly to next
                if(!match->repeatable() && !match->is_group()) {
                    ++match.pos_;
                }

                if(match.pos_.level() > pos_.level()) {
                    scopes_.push(pos_.undo_point());
                    pos_ = std::move(match.pos_);
                }
                else if(match.pos_.level() < pos_.level()) {
                    return_to_level(match.pos_.level());
                }
                else {
                    pos_ = std::move(match.pos_);
                }
            }
            posAfterLastMatch_ = pos_;
        }
        else {
            if(match.pos_.level() < pos_.level()) {
                return_to_level(match.pos_.level());
            }
            posAfterLastMatch_ = pos_;
        }
        repeatGroupContinues_ = repeat_group_continues();
    }

private:
    //-----------------------------------------------------
    bool is_last_in_current_scope(const dfs_traverser& pos) const
    {
        if(scopes_.empty()) return pos.is_last_in_path();
        //check if we would leave the current scope on ++
        auto p = pos;
        ++p;
        return p.level() < scopes_.top().level();
    }

    //-----------------------------------------------------
    void check_repeat_group_start(const scoped_dfs_traverser& newMatch)
    {
        const auto newrg = newMatch.innermost_repeat_group();
        if(!newrg) {
            repeatGroupStarted_ = false;
        }
        else if(lastMatch_.innermost_repeat_group() != newrg) {
            repeatGroupStarted_ = true;
        }
        else if(!repeatGroupContinues_ || !newMatch.repeatGroupContinues_) {
            repeatGroupStarted_ = true;
        }
        else {
            //special case: repeat group is outermost group
            //=> we can never really 'leave' and 'reenter' it
            //but if the current scope is the first element, then we are
            //conceptually at a position 'before' the group
            repeatGroupStarted_ = scopes_.empty() || (
                    newrg == pos_.root() &&
                    scopes_.top().param() == &(*pos_.root()->begin()) );
        }
        repeatGroupContinues_ = repeatGroupStarted_;
    }

    //-----------------------------------------------------
    bool repeat_group_continues() const
    {
        if(!repeatGroupContinues_) return false;
        const auto curRepGroup = pos_.innermost_repeat_group();
        if(!curRepGroup) return false;
        if(curRepGroup != lastMatch_.innermost_repeat_group()) return false;
        if(!posAfterLastMatch_) return false;
        return true;
    }

    //-----------------------------------------------------
    void check_if_left_scope()
    {
        if(posAfterLastMatch_) {
            if(pos_.level() < posAfterLastMatch_.level()) {
                while(!scopes_.empty() && scopes_.top().level() >= pos_.level()) {
                    pos_.undo(scopes_.top());
                    scopes_.pop();
                }
                posAfterLastMatch_.invalidate();
            }
        }
        while(!scopes_.empty() && scopes_.top().level() > pos_.level()) {
            pos_.undo(scopes_.top());
            scopes_.pop();
        }
        repeatGroupContinues_ = repeat_group_continues();
    }

    //-----------------------------------------------------
    void return_to_outermost_scope()
    {
        posAfterLastMatch_.invalidate();

        if(scopes_.empty()) {
            pos_.invalidate();
            repeatGroupContinues_ = false;
            return;
        }

        while(!scopes_.empty() && (!pos_ || pos_.level() >= 1)) {
            pos_.undo(scopes_.top());
            scopes_.pop();
        }
        while(!scopes_.empty()) scopes_.pop();

        repeatGroupContinues_ = repeat_group_continues();
    }

    //-----------------------------------------------------
    void return_to_level(int level)
    {
        if(pos_.level() <= level) return;
        while(!scopes_.empty() && pos_.level() > level) {
            pos_.undo(scopes_.top());
            scopes_.pop();
        }
    };

    dfs_traverser pos_;
    dfs_traverser lastMatch_;
    dfs_traverser posAfterLastMatch_;
    std::stack<dfs_traverser::memento> scopes_;
    bool ignoreBlocks_ = false;
    bool repeatGroupStarted_ = false;
    bool repeatGroupContinues_ = false;
};




/*****************************************************************************
 *
 * some parameter property predicates
 *
 *****************************************************************************/
struct select_all {
    bool operator () (const parameter&) const noexcept { return true; }
};

struct select_flags {
    bool operator () (const parameter& p) const noexcept {
        return !p.flags().empty();
    }
};

struct select_values {
    bool operator () (const parameter& p) const noexcept {
        return p.flags().empty();
    }
};



/*************************************************************************//**
 *
 * @brief result of a matching operation
 *
 *****************************************************************************/
class match_t {
public:
    using size_type = arg_string::size_type;

    match_t() = default;

    match_t(arg_string s, scoped_dfs_traverser p):
        str_{std::move(s)}, pos_{std::move(p)}
    {}

    size_type length() const noexcept { return str_.size(); }

    const arg_string& str() const noexcept { return str_; }
    const scoped_dfs_traverser& pos() const noexcept { return pos_; }

    explicit operator bool() const noexcept { return bool(pos_); }

private:
    arg_string str_;
    scoped_dfs_traverser pos_;
};



/*************************************************************************//**
 *
 * @brief finds the first parameter that matches a given string;
 *        candidate parameters are traversed using a scoped DFS traverser
 *
 *****************************************************************************/
template<class ParamSelector>
match_t
full_match(scoped_dfs_traverser pos, const arg_string& arg,
           const ParamSelector& select)
{
    while(pos) {
        if(pos->is_param()) {
            const auto& param = pos->as_param();
            if(select(param)) {
                const auto match = param.match(arg);
                if(match && match.length() == arg.size()) {
                    return match_t{arg, std::move(pos)};
                }
            }
        }
        ++pos;
    }
    return match_t{};
}



/*************************************************************************//**
 *
 * @brief finds the first parameter that matches any (non-empty) prefix
 *        of a given string;
 *        candidate parameters are traversed using a scoped DFS traverser
 *
 *****************************************************************************/
template<class ParamSelector>
match_t
longest_prefix_match(scoped_dfs_traverser pos, const arg_string& arg,
                     const ParamSelector& select)
{
    match_t longest;

    while(pos) {
        if(pos->is_param()) {
            const auto& param = pos->as_param();
            if(select(param)) {
                auto match = param.match(arg);
                if(match.prefix()) {
                    if(match.length() == arg.size()) {
                        return match_t{arg, std::move(pos)};
                    }
                    else if(match.length() > longest.length()) {
                        longest = match_t{arg.substr(match.at(), match.length()), 
                                          pos};
                    }
                }
            }
        }
        ++pos;
    }
    return longest;
}



/*************************************************************************//**
 *
 * @brief finds the first parameter that partially matches a given string;
 *        candidate parameters are traversed using a scoped DFS traverser
 *
 *****************************************************************************/
template<class ParamSelector>
match_t
partial_match(scoped_dfs_traverser pos, const arg_string& arg,
              const ParamSelector& select)
{
    while(pos) {
        if(pos->is_param()) {
            const auto& param = pos->as_param();
            if(select(param)) {
                const auto match = param.match(arg);
                if(match) {
                    return match_t{arg.substr(match.at(), match.length()),
                                   std::move(pos)};
                }
            }
        }
        ++pos;
    }
    return match_t{};
}

} //namespace detail






/***************************************************************//**
 *
 * @brief default command line arguments parser
 *
 *******************************************************************/
class parser
{
public:
    using dfs_traverser = group::depth_first_traverser;
    using scoped_dfs_traverser = detail::scoped_dfs_traverser;


    /*****************************************************//**
     * @brief arg -> parameter mapping
     *********************************************************/
    class arg_mapping {
    public:
        friend class parser;

        explicit
        arg_mapping(arg_index idx, arg_string s,
                    const dfs_traverser& match)
        :
            index_{idx}, arg_{std::move(s)}, match_{match},
            repeat_{0}, startsRepeatGroup_{false},
            blocked_{false}, conflict_{false}
        {}

        explicit
        arg_mapping(arg_index idx, arg_string s) :
            index_{idx}, arg_{std::move(s)}, match_{},
            repeat_{0}, startsRepeatGroup_{false},
            blocked_{false}, conflict_{false}
        {}

        arg_index index() const noexcept { return index_; }
        const arg_string& arg() const noexcept { return arg_; }

        const parameter* param() const noexcept {
            return match_ && match_->is_param()
                ? &(match_->as_param()) : nullptr;
        }

        std::size_t repeat() const noexcept { return repeat_; }

        bool blocked() const noexcept { return blocked_; }
        bool conflict() const noexcept { return conflict_; }

        bool bad_repeat() const noexcept {
            if(!param()) return false;
            return repeat_ > 0 && !param()->repeatable()
                && !match_.innermost_repeat_group();
        }

        bool any_error() const noexcept {
            return !match_ || blocked() || conflict() || bad_repeat();
        }

    private:
        arg_index index_;
        arg_string arg_;
        dfs_traverser match_;
        std::size_t repeat_;
        bool startsRepeatGroup_;
        bool blocked_;
        bool conflict_;
    };

    /*****************************************************//**
     * @brief references a non-matched, required parameter
     *********************************************************/
    class missing_event {
    public:
        explicit
        missing_event(const parameter* p, arg_index after):
            param_{p}, aftIndex_{after}
        {}

        const parameter* param() const noexcept { return param_; }

        arg_index after_index() const noexcept { return aftIndex_; }

    private:
        const parameter* param_;
        arg_index aftIndex_;
    };

    //-----------------------------------------------------
    using missing_events = std::vector<missing_event>;
    using arg_mappings   = std::vector<arg_mapping>;


private:
    struct miss_candidate {
        miss_candidate(dfs_traverser p, arg_index idx,
                       bool firstInRepeatGroup = false):
            pos{std::move(p)}, index{idx},
            startsRepeatGroup{firstInRepeatGroup}
        {}

        dfs_traverser pos;
        arg_index index;
        bool startsRepeatGroup;
    };
    using miss_candidates = std::vector<miss_candidate>;


public:
    //---------------------------------------------------------------
    /** @brief initializes parser with a command line interface
     *  @param offset = argument index offset used for reports
     * */
    explicit
    parser(const group& root, arg_index offset = 0):
        root_{&root}, pos_{root},
        index_{offset-1}, eaten_{0},
        args_{}, missCand_{}, blocked_{false}
    {
        for_each_potential_miss(dfs_traverser{root},
            [this](const dfs_traverser& p){
                missCand_.emplace_back(p, index_);
            });
    }


    //---------------------------------------------------------------
    /** @brief processes one command line argument */
    bool operator() (const arg_string& arg)
    {
        ++eaten_;
        ++index_;

        if(!valid()) return false;

        if(!blocked_ && try_match(arg)) return true;

        if(try_match_blocked(arg)) return false;

        //skipping of blocking & required patterns is not allowed
        if(!blocked_ && !pos_.matched() && pos_->required() && pos_->blocking()) {
            blocked_ = true;
        }

        add_nomatch(arg);
        return false;
    }


    //---------------------------------------------------------------
    /** @brief returns range of argument -> parameter mappings */
    const arg_mappings& args() const {
        return args_;
    }

    /** @brief returns list of missing events */
    missing_events missed() const {
        missing_events misses;
        misses.reserve(missCand_.size());
        for(auto i = missCand_.begin(); i != missCand_.end(); ++i) {
            misses.emplace_back(&(i->pos->as_param()), i->index);
        }
        return misses;
    }

    /** @brief returns number of processed command line arguments */
    arg_index parse_count() const noexcept { return eaten_; }

    /** @brief returns false if previously processed command line arguments
     *         lead to an invalid / inconsistent parsing result
     */
    bool valid() const noexcept { return bool(pos_); }

    /** @brief returns false if previously processed command line arguments
     *         lead to an invalid / inconsistent parsing result
     */
    explicit operator bool() const noexcept { return valid(); }


private:
    //---------------------------------------------------------------
    using match_t = detail::match_t;


    //---------------------------------------------------------------
    /** @brief try to match argument with unreachable parameter */
    bool try_match_blocked(const arg_string& arg)
    {
        //try to match ahead (using temporary parser)
        if(pos_) {
            auto ahead = *this;
            if(try_match_blocked(std::move(ahead), arg)) return true;
        }

        //try to match from the beginning (using temporary parser)
        if(root_) {
            parser all{*root_, index_+1};
            if(try_match_blocked(std::move(all), arg)) return true;
        }

        return false;
    }

    //---------------------------------------------------------------
    bool try_match_blocked(parser&& parse, const arg_string& arg)
    {
        const auto nold = int(parse.args_.size());

        parse.pos_.ignore_blocking(true);

        if(!parse.try_match(arg)) return false;

        for(auto i = parse.args_.begin() + nold; i != parse.args_.end(); ++i) {
            args_.push_back(*i);
            args_.back().blocked_ = true;
        }
        return true;
    }

    //---------------------------------------------------------------
    /** @brief try to find a parameter/pattern that matches 'arg' */
    bool try_match(const arg_string& arg)
    {
        //match greedy parameters before everything else
        if(pos_->is_param() && pos_->blocking() && pos_->as_param().greedy()) {
            const auto match = pos_->as_param().match(arg);
            if(match && match.length() == arg.size()) {
                add_match(detail::match_t{arg,pos_});
                return true;
            }
        }

        //try flags first (alone, joinable or strict sequence)
        if(try_match_full(arg, detail::select_flags{})) return true;
        if(try_match_joined_flags(arg)) return true;
        if(try_match_joined_sequence(arg, detail::select_flags{})) return true;
        //try value params (alone or strict sequence)
        if(try_match_full(arg, detail::select_values{})) return true;
        if(try_match_joined_sequence(arg, detail::select_all{})) return true;
        //try joinable params + values in any order
        if(try_match_joined_params(arg)) return true;
        return false;
    }

    //---------------------------------------------------------------
    /**
     * @brief try to match full argument
     * @param select : predicate that candidate parameters must satisfy
     */
    template<class ParamSelector>
    bool try_match_full(const arg_string& arg, const ParamSelector& select)
    {
        auto match = detail::full_match(pos_, arg, select);
        if(!match) return false;
        add_match(match);
        return true;
    }

    //---------------------------------------------------------------
    /**
     * @brief try to match argument as blocking sequence of parameters
     * @param select : predicate that a parameter matching the prefix of
     *                 'arg' must satisfy
     */
    template<class ParamSelector>
    bool try_match_joined_sequence(arg_string arg,
                                   const ParamSelector& acceptFirst)
    {
        auto fstMatch = detail::longest_prefix_match(pos_, arg, acceptFirst);

        if(!fstMatch) return false;

        if(fstMatch.str().size() == arg.size()) {
            add_match(fstMatch);
            return true;
        }

        if(!fstMatch.pos()->blocking()) return false;

        auto pos = fstMatch.pos();
        pos.ignore_blocking(true);
        const auto parent = &pos.parent();
        if(!pos->repeatable()) ++pos;

        arg.erase(0, fstMatch.str().size());
        std::vector<match_t> matches { std::move(fstMatch) };

        while(!arg.empty() && pos &&
              pos->blocking() && pos->is_param() &&
              (&pos.parent() == parent))
        {
            auto match = pos->as_param().match(arg);

            if(match.prefix()) {
                matches.emplace_back(arg.substr(0,match.length()), pos);
                arg.erase(0, match.length());
                if(!pos->repeatable()) ++pos;
            }
            else {
                if(!pos->repeatable()) return false;
                ++pos;
            }

        }
        //if arg not fully covered => discard temporary matches
        if(!arg.empty() || matches.empty()) return false;

        for(const auto& m : matches) add_match(m);
        return true;
    }

    //-----------------------------------------------------
    /** @brief try to match 'arg' as a concatenation of joinable flags */
    bool try_match_joined_flags(const arg_string& arg)
    {
        return find_join_group(pos_, [&](const group& g) {
            return try_match_joined(g, arg, detail::select_flags{},
                                    g.common_flag_prefix());
        });
    }

    //---------------------------------------------------------------
    /** @brief try to match 'arg' as a concatenation of joinable parameters */
    bool try_match_joined_params(const arg_string& arg)
    {
        return find_join_group(pos_, [&](const group& g) {
            return try_match_joined(g, arg, detail::select_all{});
        });
    }

    //-----------------------------------------------------
    /** @brief try to match 'arg' as concatenation of joinable parameters
     *         that are all contained within one group
     */
    template<class ParamSelector>
    bool try_match_joined(const group& joinGroup, arg_string arg,
                          const ParamSelector& select,
                          const arg_string& prefix = "")
    {
        //temporary parser with 'joinGroup' as top-level group
        parser parse {joinGroup};
        //records temporary matches
        std::vector<match_t> matches;

        while(!arg.empty()) {
            auto match = detail::longest_prefix_match(parse.pos_, arg, select);

            if(!match) return false;

            arg.erase(0, match.str().size());
            //make sure prefix is always present after the first match
            //so that, e.g., flags "-a" and "-b" will be found in "-ab"
            if(!arg.empty() && !prefix.empty() && arg.find(prefix) != 0 &&
                prefix != match.str())
            {
                arg.insert(0,prefix);
            }

            parse.add_match(match);
            matches.push_back(std::move(match));
        }

        if(!arg.empty() || matches.empty()) return false;

        if(!parse.missCand_.empty()) return false;
        for(const auto& a : parse.args_) if(a.any_error()) return false;

        //replay matches onto *this
        for(const auto& m : matches) add_match(m);
        return true;
    }

    //-----------------------------------------------------
    template<class GroupSelector>
    bool find_join_group(const scoped_dfs_traverser& start,
                         const GroupSelector& accept) const
    {
        if(start && start.parent().joinable()) {
            const auto& g = start.parent();
            if(accept(g)) return true;
            return false;
        }

        auto pos = start;
        while(pos) {
            if(pos->is_group() && pos->as_group().joinable()) {
                const auto& g = pos->as_group();
                if(accept(g)) return true;
                pos.next_sibling();
            }
            else {
                ++pos;
            }
        }
        return false;
    }


    //---------------------------------------------------------------
    void add_nomatch(const arg_string& arg) {
        args_.emplace_back(index_, arg);
    }


    //---------------------------------------------------------------
    void add_match(const match_t& match)
    {
        const auto& pos = match.pos();
        if(!pos || !pos->is_param()) return;

        pos_.next_after_match(pos);

        arg_mapping newArg{index_, match.str(), pos.base()};
        newArg.repeat_ = occurrences_of(&pos->as_param());
        newArg.conflict_ = check_conflicts(pos.base());
        newArg.startsRepeatGroup_ = pos_.start_of_repeat_group();
        args_.push_back(std::move(newArg));

        add_miss_candidates_after(pos);
        clean_miss_candidates_for(pos.base());
        discard_alternative_miss_candidates(pos.base());

    }

    //-----------------------------------------------------
    bool check_conflicts(const dfs_traverser& match)
    {
        if(pos_.start_of_repeat_group()) return false;
        bool conflict = false;
        for(const auto& m : match.stack()) {
            if(m.parent->exclusive()) {
                for(auto i = args_.rbegin(); i != args_.rend(); ++i) {
                    if(!i->blocked()) {
                        for(const auto& c : i->match_.stack()) {
                            //sibling within same exclusive group => conflict
                            if(c.parent == m.parent && c.cur != m.cur) {
                                conflict = true;
                                i->conflict_ = true;
                            }
                        }
                    }
                    //check for conflicts only within current repeat cycle
                    if(i->startsRepeatGroup_) break;
                }
            }
        }
        return conflict;
    }

    //-----------------------------------------------------
    void clean_miss_candidates_for(const dfs_traverser& match)
    {
        auto i = std::find_if(missCand_.rbegin(), missCand_.rend(),
            [&](const miss_candidate& m) {
                return &(*m.pos) == &(*match);
            });

        if(i != missCand_.rend()) {
            missCand_.erase(prev(i.base()));
        }
    }

    //-----------------------------------------------------
    void discard_alternative_miss_candidates(const dfs_traverser& match)
    {
        if(missCand_.empty()) return;
        //find out, if miss candidate is sibling of one of the same
        //alternative groups that the current match is a member of
        //if so, we can discard the miss

        //go through all exclusive groups of matching pattern
        for(const auto& m : match.stack()) {
            if(m.parent->exclusive()) {
                for(auto i = int(missCand_.size())-1; i >= 0; --i) {
                    bool removed = false;
                    for(const auto& c : missCand_[i].pos.stack()) {
                        //sibling within same exclusive group => discard
                        if(c.parent == m.parent && c.cur != m.cur) {
                            missCand_.erase(missCand_.begin() + i);
                            if(missCand_.empty()) return;
                            removed = true;
                            break;
                        }
                    }
                    //remove miss candidates only within current repeat cycle
                    if(i > 0 && removed) {
                        if(missCand_[i-1].startsRepeatGroup) break;
                    } else {
                        if(missCand_[i].startsRepeatGroup) break;
                    }
                }
            }
        }
    }

    //-----------------------------------------------------
    void add_miss_candidates_after(const scoped_dfs_traverser& match)
    {
        auto npos = match.base();
        if(npos.is_alternative()) npos.skip_alternatives();
        ++npos;
        //need to add potential misses if:
        //either new repeat group was started
        const auto newRepGroup = match.innermost_repeat_group();
        if(newRepGroup) {
            if(pos_.start_of_repeat_group()) {
                for_each_potential_miss(std::move(npos),
                    [&,this](const dfs_traverser& pos) {
                        //only add candidates within repeat group
                        if(newRepGroup == pos.innermost_repeat_group()) {
                            missCand_.emplace_back(pos, index_, true);
                        }
                    });
            }
        }
        //... or an optional blocking param was hit
        else if(match->blocking() && !match->required() &&
            npos.level() >= match.base().level())
        {
            for_each_potential_miss(std::move(npos),
                [&,this](const dfs_traverser& pos) {
                    //only add new candidates
                    if(std::find_if(missCand_.begin(), missCand_.end(),
                        [&](const miss_candidate& c){
                            return &(*c.pos) == &(*pos);
                        }) == missCand_.end())
                    {
                        missCand_.emplace_back(pos, index_);
                    }
                });
        }

    }

    //-----------------------------------------------------
    template<class Action>
    static void
    for_each_potential_miss(dfs_traverser pos, Action&& action)
    {
        const auto level = pos.level();
        while(pos && pos.level() >= level) {
            if(pos->is_group() ) {
                const auto& g = pos->as_group();
                if(g.all_optional() || (g.exclusive() && g.any_optional())) {
                    pos.next_sibling();
                } else {
                    ++pos;
                }
            } else {  //param
                if(pos->required()) {
                    action(pos);
                    ++pos;
                } else if(pos->blocking()) { //optional + blocking
                    pos.next_after_siblings();
                } else {
                    ++pos;
                }
            }
        }
    }


    //---------------------------------------------------------------
    std::size_t occurrences_of(const parameter* p) const
    {
        if(!p) return 0;

        auto i = std::find_if(args_.rbegin(), args_.rend(),
            [p](const arg_mapping& a){ return a.param() == p; });

        if(i != args_.rend()) return i->repeat() + 1;
        return 0;
    }


    //---------------------------------------------------------------
    const group* root_;
    scoped_dfs_traverser pos_;
    arg_index index_;
    arg_index eaten_;
    arg_mappings args_;
    miss_candidates missCand_;
    bool blocked_;
};




/*************************************************************************//**
 *
 * @brief contains argument -> parameter mappings
 *        and missing parameters
 *
 *****************************************************************************/
class parsing_result
{
public:
    using arg_mapping    = parser::arg_mapping;
    using arg_mappings   = parser::arg_mappings;
    using missing_event  = parser::missing_event;
    using missing_events = parser::missing_events;
    using iterator       = arg_mappings::const_iterator;

    //-----------------------------------------------------
    /** @brief default: empty result */
    parsing_result() = default;

    parsing_result(arg_mappings arg2param, missing_events misses):
        arg2param_{std::move(arg2param)}, missing_{std::move(misses)}
    {}

    //-----------------------------------------------------
    /** @brief returns number of arguments that could not be mapped to
     *         a parameter
     */
    arg_mappings::size_type
    unmapped_args_count() const noexcept {
        return std::count_if(arg2param_.begin(), arg2param_.end(),
            [](const arg_mapping& a){ return !a.param(); });
    }

    /** @brief returns if any argument could only be matched by an
     *         unreachable parameter
     */
    bool any_blocked() const noexcept {
        return std::any_of(arg2param_.begin(), arg2param_.end(),
            [](const arg_mapping& a){ return a.blocked(); });
    }

    /** @brief returns if any argument matched more than one parameter
     *         that were mutually exclusive */
    bool any_conflict() const noexcept {
        return std::any_of(arg2param_.begin(), arg2param_.end(),
            [](const arg_mapping& a){ return a.conflict(); });
    }

    /** @brief returns if any parameter matched repeatedly although
     *         it was not allowed to */
    bool any_bad_repeat() const noexcept {
        return std::any_of(arg2param_.begin(), arg2param_.end(),
            [](const arg_mapping& a){ return a.bad_repeat(); });
    }

    /** @brief returns true if any parsing error / violation of the
     *         command line interface definition occurred */
    bool any_error() const noexcept {
        return unmapped_args_count() > 0 || !missing().empty() ||
               any_blocked() || any_conflict() || any_bad_repeat();
    }

    /** @brief returns true if no parsing error / violation of the
     *         command line interface definition occurred */
    explicit operator bool() const noexcept { return !any_error(); }

    /** @brief access to range of missing parameter match events */
    const missing_events& missing() const noexcept { return missing_; }

    /** @brief returns non-mutating iterator to position of
     *         first argument -> parameter mapping  */
    iterator begin() const noexcept { return arg2param_.begin(); }
    /** @brief returns non-mutating iterator to position one past the
     *         last argument -> parameter mapping  */
    iterator end()   const noexcept { return arg2param_.end(); }

private:
    //-----------------------------------------------------
    arg_mappings arg2param_;
    missing_events missing_;
};




namespace detail {
namespace {

/*************************************************************************//**
 *
 * @brief correct some common problems
 *        does not - and MUST NOT - change the number of arguments
 *        (no insertions or deletions allowed)
 *
 *****************************************************************************/
void sanitize_args(arg_list& args)
{
    //e.g. {"-o12", ".34"} -> {"-o", "12.34"}

    if(args.empty()) return;

    for(auto i = begin(args)+1; i != end(args); ++i) {
        if(i != begin(args) && i->size() > 1 &&
            i->find('.') == 0 && std::isdigit((*i)[1]) )
        {
            //find trailing digits in previous arg
            using std::prev;
            auto& prv = *prev(i);
            auto fstDigit = std::find_if_not(prv.rbegin(), prv.rend(),
                [](arg_string::value_type c){
                    return std::isdigit(c);
                }).base();

            //handle leading sign
            if(fstDigit > prv.begin() &&
                (*prev(fstDigit) == '+' || *prev(fstDigit) == '-'))
            {
                --fstDigit;
            }

            //prepend digits from previous arg
            i->insert(begin(*i), fstDigit, end(prv));

            //erase digits in previous arg
            prv.erase(fstDigit, end(prv));
        }
    }
}



/*************************************************************************//**
 *
 * @brief executes actions based on a parsing result
 *
 *****************************************************************************/
void execute_actions(const parsing_result& res)
{
    for(const auto& m : res) {
        if(m.param()) {
            const auto& param = *(m.param());

            if(m.repeat() > 0) param.notify_repeated(m.index());
            if(m.blocked())    param.notify_blocked(m.index());
            if(m.conflict())   param.notify_conflict(m.index());
            //main action
            if(!m.any_error()) param.execute_actions(m.arg());
        }
    }

    for(auto m : res.missing()) {
        if(m.param()) m.param()->notify_missing(m.after_index());
    }
}



/*************************************************************************//**
 *
 * @brief parses input args
 *
 *****************************************************************************/
static parsing_result
parse_args(const arg_list& args, const group& cli,
           arg_index offset = 0)
{
    //parse args and store unrecognized arg indices
    parser parse{cli, offset};
    for(const auto& arg : args) {
        parse(arg);
        if(!parse.valid()) break;
    }

    return parsing_result{parse.args(), parse.missed()};
}

/*************************************************************************//**
 *
 * @brief parses input args & executes actions
 *
 *****************************************************************************/
static parsing_result
parse_and_execute(const arg_list& args, const group& cli,
                  arg_index offset = 0)
{
    auto result = parse_args(args, cli, offset);

    execute_actions(result);

    return result;
}

} //anonymous namespace
} // namespace detail




/*************************************************************************//**
 *
 * @brief parses vector of arg strings and executes actions
 *
 *****************************************************************************/
inline parsing_result
parse(arg_list args, const group& cli)
{
    detail::sanitize_args(args);
    return detail::parse_and_execute(args, cli);
}


/*************************************************************************//**
 *
 * @brief parses initializer_list of C-style arg strings and executes actions
 *
 *****************************************************************************/
inline parsing_result
parse(std::initializer_list<const char*> arglist, const group& cli)
{
    arg_list args;
    args.reserve(arglist.size());
    for(auto a : arglist) {
        args.push_back(a);
    }

    return parse(std::move(args), cli);
}


/*************************************************************************//**
 *
 * @brief parses range of arg strings and executes actions
 *
 *****************************************************************************/
template<class InputIterator>
inline parsing_result
parse(InputIterator first, InputIterator last, const group& cli)
{
    return parse(arg_list(first,last), cli);
}


/*************************************************************************//**
 *
 * @brief parses the standard array of command line arguments; omits argv[0]
 *
 *****************************************************************************/
inline parsing_result
parse(const int argc, char* argv[], const group& cli, arg_index offset = 1)
{
    arg_list args;
    if(offset < argc) args.assign(argv+offset, argv+argc);
    detail::sanitize_args(args);
    return detail::parse_and_execute(args, cli, offset);
}






/*************************************************************************//**
 *
 * @brief filter predicate for parameters and groups;
 *        Can be used to limit documentation generation to parameter subsets.
 *
 *****************************************************************************/
class param_filter
{
public:
    /** @brief only allow parameters with given prefix */
    param_filter& prefix(const arg_string& p) noexcept {
        prefix_ = p; return *this;
    }
    /** @brief only allow parameters with given prefix */
    param_filter& prefix(arg_string&& p) noexcept {
        prefix_ = std::move(p); return *this;
    }
    const arg_string& prefix()  const noexcept { return prefix_; }

    /** @brief only allow parameters with given requirement status */
    param_filter& required(tri t)  noexcept { required_ = t; return *this; }
    tri           required() const noexcept { return required_; }

    /** @brief only allow parameters with given blocking status */
    param_filter& blocking(tri t)  noexcept { blocking_ = t; return *this; }
    tri           blocking() const noexcept { return blocking_; }

    /** @brief only allow parameters with given repeatable status */
    param_filter& repeatable(tri t)  noexcept { repeatable_ = t; return *this; }
    tri           repeatable() const noexcept { return repeatable_; }

    /** @brief only allow parameters with given docstring status */
    param_filter& has_doc(tri t)  noexcept { hasDoc_ = t; return *this; }
    tri           has_doc() const noexcept { return hasDoc_; }


    /** @brief returns true, if parameter satisfies all filters */
    bool operator() (const parameter& p) const noexcept {
        if(!prefix_.empty()) {
            if(!std::any_of(p.flags().begin(), p.flags().end(),
                [&](const arg_string& flag){
                    return str::has_prefix(flag, prefix_);
                })) return false;
        }
        if(required()   != p.required())     return false;
        if(blocking()   != p.blocking())     return false;
        if(repeatable() != p.repeatable())   return false;
        if(has_doc()    != !p.doc().empty()) return false;
        return true;
    }

private:
    arg_string prefix_;
    tri required_   = tri::either;
    tri blocking_   = tri::either;
    tri repeatable_ = tri::either;
    tri hasDoc_     = tri::yes;
};






/*************************************************************************//**
 *
 * @brief documentation formatting options
 *
 *****************************************************************************/
class doc_formatting
{
public:
    using string = doc_string;

    /** @brief same as 'first_column' */
#if __cplusplus >= 201402L
    [[deprecated]]
#endif
    doc_formatting& start_column(int col) { return first_column(col); }
#if __cplusplus >= 201402L
    [[deprecated]]
#endif
    int start_column() const noexcept { return first_column(); }

    /** @brief determines column where documentation printing starts */
    doc_formatting&
    first_column(int col) {
        //limit to [0,last_column] but push doc_column to the right if necessary
        if(col < 0) col = 0;
        else if(col > last_column()) col = last_column();
        if(col > doc_column()) doc_column(first_column());
        firstCol_ = col;
        return *this;
    }
    int first_column() const noexcept {
        return firstCol_;
    }

    /** @brief determines column where docstrings start */
    doc_formatting&
    doc_column(int col) {
        //limit to [first_column,last_column]
        if(col < 0) col = 0;
        else if(col < first_column()) col = first_column();
        else if(col > last_column()) col = last_column();
        docCol_ = col;
        return *this;
    }
    int doc_column() const noexcept {
        return docCol_;
    }

    /** @brief determines column that no documentation text must exceed;
     *         (text should be wrapped appropriately after this column)
     */
    doc_formatting&
    last_column(int col) {
        //limit to [first_column,oo] but push doc_column to the left if necessary
        if(col < first_column()) col = first_column();
        if(col < doc_column()) doc_column(col);
        lastCol_ = col;
        return *this;
    }

    int last_column() const noexcept {
        return lastCol_;
    }

    /** @brief determines indent of documentation lines
     *         for children of a documented group */
    doc_formatting& indent_size(int indent) { indentSize_ = indent; return *this; }
    int             indent_size() const noexcept  { return indentSize_; }

    /** @brief determines string to be used
     *         if a parameter has no flags and no label  */
    doc_formatting& empty_label(const string& label) {
        emptyLabel_ = label;
        return *this;
    }
    const string& empty_label() const noexcept { return emptyLabel_; }

    /** @brief determines string for separating parameters */
    doc_formatting& param_separator(const string& sep) {
        paramSep_ = sep;
        return *this;
    }
    const string& param_separator() const noexcept { return paramSep_; }

    /** @brief determines string for separating groups (in usage lines) */
    doc_formatting& group_separator(const string& sep) {
        groupSep_ = sep;
        return *this;
    }
    const string& group_separator() const noexcept { return groupSep_; }

    /** @brief determines string for separating alternative parameters */
    doc_formatting& alternative_param_separator(const string& sep) {
        altParamSep_ = sep;
        return *this;
    }
    const string& alternative_param_separator() const noexcept { return altParamSep_; }

    /** @brief determines string for separating alternative groups */
    doc_formatting& alternative_group_separator(const string& sep) {
        altGroupSep_ = sep;
        return *this;
    }
    const string& alternative_group_separator() const noexcept { return altGroupSep_; }

    /** @brief determines string for separating flags of the same parameter */
    doc_formatting& flag_separator(const string& sep) {
        flagSep_ = sep;
        return *this;
    }
    const string& flag_separator() const noexcept { return flagSep_; }

    /** @brief determines strings surrounding parameter labels */
    doc_formatting&
    surround_labels(const string& prefix, const string& postfix) {
        labelPre_ = prefix;
        labelPst_ = postfix;
        return *this;
    }
    const string& label_prefix()  const noexcept { return labelPre_; }
    const string& label_postfix() const noexcept { return labelPst_; }

    /** @brief determines strings surrounding optional parameters/groups */
    doc_formatting&
    surround_optional(const string& prefix, const string& postfix) {
        optionPre_ = prefix;
        optionPst_ = postfix;
        return *this;
    }
    const string& optional_prefix()  const noexcept { return optionPre_; }
    const string& optional_postfix() const noexcept { return optionPst_; }

    /** @brief determines strings surrounding repeatable parameters/groups */
    doc_formatting&
    surround_repeat(const string& prefix, const string& postfix) {
        repeatPre_ = prefix;
        repeatPst_ = postfix;
        return *this;
    }
    const string& repeat_prefix()  const noexcept { return repeatPre_; }
    const string& repeat_postfix() const noexcept { return repeatPst_; }

    /** @brief determines strings surrounding exclusive groups */
    doc_formatting&
    surround_alternatives(const string& prefix, const string& postfix) {
        alternPre_ = prefix;
        alternPst_ = postfix;
        return *this;
    }
    const string& alternatives_prefix()  const noexcept { return alternPre_; }
    const string& alternatives_postfix() const noexcept { return alternPst_; }

    /** @brief determines strings surrounding alternative flags */
    doc_formatting&
    surround_alternative_flags(const string& prefix, const string& postfix) {
        alternFlagPre_ = prefix;
        alternFlagPst_ = postfix;
        return *this;
    }
    const string& alternative_flags_prefix()  const noexcept { return alternFlagPre_; }
    const string& alternative_flags_postfix() const noexcept { return alternFlagPst_; }

    /** @brief determines strings surrounding non-exclusive groups */
    doc_formatting&
    surround_group(const string& prefix, const string& postfix) {
        groupPre_ = prefix;
        groupPst_ = postfix;
        return *this;
    }
    const string& group_prefix()  const noexcept { return groupPre_; }
    const string& group_postfix() const noexcept { return groupPst_; }

    /** @brief determines strings surrounding joinable groups */
    doc_formatting&
    surround_joinable(const string& prefix, const string& postfix) {
        joinablePre_ = prefix;
        joinablePst_ = postfix;
        return *this;
    }
    const string& joinable_prefix()  const noexcept { return joinablePre_; }
    const string& joinable_postfix() const noexcept { return joinablePst_; }

    /** @brief determines maximum number of flags per parameter to be printed
     *         in detailed parameter documentation lines */
    doc_formatting& max_flags_per_param_in_doc(int max) {
        maxAltInDocs_ = max > 0 ? max : 0;
        return *this;
    }
    int max_flags_per_param_in_doc() const noexcept { return maxAltInDocs_; }

    /** @brief determines maximum number of flags per parameter to be printed
     *         in usage lines */
    doc_formatting& max_flags_per_param_in_usage(int max) {
        maxAltInUsage_ = max > 0 ? max : 0;
        return *this;
    }
    int max_flags_per_param_in_usage() const noexcept { return maxAltInUsage_; }

    /** @brief determines number of empty rows after one single-line
     *         documentation entry */
    doc_formatting& line_spacing(int lines) {
        lineSpc_ = lines > 0 ? lines : 0;
        return *this;
    }
    int line_spacing() const noexcept { return lineSpc_; }

    /** @brief determines number of empty rows before and after a paragraph;
     *         a paragraph is defined by a documented group or if
     *         a parameter documentation entry used more than one line */
    doc_formatting& paragraph_spacing(int lines) {
        paragraphSpc_ = lines > 0 ? lines : 0;
        return *this;
    }
    int paragraph_spacing() const noexcept { return paragraphSpc_; }

    /** @brief determines if alternative flags with a common prefix should
     *         be printed in a merged fashion */
    doc_formatting& merge_alternative_flags_with_common_prefix(bool yes = true) {
        mergeAltCommonPfx_ = yes;
        return *this;
    }
    bool merge_alternative_flags_with_common_prefix() const noexcept {
        return mergeAltCommonPfx_;
    }

    /** @brief determines if joinable flags with a common prefix should
     *         be printed in a merged fashion */
    doc_formatting& merge_joinable_with_common_prefix(bool yes = true) {
        mergeJoinableCommonPfx_ = yes;
        return *this;
    }
    bool merge_joinable_with_common_prefix() const noexcept {
        return mergeJoinableCommonPfx_;
    }

    /** @brief determines if children of exclusive groups should be printed
     *         on individual lines if the exceed 'alternatives_min_split_size'
     */
    doc_formatting& split_alternatives(bool yes = true) {
        splitTopAlt_ = yes;
        return *this;
    }
    bool split_alternatives() const noexcept {
        return splitTopAlt_;
    }

    /** @brief determines how many children exclusive groups can have before
     *         their children are printed on individual usage lines */
    doc_formatting& alternatives_min_split_size(int size) {
        groupSplitSize_ = size > 0 ? size : 0;
        return *this;
    }
    int alternatives_min_split_size() const noexcept { return groupSplitSize_; }

    /** @brief determines whether to ignore new line characters in docstrings
     */
    doc_formatting& ignore_newline_chars(bool yes = true) {
        ignoreNewlines_ = yes;
        return *this;
    }
    bool ignore_newline_chars() const noexcept {
        return ignoreNewlines_;
    }

private:
    string paramSep_      = string(" ");
    string groupSep_      = string(" ");
    string altParamSep_   = string("|");
    string altGroupSep_   = string(" | ");
    string flagSep_       = string(", ");
    string labelPre_      = string("<");
    string labelPst_      = string(">");
    string optionPre_     = string("[");
    string optionPst_     = string("]");
    string repeatPre_     = string("");
    string repeatPst_     = string("...");
    string groupPre_      = string("(");
    string groupPst_      = string(")");
    string alternPre_     = string("(");
    string alternPst_     = string(")");
    string alternFlagPre_ = string("");
    string alternFlagPst_ = string("");
    string joinablePre_   = string("(");
    string joinablePst_   = string(")");
    string emptyLabel_    = string("");
    int firstCol_ = 8;
    int docCol_ = 20;
    int lastCol_ = 100;
    int indentSize_ = 4;
    int maxAltInUsage_ = 1;
    int maxAltInDocs_ = 32;
    int lineSpc_ = 0;
    int paragraphSpc_ = 1;
    int groupSplitSize_ = 3;
    bool splitTopAlt_ = true;
    bool mergeAltCommonPfx_ = false;
    bool mergeJoinableCommonPfx_ = true;
    bool ignoreNewlines_ = false;
};



namespace detail {

/*************************************************************************//**
 *
 * @brief stream decorator
 *        that applies formatting like line wrapping
 *
 *****************************************************************************/
template<class OStream = std::ostream, class StringT = doc_string>
class formatting_ostream
{
public:
    using string_type = StringT;
    using size_type   = typename string_type::size_type;
    using char_type   = typename string_type::value_type;

    formatting_ostream(OStream& os):
        os_(os),
        curCol_{0}, firstCol_{0}, lastCol_{100},
        hangingIndent_{0}, paragraphSpacing_{0}, paragraphSpacingThreshold_{2},
        curBlankLines_{0}, curParagraphLines_{1},
        totalNonBlankLines_{0},
        ignoreInputNls_{false}
    {}


    //---------------------------------------------------------------
    const OStream& base() const noexcept { return os_; }
          OStream& base()       noexcept { return os_; }

    bool good() const { return os_.good(); }


    //---------------------------------------------------------------
    /** @brief determines the leftmost border of the text body */
    formatting_ostream& first_column(int c) {
        firstCol_ = c < 0 ? 0 : c;
        return *this;
    }
    int first_column() const noexcept { return firstCol_; }

    /** @brief determines the rightmost border of the text body */
    formatting_ostream& last_column(int c) {
        lastCol_ = c < 0 ? 0 : c;
        return *this;
    }

    int last_column() const noexcept { return lastCol_; }

    int text_width() const noexcept {
        return lastCol_ - firstCol_;
    }

    /** @brief additional indentation for the 2nd, 3rd, ... line of
               a paragraph (sequence of soft-wrapped lines) */
    formatting_ostream& hanging_indent(int amount) {
        hangingIndent_ = amount;
        return *this;
    }
    int hanging_indent() const noexcept {
        return hangingIndent_;
    }

    /** @brief amount of blank lines between paragraphs */
    formatting_ostream& paragraph_spacing(int lines) {
        paragraphSpacing_ = lines;
        return *this;
    }
    int paragraph_spacing() const noexcept {
        return paragraphSpacing_;
    }

    /** @brief insert paragraph spacing
               if paragraph is at least 'lines' lines long */
    formatting_ostream& min_paragraph_lines_for_spacing(int lines) {
        paragraphSpacingThreshold_ = lines;
        return *this;
    }
    int min_paragraph_lines_for_spacing() const noexcept {
        return paragraphSpacingThreshold_;
    }

    /** @brief if set to true, newline characters will be ignored */
    formatting_ostream& ignore_newline_chars(bool yes) {
        ignoreInputNls_ = yes;
        return *this;
    }

    bool ignore_newline_chars() const noexcept {
        return ignoreInputNls_;
    }


    //---------------------------------------------------------------
    /* @brief insert 'n' spaces */
    void write_spaces(int n) {
        if(n < 1) return;
        os_ << string_type(size_type(n), ' ');
        curCol_ += n;
    }

    /* @brief go to new line, but continue current paragraph */
    void wrap_soft(int times = 1) {
        if(times < 1) return;
        if(times > 1) {
            os_ << string_type(size_type(times), '\n');
        } else {
            os_ << '\n';
        }
        curCol_ = 0;
        ++curParagraphLines_;
    }

    /* @brief go to new line, and start a new paragraph */
    void wrap_hard(int times = 1) {
        if(times < 1) return;

        if(paragraph_spacing() > 0 &&
           paragraph_lines() >= min_paragraph_lines_for_spacing())
        {
            times = paragraph_spacing() + 1;
        }

        if(times > 1) {
            os_ << string_type(size_type(times), '\n');
            curBlankLines_ += times - 1;
        } else {
            os_ << '\n';
        }
        if(at_begin_of_line()) {
            ++curBlankLines_;
        }
        curCol_ = 0;
        curParagraphLines_ = 1;
    }


    //---------------------------------------------------------------
    bool at_begin_of_line() const noexcept {
        return curCol_ <= current_line_begin();
    }
    int current_line_begin() const noexcept {
        return in_hanging_part_of_paragraph()
            ? firstCol_ + hangingIndent_
            : firstCol_;
    }

    int current_column() const noexcept {
        return curCol_;
    }

    int total_non_blank_lines() const noexcept {
        return totalNonBlankLines_;
    }
    int paragraph_lines() const noexcept {
        return curParagraphLines_;
    }
    int blank_lines_before_paragraph() const noexcept {
        return curBlankLines_;
    }


    //---------------------------------------------------------------
    template<class T>
    friend formatting_ostream&
    operator << (formatting_ostream& os, const T& x) {
        os.write(x);
        return os;
    }

    void flush() {
        os_.flush();
    }


private:
    bool in_hanging_part_of_paragraph() const noexcept {
        return hanging_indent() > 0 && paragraph_lines() > 1;
    }
    bool current_line_empty() const noexcept {
        return curCol_ < 1;
    }
    bool left_of_text_area() const noexcept {
        return curCol_ < current_line_begin();
    }
    bool right_of_text_area() const noexcept {
        return curCol_ > lastCol_;
    }
    int columns_left_in_line() const noexcept {
        return lastCol_ - std::max(current_line_begin(), curCol_);
    }

    void fix_indent() {
        if(left_of_text_area()) {
            const auto fst = current_line_begin();
            write_spaces(fst - curCol_);
            curCol_ = fst;
        }
    }

    template<class Iter>
    bool only_whitespace(Iter first, Iter last) const {
        return last == std::find_if_not(first, last,
                [](char_type c) { return std::isspace(c); });
    }

    /** @brief write any object */
    template<class T>
    void write(const T& x) {
        std::ostringstream ss;
        ss << x;
        write(std::move(ss).str());
    }

    /** @brief write a stringstream */
    void write(const std::ostringstream& s) {
        write(s.str());
    }

    /** @brief write a string */
    void write(const string_type& s) {
        write(s.begin(), s.end());
    }

    /** @brief partition output into lines */
    template<class Iter>
    void write(Iter first, Iter last)
    {
        if(first == last) return;
        if(*first == '\n') {
            if(!ignore_newline_chars()) wrap_hard();
            ++first;
            if(first == last) return;
        }
        auto i = std::find(first, last, '\n');
        if(i != last) {
            if(ignore_newline_chars()) ++i;
            if(i != last) {
                write_line(first, i);
                write(i, last);
            }
        }
        else {
            write_line(first, last);
        }
    }

    /** @brief handle line wrapping due to column constraints */
    template<class Iter>
    void write_line(Iter first, Iter last)
    {
        if(first == last) return;
        if(only_whitespace(first, last)) return;

        if(right_of_text_area()) wrap_soft();

        if(at_begin_of_line()) {
            //discard whitespace, it we start a new line
            first = std::find_if(first, last,
                        [](char_type c) { return !std::isspace(c); });
            if(first == last) return;
        }

        const auto n = int(std::distance(first,last));
        const auto m = columns_left_in_line();
        //if text to be printed is too long for one line -> wrap
        if(n > m) {
            //break before word, if break is mid-word
            auto breakat = first + m;
            while(breakat > first && !std::isspace(*breakat)) --breakat;
            //could not find whitespace before word -> try after the word
            if(!std::isspace(*breakat) && breakat == first) {
                breakat = std::find_if(first+m, last,
                          [](char_type c) { return std::isspace(c); });
            }
            if(breakat > first) {
                if(curCol_ < 1) ++totalNonBlankLines_;
                fix_indent();
                std::copy(first, breakat, std::ostream_iterator<char_type>(os_));
                curBlankLines_ = 0;
            }
            if(breakat < last) {
                wrap_soft();
                write_line(breakat, last);
            }
        }
        else {
            if(curCol_ < 1) ++totalNonBlankLines_;
            fix_indent();
            std::copy(first, last, std::ostream_iterator<char_type>(os_));
            curCol_ += n;
            curBlankLines_ = 0;
        }
    }

    /** @brief write a single character */
    void write(char_type c)
    {
        if(c == '\n') {
            if(!ignore_newline_chars()) wrap_hard();
        }
        else {
            if(at_begin_of_line()) ++totalNonBlankLines_;
            fix_indent();
            os_ << c;
            ++curCol_;
        }
    }

    OStream& os_;
    int curCol_;
    int firstCol_;
    int lastCol_;
    int hangingIndent_;
    int paragraphSpacing_;
    int paragraphSpacingThreshold_;
    int curBlankLines_;
    int curParagraphLines_;
    int totalNonBlankLines_;
    bool ignoreInputNls_;
};


}




/*************************************************************************//**
 *
 * @brief   generates usage lines
 *
 * @details lazily evaluated
 *
 *****************************************************************************/
class usage_lines
{
public:
    using string = doc_string;

    usage_lines(const group& cli, string prefix = "",
                const doc_formatting& fmt = doc_formatting{})
    :
        cli_(cli), fmt_(fmt), prefix_(std::move(prefix))
    {
        if(!prefix_.empty()) prefix_ += ' ';
    }

    usage_lines(const group& cli, const doc_formatting& fmt):
        usage_lines(cli, "", fmt)
    {}

    usage_lines& ommit_outermost_group_surrounders(bool yes) {
        ommitOutermostSurrounders_ = yes;
        return *this;
    }
    bool ommit_outermost_group_surrounders() const {
        return ommitOutermostSurrounders_;
    }

    template<class OStream>
    inline friend OStream& operator << (OStream& os, const usage_lines& p) {
        p.write(os);
        return os;
    }

    string str() const {
        std::ostringstream os; os << *this; return os.str();
    }


private:
    using stream_t = detail::formatting_ostream<>;
    const group& cli_;
    doc_formatting fmt_;
    string prefix_;
    bool ommitOutermostSurrounders_ = false;


    //-----------------------------------------------------
    struct context {
        group::depth_first_traverser pos;
        std::stack<string> separators;
        std::stack<string> postfixes;
        int level = 0;
        const group* outermost = nullptr;
        bool linestart = false;
        bool useOutermost = true;
        int line = 0;

        bool is_singleton() const noexcept {
            return linestart && pos.is_last_in_path();
        }
        bool is_alternative() const noexcept {
            return pos.parent().exclusive();
        }
    };


    /***************************************************************//**
     *
     * @brief writes usage text for command line parameters
     *
     *******************************************************************/
    template<class OStream>
    void write(OStream& os) const
    {
        detail::formatting_ostream<OStream> fos(os);
        fos.first_column(fmt_.first_column());
        fos.last_column(fmt_.last_column());

        auto hindent = int(prefix_.size());
        if(fos.first_column() + hindent >= int(0.4 * fos.text_width())) {
            hindent = fmt_.indent_size();
        }
        fos.hanging_indent(hindent);

        fos.paragraph_spacing(fmt_.paragraph_spacing());
        fos.min_paragraph_lines_for_spacing(2);
        fos.ignore_newline_chars(fmt_.ignore_newline_chars());

        context cur;
        cur.pos = cli_.begin_dfs();
        cur.linestart = true;
        cur.level = cur.pos.level();
        cur.outermost = &cli_;

        write(fos, cur, prefix_);
    }


    /***************************************************************//**
     *
     * @brief writes usage text for command line parameters
     *
     * @param prefix   all that goes in front of current things to print
     *
     *******************************************************************/
    template<class OStream>
    void write(OStream& os, context cur, string prefix) const
    {
        if(!cur.pos) return;

        std::ostringstream buf;
        if(cur.linestart) buf << prefix;
        const auto initPos = buf.tellp();

        cur.level = cur.pos.level();

        if(cur.useOutermost) {
            //we cannot start outside of the outermost group
            //so we have to treat it separately
            start_group(buf, cur.pos.parent(), cur);
            if(!cur.pos) {
                os << buf.str();
                return;
            }
        }
        else {
            //don't visit siblings of starter node
            cur.pos.skip_siblings();
        }
        check_end_group(buf, cur);

        do {
            if(buf.tellp() > initPos) cur.linestart = false;
            if(!cur.linestart && !cur.pos.is_first_in_parent()) {
                buf << cur.separators.top();
            }
            if(cur.pos->is_group()) {
                start_group(buf, cur.pos->as_group(), cur);
                if(!cur.pos) {
                    os << buf.str();
                    return;
                }
            }
            else {
                buf << param_label(cur.pos->as_param(), cur);
                ++cur.pos;
            }
            check_end_group(buf, cur);
        } while(cur.pos);

        os << buf.str();
    }


    /***************************************************************//**
     *
     * @brief handles pattern group surrounders and separators
     *        and alternative splitting
     *
     *******************************************************************/
    void start_group(std::ostringstream& os,
                     const group& group, context& cur) const
    {
        //does cur.pos already point to a member or to group itself?
        //needed for special treatment of outermost group
        const bool alreadyInside = &(cur.pos.parent()) == &group;

        if(group.empty()) {
            os << group.doc();
            cur.pos.next_sibling();
            return;
        }

        auto lbl = joined_label(group, cur);
        if(!lbl.empty()) {
            os << lbl;
            cur.linestart = false;
            //skip over entire group as its label has already been created
            if(alreadyInside) {
                cur.pos.next_after_siblings();
            } else {
                cur.pos.next_sibling();
            }
        }
        else {
            const bool splitAlternatives = group.exclusive() &&
                fmt_.split_alternatives() &&
                std::any_of(group.begin(), group.end(),
                    [this](const pattern& p) {
                        return int(p.param_count()) >= fmt_.alternatives_min_split_size();
                    });

            if(splitAlternatives) {
                cur.postfixes.push("");
                cur.separators.push("");
                //recursively print alternative paths in decision-DAG
                //enter group?
                if(!alreadyInside) ++cur.pos;
                cur.linestart = true;
                cur.useOutermost = false;
                auto pfx = os.str();
                os.str("");
                //print paths in DAG starting at each group member
                for(std::size_t i = 0; i < group.size(); ++i) {
                    std::stringstream buf;
                    cur.outermost = cur.pos->is_group() ? &(cur.pos->as_group()) : nullptr;
                    write(buf, cur, pfx);
                    if(buf.tellp() > int(pfx.size())) {
                        os << buf.str();
                        if(i < group.size()-1) {
                            if(cur.line > 0) {
                                os << string(fmt_.line_spacing(), '\n');
                            }
                            ++cur.line;
                            os << '\n';
                        }
                    }
                    cur.pos.next_sibling(); //do not descend into members
                }
                cur.pos.invalidate(); //signal end-of-path
                return;
            }
            else {
                //pre & postfixes, separators
                auto surround = group_surrounders(group, cur);
                os << surround.first;
                cur.postfixes.push(std::move(surround.second));
                cur.separators.push(group_separator(group, fmt_));
                //descend into group?
                if(!alreadyInside) ++cur.pos;
            }
        }
        cur.level = cur.pos.level();
    }


    /***************************************************************//**
     *
     *******************************************************************/
    void check_end_group(std::ostringstream& os, context& cur) const
    {
        for(; cur.level > cur.pos.level(); --cur.level) {
            os << cur.postfixes.top();
            cur.postfixes.pop();
            cur.separators.pop();
        }
        cur.level = cur.pos.level();
    }


    /***************************************************************//**
     *
     * @brief makes usage label for one command line parameter
     *
     *******************************************************************/
    string param_label(const parameter& p, const context& cur) const
    {
        const auto& parent = cur.pos.parent();

        const bool startsOptionalSequence =
            parent.size() > 1 && p.blocking() && cur.pos.is_first_in_parent();

        const bool outermost =
            ommitOutermostSurrounders_ && cur.outermost == &parent;

        const bool showopt = !cur.is_alternative() && !p.required()
            && !startsOptionalSequence && !outermost;

        const bool showrep = p.repeatable() && !outermost;

        string lbl;

        if(showrep) lbl += fmt_.repeat_prefix();
        if(showopt) lbl += fmt_.optional_prefix();

        const auto& flags = p.flags();
        if(!flags.empty()) {
            const int n = std::min(fmt_.max_flags_per_param_in_usage(),
                                   int(flags.size()));

            const bool surrAlt = n > 1 && !showopt && !cur.is_singleton();

            if(surrAlt) lbl += fmt_.alternative_flags_prefix();
            bool sep = false;
            for(int i = 0; i < n; ++i) {
                if(sep) {
                    if(cur.is_singleton())
                        lbl += fmt_.alternative_group_separator();
                    else
                        lbl += fmt_.flag_separator();
                }
                lbl += flags[i];
                sep = true;
            }
            if(surrAlt) lbl += fmt_.alternative_flags_postfix();
        }
        else {
             if(!p.label().empty()) {
                 lbl += fmt_.label_prefix()
                     + p.label()
                     + fmt_.label_postfix();
             } else if(!fmt_.empty_label().empty()) {
                 lbl += fmt_.label_prefix()
                     + fmt_.empty_label()
                     + fmt_.label_postfix();
             } else {
                 return "";
             }
        }

        if(showopt) lbl += fmt_.optional_postfix();
        if(showrep) lbl += fmt_.repeat_postfix();

        return lbl;
    }


    /***************************************************************//**
     *
     * @brief prints flags in one group in a merged fashion
     *
     *******************************************************************/
    string joined_label(const group& g, const context& cur) const
    {
        if(!fmt_.merge_alternative_flags_with_common_prefix() &&
           !fmt_.merge_joinable_with_common_prefix()) return "";

        const bool flagsonly = std::all_of(g.begin(), g.end(),
            [](const pattern& p){
                return p.is_param() && !p.as_param().flags().empty();
            });

        if(!flagsonly) return "";

        const bool showOpt = g.all_optional() &&
            !(ommitOutermostSurrounders_ && cur.outermost == &g);

        auto pfx = g.common_flag_prefix();
        if(pfx.empty()) return "";

        const auto n = pfx.size();
        if(g.exclusive() &&
           fmt_.merge_alternative_flags_with_common_prefix())
        {
            string lbl;
            if(showOpt) lbl += fmt_.optional_prefix();
            lbl += pfx + fmt_.alternatives_prefix();
            bool first = true;
            for(const auto& p : g) {
                if(p.is_param()) {
                    if(first)
                        first = false;
                    else
                        lbl += fmt_.alternative_param_separator();
                    lbl += p.as_param().flags().front().substr(n);
                }
            }
            lbl += fmt_.alternatives_postfix();
            if(showOpt) lbl += fmt_.optional_postfix();
            return lbl;
        }
        //no alternatives, but joinable flags
        else if(g.joinable() &&
            fmt_.merge_joinable_with_common_prefix())
        {
            const bool allSingleChar = std::all_of(g.begin(), g.end(),
                [&](const pattern& p){
                    return p.is_param() &&
                        p.as_param().flags().front().substr(n).size() == 1;
                });

            if(allSingleChar) {
                string lbl;
                if(showOpt) lbl += fmt_.optional_prefix();
                lbl += pfx;
                for(const auto& p : g) {
                    if(p.is_param())
                        lbl += p.as_param().flags().front().substr(n);
                }
                if(showOpt) lbl += fmt_.optional_postfix();
                return lbl;
            }
        }

        return "";
    }


    /***************************************************************//**
     *
     * @return symbols with which to surround a group
     *
     *******************************************************************/
    std::pair<string,string>
    group_surrounders(const group& group, const context& cur) const
    {
        string prefix;
        string postfix;

        const bool isOutermost = &group == cur.outermost;
        if(isOutermost && ommitOutermostSurrounders_)
            return {string{}, string{}};

        if(group.exclusive()) {
            if(group.all_optional()) {
                prefix  = fmt_.optional_prefix();
                postfix = fmt_.optional_postfix();
                if(group.all_flagless()) {
                    prefix  += fmt_.label_prefix();
                    postfix = fmt_.label_prefix() + postfix;
                }
            } else if(group.all_flagless()) {
                prefix  = fmt_.label_prefix();
                postfix = fmt_.label_postfix();
            } else if(!cur.is_singleton() || !isOutermost) {
                prefix  = fmt_.alternatives_prefix();
                postfix = fmt_.alternatives_postfix();
            }
        }
        else if(group.size() > 1 &&
                group.front().blocking() && !group.front().required())
        {
            prefix  = fmt_.optional_prefix();
            postfix = fmt_.optional_postfix();
        }
        else if(group.size() > 1 && cur.is_alternative() &&
                &group != cur.outermost)
        {
            prefix  = fmt_.group_prefix();
            postfix = fmt_.group_postfix();
        }
        else if(!group.exclusive() &&
            group.joinable() && !cur.linestart)
        {
            prefix  = fmt_.joinable_prefix();
            postfix = fmt_.joinable_postfix();
        }

        if(group.repeatable()) {
            if(prefix.empty()) prefix = fmt_.group_prefix();
            prefix = fmt_.repeat_prefix() + prefix;
            if(postfix.empty()) postfix = fmt_.group_postfix();
            postfix += fmt_.repeat_postfix();
        }

        return {std::move(prefix), std::move(postfix)};
    }


    /***************************************************************//**
     *
     * @return symbol that separates members of a group
     *
     *******************************************************************/
    static string
    group_separator(const group& group, const doc_formatting& fmt)
    {
        const bool only1ParamPerMember = std::all_of(group.begin(), group.end(),
            [](const pattern& p) { return p.param_count() < 2; });

        if(only1ParamPerMember) {
            if(group.exclusive()) {
                return fmt.alternative_param_separator();
            } else {
                return fmt.param_separator();
            }
        }
        else { //there is at least one large group inside
            if(group.exclusive()) {
                return fmt.alternative_group_separator();
            } else {
                return fmt.group_separator();
            }
        }
    }
};




/*************************************************************************//**
 *
 * @brief   generates parameter and group documentation from docstrings
 *
 * @details lazily evaluated
 *
 *****************************************************************************/
class documentation
{
public:
    using string          = doc_string;
    using filter_function = std::function<bool(const parameter&)>;

    documentation(const group& cli,
                  const doc_formatting& fmt = doc_formatting{},
                  filter_function filter = param_filter{})
    :
        cli_(cli), fmt_{fmt}, usgFmt_{fmt}, filter_{std::move(filter)}
    {
        //necessary, because we re-use "usage_lines" to generate
        //labels for documented groups
        usgFmt_.max_flags_per_param_in_usage(
            usgFmt_.max_flags_per_param_in_doc());
    }

    documentation(const group& cli, filter_function filter) :
        documentation{cli, doc_formatting{}, std::move(filter)}
    {}

    documentation(const group& cli, const param_filter& filter) :
        documentation{cli, doc_formatting{},
                      [filter](const parameter& p) { return filter(p); }}
    {}

    template<class OStream>
    inline friend OStream& operator << (OStream& os, const documentation& p) {
        p.write(os);
        return os;
    }

    string str() const {
        std::ostringstream os;
        write(os);
        return os.str();
    }


private:
    using dfs_traverser = group::depth_first_traverser;

    const group& cli_;
    doc_formatting fmt_;
    doc_formatting usgFmt_;
    filter_function filter_;
    enum class paragraph { param, group };


    /***************************************************************//**
     *
     * @brief writes documentation to output stream
     *
     *******************************************************************/
     template<class OStream>
     void write(OStream& os) const {
        detail::formatting_ostream<OStream> fos(os);
        fos.first_column(fmt_.first_column());
        fos.last_column(fmt_.last_column());
        fos.hanging_indent(0);
        fos.paragraph_spacing(0);
        fos.ignore_newline_chars(fmt_.ignore_newline_chars());
        print_doc(fos, cli_);
     }


    /***************************************************************//**
     *
     * @brief writes full documentation text for command line parameters
     *
     *******************************************************************/
    template<class OStream>
    void print_doc(detail::formatting_ostream<OStream>& os,
                   const group& cli, int indentLvl = 0) const
    {
        if(cli.empty()) return;

        //if group itself doesn't have docstring
        if(cli.doc().empty()) {
            for(const auto& p : cli) {
                print_doc(os, p, indentLvl);
            }
        }
        else { //group itself does have docstring
            bool anyDocInside = std::any_of(cli.begin(), cli.end(),
                [](const pattern& p){ return !p.doc().empty(); });

            if(anyDocInside) { //group docstring as title, then child entries
                handle_spacing(os, paragraph::group, indentLvl);
                os << cli.doc();
                for(const auto& p : cli) {
                    print_doc(os, p, indentLvl + 1);
                }
            }
            else { //group label first then group docstring
                auto lbl = usage_lines(cli, usgFmt_)
                           .ommit_outermost_group_surrounders(true).str();

                str::trim(lbl);
                handle_spacing(os, paragraph::param, indentLvl);
                print_entry(os, lbl, cli.doc());
            }
        }
    }


    /***************************************************************//**
     *
     * @brief writes documentation text for one group or parameter
     *
     *******************************************************************/
    template<class OStream>
    void print_doc(detail::formatting_ostream<OStream>& os,
                   const pattern& ptrn, int indentLvl) const
    {
        if(ptrn.is_group()) {
            print_doc(os, ptrn.as_group(), indentLvl);
        }
        else {
            const auto& p = ptrn.as_param();
            if(!filter_(p)) return;

            handle_spacing(os, paragraph::param, indentLvl);
            print_entry(os, param_label(p, fmt_), p.doc());
        }
    }

    /***************************************************************//**
     *
     * @brief handles line and paragraph spacings
     *
     *******************************************************************/
    template<class OStream>
    void handle_spacing(detail::formatting_ostream<OStream>& os,
                        paragraph p, int indentLvl) const
    {
        const auto oldIndent = os.first_column();
        const auto indent = fmt_.first_column() + indentLvl * fmt_.indent_size();

        if(os.total_non_blank_lines() < 1) {
            os.first_column(indent);
            return;
        }

        if(os.paragraph_lines() > 1 || indent < oldIndent) {
            os.wrap_hard(fmt_.paragraph_spacing() + 1);
        } else {
            os.wrap_hard();
        }

        if(p == paragraph::group) {
            if(os.blank_lines_before_paragraph() < fmt_.paragraph_spacing()) {
                os.wrap_hard(fmt_.paragraph_spacing() - os.blank_lines_before_paragraph());
            }
        }
        else if(os.blank_lines_before_paragraph() < fmt_.line_spacing()) {
            os.wrap_hard(fmt_.line_spacing() - os.blank_lines_before_paragraph());
        }
        os.first_column(indent);
    }

    /*********************************************************************//**
     *
     * @brief prints one entry = label + docstring
     *
     ************************************************************************/
    template<class OStream>
    void print_entry(detail::formatting_ostream<OStream>& os,
                     const string& label, const string& docstr) const
    {
        if(label.empty()) return;

        os << label;

        if(!docstr.empty()) {
            if(os.current_column() >= fmt_.doc_column()) os.wrap_soft();
            const auto oldcol = os.first_column();
            os.first_column(fmt_.doc_column());
            os << docstr;
            os.first_column(oldcol);
        }
    }


    /*********************************************************************//**
     *
     * @brief makes label for one parameter
     *
     ************************************************************************/
    static doc_string
    param_label(const parameter& param, const doc_formatting& fmt)
    {
        doc_string lbl;

        if(param.repeatable()) lbl += fmt.repeat_prefix();

        const auto& flags = param.flags();
        if(!flags.empty()) {
            lbl += flags[0];
            const int n = std::min(fmt.max_flags_per_param_in_doc(),
                                   int(flags.size()));
            for(int i = 1; i < n; ++i) {
                lbl += fmt.flag_separator() + flags[i];
            }
        }
        else if(!param.label().empty() || !fmt.empty_label().empty()) {
            lbl += fmt.label_prefix();
            if(!param.label().empty()) {
                lbl += param.label();
            } else {
                lbl += fmt.empty_label();
            }
            lbl += fmt.label_postfix();
        }

        if(param.repeatable()) lbl += fmt.repeat_postfix();

        return lbl;
    }

};




/*************************************************************************//**
 *
 * @brief stores strings for man page sections
 *
 *****************************************************************************/
class man_page
{
public:
    //---------------------------------------------------------------
    using string = doc_string;

    //---------------------------------------------------------------
    /** @brief man page section */
    class section {
    public:
        using string = doc_string;

        section(string stitle, string scontent):
            title_{std::move(stitle)}, content_{std::move(scontent)}
        {}

        const string& title()   const noexcept { return title_; }
        const string& content() const noexcept { return content_; }

    private:
        string title_;
        string content_;
    };

private:
    using section_store = std::vector<section>;

public:
    //---------------------------------------------------------------
    using value_type     = section;
    using const_iterator = section_store::const_iterator;
    using size_type      = section_store::size_type;


    //---------------------------------------------------------------
    man_page&
    append_section(string title, string content)
    {
        sections_.emplace_back(std::move(title), std::move(content));
        return *this;
    }
    //-----------------------------------------------------
    man_page&
    prepend_section(string title, string content)
    {
        sections_.emplace(sections_.begin(),
                          std::move(title), std::move(content));
        return *this;
    }


    //---------------------------------------------------------------
    const section& operator [] (size_type index) const noexcept {
        return sections_[index];
    }

    //---------------------------------------------------------------
    size_type size() const noexcept { return sections_.size(); }

    bool empty() const noexcept { return sections_.empty(); }


    //---------------------------------------------------------------
    const_iterator begin() const noexcept { return sections_.begin(); }
    const_iterator end()   const noexcept { return sections_.end(); }


    //---------------------------------------------------------------
    man_page& program_name(const string& n) {
        progName_ = n;
        return *this;
    }
    man_page& program_name(string&& n) {
        progName_ = std::move(n);
        return *this;
    }
    const string& program_name() const noexcept {
        return progName_;
    }


    //---------------------------------------------------------------
    man_page& section_row_spacing(int rows) {
        sectionSpc_ = rows > 0 ? rows : 0;
        return *this;
    }
    int section_row_spacing() const noexcept { return sectionSpc_; }


private:
    int sectionSpc_ = 1;
    section_store sections_;
    string progName_;
};



/*************************************************************************//**
 *
 * @brief generates man sections from command line parameters
 *        with sections "synopsis" and "options"
 *
 *****************************************************************************/
inline man_page
make_man_page(const group& cli,
              doc_string progname = "",
              const doc_formatting& fmt = doc_formatting{})
{
    man_page man;
    man.append_section("SYNOPSIS", usage_lines(cli,progname,fmt).str());
    man.append_section("OPTIONS", documentation(cli,fmt).str());
    return man;
}



/*************************************************************************//**
 *
 * @brief   generates man page based on command line parameters
 *
 *****************************************************************************/
template<class OStream>
OStream&
operator << (OStream& os, const man_page& man)
{
    bool first = true;
    const auto secSpc = doc_string(man.section_row_spacing() + 1, '\n');
    for(const auto& section : man) {
        if(!section.content().empty()) {
            if(first) first = false; else os << secSpc;
            if(!section.title().empty()) os << section.title() << '\n';
            os << section.content();
        }
    }
    os << '\n';
    return os;
}





/*************************************************************************//**
 *
 * @brief printing methods for debugging command line interfaces
 *
 *****************************************************************************/
namespace debug {


/*************************************************************************//**
 *
 * @brief prints first flag or value label of a parameter
 *
 *****************************************************************************/
inline doc_string doc_label(const parameter& p)
{
    if(!p.flags().empty()) return p.flags().front();
    if(!p.label().empty()) return p.label();
    return doc_string{"<?>"};
}

inline doc_string doc_label(const group&)
{
    return "<group>";
}

inline doc_string doc_label(const pattern& p)
{
    return p.is_group() ? doc_label(p.as_group()) : doc_label(p.as_param());
}


/*************************************************************************//**
 *
 * @brief prints parsing result
 *
 *****************************************************************************/
template<class OStream>
void print(OStream& os, const parsing_result& result)
{
    for(const auto& m : result) {
        os << "#" << m.index() << " " << m.arg() << " -> ";
        auto p = m.param();
        if(p) {
            os << doc_label(*p) << " \t";
            if(m.repeat() > 0) {
                os << (m.bad_repeat() ? "[bad repeat " : "[repeat ")
                   <<  m.repeat() << "]";
            }
            if(m.blocked())  os << " [blocked]";
            if(m.conflict()) os << " [conflict]";
            os << '\n';
        }
        else {
            os << " [unmapped]\n";
        }
    }

    for(const auto& m : result.missing()) {
        auto p = m.param();
        if(p) {
            os << doc_label(*p) << " \t";
            os << " [missing after " << m.after_index() << "]\n";
        }
    }
}


/*************************************************************************//**
 *
 * @brief prints parameter label and some properties
 *
 *****************************************************************************/
template<class OStream>
void print(OStream& os, const parameter& p)
{
    if(p.greedy()) os << '!';
    if(p.blocking()) os << '~';
    if(!p.required()) os << '[';
    os << doc_label(p);
    if(p.repeatable()) os << "...";
    if(!p.required()) os << "]";
}


//-------------------------------------------------------------------
template<class OStream>
void print(OStream& os, const group& g, int level = 0);


/*************************************************************************//**
 *
 * @brief prints group or parameter; uses indentation
 *
 *****************************************************************************/
template<class OStream>
void print(OStream& os, const pattern& param, int level = 0)
{
    if(param.is_group()) {
        print(os, param.as_group(), level);
    }
    else {
        os << doc_string(4*level, ' ');
        print(os, param.as_param());
    }
}


/*************************************************************************//**
 *
 * @brief prints group and its contents; uses indentation
 *
 *****************************************************************************/
template<class OStream>
void print(OStream& os, const group& g, int level)
{
    auto indent = doc_string(4*level, ' ');
    os << indent;
    if(g.blocking()) os << '~';
    if(g.joinable()) os << 'J';
    os << (g.exclusive() ? "(|\n" : "(\n");
    for(const auto& p : g) {
        print(os, p, level+1);
    }
    os << '\n' << indent << (g.exclusive() ? "|)" : ")");
    if(g.repeatable()) os << "...";
    os << '\n';
}


} // namespace debug
} //namespace clipp

#endif

