// -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 4 -*-

/*
 * (c) 2015-2016 Petr Ročkai <code@fixp.eu>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#pragma once

#include <vector>
#include <deque>
#include <stack>
#include <unordered_map>

#include <brick-unittest>
#include <brick-types>
#include <brick-except>

namespace brick {
namespace cmd {

using Tokens = std::vector< std::string >;
enum ErrorType { BadContent, BadFormat };

struct Error
{
    std::string _description;
    ErrorType _type;
};

template< typename T >
using ErrorOr = types::Union< Error, T >;

struct ValidatorInterface
{
    virtual bool validate( std::string t, std::string v ) = 0;
    virtual ~ValidatorInterface() {}
};

template< typename... > struct Validator;

static const auto ignore2 = []( auto, auto ) {};

template< typename Parse, typename... Ps >
struct Validator< Parse, Ps... >
    : ValidatorInterface, std::enable_shared_from_this< Validator< Parse, Ps... > >
{
    using Next = std::shared_ptr< Validator< Ps... > >;

    std::string _name;
    Parse _parse;
    Next _next;


    bool validate( std::string t, std::string v ) override
    {
        if ( t != _name && _next )
            return _next->validate( t, v );

        /* TODO */
        bool result = true;
        _parse( v, [&]( auto ) { result = true; },
                [&]( ErrorType e, auto ) { result = e != BadFormat; } );
        return result;
    }

    template< typename S >
    void parse( S set, std::string t, std::string v )
    {
        if ( _name != t )
            return _next->parse( set, t, v );
        return _parse( v, set,
                       []( auto, std::string s )
                       {
                           throw std::logic_error( "validator error: " + s );
                       } );
    }

    Validator( std::string t, Parse parse, Next next )
        : _name( t ), _parse( parse ), _next( next )
    {}

    virtual ~Validator() {}

    template< typename P >
    auto add( std::string t, P parse )
    {
        return std::make_shared< Validator< P, Parse, Ps... > >(
            t, parse, this->shared_from_this() );
    }
};

template<>
struct Validator<> : ValidatorInterface, std::enable_shared_from_this< Validator<> >
{
    bool validate( std::string t, std::string ) override
    {
        throw std::logic_error( "Unknown capture type " + t );
    }

    template< typename S >
    void parse( S, std::string t, std::string )
    {
        throw std::logic_error( "Unknown or mismatched capture of type " + t );
    }

    template< typename P >
    auto add( std::string t, P parse )
    {
        return std::make_shared< Validator< P > >( t, parse, this->shared_from_this() );
    }
};

using ValidatorPtr = std::shared_ptr< ValidatorInterface >;

namespace {

auto make_validator()
{
    return std::make_shared< Validator<> >()->
        add( "string", []( std::string s, auto good, auto ) { return good( s ); } ) ->
        add( "int", []( std::string s, auto good, auto bad )
             {
                int r = 0;
                try {
                     r = std::stoi( s );
                } catch ( std::invalid_argument &e ) {
                    return bad( BadFormat, e.what() );
                } catch ( std::out_of_range &e ) {
                    return bad( BadFormat, e.what() );
                }
                return good( r );
             } );
}

std::pair< std::string, std::string > next_token( std::string s )
{
    ASSERT( !s.empty() );
    std::stack< char > stack;

    int skip = 0;
    int i = 0; /* index */

    for ( ; skip < int( s.size() ); ++skip )
        if ( !std::isblank( s[skip] ) )
            break;

    for ( i = skip; i < int( s.size() ); ++i )
    {
        if ( stack.empty() )
            if ( ( i > skip && s[i] == '|' ) || std::isblank( s[i] ) )
                break;
        if ( s[i] == '[' || s[i] == '(' )
            stack.push( s[i] );
        if ( s[i] == ']' || s[i] == ')' )
        {
            if ( stack.empty() )
                break;
            if ( ( stack.top() == '(' && s[i] != ')' ) ||
                 ( stack.top() == '[' && s[i] != ']' ) )
                throw std::logic_error( "Mismatched () or [] in a pattern: " + s );
            stack.pop();
            if ( stack.empty() )
            {
                ++ i;
                break;
            }
        }
    }

    int j = i;
    while ( std::isblank( s[j] ) && j < int( s.size() ) )
        ++ j;

    return std::make_pair( std::string( s, skip, i ),
                           std::string( s, j, std::string::npos ) );
}

}

/*
 * Patterns (represented as format strings):
 * - tokens are whitespace-separated
 * - [x|y] matches either x or y
 * - {x} matches (and captures) a value of type x [bool, int, string, ...]
 * - [,{int}]+ matches zero or more times
 * - constant (no quantifier) {...} captures produce tuples
 * - variable (with the + quantifier) capture produces vectors
 * - all captures in a quantified fmt string must be of the same type
 * - to capture a list of tuples, register a new type
 *
 * - the (regular) language for a set of patterns is obtained as [x|y|...]+ for
 *   all components x, y, ... unioned with .*X.*, .*Y.*, etc. for all X, Y
 *   non-optional
 */

struct Pattern
{
    /*
     * Match: prefix match
     * Mixed: full-string match with (possibly multiple) captures
     */
    enum { Match, Capture, Group, Mixed, Empty } _type;
    std::string _text;
    std::string _capture;
    int _capture_id;
    ValidatorPtr _validator;
    bool _required, _many;
    using Ptr = std::shared_ptr< Pattern >;

    Ptr _next;
    Ptr _alt;

    std::vector< Ptr > _subs;

    std::string fmt()
    {
        std::string str;

        if ( _type == Group )
            for ( auto a : _subs )
            {
                if ( a->_type == Group && a->_required )
                    str += "(" + a->fmt() + ")|";
                else
                    str += a->fmt() + "|";
            }

        str = std::string( str, 0, str.length() - 1 );

        if ( _type == Capture )
            str = "{" + _capture + "}";
        if ( _type == Match )
            str = _text;
        if ( _type == Mixed )
            str = _text + "{" + _capture + "}";
        if ( !_required )
            str = "[" + str + "]";
        else if ( _next && _type == Group )
            str = "(" + str + ")";
        if ( _next )
            str += " " + _next->fmt();
        return str;
    }

    Tokens::iterator match( Tokens::iterator begin, Tokens::iterator end )
    {
        auto ignore = []( int, bool, std::string, std::string ) {};
        return match( ignore, begin, end );
    }

    template< typename C >
    Tokens::iterator match( C capture, Tokens::iterator begin, Tokens::iterator end )
    {
        _capture_id = 0;
        return match( capture, begin, end, begin );
    }

    template< typename C >
    Tokens::iterator match( C capture, Tokens::iterator begin, Tokens::iterator end,
                            Tokens::iterator fail )
    {
        Tokens::iterator next = begin;
        if ( _type == Match && *begin == _text )
            next = begin + 1;
        if ( _type == Capture && _validator->validate( _capture, *begin ) )
        {
            capture( _capture_id++, _many, _capture, *begin );
            next = begin + 1;
        }
        if ( _type == Mixed && begin->find( _text ) == 0 && begin->size() > _text.size() )
        {
            std::string rest = begin->substr( _text.length() );
            if ( _validator->validate( _capture, rest ) )
            {
                capture( _capture_id++, false, _capture, rest );
            }
            next = begin + 1;
        }
        if ( _type == Group )
        {
            std::vector< Tokens::iterator > res;
            for ( auto a : _subs )
                res.push_back( a->match( capture, begin, end ) );
            int count = 0;
            for ( auto r : res )
                if ( r > next )
                    next = r, count = 0;
                else if ( r == next && next > begin )
                    ++ count;
            if ( count )
            {
                std::string line, alts;
                for ( auto i = begin; i != next; ++i )
                    line += " " + *i;
                for ( int i = 0; i < int( res.size() ); ++i )
                    if ( res[i] == next )
                        alts += "\n matched " + _subs[i]->fmt();
                throw except::Error( "Ambiguous parse:" + line + alts );
            }
        }

        if ( _required && next == begin )
            return fail;
        if ( _next )
            next = _next->match( capture, next, end, fail );
        return next;
    }

    Pattern( ValidatorPtr v )
        : _type( Empty ), _validator( v ), _required( true ), _many( false )
    {}

    Pattern( ValidatorPtr v, std::string s )
        : Pattern( v )
    {
        create( s, this );
    }

    void simplify()
    {
        bool r = _required;
        if ( _type == Group && _subs.size() == 1 && _subs[0]->_type == Group )
        {
            auto a = _subs[0];
            *this = *a;
            _required = _required && r;
        }
    }

    bool create( std::string s, Pattern *prev_alt )
    {
        std::string me, rest;
        bool alt = false;
        std::tie( me, rest ) = next_token( s );
        ASSERT( !me.empty() );

        if ( me[0] == '|' )
        {
            alt = true;
            me = std::string( me, 1, me.size() - 1 );
        }

        if ( me[0] == '[' || me[0] == '(' )
        {
            _subs.emplace_back( new Pattern( _validator, std::string( me, 1, me.size() - 2 ) ) );
            _type = Group;
            if ( me[0] == '[' )
                _required = false;
            simplify();
        }

        else if ( me[0] == '{' )
        {
            _type = Capture;
            _capture = std::string( me, 1, me.size() - 2 );
            if ( me.back() == '+' )
            {
                _capture = std::string( _capture, 0, _capture.size() - 1 );
                _many = true;
            }
        }

        else
        {
            auto capture = me.find( "{" );
            if ( capture != std::string::npos )
            {
                _text = std::string( me, 0, capture );
                _capture = std::string( me, capture + 1, me.size() - capture - 2 );
                _type = Mixed;
            }
            else
            {
                _type = Match;
                _text = me;
            }
        }

        if ( !rest.empty() )
        {
            auto n = new Pattern( _validator );
            if ( n->create( rest, alt ? this : prev_alt ) )
                _next.reset( n );

            if ( prev_alt == this && _alt )
            {
                ASSERT( _type != Empty );
                Ptr a( new Pattern( *this ) );
                *this = Pattern( _validator );
                _type = Group;
                while ( a )
                {
                    _subs.emplace_back( a );
                    a = a->_alt;
                    _subs.back()->_alt.reset();
                }
            }
        }

        if ( alt )
        {
            ASSERT( !prev_alt->_alt );
            prev_alt->_alt.reset( this );
            return false;
        };

        return true;
    }
};

enum class OptionFlag { Unique = 1, Final = 2, Required = 4 };
using OptionFlags = brick::types::StrongEnumFlags< OptionFlag >;

template< typename T >
struct OptionInterface
{
    virtual Tokens::iterator parse( T &edit, Tokens::iterator, Tokens::iterator ) = 0;
    virtual Tokens::iterator match( Tokens::iterator, Tokens::iterator ) = 0;
    virtual OptionFlags flags() = 0;
    virtual int priority() = 0;
    virtual std::string fmt() = 0;
    virtual std::string describe() { return ""; }
    virtual ~OptionInterface() {}
};

template< typename Validator, typename T, typename V >
struct Option : OptionInterface< T >
{
    using Get = std::function< V &( T & ) >;
    std::shared_ptr< Validator > _validator;
    Pattern _pattern;
    Get _get;
    std::string _descr;
    OptionFlags _flags;

    template< typename X >
    struct Set
    {
        X &x;
        Set( X &x ) : x( x ) {}

        template< typename Y >
        void operator()( Y y ) { _set( types::Preferred(), x, y ); }

        template< typename X_, typename Y >
        auto _set( types::Preferred, X_ &x, Y y )
            -> typename std::enable_if< std::is_convertible< Y, typename X_::value_type >::value >::type
        {
            x.push_back( y );
        }

        template< typename X_, typename Y >
        auto _set( types::NotPreferred, X_ &x, Y y )
            -> typename std::enable_if< std::is_convertible< Y, X_ >::value >::type
        {
            x = y;
        }

        template< typename X_, typename Y >
        auto _set( types::NotPreferred, X_ &, Y )
            -> typename std::enable_if< !std::is_convertible< Y, X_ >::value >::type
        {
            UNREACHABLE( "capture type mismatch" );
        }
    };

    void set_true( bool &edit )
    {
        edit = true;
    }

    template< typename E >
    void set_true( E & )
    {
        UNREACHABLE( "cannot set true to not bool" );
    }

    Tokens::iterator parse( T &edit, Tokens::iterator b, Tokens::iterator e ) override
    {
        bool captured = false;
        auto result = _pattern.match(
            [&]( int, bool, std::string type, std::string val )
            {
                _validator->parse( Set< decltype( _get( edit ) ) >( _get( edit ) ), type, val );
                captured = true;
            }, b, e );
        if ( !captured )
            set_true( _get( edit ) );
        return result;
    }

    Tokens::iterator match( Tokens::iterator b, Tokens::iterator e ) override
    {
        return _pattern.match( b, e );
    }

    OptionFlags flags() override
    {
        if ( _pattern._required )
            return _flags | OptionFlag::Required;
        return _flags;
    }

    std::string fmt() override { return _pattern.fmt(); };
    std::string describe() override
    {
        std::stringstream str;
        str << std::setw(30) << fmt() << "   " << _descr;
        return str.str();
    };

    int priority( Pattern &p )
    {
        int r = 0;
        switch ( p._type )
        {
            case Pattern::Capture: return 0;
            case Pattern::Mixed: return 1;
            case Pattern::Match : return 2;
            case Pattern::Group:
                for ( size_t i = 0; i < p._subs.size(); ++i )
                    r = std::max( r, priority( *p._subs[i] ) );
                return r;
            default: UNREACHABLE( "impossible pattern type" );
        }
    }

    int priority() override { return priority( _pattern ); }

    Option( std::shared_ptr< Validator > v, std::string fmt, Get get, std::string descr,
            OptionFlags flags = OptionFlags() )
            : _validator( v ), _pattern( v, fmt ), _get( get ), _descr( descr ), _flags( flags )
    {}
};

template< typename T, typename S >
struct OptionWrapper : OptionInterface< T >
{
    using Get = std::function< S &( T & ) >;
    using Sub = std::shared_ptr< OptionInterface< S > >;

    Get _get;
    Sub _sub;

    OptionWrapper( Sub sub, Get get ) : _get( get ), _sub( sub ) {}

    Tokens::iterator parse( T &edit, Tokens::iterator b, Tokens::iterator e ) override
    {
        return _sub->parse( _get( edit ), b, e );
    }

    Tokens::iterator match( Tokens::iterator b, Tokens::iterator e ) override
    {
        return _sub->match( b, e );
    }

    OptionFlags flags() override { return _sub->flags(); }
    int priority() override { return _sub->priority(); }
    std::string fmt() override { return _sub->fmt(); }
    std::string describe() override { return _sub->describe(); }
};

template< typename T >
using OptionPtr = std::shared_ptr< OptionInterface< T > >;

template< typename T, typename Value, typename Validator >
OptionPtr< T > make_option( std::shared_ptr< Validator > validator, std::string pattern,
                  Value T::*member, std::string descr = "", OptionFlags flags = OptionFlags() )
{
    return std::make_shared< Option< Validator, T, Value > >(
        validator, pattern, [member]( auto &x ) -> auto& { return x.*member; }, descr, flags );
}

template< typename Validator, typename T >
struct OptionSet
{
    std::shared_ptr< Validator > _validator;
    std::vector< OptionPtr< T > > _opts;

    template< typename V >
    auto &option( std::string fmt, V T::*member, std::string desc = "",
                  OptionFlags flags = OptionFlags() )
    {
        _opts.emplace_back( make_option( _validator, fmt, member, desc, flags ) );
        return *this;
    };

    template< typename... Args >
    auto &options( OptionPtr< T > opt, Args... args )
    {
        _opts.emplace_back( opt );
        return options( args... );
    };

    template< typename S, typename... Args >
    auto &options( OptionSet< Validator, S > opts, S T::*member, Args... args )
    {
        for ( auto o : opts._opts )
            _opts.emplace_back(
                new OptionWrapper< T, S >(
                    o, [member]( T &x ) -> S& { return x.*member; } ) );
        return options( args... );
    }

    template< typename S, typename... Args >
    auto &options( OptionSet< Validator, S > opts, Args... args )
    {
        for ( auto o : opts._opts )
            _opts.emplace_back(
                new OptionWrapper< T, S >(
                    o, []( T &x ) -> S& { return x; } ) );
        return options( args... );
    }

    template< typename... Args >
    explicit OptionSet( std::shared_ptr< Validator > v, Args... args )
        : _validator( v )
    {
        options( args... );
    }

    auto &options() { return *this; }

    virtual std::string describe()
    {
        return fmt();
    }

    std::string fmt()
    {
        std::stringstream s;
        for ( auto o : _opts )
            s << "  " << o->describe() << std::endl;
        return s.str();
    }

    Tokens::iterator parse( T &t, Tokens::iterator b, Tokens::iterator e )
    {
        std::set< OptionPtr< T > > parsed;
        while ( b != e )
        {
            std::set< OptionPtr< T > > matched, selected;
            int prio = 0;
            for ( auto o : _opts )
                if ( o->match( b, e ) != b )
                    matched.insert( o );
            for ( auto &o : matched )
                prio = std::max( prio, o->priority() );
            for ( auto &o : matched )
                if ( o->priority() == prio )
                    selected.insert( o );
            if ( selected.size() > 1 )
            {
                std::string alts;
                for ( auto o : selected )
                    alts += "\n" + o->fmt();
                throw except::Error( "Ambiguous option " + *b + ":" + alts );
            }
            if ( selected.empty() )
                break;
            auto o = *selected.begin();
            if ( parsed.count( o ) && ( o->flags() & OptionFlag::Unique ) )
                throw except::Error( "Option " + o->fmt() + " given more than once\n" );
            parsed.insert( o );

            b = o->parse( t, b, e );
            if ( o->flags() & OptionFlag::Final )
                break;
        }
        for ( auto o : _opts )
            if ( ( o->flags() & OptionFlag::Required ) && !parsed.count( o ) )
                throw except::Error( "Missing option: " + o->fmt() + ", expected:\n"
                                     + this->describe() );
        return b;
    }

    virtual ~OptionSet() {}
};

template< typename T, typename V, typename... Args >
auto make_option_set( std::shared_ptr< V > v, Args... args )
{
    return OptionSet< V, T >( v, args... );
}

template< typename Validator, typename T >
struct Command : OptionSet< Validator, T >
{
    std::vector< std::string > T::*_extra;
    std::string _description;

    /* not foolproof, but good enough? will misfire on types nested inside
     * templates */
    std::string name()
    {
        auto s = brick::unittest::_typeid< T >();
        size_t p = s.find( "<" );
        if ( p != std::string::npos )
            s = std::string( s, 0, p );
        p = s.rfind( "::" );
        if ( p != std::string::npos )
            s = std::string( s, p + 2, std::string::npos );
        for ( size_t i = 0; i < s.size(); ++i )
            s[i] = std::tolower( s[i] );
        return s;
    }

    virtual std::string describe_header( int setw = 0 )
    {
        std::stringstream req;
        for ( auto o : this->_opts )
            if ( o->flags() & OptionFlag::Required )
                req << o->fmt() << " ";
        std::stringstream res;
        res << std::setw( setw ) << name() <<  " [options] " << req.str() << " " << _description;
        return res.str();
    }

    virtual std::string describe()
    {
        return describe_header() + "\n" + this->fmt();
    }

    bool extra( T &t, std::string e )
    {
        if ( _extra )
            (t.*_extra).push_back( e );
        return _extra;
    }

    template< typename... Args >
    Command( std::shared_ptr< Validator > v, Args... args )
        : OptionSet< Validator, T >( v, args... ), _extra( nullptr )
    {}
};

template< typename... > struct ParserT;

template< typename Validator, typename T, typename... Ts >
struct ParserT< Validator, T, Ts... >
{
    Command< Validator, T > car;
    ParserT< Validator, Ts... > cdr;
    std::shared_ptr< Validator > validator;

    using Type = T;
    using Types = types::Union< T, Ts... >;

    template< typename... Args >
    ParserT( std::shared_ptr< Validator > v, ParserT< Validator, Ts... > cdr,
             Args... args ) : car( v, args... ), cdr( cdr ), validator( v ) {}
};

template< typename Validator >
struct ParserT< Validator >
{
    std::shared_ptr< Validator > validator;
    ParserT( std::shared_ptr< Validator > v ) : validator( v ) {}
};

template< typename Validator, typename... Ts >
struct Parser
{
    using PT = ParserT< Validator, Ts... >;
    PT _rep;

    template< typename U >
    auto parse( U &res, Tokens::iterator b, Tokens::iterator e )
    {
        return parse_rec( _rep, res, b, e );
    }

    template< typename U >
    auto parse_rec( ParserT< Validator > &, U &, Tokens::iterator b, Tokens::iterator )
    {
        return b;
    }

    template< typename Rec, typename U >
    auto parse_rec( Rec &rec, U &res, Tokens::iterator b, Tokens::iterator e )
    {
        if (b == e)
        {
            return b;
        }
        if ( rec.car.name() == *b )
        {
            typename Rec::Type t;
            auto x = rec.car.parse( t, b + 1, e );
            while ( x != e && rec.car.extra( t, *x ) )
                ++ x;
            res = t;
            return x;
        }
        else
            return parse_rec( rec.cdr, res, b, e );
    }

    auto parse( Tokens::iterator b, Tokens::iterator e, std::vector<std::string> &unexpected )
    {
        typename PT::Types _result;
        auto x = parse( _result, b, e );
        while ( x != e )
            unexpected.push_back( *x++ );
        return _result;
    };

    auto parse( Tokens::iterator b, Tokens::iterator e )
    {
        std::vector< std::string > unexpected;
        auto _result = parse( b, e, unexpected );
        if ( !unexpected.empty() )
        {
            std::stringstream extra;
            for (std::string str : unexpected)
                extra << "'" << str << "' ";
            throw except::Error( "unexpected options: " + extra.str() );
        }
        return _result;
    }

    template< typename T, typename... Args >
    auto command( Args... args )
    {
        return Parser< Validator, T, Ts... >(
            ParserT< Validator, T, Ts... >( _rep.validator, _rep, args... ) );
    }

    template< typename T, typename U, typename... Args >
    auto command( Tokens (U::*extra), Args... args )
    {
        auto cmd = command< T >( args... );
        cmd._rep.car._extra = extra;
        return cmd;
    }

    template< typename T, typename... Args >
    auto command( std::string dsc, Args... args )
    {
        auto cmd = command< T >( args... );
        cmd._rep.car._description = dsc;
        return cmd;
    }

    std::string describe( std::string name = "" ) { return describe_rec( _rep, name ); }

    template< typename Rec >
    std::string describe_rec( Rec &rec, std::string name )
    {
        if ( name.empty() ) {
            auto d = describe_rec( rec.cdr, name );
            if ( !d.empty() )
                d += "\n";
            return d + rec.car.describe_header( 10 );
        }
        if ( rec.car.name() == name )
            return rec.car.describe();
        return describe_rec( rec.cdr, name );
    }

    std::string describe_rec( ParserT< Validator > &, std::string ) { return ""; }

    Parser( PT rep ) : _rep( rep ) {}
};

template< typename V >
auto make_parser( std::shared_ptr< V > v )
{
    return Parser< V >( ParserT< V >( v ) );
}

namespace {

std::vector< std::string > from_argv( int argc, const char **argv )
{
    std::vector< std::string > args;
    std::copy( argv + 1, argv + argc, std::back_inserter( args ) );
    return args;
}

}

}

namespace t_cmd {

struct Common
{
    int a, b;
    bool x,y;
    int range;
    std::vector< std::string > cflags;
    std::vector< bool > cbool;
};

struct Foo : Common
{
    bool bar;
    int value;
};

struct Bar : Common {};

struct TestCmd
{
    auto validator()
    {
        return cmd::make_validator();
    }

    TEST(pattern_fmt)
    {
        auto v = validator();

        cmd::Pattern p( v );
        cmd::Pattern l( v );
        cmd::Pattern r( v );
        p._type = cmd::Pattern::Group;
        l._type = cmd::Pattern::Match;
        l._text = "foo";
        r._type = cmd::Pattern::Match;
        r._text = "for";
        p._subs.emplace_back( new cmd::Pattern( l ) );
        p._subs.emplace_back( new cmd::Pattern( r ) );
        ASSERT_EQ( p.fmt(), "foo|for" );
        p._next.reset( new cmd::Pattern( v, "bar" ) );
        ASSERT_EQ( p.fmt(), "(foo|for) bar" );
        p._next.reset();
        p._subs[0]->_next.reset( new cmd::Pattern( v, "bar" ) );
        p._subs[1]->_next.reset( new cmd::Pattern( v, "baz" ) );
        ASSERT_EQ( p.fmt(), "foo bar|for baz" );
        p._required = false;
        ASSERT_EQ( p.fmt(), "[foo bar|for baz]" );
        p._subs.emplace_back( new cmd::Pattern( v, "x" ) );
        ASSERT_EQ( p.fmt(), "[foo bar|for baz|x]" );
    }

    TEST(pattern_match)
    {
        auto v = validator();
        cmd::Pattern p( v, "foo|for" );

        cmd::Tokens in = { "foo" };
        ASSERT( p.match( in.begin(), in.end() ) == in.end() );

        in = { "foo", "foo" };
        ASSERT( p.match( in.begin(), in.end() ) == in.begin() + 1 );

        cmd::Pattern q( v, "(foo|for) foo" );
        in = { "foo", "foo" };
        ASSERT( q.match( in.begin(), in.end() ) == in.end() );
    }

    void check_parse( std::string s )
    {
        auto v = validator();
        cmd::Pattern p( v, s );
        ASSERT_EQ( p.fmt(), s );
    }

    TEST(pattern_parse)
    {
        check_parse( "[a|b]" );
        check_parse( "a|b" );
        check_parse( "a x|b x" );
        check_parse( "[a x|b x]" );
        check_parse( "a [x]|b x" );
        check_parse( "(a|b) c" );
        check_parse( "(a|b)|c" );
        check_parse( "a|b|c" );
        check_parse( "[a|b|c]" );
        check_parse( "a|(b|c)" );
        check_parse( "a b|[b c]" );
        check_parse( "[a b]|b c" );
    }

    TEST(cmd_parse)
    {
        auto v = validator();

        auto common = cmd::make_option_set< Common >( v )
                      .option( "[-r {int}|--range {int}]", &Common::range )
                      .option( "[-Wc,{string}|-Xcompiler {string}]", &Common::cflags );

        auto p = cmd::make_parser( v )
                 .command< Foo >( common,
                                  cmd::make_option( v, "[--bar|--no-bar]", &Foo::bar ),
                                  cmd::make_option( v, "{int}", &Foo::value ) )
                 .command< Bar >( common );

        ASSERT_EQ( p._rep.car.name(), "bar" );

        cmd::Tokens in = { "foo", "32" };
        bool is_foo = false;

        auto r = p.parse( in.begin(), in.end() );
        r.match( [&]( Foo f ) { is_foo = true; ASSERT_EQ( f.value, 32 ); },
                 [&]( Bar ) { is_foo = false; } );

        ASSERT( is_foo );
    }

    TEST(option)
    {
        auto v = validator();
        auto opts = cmd::make_option_set< Common >( v )
                    .option( "[-a {int}]", &Common::a )
                    .option( "[-b {int}]", &Common::b );
        Common c;

        cmd::Tokens in = { "-a", "10" };
        opts.parse( c, in.begin(), in.end() );
        ASSERT_EQ( c.a, 10 );
    }

    TEST(option_bool)
    {
        auto v = validator();
        auto opts = cmd::make_option_set< Common >( v )
                    .option( "[-x]", &Common::x )
                    .option( "[-y]", &Common::y );
        Common c = { .x = false, .y = false };

        cmd::Tokens in = { "-x" };
        opts.parse( c, in.begin(), in.end() );
        ASSERT( c.x );
        ASSERT( !c.y );
    }

    TEST(option_bool_match)
    {
        auto v = validator();
        auto opts = cmd::make_option_set< Common >( v )
                    .option( "[-x]", &Common::x )
                    .option( "[-y]", &Common::y )
                    .option( "[(-a {int}|-b {int})]", &Common::a );

        Common c = { .x = false, .y = false };

        cmd::Tokens in = { "-x", "-b", "10", "-y" };
        opts.parse( c, in.begin(), in.end() );
        ASSERT_EQ( c.a, 10 );
        ASSERT( c.x );
        ASSERT( c.y );
    }

    TEST(option_match_alt)
    {
        auto v = validator();
        auto opts = cmd::make_option_set< Common >( v )
                    .option( "[(-a|-b) {int}]", &Common::a );

        Common c;

        cmd::Tokens in = { "-b", "10" };
        opts.parse( c, in.begin(), in.end() );
        ASSERT_EQ( c.a, 10 );
    }

    TEST(nonoption_int)
    {
        struct X { int a, b; };
        auto v = validator();
        auto opts = cmd::make_option_set< X >( v )
                    .option( "[-a {int}]", &X::a )
                    .option( "{int}", &X::b );
        X x;

        cmd::Tokens in = { "10" };
        opts.parse( x, in.begin(), in.end() );
        ASSERT_EQ( x.b, 10 );

        in = { "-a", "15", "20" };
        opts.parse( x, in.begin(), in.end() );
        ASSERT_EQ( x.a, 15 );
        ASSERT_EQ( x.b, 20 );
    }

    TEST(nonoption_string)
    {
        struct X { std::string a, b; };
        auto v = validator();
        auto opts = cmd::make_option_set< X >( v )
                    .option( "[-a {string}]", &X::a )
                    .option( "{string}", &X::b );
        X x;

        cmd::Tokens in = { "10" };
        opts.parse( x, in.begin(), in.end() );
        ASSERT_EQ( x.b, "10" );

        in = { "-a", "15", "20" };
        opts.parse( x, in.begin(), in.end() );
        ASSERT_EQ( x.a, "15" );
        ASSERT_EQ( x.b, "20" );
    }

    TEST(multi)
    {
        struct X { std::vector< std::string > a; };
        auto v = validator();
        auto opts = cmd::make_option_set< X >( v )
                    .option( "{string}+", &X::a );
        X x;

        cmd::Tokens in = { "10", "20" };
        opts.parse( x, in.begin(), in.end() );
        ASSERT_EQ( x.a.size(), 2u );
        ASSERT_EQ( x.a[ 0 ], "10" );
        ASSERT_EQ( x.a[ 1 ], "20" );
    }

    TEST(cmd_multi)
    {
        struct X { std::vector< std::string > a; int x; };
        auto v = validator();
        auto opts = cmd::make_option_set< X >( v )
                    .option( "[-y {int}]", &X::x )
                    .option( "{string}+", &X::a );
        auto p = cmd::make_parser( v ).command< X >( opts );

        cmd::Tokens in = { "x", "10", "20" };
        auto r = p.parse( in.begin(), in.end() );
        bool matched = false;
        r.apply( [&]( X x )
                 {
                     matched = true;
                     ASSERT_EQ( x.a.size(), 2 );
                 } );
        ASSERT( matched );
    }
};

}
}

// vim: syntax=cpp tabstop=4 shiftwidth=4 expandtab ft=cpp
