// v1.0-develop.2
// See https://github.com/philsquared/Clara

#ifndef CLARA_HPP_INCLUDED
#define CLARA_HPP_INCLUDED

#ifndef CLARA_CONFIG_CONSOLE_WIDTH
#define CLARA_CONFIG_CONSOLE_WIDTH 80
#endif

#ifndef CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH
#define CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH
#endif

// ----------- #included from clara_textflow.hpp -----------

// TextFlowCpp
//
// A single-header library for wrapping and laying out basic text, by Phil Nash
//
// This work is licensed under the BSD 2-Clause license.
// See the accompanying LICENSE file, or the one at https://opensource.org/licenses/BSD-2-Clause
//
// This project is hosted at https://github.com/philsquared/textflowcpp

#ifndef CLARA_TEXTFLOW_HPP_INCLUDED
#define CLARA_TEXTFLOW_HPP_INCLUDED

#include <cassert>
#include <ostream>
#include <sstream>
#include <vector>

#ifndef CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH
#define CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 80
#endif


namespace clara {
namespace TextFlow {

inline auto isWhitespace(char c) -> bool {
    static std::string chars = " \t\n\r";
    return chars.find(c) != std::string::npos;
}
inline auto isBreakableBefore(char c) -> bool {
    static std::string chars = "[({<|";
    return chars.find(c) != std::string::npos;
}
inline auto isBreakableAfter(char c) -> bool {
    static std::string chars = "])}>.,:;*+-=&/\\";
    return chars.find(c) != std::string::npos;
}

class Columns;

class Column {
    std::vector<std::string> m_strings;
    size_t m_width = CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH;
    size_t m_indent = 0;
    size_t m_initialIndent = std::string::npos;

   public:
    class iterator {
        friend Column;

        Column const& m_column;
        size_t m_stringIndex = 0;
        size_t m_pos = 0;

        size_t m_len = 0;
        size_t m_end = 0;
        bool m_suffix = false;

        iterator(Column const& column, size_t stringIndex)
            : m_column(column), m_stringIndex(stringIndex) {}

        auto line() const -> std::string const& { return m_column.m_strings[m_stringIndex]; }

        auto isBoundary(size_t at) const -> bool {
            assert(at > 0);
            assert(at <= line().size());

            return at == line().size() ||
                   (isWhitespace(line()[at]) && !isWhitespace(line()[at - 1])) ||
                   isBreakableBefore(line()[at]) || isBreakableAfter(line()[at - 1]);
        }

        void calcLength() {
            assert(m_stringIndex < m_column.m_strings.size());

            m_suffix = false;
            auto width = m_column.m_width - indent();
            m_end = m_pos;
            while (m_end < line().size() && line()[m_end] != '\n') ++m_end;

            if (m_end < m_pos + width) {
                m_len = m_end - m_pos;
            } else {
                size_t len = width;
                while (len > 0 && !isBoundary(m_pos + len)) --len;
                while (len > 0 && isWhitespace(line()[m_pos + len - 1])) --len;

                if (len > 0) {
                    m_len = len;
                } else {
                    m_suffix = true;
                    m_len = width - 1;
                }
            }
        }

        auto indent() const -> size_t {
            auto initial =
                m_pos == 0 && m_stringIndex == 0 ? m_column.m_initialIndent : std::string::npos;
            return initial == std::string::npos ? m_column.m_indent : initial;
        }

        auto addIndentAndSuffix(std::string const& plain) const -> std::string {
            return std::string(indent(), ' ') + (m_suffix ? plain + "-" : plain);
        }

       public:
        explicit iterator(Column const& column) : m_column(column) {
            assert(m_column.m_width > m_column.m_indent);
            assert(m_column.m_initialIndent == std::string::npos ||
                   m_column.m_width > m_column.m_initialIndent);
            calcLength();
            if (m_len == 0) m_stringIndex++;  // Empty string
        }

        auto operator*() const -> std::string {
            assert(m_stringIndex < m_column.m_strings.size());
            assert(m_pos <= m_end);
            if (m_pos + m_column.m_width < m_end)
                return addIndentAndSuffix(line().substr(m_pos, m_len));
            else
                return addIndentAndSuffix(line().substr(m_pos, m_end - m_pos));
        }

        auto operator++() -> iterator& {
            m_pos += m_len;
            if (m_pos < line().size() && line()[m_pos] == '\n')
                m_pos += 1;
            else
                while (m_pos < line().size() && isWhitespace(line()[m_pos])) ++m_pos;

            if (m_pos == line().size()) {
                m_pos = 0;
                ++m_stringIndex;
            }
            if (m_stringIndex < m_column.m_strings.size()) calcLength();
            return *this;
        }
        auto operator++(int) -> iterator {
            iterator prev(*this);
            operator++();
            return prev;
        }

        auto operator==(iterator const& other) const -> bool {
            return m_pos == other.m_pos && m_stringIndex == other.m_stringIndex &&
                   &m_column == &other.m_column;
        }
        auto operator!=(iterator const& other) const -> bool { return !operator==(other); }
    };
    using const_iterator = iterator;

    explicit Column(std::string const& text) { m_strings.push_back(text); }

    auto width(size_t newWidth) -> Column& {
        assert(newWidth > 0);
        m_width = newWidth;
        return *this;
    }
    auto indent(size_t newIndent) -> Column& {
        m_indent = newIndent;
        return *this;
    }
    auto initialIndent(size_t newIndent) -> Column& {
        m_initialIndent = newIndent;
        return *this;
    }

    auto width() const -> size_t { return m_width; }
    auto begin() const -> iterator { return iterator(*this); }
    auto end() const -> iterator { return {*this, m_strings.size()}; }

    inline friend std::ostream& operator<<(std::ostream& os, Column const& col) {
        bool first = true;
        for (auto line : col) {
            if (first)
                first = false;
            else
                os << "\n";
            os << line;
        }
        return os;
    }

    auto operator+(Column const& other) -> Columns;

    auto toString() const -> std::string {
        std::ostringstream oss;
        oss << *this;
        return oss.str();
    }
};

class Spacer : public Column {
   public:
    explicit Spacer(size_t spaceWidth) : Column("") { width(spaceWidth); }
};

class Columns {
    std::vector<Column> m_columns;

   public:
    class iterator {
        friend Columns;
        struct EndTag {};

        std::vector<Column> const& m_columns;
        std::vector<Column::iterator> m_iterators;
        size_t m_activeIterators;

        iterator(Columns const& columns, EndTag)
            : m_columns(columns.m_columns), m_activeIterators(0) {
            m_iterators.reserve(m_columns.size());

            for (auto const& col : m_columns) m_iterators.push_back(col.end());
        }

       public:
        explicit iterator(Columns const& columns)
            : m_columns(columns.m_columns), m_activeIterators(m_columns.size()) {
            m_iterators.reserve(m_columns.size());

            for (auto const& col : m_columns) m_iterators.push_back(col.begin());
        }

        auto operator==(iterator const& other) const -> bool {
            return m_iterators == other.m_iterators;
        }
        auto operator!=(iterator const& other) const -> bool {
            return m_iterators != other.m_iterators;
        }
        auto operator*() const -> std::string {
            std::string row, padding;

            for (size_t i = 0; i < m_columns.size(); ++i) {
                auto width = m_columns[i].width();
                if (m_iterators[i] != m_columns[i].end()) {
                    std::string col = *m_iterators[i];
                    row += padding + col;
                    if (col.size() < width)
                        padding = std::string(width - col.size(), ' ');
                    else
                        padding = "";
                } else {
                    padding += std::string(width, ' ');
                }
            }
            return row;
        }
        auto operator++() -> iterator& {
            for (size_t i = 0; i < m_columns.size(); ++i) {
                if (m_iterators[i] != m_columns[i].end()) ++m_iterators[i];
            }
            return *this;
        }
        auto operator++(int) -> iterator {
            iterator prev(*this);
            operator++();
            return prev;
        }
    };
    using const_iterator = iterator;

    auto begin() const -> iterator { return iterator(*this); }
    auto end() const -> iterator { return {*this, iterator::EndTag()}; }

    auto operator+=(Column const& col) -> Columns& {
        m_columns.push_back(col);
        return *this;
    }
    auto operator+(Column const& col) -> Columns {
        Columns combined = *this;
        combined += col;
        return combined;
    }

    inline friend std::ostream& operator<<(std::ostream& os, Columns const& cols) {
        bool first = true;
        for (auto line : cols) {
            if (first)
                first = false;
            else
                os << "\n";
            os << line;
        }
        return os;
    }

    auto toString() const -> std::string {
        std::ostringstream oss;
        oss << *this;
        return oss.str();
    }
};

inline auto Column::operator+(Column const& other) -> Columns {
    Columns cols;
    cols += *this;
    cols += other;
    return cols;
}
}  // namespace TextFlow
}  // namespace clara

#endif  // CLARA_TEXTFLOW_HPP_INCLUDED

// ----------- end of #include from clara_textflow.hpp -----------
// ........... back in clara.hpp


#include <memory>
#include <set>
#include <algorithm>

#if !defined(CLARA_PLATFORM_WINDOWS) &&                                                            \
    (defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER))
#define CLARA_PLATFORM_WINDOWS
#endif

namespace clara {
namespace detail {

// Traits for extracting arg and return type of lambdas (for single argument lambdas)
template <typename L>
struct UnaryLambdaTraits : UnaryLambdaTraits<decltype(&L::operator())> {};

template <typename ClassT, typename ReturnT, typename... Args>
struct UnaryLambdaTraits<ReturnT (ClassT::*)(Args...) const> {
    static const bool isValid = false;
};

template <typename ClassT, typename ReturnT, typename ArgT>
struct UnaryLambdaTraits<ReturnT (ClassT::*)(ArgT) const> {
    static const bool isValid = true;
    using ArgType = typename std::remove_const<typename std::remove_reference<ArgT>::type>::type;
    ;
    using ReturnType = ReturnT;
};

class TokenStream;

// Transport for raw args (copied from main args, or supplied via init list for testing)
class Args {
    friend TokenStream;
    std::string m_exeName;
    std::vector<std::string> m_args;

   public:
    Args(int argc, char* argv[]) {
        m_exeName = argv[0];
        for (int i = 1; i < argc; ++i) m_args.push_back(argv[i]);
    }

    Args(std::initializer_list<std::string> args)
        : m_exeName(*args.begin()), m_args(args.begin() + 1, args.end()) {}

    auto exeName() const -> std::string { return m_exeName; }
};

// Wraps a token coming from a token stream. These may not directly correspond to strings as a
// single string may encode an option + its argument if the : or = form is used
enum class TokenType { Option, Argument };
struct Token {
    TokenType type;
    std::string token;
};

inline auto isOptPrefix(char c) -> bool {
    return c == '-'
#ifdef CLARA_PLATFORM_WINDOWS
           || c == '/'
#endif
        ;
}

// Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled
class TokenStream {
    using Iterator = std::vector<std::string>::const_iterator;
    Iterator it;
    Iterator itEnd;
    std::vector<Token> m_tokenBuffer;

    void loadBuffer() {
        m_tokenBuffer.resize(0);

        // Skip any empty strings
        while (it != itEnd && it->empty()) ++it;

        if (it != itEnd) {
            auto const& next = *it;
            if (isOptPrefix(next[0])) {
                auto delimiterPos = next.find_first_of(" :=");
                if (delimiterPos != std::string::npos) {
                    m_tokenBuffer.push_back({TokenType::Option, next.substr(0, delimiterPos)});
                    m_tokenBuffer.push_back({TokenType::Argument, next.substr(delimiterPos + 1)});
                } else {
                    if (next[1] != '-' && next.size() > 2) {
                        std::string opt = "- ";
                        for (size_t i = 1; i < next.size(); ++i) {
                            opt[1] = next[i];
                            m_tokenBuffer.push_back({TokenType::Option, opt});
                        }
                    } else {
                        m_tokenBuffer.push_back({TokenType::Option, next});
                    }
                }
            } else {
                m_tokenBuffer.push_back({TokenType::Argument, next});
            }
        }
    }

   public:
    explicit TokenStream(Args const& args) : TokenStream(args.m_args.begin(), args.m_args.end()) {}

    TokenStream(Iterator it, Iterator itEnd) : it(it), itEnd(itEnd) { loadBuffer(); }

    explicit operator bool() const { return !m_tokenBuffer.empty() || it != itEnd; }

    auto count() const -> size_t { return m_tokenBuffer.size() + (itEnd - it); }

    auto operator*() const -> Token {
        assert(!m_tokenBuffer.empty());
        return m_tokenBuffer.front();
    }

    auto operator-> () const -> Token const* {
        assert(!m_tokenBuffer.empty());
        return &m_tokenBuffer.front();
    }

    auto operator++() -> TokenStream& {
        if (m_tokenBuffer.size() >= 2) {
            m_tokenBuffer.erase(m_tokenBuffer.begin());
        } else {
            if (it != itEnd) ++it;
            loadBuffer();
        }
        return *this;
    }
};


class ResultBase {
   public:
    enum Type { Ok, LogicError, RuntimeError };

   protected:
    ResultBase(Type type) : m_type(type) {}
    virtual ~ResultBase() = default;

    virtual void enforceOk() const = 0;

    Type m_type;
};

template <typename T>
class ResultValueBase : public ResultBase {
   public:
    auto value() const -> T const& {
        enforceOk();
        return m_value;
    }

   protected:
    ResultValueBase(Type type) : ResultBase(type) {}

    ResultValueBase(ResultValueBase const& other) : ResultBase(other) {
        if (m_type == ResultBase::Ok) new (&m_value) T(other.m_value);
    }

    ResultValueBase(Type, T const& value) : ResultBase(Ok) { new (&m_value) T(value); }

    auto operator=(ResultValueBase const& other) -> ResultValueBase& {
        if (m_type == ResultBase::Ok) m_value.~T();
        ResultBase::operator=(other);
        if (m_type == ResultBase::Ok) new (&m_value) T(other.m_value);
        return *this;
    }

    ~ResultValueBase() {
        if (m_type == Ok) m_value.~T();
    }

    union {
        T m_value;
    };
};

template <>
class ResultValueBase<void> : public ResultBase {
   protected:
    using ResultBase::ResultBase;
};

template <typename T = void>
class BasicResult : public ResultValueBase<T> {
   public:
    template <typename U>
    explicit BasicResult(BasicResult<U> const& other)
        : ResultValueBase<T>(other.type()), m_errorMessage(other.errorMessage()) {
        assert(type() != ResultBase::Ok);
    }

    template <typename U>
    static auto ok(U const& value) -> BasicResult {
        return {ResultBase::Ok, value};
    }
    static auto ok() -> BasicResult { return {ResultBase::Ok}; }
    static auto logicError(std::string const& message) -> BasicResult {
        return {ResultBase::LogicError, message};
    }
    static auto runtimeError(std::string const& message) -> BasicResult {
        return {ResultBase::RuntimeError, message};
    }

    explicit operator bool() const { return m_type == ResultBase::Ok; }
    auto type() const -> ResultBase::Type { return m_type; }
    auto errorMessage() const -> std::string { return m_errorMessage; }

   protected:
    virtual void enforceOk() const {
        // !TBD: If no exceptions, std::terminate here or something
        switch (m_type) {
            case ResultBase::LogicError:
                throw std::logic_error(m_errorMessage);
            case ResultBase::RuntimeError:
                throw std::runtime_error(m_errorMessage);
            case ResultBase::Ok:
                break;
        }
    }

    std::string m_errorMessage;  // Only populated if resultType is an error

    BasicResult(ResultBase::Type type, std::string const& message)
        : ResultValueBase<T>(type), m_errorMessage(message) {
        assert(m_type != ResultBase::Ok);
    }

    using ResultValueBase<T>::ResultValueBase;
    using ResultBase::m_type;
};

enum class ParseResultType { Matched, NoMatch, ShortCircuitAll, ShortCircuitSame };

class ParseState {
   public:
    ParseState(ParseResultType type, TokenStream const& remainingTokens)
        : m_type(type), m_remainingTokens(remainingTokens) {}

    auto type() const -> ParseResultType { return m_type; }
    auto remainingTokens() const -> TokenStream { return m_remainingTokens; }

   private:
    ParseResultType m_type;
    TokenStream m_remainingTokens;
};

using Result = BasicResult<void>;
using ParserResult = BasicResult<ParseResultType>;
using InternalParseResult = BasicResult<ParseState>;

struct HelpColumns {
    std::string left;
    std::string right;
};

template <typename T>
inline auto convertInto(std::string const& source, T& target) -> ParserResult {
    std::stringstream ss;
    ss << source;
    ss >> target;
    if (ss.fail())
        return ParserResult::runtimeError("Unable to convert '" + source + "' to destination type");
    else
        return ParserResult::ok(ParseResultType::Matched);
}
inline auto convertInto(std::string const& source, std::string& target) -> ParserResult {
    target = source;
    return ParserResult::ok(ParseResultType::Matched);
}
inline auto convertInto(std::string const& source, bool& target) -> ParserResult {
    std::string srcLC = source;
    std::transform(srcLC.begin(), srcLC.end(), srcLC.begin(),
                   [](char c) { return static_cast<char>(::tolower(c)); });
    if (srcLC == "y" || srcLC == "1" || srcLC == "true" || srcLC == "yes" || srcLC == "on")
        target = true;
    else if (srcLC == "n" || srcLC == "0" || srcLC == "false" || srcLC == "no" || srcLC == "off")
        target = false;
    else
        return ParserResult::runtimeError("Expected a boolean value but did not recognise: '" +
                                          source + "'");
    return ParserResult::ok(ParseResultType::Matched);
}

struct BoundRefBase {
    BoundRefBase() = default;
    BoundRefBase(BoundRefBase const&) = delete;
    BoundRefBase(BoundRefBase&&) = delete;
    BoundRefBase& operator=(BoundRefBase const&) = delete;
    BoundRefBase& operator=(BoundRefBase&&) = delete;

    virtual ~BoundRefBase() = default;

    virtual auto isFlag() const -> bool = 0;
    virtual auto isContainer() const -> bool { return false; }
    virtual auto setValue(std::string const& arg) -> ParserResult = 0;
    virtual auto setFlag(bool flag) -> ParserResult = 0;
};

struct BoundValueRefBase : BoundRefBase {
    auto isFlag() const -> bool override { return false; }

    auto setFlag(bool) -> ParserResult override {
        return ParserResult::logicError("Flags can only be set on boolean fields");
    }
};

struct BoundFlagRefBase : BoundRefBase {
    auto isFlag() const -> bool override { return true; }

    auto setValue(std::string const& arg) -> ParserResult override {
        bool flag;
        auto result = convertInto(arg, flag);
        if (result) setFlag(flag);
        return result;
    }
};

template <typename T>
struct BoundRef : BoundValueRefBase {
    T& m_ref;

    explicit BoundRef(T& ref) : m_ref(ref) {}

    auto setValue(std::string const& arg) -> ParserResult override {
        return convertInto(arg, m_ref);
    }
};

template <typename T>
struct BoundRef<std::vector<T>> : BoundValueRefBase {
    std::vector<T>& m_ref;

    explicit BoundRef(std::vector<T>& ref) : m_ref(ref) {}

    auto isContainer() const -> bool override { return true; }

    auto setValue(std::string const& arg) -> ParserResult override {
        T temp;
        auto result = convertInto(arg, temp);
        if (result) m_ref.push_back(temp);
        return result;
    }
};

struct BoundFlagRef : BoundFlagRefBase {
    bool& m_ref;

    explicit BoundFlagRef(bool& ref) : m_ref(ref) {}

    auto setFlag(bool flag) -> ParserResult override {
        m_ref = flag;
        return ParserResult::ok(ParseResultType::Matched);
    }
};

template <typename ReturnType>
struct LambdaInvoker {
    static_assert(std::is_same<ReturnType, ParserResult>::value,
                  "Lambda must return void or clara::ParserResult");

    template <typename L, typename ArgType>
    static auto invoke(L const& lambda, ArgType const& arg) -> ParserResult {
        return lambda(arg);
    }
};

template <>
struct LambdaInvoker<void> {
    template <typename L, typename ArgType>
    static auto invoke(L const& lambda, ArgType const& arg) -> ParserResult {
        lambda(arg);
        return ParserResult::ok(ParseResultType::Matched);
    }
};

template <typename ArgType, typename L>
inline auto invokeLambda(L const& lambda, std::string const& arg) -> ParserResult {
    ArgType temp;
    auto result = convertInto(arg, temp);
    return !result ? result
                   : LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke(lambda, temp);
};


template <typename L>
struct BoundLambda : BoundValueRefBase {
    L m_lambda;

    static_assert(UnaryLambdaTraits<L>::isValid, "Supplied lambda must take exactly one argument");
    explicit BoundLambda(L const& lambda) : m_lambda(lambda) {}

    auto setValue(std::string const& arg) -> ParserResult override {
        return invokeLambda<typename UnaryLambdaTraits<L>::ArgType>(m_lambda, arg);
    }
};

template <typename L>
struct BoundFlagLambda : BoundFlagRefBase {
    L m_lambda;

    static_assert(UnaryLambdaTraits<L>::isValid, "Supplied lambda must take exactly one argument");
    static_assert(std::is_same<typename UnaryLambdaTraits<L>::ArgType, bool>::value,
                  "flags must be boolean");

    explicit BoundFlagLambda(L const& lambda) : m_lambda(lambda) {}

    auto setFlag(bool flag) -> ParserResult override {
        return LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke(m_lambda, flag);
    }
};

enum class Optionality { Optional, Required };

struct Parser;

class ParserBase {
   public:
    virtual ~ParserBase() = default;
    virtual auto validate() const -> Result { return Result::ok(); }
    virtual auto parse(std::string const& exeName, TokenStream const& tokens) const
        -> InternalParseResult = 0;
    virtual auto cardinality() const -> size_t { return 1; }

    auto parse(Args const& args) const -> InternalParseResult {
        return parse(args.exeName(), TokenStream(args));
    }
};

template <typename DerivedT>
class ComposableParserImpl : public ParserBase {
   public:
    template <typename T>
    auto operator|(T const& other) const -> Parser;
};

// Common code and state for Args and Opts
template <typename DerivedT>
class ParserRefImpl : public ComposableParserImpl<DerivedT> {
   protected:
    Optionality m_optionality = Optionality::Optional;
    std::shared_ptr<BoundRefBase> m_ref;
    std::string m_hint;
    std::string m_description;

    explicit ParserRefImpl(std::shared_ptr<BoundRefBase> const& ref) : m_ref(ref) {}

   public:
    template <typename T>
    ParserRefImpl(T& ref, std::string const& hint)
        : m_ref(std::make_shared<BoundRef<T>>(ref)), m_hint(hint) {}

    template <typename LambdaT>
    ParserRefImpl(LambdaT const& ref, std::string const& hint)
        : m_ref(std::make_shared<BoundLambda<LambdaT>>(ref)), m_hint(hint) {}

    auto operator()(std::string const& description) -> DerivedT& {
        m_description = description;
        return static_cast<DerivedT&>(*this);
    }

    auto optional() -> DerivedT& {
        m_optionality = Optionality::Optional;
        return static_cast<DerivedT&>(*this);
    };

    auto required() -> DerivedT& {
        m_optionality = Optionality::Required;
        return static_cast<DerivedT&>(*this);
    };

    auto isOptional() const -> bool { return m_optionality == Optionality::Optional; }

    auto cardinality() const -> size_t override {
        if (m_ref->isContainer())
            return 0;
        else
            return 1;
    }

    auto hint() const -> std::string { return m_hint; }
};

class ExeName : public ComposableParserImpl<ExeName> {
    std::shared_ptr<std::string> m_name;
    std::shared_ptr<BoundRefBase> m_ref;

    template <typename LambdaT>
    static auto makeRef(LambdaT const& lambda) -> std::shared_ptr<BoundRefBase> {
        return std::make_shared<BoundLambda<LambdaT>>(lambda);
    }

   public:
    ExeName() : m_name(std::make_shared<std::string>("<executable>")) {}

    explicit ExeName(std::string& ref) : ExeName() {
        m_ref = std::make_shared<BoundRef<std::string>>(ref);
    }

    template <typename LambdaT>
    explicit ExeName(LambdaT const& lambda) : ExeName() {
        m_ref = std::make_shared<BoundLambda<LambdaT>>(lambda);
    }

    // The exe name is not parsed out of the normal tokens, but is handled specially
    auto parse(std::string const&, TokenStream const& tokens) const
        -> InternalParseResult override {
        return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, tokens));
    }

    auto name() const -> std::string { return *m_name; }
    auto set(std::string const& newName) -> ParserResult {
        auto lastSlash = newName.find_last_of("\\/");
        auto filename = (lastSlash == std::string::npos) ? newName : newName.substr(lastSlash + 1);

        *m_name = filename;
        if (m_ref)
            return m_ref->setValue(filename);
        else
            return ParserResult::ok(ParseResultType::Matched);
    }
};

class Arg : public ParserRefImpl<Arg> {
   public:
    using ParserRefImpl::ParserRefImpl;

    auto parse(std::string const&, TokenStream const& tokens) const
        -> InternalParseResult override {
        auto validationResult = validate();
        if (!validationResult) return InternalParseResult(validationResult);

        auto remainingTokens = tokens;
        auto const& token = *remainingTokens;
        if (token.type != TokenType::Argument)
            return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, remainingTokens));

        auto result = m_ref->setValue(remainingTokens->token);
        if (!result)
            return InternalParseResult(result);
        else
            return InternalParseResult::ok(ParseState(ParseResultType::Matched, ++remainingTokens));
    }
};

inline auto normaliseOpt(std::string const& optName) -> std::string {
#ifdef CLARA_PLATFORM_WINDOWS
    if (optName[0] == '/')
        return "-" + optName.substr(1);
    else
#endif
        return optName;
}

class Opt : public ParserRefImpl<Opt> {
   protected:
    std::vector<std::string> m_optNames;

   public:
    template <typename LambdaT>
    explicit Opt(LambdaT const& ref)
        : ParserRefImpl(std::make_shared<BoundFlagLambda<LambdaT>>(ref)) {}

    explicit Opt(bool& ref) : ParserRefImpl(std::make_shared<BoundFlagRef>(ref)) {}

    template <typename LambdaT>
    Opt(LambdaT const& ref, std::string const& hint) : ParserRefImpl(ref, hint) {}

    template <typename T>
    Opt(T& ref, std::string const& hint) : ParserRefImpl(ref, hint) {}

    auto operator[](std::string const& optName) -> Opt& {
        m_optNames.push_back(optName);
        return *this;
    }

    auto getHelpColumns() const -> std::vector<HelpColumns> {
        std::ostringstream oss;
        bool first = true;
        for (auto const& opt : m_optNames) {
            if (first)
                first = false;
            else
                oss << ", ";
            oss << opt;
        }
        if (!m_hint.empty()) oss << " <" << m_hint << ">";
        return {{oss.str(), m_description}};
    }

    auto isMatch(std::string const& optToken) const -> bool {
        auto normalisedToken = normaliseOpt(optToken);
        for (auto const& name : m_optNames) {
            if (normaliseOpt(name) == normalisedToken) return true;
        }
        return false;
    }

    using ParserBase::parse;

    auto parse(std::string const&, TokenStream const& tokens) const
        -> InternalParseResult override {
        auto validationResult = validate();
        if (!validationResult) return InternalParseResult(validationResult);

        auto remainingTokens = tokens;
        if (remainingTokens && remainingTokens->type == TokenType::Option) {
            auto const& token = *remainingTokens;
            if (isMatch(token.token)) {
                if (m_ref->isFlag()) {
                    auto result = m_ref->setFlag(true);
                    if (!result) return InternalParseResult(result);
                    if (result.value() == ParseResultType::ShortCircuitAll)
                        return InternalParseResult::ok(ParseState(result.value(), remainingTokens));
                } else {
                    ++remainingTokens;
                    if (!remainingTokens)
                        return InternalParseResult::runtimeError("Expected argument following " +
                                                                 token.token);
                    auto const& argToken = *remainingTokens;
                    if (argToken.type != TokenType::Argument)
                        return InternalParseResult::runtimeError("Expected argument following " +
                                                                 token.token);
                    auto result = m_ref->setValue(argToken.token);
                    if (!result) return InternalParseResult(result);
                    if (result.value() == ParseResultType::ShortCircuitAll)
                        return InternalParseResult::ok(ParseState(result.value(), remainingTokens));
                }
                return InternalParseResult::ok(
                    ParseState(ParseResultType::Matched, ++remainingTokens));
            }
        }
        return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, remainingTokens));
    }

    auto validate() const -> Result override {
        if (m_optNames.empty()) return Result::logicError("No options supplied to Opt");
        for (auto const& name : m_optNames) {
            if (name.empty()) return Result::logicError("Option name cannot be empty");
#ifdef CLARA_PLATFORM_WINDOWS
            if (name[0] != '-' && name[0] != '/')
                return Result::logicError("Option name must begin with '-' or '/'");
#else
            if (name[0] != '-') return Result::logicError("Option name must begin with '-'");
#endif
        }
        return ParserRefImpl::validate();
    }
};

struct Help : Opt {
    Help(bool& showHelpFlag)
        : Opt([&](bool flag) {
              showHelpFlag = flag;
              return ParserResult::ok(ParseResultType::ShortCircuitAll);
          }) {
        static_cast<Opt&> (*this)("display usage information")["-?"]["-h"]["--help"].optional();
    }
};


struct Parser : ParserBase {
    mutable ExeName m_exeName;
    std::vector<Opt> m_options;
    std::vector<Arg> m_args;

    auto operator|=(ExeName const& exeName) -> Parser& {
        m_exeName = exeName;
        return *this;
    }

    auto operator|=(Arg const& arg) -> Parser& {
        m_args.push_back(arg);
        return *this;
    }

    auto operator|=(Opt const& opt) -> Parser& {
        m_options.push_back(opt);
        return *this;
    }

    auto operator|=(Parser const& other) -> Parser& {
        m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end());
        m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end());
        return *this;
    }

    template <typename T>
    auto operator|(T const& other) const -> Parser {
        return Parser(*this) |= other;
    }

    auto getHelpColumns() const -> std::vector<HelpColumns> {
        std::vector<HelpColumns> cols;
        for (auto const& o : m_options) {
            auto childCols = o.getHelpColumns();
            cols.insert(cols.end(), childCols.begin(), childCols.end());
        }
        return cols;
    }

    void writeToStream(std::ostream& os) const {
        if (!m_exeName.name().empty()) {
            os << "usage:\n"
               << "  " << m_exeName.name() << " ";
            bool required = true, first = true;
            for (auto const& arg : m_args) {
                if (first)
                    first = false;
                else
                    os << " ";
                if (arg.isOptional() && required) {
                    os << "[";
                    required = false;
                }
                os << "<" << arg.hint() << ">";
                if (arg.cardinality() == 0) os << " ... ";
            }
            if (!required) os << "]";
            if (!m_options.empty()) os << " options";
            os << "\n\nwhere options are:" << std::endl;
        }

        auto rows = getHelpColumns();
        size_t consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH;
        size_t optWidth = 0;
        for (auto const& cols : rows) optWidth = (std::max)(optWidth, cols.left.size() + 2);

        for (auto const& cols : rows) {
            auto row = TextFlow::Column(cols.left).width(optWidth).indent(2) + TextFlow::Spacer(4) +
                       TextFlow::Column(cols.right).width(consoleWidth - 7 - optWidth);
            os << row << std::endl;
        }
    }

    friend auto operator<<(std::ostream& os, Parser const& parser) -> std::ostream& {
        parser.writeToStream(os);
        return os;
    }

    auto validate() const -> Result override {
        for (auto const& opt : m_options) {
            auto result = opt.validate();
            if (!result) return result;
        }
        for (auto const& arg : m_args) {
            auto result = arg.validate();
            if (!result) return result;
        }
        return Result::ok();
    }

    using ParserBase::parse;

    auto parse(std::string const& exeName, TokenStream const& tokens) const
        -> InternalParseResult override {
        struct ParserInfo {
            ParserBase const* parser = nullptr;
            size_t count = 0;
        };
        const size_t totalParsers = m_options.size() + m_args.size();
        assert(totalParsers < 512);
        // ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do
        ParserInfo parseInfos[512];

        {
            size_t i = 0;
            for (auto const& opt : m_options) parseInfos[i++].parser = &opt;
            for (auto const& arg : m_args) parseInfos[i++].parser = &arg;
        }

        m_exeName.set(exeName);

        auto result = InternalParseResult::ok(ParseState(ParseResultType::NoMatch, tokens));
        while (result.value().remainingTokens()) {
            bool tokenParsed = false;

            for (size_t i = 0; i < totalParsers; ++i) {
                auto& parseInfo = parseInfos[i];
                if (parseInfo.parser->cardinality() == 0 ||
                    parseInfo.count < parseInfo.parser->cardinality()) {
                    result = parseInfo.parser->parse(exeName, result.value().remainingTokens());
                    if (!result) return result;
                    if (result.value().type() != ParseResultType::NoMatch) {
                        tokenParsed = true;
                        ++parseInfo.count;
                        break;
                    }
                }
            }

            if (result.value().type() == ParseResultType::ShortCircuitAll) return result;
            if (!tokenParsed)
                return InternalParseResult::runtimeError("Unrecognised token: " +
                                                         result.value().remainingTokens()->token);
        }
        // !TBD Check missing required options
        return result;
    }
};

template <typename DerivedT>
template <typename T>
auto ComposableParserImpl<DerivedT>::operator|(T const& other) const -> Parser {
    return Parser() | static_cast<DerivedT const&>(*this) | other;
}
}  // namespace detail


// A Combined parser
using detail::Parser;

// A parser for options
using detail::Opt;

// A parser for arguments
using detail::Arg;

// Wrapper for argc, argv from main()
using detail::Args;

// Specifies the name of the executable
using detail::ExeName;

// Convenience wrapper for option parser that specifies the help option
using detail::Help;

// enum of result types from a parse
using detail::ParseResultType;

// Result type for parser operation
using detail::ParserResult;


}  // namespace clara

#endif  // CLARA_HPP_INCLUDED
