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

/*
 * Assorted types, mostly for C++11.
 * - Maybe a = Just a | Nothing (w/ a limited variant for C++98)
 * - Unit: single-valued type (empty structure)
 * - Union: discriminated (tagged) union
 * - StrongEnumFlags
 */

/*
 * (c) 2006, 2014 Petr Ročkai <me@mornfall.net>
 * (c) 2013-2014 Vladimír Štill <xstill@fi.muni.cz>
 */

/* Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE. */

#include <brick-assert.h>

#include <memory>
#include <cstring>
#include <type_traits>
#include <functional>

#ifndef BRICK_TYPES_H
#define BRICK_TYPES_H

#if __cplusplus >= 201103L
#define CONSTEXPR constexpr
#else
#define CONSTEXPR
#endif

#if __cplusplus > 201103L
#define CPP1Y_CONSTEXPR constexpr // C++1y
#else
#define CPP1Y_CONSTEXPR // C++11
#endif

namespace brick {
namespace types {

struct Unit {
    bool operator<( Unit ) const { return false; }
    bool operator==( Unit ) const { return true; }
};

struct Preferred { CONSTEXPR Preferred() { } };
struct NotPreferred { CONSTEXPR NotPreferred( Preferred ) {} };

struct Comparable {
    typedef bool IsComparable;
};

template< typename T >
typename T::IsComparable operator!=( const T &a, const T &b ) {
    return not( a == b );
}

template< typename T >
typename T::IsComparable operator==( const T &a, const T &b ) {
    return a <= b && b <= a;
}

template< typename T >
typename T::IsComparable operator<( const T &a, const T &b ) {
    return a <= b && a != b;
}

template< typename T >
typename T::IsComparable operator>( const T &a, const T &b ) {
    return b <= a && a != b;
}

template< typename T >
typename T::IsComparable operator>=( const T &a, const T &b ) {
    return b <= a;
}

struct Defer {
    template< typename F >
    Defer( F fn ) : fn( fn ), _deleted( false ) { }

    void run() {
        if ( !_deleted ) {
            fn();
            _deleted = true;
        }
    }

    bool deleted() const { return _deleted; }
    void pass() { _deleted = true; }
    ~Defer() { run(); }
  private:
    std::function< void() > fn;
    bool _deleted;
};

namespace mixin {

#if __cplusplus >= 201103L
template< typename Self >
struct LexComparable {
    const Self &lcSelf() const { return *static_cast< const Self * >( this ); }

    bool operator==( const Self &o ) const {
        return lcSelf().toTuple() == o.toTuple();
    }

    bool operator!=( const Self &o ) const {
        return lcSelf().toTuple() != o.toTuple();
    }

    bool operator<( const Self &o ) const {
        return lcSelf().toTuple() < o.toTuple();
    }

    bool operator<=( const Self &o ) const {
        return lcSelf().toTuple() <= o.toTuple();
    }

    bool operator>( const Self &o ) const {
        return lcSelf().toTuple() > o.toTuple();
    }

    bool operator>=( const Self &o ) const {
        return lcSelf().toTuple() >= o.toTuple();
    }
};
#endif

}

#if __cplusplus < 201103L

/*
  A Maybe type. Values of type Maybe< T > can be either Just T or Nothing.

  Maybe< int > foo;
  foo = Maybe::Nothing();
  // or
  foo = Maybe::Just( 5 );
  if ( !foo.nothing() ) {
    int real = foo;
  } else {
    // we haven't got anythig in foo
  }

  Maybe takes a default value, which is normally T(). That is what you
  get if you try to use Nothing as T.
*/

template <typename T>
struct Maybe : Comparable {
    bool nothing() const { return m_nothing; }
    bool isNothing() const { return nothing(); }
    T &value() { return m_value; }
    const T &value() const { return m_value; }
    Maybe( bool n, const T &v ) : m_nothing( n ), m_value( v ) {}
    Maybe( const T &df = T() )
       : m_nothing( true ), m_value( df ) {}
    static Maybe Just( const T &t ) { return Maybe( false, t ); }
    static Maybe Nothing( const T &df = T() ) {
        return Maybe( true, df ); }
    operator T() const { return value(); }

    bool operator <=( const Maybe< T > &o ) const {
        if (o.nothing())
            return true;
        if (nothing())
            return false;
        return value() <= o.value();
    }
protected:
    bool m_nothing:1;
    T m_value;
};

#else

template< typename T >
struct StorableRef {
    T _t;
    T &t() { return _t; }
    const T &t() const { return _t; }
    StorableRef( T t ) : _t( t ) {}
};

template< typename T >
struct StorableRef< T & > {
    T *_t;
    T &t() { return *_t; }
    const T &t() const { return *_t; }
    StorableRef( T &t ) : _t( &t ) {}
};

template< typename _T >
struct Maybe : Comparable
{
    using T = _T;

    bool isNothing() const { return _nothing; }
    bool isJust() const { return !_nothing; }

    T &value() {
        ASSERT( isJust() );
        return _v.t.t();
    }

    const T &value() const {
        ASSERT( isJust() );
        return _v.t.t();
    }

    T fromMaybe( T x ) const { return isJust() ? value() : x; }

    explicit operator bool() const { return isJust(); }

    static Maybe Just( const T &t ) { return Maybe( t ); }
    static Maybe Nothing() { return Maybe(); }

    Maybe( const Maybe &m ) {
        _nothing = m.isNothing();
        if ( !_nothing )
            _v.t = m._v.t;
    }

    ~Maybe() {
        if ( !_nothing )
            _v.t.~StorableRef< T >();
    }

    bool operator <=( const Maybe< T > &o ) const {
        if (o.isNothing())
            return true;
        if (isNothing())
            return false;
        return value() <= o.value();
    }

protected:

    Maybe( const T &v ) : _v( v ), _nothing( false ) {}
    Maybe() : _nothing( true ) {}
    struct Empty {
        char x[ sizeof( T ) ];
    };

    union V {
        StorableRef< T > t;
        Empty empty;
        V() : empty() {}
        V( const T &t ) : t( t ) {}
        ~V() { } // see dtor of Maybe
    };
    V _v;
    bool _nothing;
};

#endif

template<>
struct Maybe< void > {
    typedef void T;
    static Maybe Just() { return Maybe( false ); }
    static Maybe Nothing() { return Maybe( true ); }
    bool isNothing() { return _nothing; }
    bool isJust() { return !_nothing; }
private:
    Maybe( bool nothing ) : _nothing( nothing ) {}
    bool _nothing;
};

#if __cplusplus >= 201103L

template< typename E >
using is_enum_class = std::integral_constant< bool,
        std::is_enum< E >::value && !std::is_convertible< E, int >::value >;

template< typename Self >
struct StrongEnumFlags {
    static_assert( is_enum_class< Self >::value, "Not an enum class." );
    using This = StrongEnumFlags< Self >;
    using UnderlyingType = typename std::underlying_type< Self >::type;

    constexpr StrongEnumFlags() noexcept : store( 0 ) { }
    constexpr StrongEnumFlags( Self flag ) noexcept :
        store( static_cast< UnderlyingType >( flag ) )
    { }
    explicit constexpr StrongEnumFlags( UnderlyingType st ) noexcept : store( st ) { }

    constexpr explicit operator UnderlyingType() const noexcept {
        return store;
    }

    This &operator|=( This o ) noexcept {
        store |= o.store;
        return *this;
    }

    This &operator&=( This o ) noexcept {
        store &= o.store;
        return *this;
    }

    This &operator^=( This o ) noexcept {
        store ^= o.store;
        return *this;
    }

    friend constexpr This operator|( This a, This b ) noexcept {
        return This( a.store | b.store );
    }

    friend constexpr This operator&( This a, This b ) noexcept {
        return This( a.store & b.store );
    }

    friend constexpr This operator^( This a, This b ) noexcept {
        return This( a.store ^ b.store );
    }

    friend constexpr bool operator==( This a, This b ) noexcept {
        return a.store == b.store;
    }

    friend constexpr bool operator!=( This a, This b ) noexcept {
        return a.store != b.store;
    }

    constexpr bool has( Self x ) const noexcept {
        return ((*this) & x) == x;
    }

    This clear( Self x ) noexcept {
        store &= ~UnderlyingType( x );
        return *this;
    }

    explicit constexpr operator bool() const noexcept {
        return store;
    }

  private:
    UnderlyingType store;
};

// don't catch integral types and classical enum!
template< typename Self, typename = typename
          std::enable_if< is_enum_class< Self >::value >::type >
constexpr StrongEnumFlags< Self > operator|( Self a, Self b ) noexcept {
    using Ret = StrongEnumFlags< Self >;
    return Ret( a ) | Ret( b );
}

template< typename Self, typename = typename
          std::enable_if< is_enum_class< Self >::value >::type >
constexpr StrongEnumFlags< Self > operator&( Self a, Self b ) noexcept {
    using Ret = StrongEnumFlags< Self >;
    return Ret( a ) & Ret( b );
}

/* implementation of Union */

namespace _impl {
    template< size_t val, typename... >
    struct MaxSizeof : std::integral_constant< size_t, val > { };

    template< size_t val, typename T, typename... Ts >
    struct MaxSizeof< val, T, Ts... > :
        MaxSizeof< ( val > sizeof( T ) ) ? val : sizeof( T ), Ts... >
    { };

    template< size_t val, typename... >
    struct MaxAlign : std::integral_constant< size_t, val > { };

    template< size_t val, typename T, typename... Ts >
    struct MaxAlign< val, T, Ts... > :
        MaxAlign< ( val > std::alignment_of< T >::value )
                      ? val : std::alignment_of< T >::value, Ts... >
    { };

    template< typename... >
    struct AllDistinct : std::true_type { };

    template< typename, typename... >
    struct In : std::false_type { };

    template< typename Needle, typename T, typename... Ts >
    struct In< Needle, T, Ts... > : std::integral_constant< bool,
        std::is_same< Needle, T >::value || In< Needle, Ts... >::value >
    { };

    template< typename _T >
    struct Witness { using T = _T; };

    template< typename, typename... >
    struct _OneConversion { };

    template< typename From, typename To, typename... >
    struct NoneConvertible { using T = To; };

    template< typename From, typename To, typename T, typename... Ts >
    struct NoneConvertible< From, To, T, Ts... > : std::conditional<
        std::is_convertible< From, T >::value,
        Unit,
        NoneConvertible< From, To, Ts... > >::type { };

    static_assert( std::is_convertible< Witness< int >, Witness< int > >::value, "is_convertible" );

    template< typename Needle, typename T, typename... Ts >
    struct _OneConversion< Needle, T, Ts... > : std::conditional<
        std::is_convertible< Needle, T >::value,
        NoneConvertible< Needle, T, Ts... >,
        _OneConversion< Needle, Ts... > >::type { };

    template< typename Needle, typename... Ts >
    struct OneConversion : std::conditional<
        In< Needle, Ts... >::value,
        Witness< Needle >,
        _OneConversion< Needle, Ts... > >::type { };

    static_assert( std::is_same< OneConversion< int, int >::T, int >::value, "OneConversion" );
    static_assert( std::is_same< OneConversion< long, int >::T, int >::value, "OneConversion" );
    static_assert( std::is_same< OneConversion< long, std::string, int >::T, int >::value, "OneConversion" );
    static_assert( std::is_same< OneConversion< long, int, long, int >::T, long >::value, "OneConversion" );

    template< typename T, typename... Ts >
    struct AllDistinct< T, Ts... > : std::integral_constant< bool,
        !In< T, Ts... >::value && AllDistinct< Ts... >::value >
    { };

template< typename F, typename T, typename Fallback, typename Check = bool >
struct _ApplyResult : Fallback {};

template< typename F, typename T, typename Fallback >
struct _ApplyResult< F, T, Fallback, decltype( std::declval< F >()( std::declval< T >() ), true ) >
{
    using Parameter = T;
    using Result = decltype( std::declval< F >()( std::declval< T >() ) );
};

template< typename F, typename... Ts > struct ApplyResult;

template< typename F, typename T, typename... Ts >
struct ApplyResult< F, T, Ts... > : _ApplyResult< F, T, ApplyResult< F, Ts... > > {};

template< typename F > struct ApplyResult< F > {};

}

struct UnionException : std::exception {
    UnionException( std::string msg ) : msg( msg ) { }

    virtual const char *what() const noexcept override { return msg.c_str(); }

    std::string msg;
};

template< typename T >
struct InPlace { };

struct NullUnion { };

template< typename... Types >
struct Union : Comparable {
    static_assert( sizeof...( Types ) < 0xff, "Too much unioned types, sorry" );
    static_assert( _impl::AllDistinct< Types... >::value,
            "All types in union must be distinct" );

    constexpr Union() : _discriminator( 0 ) { }
    constexpr Union( NullUnion ) : _discriminator( 0 ) { }

    Union( const Union &other ) {
        ASSERT_LEQ( size_t( other._discriminator ), sizeof...( Types ) );
        if ( other._discriminator > 0 )
            _copyConstruct< 1, Types... >( other._discriminator, other );
        _discriminator = other._discriminator;
    }

    Union( Union &&other ) {
        ASSERT_LEQ( size_t( other._discriminator ), sizeof...( Types ) );
        auto target = other._discriminator;
        other._discriminator = 0;
        if ( target > 0 )
            _moveConstruct< 1, Types... >( target, std::move( other ) );
        _discriminator = target;
    }

    template< typename T, typename U = typename _impl::OneConversion< T, Types... >::T >
    CPP1Y_CONSTEXPR Union( T val ) {
        new ( &storage ) U( std::move( val ) );
        _discriminator = discriminator< U >();
    }

    template< typename T, typename... Args >
    Union( InPlace< T >, Args &&... args ) : _discriminator( discriminator< T >() ) {
        new ( &storage ) T( std::forward< Args >( args )... );
    }

    // use copy and swap
    Union &operator=( Union other ) {
        swap( other );
        return *this;
    }

    template< typename T >
    auto operator=( const T &other ) -> typename
        std::enable_if< std::is_lvalue_reference< T & >::value, Union & >::type
    {
        if ( is< T >() )
            unsafeGet< T >() = other;
        else
            _copyAssignDifferent( Union( other ) );
        return *this;
    }

    template< typename T >
    auto operator=( T &&other ) -> typename
        std::enable_if< std::is_rvalue_reference< T && >::value, Union & >::type
    {
        if ( is< T >() )
            unsafeGet< T >() = std::move( other );
        else
            _moveAssignDifferent( std::move( other ) );
        return *this;
    }

    void swap( Union other ) {
        typename std::aligned_storage< size, algignment >::type tmpStor;
        unsigned char tmpDis;

        std::memcpy( &tmpStor, &other.storage, size );
        tmpDis = other._discriminator;
        other._discriminator = 0;
        std::memcpy( &other.storage, &storage, size );
        other._discriminator = _discriminator;
        _discriminator = 0;
        std::memcpy( &storage, &tmpStor, size );
        _discriminator = tmpDis;
    }

    bool empty() {
        return _discriminator == 0;
    }

    explicit operator bool() {
        return !empty();
    }

    template< typename T >
    bool is() const {
        return discriminator< T >() == _discriminator;
    }

    template< typename T >
    explicit operator T() const {
        return convert< T >();
    }

    template< typename T >
    T &get() {
        ASSERT( is< T >() );
        return unsafeGet< T >();
    }

    template< typename T >
    const T &get() const {
        return cget< T >();
    }

    template< typename T >
    const T &cget() const {
        ASSERT( is< T >() );
        return unsafeGet< T >();
    }

    template< typename T >
    const T &getOr( const T &val ) const {
        if ( is< T >() )
            return unsafeGet< T >();
        return val;
    }

    template< typename T >
    T convert() const { return _convert< T >(); }

    template< typename T >
    T &unsafeGet() {
        return *reinterpret_cast< T * >( &storage );
    }

    template< typename T >
    const T &unsafeGet() const {
        return *reinterpret_cast< const T * >( &storage );
    }

    template< typename T >
    T &&moveOut() {
        ASSERT( is< T >() );
        return unsafeMoveOut< T >();
    }

    template< typename T >
    T &&unsafeMoveOut() {
        return std::move( *reinterpret_cast< T * >( &storage ) );
    }

    template< typename F >
    using Applied = Maybe< typename _impl::ApplyResult< F, Types... >::Result >;

    // invoke `f` on the stored value if the type currently stored in the union
    // can be legally passed to that function as an argument
    template< typename F >
    auto apply( F f ) -> Applied< F > {
        return _apply< F, Types... >( Preferred(), f );
    }

    template< typename R >
    R _match() { return R::Nothing(); }

    // invoke the first function that can handle the currently stored value
    // (type-based pattern matching)
    template< typename R, typename F, typename... Args >
    R _match( F f, Args... args ) {
        auto x = apply( f );
        if ( x.isNothing() )
            return _match< R >( args... );
        else
            return x;
    }

    template< typename F, typename... Args >
    Applied< F > match( F f, Args... args ) {
        return _match< Applied< F > >( f, args... );
    }

    bool operator==( const Union &other ) const {
        return _discriminator == other._discriminator
            && _compare< std::equal_to >( other );
    }

    bool operator!=( const Union &other ) const {
        return _discriminator != other._discriminator
            || _compare< std::not_equal_to >( other );
    }

    bool operator<( const Union &other ) const {
        return _discriminator < other._discriminator
            || (_discriminator == other._discriminator
                    && _compare< std::less >( other ) );
    }

    unsigned char discriminator() const { return _discriminator; }

    template< typename T >
    unsigned char discriminator() const {
        static_assert( _impl::In< T, Types... >::value,
                "Trying to construct Union from value of type not allowed for it." );
        return _discriminatorF< 1, T, Types... >();
    }

  private:
    static constexpr size_t size = _impl::MaxSizeof< 1, Types... >::value;
    static constexpr size_t algignment = _impl::MaxAlign< 1, Types... >::value;
    typename std::aligned_storage< size, algignment >::type storage;
    unsigned char _discriminator;


    template< unsigned char i, typename Needle, typename T, typename... Ts >
    constexpr unsigned char _discriminatorF() const {
        return std::is_same< Needle, T >::value
            ? i : _discriminatorF< i + 1, Needle, Ts... >();
    }

    template< unsigned char, typename >
    constexpr unsigned char _discriminatorF() const { return 0; /* cannot happen */ }

    template< unsigned char i, typename T, typename... Ts >
    void _copyConstruct( unsigned char d, const Union &other ) {
        if ( i == d )
            new ( &storage ) T( other.unsafeGet< T >() );
        else
            _copyConstruct< i + 1, Ts... >( d, other );
    }

    template< unsigned char >
    unsigned char _copyConstruct( unsigned char, const Union & )
    { ASSERT_UNREACHABLE( "invalid _copyConstruct" ); }

    template< unsigned char i, typename T, typename... Ts >
    void _moveConstruct( unsigned char d, Union &&other ) {
        if ( i == d )
            new ( &storage ) T( other.unsafeMoveOut< T >() );
        else
            _moveConstruct< i + 1, Ts... >( d, std::move( other ) );
    }

    template< unsigned char >
    unsigned char _moveConstruct( unsigned char, Union && )
    { ASSERT_UNREACHABLE( "invalid _moveConstruct" ); }

    void _copyAssignDifferent( const Union &other ) {
        auto tmp = _discriminator;
        _discriminator = 0;
        if ( tmp )
            _destruct< 1, Types... >( tmp );
        if ( other._discriminator )
            _copyConstruct< 1, Types... >( other._discriminator, other );
        _discriminator = other._discriminator;
    }

    void _copyAssignSame( const Union &other ) {
        ASSERT_EQ( _discriminator, other._discriminator );
        if ( _discriminator == 0 )
            return;
        _copyAssignSame< 1, Types... >( other );
    }

    template< unsigned char i, typename T, typename... Ts >
    void _copyAssignSame( const Union &other ) {
        if ( i == _discriminator )
            unsafeGet< T >() = other.unsafeGet< T >();
        else
            _copyAssignSame< i + 1, Ts... >( other );
    }

    template< unsigned char >
    void _copyAssignSame( const Union & ) { ASSERT_UNREACHABLE( "invalid _copyAssignSame" ); }

    template< unsigned char i, typename T, typename... Ts >
    void _destruct( unsigned char d ) {
        if ( i == d )
            unsafeGet< T >().~T();
        else
            _destruct< i + 1, Ts... >( d );
    }

    template< unsigned char >
    void _destruct( unsigned char ) { ASSERT_UNREACHABLE( "invalid _destruct" ); }

    void _moveAssignSame( Union &&other ) {
        ASSERT_EQ( _discriminator, other._discriminator );
        if ( _discriminator == 0 )
            return;
        _moveAssignSame< 1, Types... >( std::move( other ) );
    }

    template< unsigned char i, typename T, typename... Ts >
    void _moveAssignSame( Union &&other ) {
        if ( i == _discriminator )
            unsafeGet< T >() = other.unsafeMoveOut< T >();
        else
            _moveAssignSame< i + 1, Ts... >( std::move( other ) );
    }

    template< unsigned char >
    void _moveAssignSame( Union && ) { ASSERT_UNREACHABLE( "invalid _moveAssignSame" ); }

    void _moveAssignDifferent( Union &&other ) {
        auto tmp = _discriminator;
        auto target = other._discriminator;
        _discriminator = 0;
        if ( tmp )
            _destruct< 1, Types... >( tmp );
        if ( target )
            _moveConstruct< 1, Types... >( target, std::move( other ) );
        _discriminator = target;
    }

    template< typename F > Applied< F > _apply( Preferred, F ) { return Applied< F >::Nothing(); }

    template< typename F, typename T >
    auto fixvoid( F f ) ->
        typename std::enable_if< std::is_void< typename Applied< F >::T >::value, Applied< F > >::type
    {
        f( get< T >() );
        return Maybe< void >::Just();
    }

    template< typename F, typename T >
    auto fixvoid( F f ) ->
        typename std::enable_if< !std::is_void< typename Applied< F >::T >::value, Applied< F > >::type
    {
        return Applied< F >::Just( f( get< T >() ) );
    }

    template< typename F, typename T, typename... Args >
    auto _apply( Preferred, F f ) -> Maybe< typename _impl::_ApplyResult< F, T, Unit >::Result >
    {
        if ( !is< T >() )
            return _apply< F, Args... >( Preferred(), f );

        return fixvoid< F, T >( f );
    }

    template< typename F, typename T, typename... Args >
    auto _apply( NotPreferred, F f ) -> Applied< F >
    {
        return _apply< F, Args... >( Preferred(), f );
    }

    template< template< typename > class Compare, int d >
    bool _compare2( const Union & ) const { ASSERT_UNREACHABLE( "invalid discriminator" ); }

    template< template< typename > class Compare, int d, typename T, typename... Ts >
    bool _compare2( const Union &other ) const {
        return d == _discriminator
            ? Compare< T >()( get< T >(), other.template get< T >() )
            : _compare2< Compare, d + 1, Ts... >( other );
    }

    template< template< typename > class Compare >
    bool _compare( const Union &other ) const {
        return _compare2< Compare, 1, Types... >( other );
    }

    template< typename Target, bool anyCastPossible, int >
    Target _convert2( Preferred ) const {
        static_assert( anyCastPossible, "Cast of Union can never succeed" );
        ASSERT_UNREACHABLE( "wrong _convert2 in Union" );
    }

    template< typename Target, bool any, int d, typename, typename... Ts >
    Target _convert2( NotPreferred ) const {
        return _convert2< Target, any, d + 1, Ts... >( Preferred() );
    }

    template< typename Target, bool any, int d, typename T, typename... Ts >
    auto _convert2( Preferred ) const -> decltype( static_cast< Target >( this->unsafeGet< T >() ) )
    {
        if ( _discriminator == d )
            return static_cast< Target >( unsafeGet< T >() );
        return _convert2< Target, true, d + 1, Ts... >( Preferred() );
    }

    template< typename Target >
    Target _convert() const {
        return _convert2< Target, false, 1, Types... >( Preferred() );
    }

};

template< template< typename > class C, typename T, typename F >
using FMap = C< typename std::result_of< F( T ) >::type >;

template< typename T >
struct NewType
{
    T _value;

    template< typename X > using FMap = NewType< X >;
    NewType() noexcept {}
    NewType( const T &t ) noexcept : _value( t ) {}

    T &unwrap() { return _value; }
    const T &unwrap() const { return _value; }
};

template< typename T >
struct Wrapper : NewType< T >
{
    Wrapper() = default;
    Wrapper( const T &t ) : NewType< T >( t ) {}
    operator T() { return this->unwrap(); }
    T &value() { return this->unwrap(); }
    T &operator*() { return this->unwrap(); }
    T *operator->() { return &this->unwrap(); }
};

template< template< typename > class C, typename S, typename F >
auto fmap( F, C< S > n ) -> decltype( FMap< C, S, F >( n.unwrap() ) ) {
    return FMap< C, S, F >( n.unwrap() );
}

template< typename T >
struct IsUnion : std::false_type { };

template< typename... Ts >
struct IsUnion< Union< Ts... > > : std::true_type { };

template< typename A, typename B >
struct _OneUnion : std::enable_if<
                       ( IsUnion< A >::value || IsUnion< B >::value )
                       && !(IsUnion< A >::value && IsUnion< B >::value ),
                   bool > { };

template< typename A, typename B >
auto operator==( const A &a, const B &b ) ->
    typename std::enable_if< IsUnion< A >::value && !IsUnion< B >::value, bool >::type
{
    return a.template is< B >() && a.template get< B >() == b;
}

template< typename A, typename B >
auto operator==( const A &a, const B &b ) ->
    typename std::enable_if< !IsUnion< A >::value && IsUnion< B >::value, bool >::type
{ return b == a; }


template< typename A, typename B >
auto operator<( const A &a, const B &b ) ->
    typename std::enable_if< IsUnion< A >::value && !IsUnion< B >::value, bool >::type
{
    return a.discriminator() < a.template discriminator< B >()
        || (a.template is< B >() && a.template get< B >() < b);
}

template< typename A, typename B >
auto operator<( const A &a, const B &b ) ->
    typename std::enable_if< !IsUnion< A >::value && IsUnion< B >::value, bool >::type
{
    return b.template discriminator< A >() < b.discriminator()
        || (b.template is< A >() && a < b.template get< A >());
}

template< typename A, typename B >
auto operator!=( const A &a, const B &b ) -> typename _OneUnion< A, B >::type
{ return !(a == b); }

template< typename A, typename B >
auto operator<=( const A &a, const B &b ) -> typename _OneUnion< A, B >::type
{ return a < b || a == b; }

template< typename A, typename B >
auto operator>( const A &a, const B &b ) -> typename _OneUnion< A, B >::type
{ return b < a; }

template< typename A, typename B >
auto operator>=( const A &a, const B &b ) -> typename _OneUnion< A, B >::type
{ return b <= a; }

#endif // C++11

}
}

namespace brick_test {
namespace types {

using namespace ::brick::types;

struct Integer : Comparable
{
    int val;
public:
    Integer(int val) : val(val) {}
    bool operator<=( const Integer& o ) const { return val <= o.val; }
};

struct Mixins {

    TEST(comparable) {
        Integer i10(10);
        Integer i10a(10);
        Integer i20(20);

        ASSERT(i10 <= i10a);
        ASSERT(i10a <= i10);
        ASSERT(i10 <= i20);
        ASSERT(! (i20 <= i10));

        ASSERT(i10 != i20);
        ASSERT(!(i10 != i10a));

        ASSERT(i10 == i10a);
        ASSERT(!(i10 == i20));

        ASSERT(i10 < i20);
        ASSERT(!(i20 < i10));
        ASSERT(!(i10 < i10a));

        ASSERT(i20 > i10);
        ASSERT(!(i10 > i20));
        ASSERT(!(i10 > i10a));

        ASSERT(i10 >= i10a);
        ASSERT(i10a >= i10);
        ASSERT(i20 >= i10);
        ASSERT(! (i10 >= i20));
    }

};

#if __cplusplus >= 201103L

struct A { };
struct B { B() { }; ~B() { } };
struct C { int x; C( int x ) : x( x ) {} C() : x( 0 ) {} };

static_assert( _impl::In< int, int >::value, "" );
static_assert( _impl::In< A, A, B >::value, "" );
static_assert( _impl::In< A, B, A >::value, "" );

// test instances
struct UnionInstances {
    Union<> a;
    Union< int, long > b;
    Union< int, long, A > c;
    Union< int, long, A, B > d;
    Union< int, long, A, B, std::string > e;
};

struct UnionTest {
    TEST(basic) {
        Union< int > u( 1 );
        ASSERT( !!u );
        ASSERT( !u.empty() );
        ASSERT( u.is< int >() );
        ASSERT_EQ( u.get< int >(), 1 );
        u = 2; // move
        ASSERT( !!u );
        ASSERT_EQ( u.get< int >(), 2 );
        int i = 3;
        u = i; // copy
        ASSERT( !!u );
        ASSERT_EQ( u.get< int >(), 3 );
        u = types::Union< int >( 4 );
        ASSERT( u.is< int >() );
        ASSERT_EQ( u.get< int >(), 4 );
        u = types::Union< int >();
        ASSERT( !u );
        ASSERT( !u.is< int >() );
        u = 5;
        ASSERT( u );
        ASSERT( u.is< int >() );
        ASSERT_EQ( u.get< int >(), 5 );
    }

    TEST(moveNoCopy) {
        // if one of contained structures does not define copy ctor+assignment
        // move should still be available
        struct Move {
            Move() = default;
            Move( const Move & ) = delete;
            Move( Move && ) = default;

            Move &operator=( Move ) { return *this; }
        };
        Union< long, Move > wierd;
        ASSERT( wierd.empty() );

        wierd = 2L;
        ASSERT( !!wierd );
        ASSERT( wierd.is< long >() );
        ASSERT_EQ( wierd.get< long >(), 2L );

        wierd = Move();
        ASSERT( !!wierd );
        ASSERT( wierd.is< Move >() );
    }

    TEST(ctorCast) {
        ASSERT( ( Union< int, long >{ int( 1 ) }.is< int >() ) );
        ASSERT( ( Union< int, long >{ long( 1 ) }.is< long >() ) );

        ASSERT( ( Union< long, std::string >{ int( 1 ) }.is< long >() ) );

        struct A { operator int(){ return 1; } };
        ASSERT( ( Union< int, A >{ A() }.is< A >() ) );
        ASSERT( ( Union< int, std::string >{ A() }.is< int >() ) );

        struct B { B( int ) { } B() = default; };
        ASSERT( ( Union< int, B >{ B() }.is< B >() ) );
        ASSERT( ( Union< int, B >{ 1 }.is< int >() ) );
        ASSERT( ( Union< B, std::string >{ 1 }.is< B >() ) );
    }

    static C idC( C c ) { return c; };
    static C constC( B ) { return C( 32 ); };

    TEST(apply) {
        Union< B, C > u;
        u = B();

        Maybe< C > result = u.match( idC, constC );
        ASSERT( !result.isNothing() );
        ASSERT_EQ( result.value().x, 32 );

        u = C( 12 );
        result = u.match( idC, constC );
        ASSERT( !result.isNothing() );
        ASSERT_EQ( result.value().x, 12 );

        result = u.match( constC );
        ASSERT( result.isNothing() );
    }

    TEST(eq) {
        Union< int, long > u{ 1 };
        Union< int, long > v{ 2 };
        Union< int, long > w{ 2l };

        ASSERT( u == u );
        ASSERT( u != v );
        ASSERT( v != w );
        ASSERT( u != w );

        ASSERT( u == 1 );
        ASSERT( v == 2 );
        ASSERT( w == 2l );

        ASSERT( u != 1l );
        ASSERT( v != 2l );
        ASSERT( w != 2 );
    }

    TEST(ord) {
        Union< int, long > u{ 1 };
        Union< int, long > v{ 2 };
        Union< int, long > w{ 2l };

        ASSERT( u < v );
        ASSERT( !(v < u) );
        ASSERT( u < w );
        ASSERT( !(w < u) );
        ASSERT( v < w );
        ASSERT( !(w < v) );

        ASSERT( u <= 1 );
        ASSERT( v > 1 );
        ASSERT( w > 1 );

        ASSERT( u < 1l );
        ASSERT( v < 1l );
        ASSERT( w > 1l );

        ASSERT( u < 2 );
        ASSERT( v <= 2 );
        ASSERT( w > 2 );

        ASSERT( u < 2l );
        ASSERT( v < 2l );
        ASSERT( w <= 2l );
    }
};

enum class FA : unsigned char  { X = 1, Y = 2, Z = 4 };
enum class FB : unsigned short { X = 1, Y = 2, Z = 4 };
enum class FC : unsigned       { X = 1, Y = 2, Z = 4 };
enum class FD : unsigned long  { X = 1, Y = 2, Z = 4 };

struct StrongEnumFlagsTest {
    template< typename Enum >
    void testEnum() {
        StrongEnumFlags< Enum > e1;
        StrongEnumFlags< Enum > e2( Enum::X );

        ASSERT( !e1 );
        ASSERT( e2 );

        ASSERT( e1 | e2 );
        ASSERT( Enum::X | Enum::Y );
        ASSERT( e2 | Enum::Z );
        ASSERT( e2.has( Enum::X ) );

        ASSERT( e2 & Enum::X );
        ASSERT( !( Enum::X & Enum::Y ) );

        ASSERT( Enum::X | Enum::Y | Enum::Z );
        ASSERT( !( Enum::X & Enum::Y & Enum::Z ) );
        ASSERT( ( Enum::X | Enum::Y | Enum::Z ) & Enum::X );
    }

    // we don't want to break classical enums and ints by out operators
    TEST(regression) {
        enum Classic { C_X = 1, C_Y = 2, C_Z = 4 };

        ASSERT( C_X | C_Y | C_Z );
        ASSERT( 1 | 2 | 4 );
        ASSERT( C_X & 1 );
    }

    TEST(enum_uchar) { testEnum< FA >(); }
    TEST(enum_ushort) { testEnum< FB >(); }
    TEST(enum_uint) { testEnum< FC >(); }
    TEST(enum_ulong) { testEnum< FD >(); }
};

#endif

}
}

#endif
// vim: syntax=cpp tabstop=4 shiftwidth=4 expandtab
