#ifndef NOMINMAX
#define NOMINMAX
#endif

#include <iostream>
#include <sstream>
#include <random>
#include <cstdint>

#include <functional>
#include "taus88.hpp"
// #include <boost/random/taus88.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/seed_seq.hpp>
#include <boost/random/random_device.hpp>
#include "seed_seq_fe.hpp"
#include "xoroshiro.hpp"
#include "complementary_multiply_with_carry.hpp"

#include <pcg/pcg_random.hpp>
#include "xoroshiro_meo.hpp"

#define PCG_USE_ZEROCHECK_ROTATE_IDIOM 1

#include "autotimer.hpp"

std::uint64_t integer_hash ( std::uint64_t x )
{
    x = ( ( x >> 32 ) ^ x ) * std::uint64_t { 0xDE17C195AA959A81 };
    x = ( ( x >> 32 ) ^ x ) * std::uint64_t { 0xDE17C195AA959A81 };
    x = ( ( x >> 32 ) ^ x ) * std::uint64_t { 0xDE17C195AA959A81 };
    return ( x >> 32 ) ^ x;
}

#define VOLATILE volatile

template<typename Generator>
struct GeneratorCache : private Generator {

    using result_type = typename Generator::result_type;
    using size_type = std::size_t;

    GeneratorCache ( ) : Generator ( ) { }
    GeneratorCache ( result_type v_ ) : Generator ( v_ ) { }
    template<typename SeedSeq>
    GeneratorCache ( const SeedSeq & seq_ ) : Generator ( seq_ ) { }
    template<typename It>
    GeneratorCache ( It & first_, It last_ ) : Generator ( first_, last_ ) { }

    result_type operator ( ) ( ) noexcept {

        if ( m_index == data_size ( ) ) {

            Generator::generate ( std::begin ( m_data ), std::end ( m_data ) );
            // std::generate ( std::begin ( m_data ), std::end ( m_data ), [ this ] ( ) { return Generator::operator ( ) ( ); } );
            m_index = 1;

            return m_data [ 0 ];
        }

        return m_data [ m_index++ ];
    }

    static constexpr result_type min ( ) noexcept { return Generator::min ( ); }
    static constexpr result_type max ( ) noexcept { return Generator::max ( ); }
    static constexpr std::size_t data_size ( ) noexcept { return 64 / sizeof ( result_type ); }

    result_type m_data [ data_size ( ) ];
    size_type m_index = data_size ( );
};

int main ( ) {

    boost::random::seed_seq_fe256 seq_fe { INT32_MAX, 3466, 456454, 23423 };

    std::uint64_t cnt = 1;

    xoroshiro128plus64 gen1 ( 0xBE1C0467EBA5FAC1 );
    xoroshiro128plus64 gen2 ( 0xBE2C0467EBA5FAC2 );
    xoroshiro128plus64 gen3 ( 0xBE3C0467EBA5FAC3 );
    xoroshiro128plus64 gen4 ( 0xBE4C0467EBA5FAC4 );
    xoroshiro128plus64 gen5 ( 0xBE5C0467EBA5FAC5 );

    while ( cnt-- ) {

        std::int64_t cnt1 = 1'000'000'000, cnt2 = cnt1, cnt3 = cnt1, cnt4 = cnt1, cnt5 = cnt1;

        VOLATILE std::uint64_t acc1 = 0;

        {
            degski::AutoTimer t ( degski::milliseconds );

            while ( cnt1-- )
                acc1 += std::uniform_int_distribution<std::uint64_t> ( ) ( gen1 );
        }

        VOLATILE std::uint64_t acc2 = 0;

        {
            degski::AutoTimer t ( degski::milliseconds );

            while ( cnt2-- )
                acc2 += std::uniform_int_distribution<std::uint64_t> ( ) ( gen2 );
        }

        VOLATILE std::uint64_t acc3 = 0;

        {
            degski::AutoTimer t ( degski::milliseconds );

            while ( cnt3-- )
                acc3 += std::uniform_int_distribution<std::uint64_t> ( ) ( gen3 );
        }

        VOLATILE std::uint64_t acc4 = 0;

        {
            degski::AutoTimer t ( degski::milliseconds );

            while ( cnt4-- )
                acc4 += std::uniform_int_distribution<std::uint64_t> ( ) ( gen4 );
        }

        VOLATILE std::uint64_t acc5 = 0;

        {
            degski::AutoTimer t ( degski::milliseconds );

            while ( cnt5-- )
                acc5 += std::uniform_int_distribution<std::uint64_t> ( ) ( gen5 );
        }

        std::cout << std::boolalpha << !( ( acc2 == acc1 ) && ( acc3 == acc2 ) && ( acc4 == acc3 ) && ( acc5 == acc4 ) ) << '\n';
    }

    exit ( 0 );

#if 0

    std::cout << std::boolalpha << ( gen1 == gen2 ) << "\n";

    std::stringstream s;

    gen1 ( );
    gen1 ( );
    gen1 ( );
    gen1 ( );
    gen1 ( );
    gen1 ( );
    gen1 ( );
    gen1 ( );
    gen1 ( );
    gen1 ( );
    gen1 ( );
    gen1 ( );
    gen1 ( );
    gen1 ( );
    gen1 ( );
    gen1 ( );
    gen1 ( );
    gen1 ( );
    gen1 ( );
    gen1 ( );
    gen1 ( );
    gen1 ( );
    gen1 ( );

    s << gen1;

    std::cout << gen1 << '\n';
    std::cout << gen2 << '\n';

    s >> gen2;

    std::cout << gen2 << '\n';

    std::cout << std::boolalpha << ( gen1 == gen2 ) << "\n";

    std::seed_seq seq { INT32_MAX,0,0,0,0,0,0,346,456454,23423 };

    boost::random::xoroshiro128plus gen4 ( seq );

    std::vector<int> inp { 0, 5, 0, 0, 1, 3,4,43,3,4,4,4,45,43,463,63,63,356,36,25,141,144,0, 5, 0, 0, 1, 3,4,43,3,4,4,4,45,43,463,63,63,356,36,25,141,144 };

    auto start = std::begin ( inp );

    boost::random::xoroshiro128plus gen5 ( start, std::end(inp) );

    std::uint64_t seed = 0;

    std::vector<seq::result_type> v;
    v.reserve ( seq.size ( ) );
    seq.param ( std::back_inserter ( v ) );

    int shift = 32;

    for ( auto i : v ) {
        // std::cout << seed << '\n';
        seed ^= boost::random::detail::xoroshiro_integer_hash ( ( static_cast<std::uint64_t>( i ) << ( shift ^= int { 32 } ) ) );
    }

    seed = 0;

    for ( std::uint32_t i = 1; i < 1025; ++i ) {
        seed ^= boost::random::detail::xoroshiro_integer_hash ( ( static_cast<std::uint64_t>( i ) << ( shift ^= int { 32 } ) ) ^ seed );
        std::cout << seed << '\n';
    }

#endif

}

std::uint32_t Q1 [ 256 ], c1 = 362436;

std::uint32_t MWC256 ( ) {

    std::uint64_t t, a = 1540315826LL;
    static std::size_t i = 255;
    unsigned long x;
    i = ( i + 1 ) & 255;
    t = a * Q1 [ i ] + c1;
    c1 = ( t >> 32 );
    x = t + c1;
    if (x < c1) { x++; c1++; }
    return ( Q1 [ i ] = x );
}

std::uint32_t Q2 [ 4096 ], c2 = 362436;

std::uint32_t CMWC4096 ( ) {

    std::uint64_t t, a = 18782LL;
    static std::size_t i = 4095;
    std::uint32_t x, r = 0xfffffffe;
    i = ( i + 1 ) & 4095;
    t = a*Q2 [ i ] + c2;
    c2 = ( t >> 32 );
    x = t + c2;
    if ( x < c2 ) { x++; c2++; }
    return ( Q2 [ i ] = r - x );
}

int main436463466 ( ) {

    std::seed_seq seq { INT32_MAX, 3466, 456454, 23423 };

    boost::random::taus88 gen ( seq );

    std::cout << gen << "\n\n";

    return 0;
}

int main34342 ( ) {

    typedef boost::random::taus88 Engine;
    // typedef std::ranlux48 Engine;

    boost::random::random_device rng1;
    Engine eng1 ( rng1 );

    std::cout << eng1 << '\n';

    //std::random_device rng2;
    //Engine eng2 ( rng2 );

    //std::cout << eng2 << '\n';

    boost::random::mt19937 rng3;
    Engine eng3 ( rng3 );

    std::cout << eng3 << '\n';

    //std::mt19937 rng4;
    //Engine eng4 ( rng4 );

    //std::cout << eng4 << '\n';

    boost::random::seed_seq seq5 { INT32_MAX, 3466, 456454, 23423 };
    Engine eng5 ( seq5 );

    std::cout << eng5 << '\n';

    boost::random::seed_seq_fe256 seq6 { INT32_MAX, 3466, 456454, 23423 };
    Engine eng6 ( seq6 );

    std::cout << eng6 << '\n';

    std::seed_seq seq7 { INT32_MAX, 3466, 456454, 23423 };
    Engine eng7 ( seq7 );

    std::cout << eng7 << '\n';

    Engine eng8 ( eng7 ); // Copy constructor...

    std::cout << eng8 << '\n';

    Engine eng9 ( 4563546 );

    std::cout << eng9 << '\n';

#if 0

    exit ( 0 );

    boost::random::seed_seq seq { INT32_MAX, 3466, 456454, 23423 };

    boost::random::cmwc4 gen ( 6568 );

    std::int64_t cnt = 1'000'000'000;

    std::uint64_t acc = 0;

    {
        degski::AutoTimer t ( degski::milliseconds );

        while ( cnt-- )
        acc += std::uniform_int_distribution<std::uint32_t> ( ) ( gen );
    }

    std::cout << gen << '\n';

#endif
    return 0;
}

// https://genix.wordpress.com/2014/07/29/detecting-the-presence-of-a-member-function/

namespace boost {
namespace random {

template<typename G, std::enable_if_t<boost::random::traits::is_generator<G>::value>* = nullptr>
using generator = G;
template<typename D, std::enable_if_t<boost::random::traits::is_random_device<D>::value>* = nullptr>
using device = D;
template<typename S, std::enable_if_t<boost::random::traits::is_seed_seq<S>::value>* = nullptr>
using sequence = S;

} // namespace random
} // namespace boost

auto main4543543 ( ) -> int {

    std::cout << std::boolalpha
        << '\n'
        << boost::random::traits::is_generator<int>::value
        << '\n'
        << boost::random::traits::is_generator<boost::random::taus88>::value
        << '\n'
        << boost::random::traits::is_random_device<std::random_device>::value
        << '\n'
        << boost::random::traits::is_random_device<boost::random::random_device>::value
        << '\n'
        << boost::random::traits::is_seed_seq<std::seed_seq>::value
        << '\n';

    return 0;
}

#if 0
template <typename...>
using void_t = void;

// We have our base case:

template<class T, class Index, typename = void>
struct has_subscript_operator : std::false_type { };

// And our expression SFINAE valid case:

template<class T, class Index>
struct has_subscript_operator<T, Index, void_t<
    decltype(std::declval<T>( )[std::declval<Index>( )])
    >> : std::true_type { };

namespace details {
    template<class...>struct voider { using type = void; };
    template<class...Ts>using void_t = typename voider<Ts...>::type;

    template<template<class...>class Z, class, class...Ts>
    struct can_apply :
        std::false_type
    {};
    template<template<class...>class Z, class...Ts>
    struct can_apply<Z, void_t<Z<Ts...>>, Ts...> :
        std::true_type
    {};
}
template<template<class...>class Z, class...Ts>
using can_apply = details::can_apply<Z, void, Ts...>;

// You can then simply write properties :

template <class T, class Index>
using subscript_t = decltype(std::declval<T>()[std::declval<Index> ( )]);

template <class T, class Index>
using has_subscript = can_apply<subscript_t, T, Index>;

template <class T>
using call_t = decltype(std::declval<T>()());

template <class T>
using has_call_operator = can_apply<call_t, T>;

template <class T>
using has_seed_t = decltype(std::declval<T>().seed());

template <class T>
using has_seed = can_apply<has_seed_t, T>;

template <class T>
using has_max_t = decltype(std::declval<T> ( ).max());

template <class T>
using has_max = can_apply<has_max_t, T>;

template<typename T, typename R, typename RT = typename T::result_type>
struct has_method_max : boost::random::traits::has_method_max<T, R> {};
#endif

struct Foo {

    typedef int result_type;

    result_type i;

#if 1
    static result_type max ( int ) { return INT_MAX; }
#else
    result_type max ( int ) { return INT_MAX; }
#endif

    Foo ( int i_ ) : i ( i_ ) {}

    result_type get ( ) { return i; }
    result_type get ( int ) { return i; }
};

#define DEFINE_HAS_STATIC(func_name)                                                                            \
template <typename U, typename return_type, typename... args>                                                   \
class has_trait_static_##func_name                                                                              \
{                                                                                \
private:                                                                                \
    template<typename T, T> struct helper;                                                                      \
    template<typename check_type, typename... check_args>                                                       \
    static decltype(std::declval<helper<return_type(*)(check_args...), &check_type::func_name>>()) check(int);  \
    template<typename check_type, typename... check_args>                                                       \
    static boost::random::traits::oops check(...);                                                              \
public:                                                                                \
    static constexpr bool value = !std::is_same_v<decltype(check<U, args...>(0)), boost::random::traits::oops>; \
};

DEFINE_HAS_STATIC ( max )

#include <boost/tti/has_type.hpp>
#include <boost/tti/has_static_member_function.hpp>

BOOST_TTI_HAS_TYPE(result_type)

template<typename... Ts> struct make_void { typedef void type; };
template<typename... Ts> using void_t = typename make_void<Ts...>::type;

struct nonesuch {
    nonesuch ( ) = delete;
    ~nonesuch ( ) = delete;
    nonesuch ( nonesuch const& ) = delete;
    void operator=( nonesuch const& ) = delete;
};

template<class Default, class AlwaysVoid, template<class...> class Op, class... Args>
struct detector {
    using value_t = std::false_type;
    using type = Default;
};

template<class Default, template<class...> class Op, class... Args>
struct detector<Default, void_t<Op<Args...>>, Op, Args...> {
    using value_t = std::true_type;
    using type = Op<Args...>;
};

template<template<class...> class Op, class... Args>
using is_detected = typename detector<nonesuch, void, Op, Args...>::value_t;
template<template<class...> class Op, class... Args>
using detected_t = typename detector<nonesuch, void, Op, Args...>::type;
template <class Default, template<class...> class Op, class... Args>
using detected_or = detector<Default, void, Op, Args...>;

template< template<class...> class Op, class... Args >
constexpr bool is_detected_v = is_detected<Op, Args...>::value;
template< class Default, template<class...> class Op, class... Args >
using detected_or_t = typename detected_or<Default, Op, Args...>::type;
template <class Expected, template<class...> class Op, class... Args>
using is_detected_exact = std::is_same<Expected, detected_t<Op, Args...>>;
template <class Expected, template<class...> class Op, class... Args>
constexpr bool is_detected_exact_v = is_detected_exact<Expected, Op, Args...>::value;
template <class To, template<class...> class Op, class... Args>
using is_detected_convertible = std::is_convertible<detected_t<Op, Args...>, To>;
template <class To, template<class...> class Op, class... Args>
constexpr bool is_detected_convertible_v = is_detected_convertible<To, Op, Args...>::value;

template <typename T, template <typename> class Expression, typename AlwaysVoid = void_t<>>
struct compiles : std::false_type {};
template <typename T, template <typename> class Expression>
struct compiles<T, Expression, void_t<Expression<T>>> : std::true_type {};

template <typename ResultType, typename CheckType, template <typename> class... Values>
using requires = std::enable_if_t<std::conjunction<Values<CheckType>...>::value, ResultType>;

auto main3253523 ( ) -> int {

    std::cout << std::boolalpha
        << true
        << '\n'
        << true
        << '\n';

    return 0;
}

class A {};

template <typename T, typename = void>
struct is_iterable : std::false_type {};
template <typename T>
struct is_iterable<T, std::void_t<decltype( std::declval<T> ( ).begin ( ) ),
    decltype( std::declval<T> ( ).end ( ) )>>
    : std::true_type {};

int main34242 ( )
{
    std::cout << std::boolalpha;
    std::cout << is_iterable<std::vector<double>>::value << '\n';
    std::cout << is_iterable<double>::value << '\n';
    std::cout << is_iterable<A>::value << '\n';

    return 0;
}

//decltype(return_type(refval<check_args>()...))
//template<typename check_type, typename... check_args>
//static std::uint16_t check ( ... );

#include <iostream>
#include <string>

using namespace std;

template<typename ...Args> auto sum ( Args ...args )
{
    return ( args + ... + 0 );
}

template<typename ...Args> auto sum2 ( Args ...args )
{
    return ( args * ... );
}

template <auto ... vs> struct HeterogenousValueList {};

using MyList = HeterogenousValueList<'a', 100, 'b'>;

int main325245 ( )
{
    cout << sum ( 1, 2, 3, 4, 5, 6, 7 ) << "\n";
    cout << sum2 ( 1, 2, 3, 4, 5, 6, 7 ) << "\n";

    // std::pair<int, double> p ( 10, 0.0 );
    // same as
    std::pair p ( 10, 0.0 ); // deduced automatically!

    return 0;
}

template< typename s, typename t, typename u, typename = void >
struct is_product_serializable : std::false_type {};

template< typename s, typename t, typename u >
struct is_product_serializable< s, t, u, typename std::conditional< true, void,
    decltype( std::declval< s > ( ) << std::declval< t > ( ) * std::declval< u > ( ) )
>::type >
    : std::true_type {};


// Boost.Geometry (aka GGL, Generic Geometry Library)

// Copyright (c) 2007-2015 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2008-2015 Bruno Lalande, Paris, France.
// Copyright (c) 2009-2015 Mateusz Loskot, London, UK.
// Copyright (c) 2014-2015 Adam Wulkiewicz, Lodz, Poland.

// This file was modified by Oracle on 2014-2020.
// Modifications copyright (c) 2014-2020 Oracle and/or its affiliates.

// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle
// Contributed and/or modified by Menelaos Karavelas, on behalf of Oracle

// Parts of Boost.Geometry are redesigned from Geodan's Geographic Library
// (geolib/GGL), copyright (c) 1995-2010 Geodan, Amsterdam, the Netherlands.

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_GEOMETRY_ALGORITHMS_DETAIL_EQUALS_IMPLEMENTATION_HPP
#define BOOST_GEOMETRY_ALGORITHMS_DETAIL_EQUALS_IMPLEMENTATION_HPP

#include <cstddef>
#include <type_traits>
#include <vector>

#include <boost/range/size.hpp>

#include <boost/geometry/core/access.hpp>
#include <boost/geometry/core/tags.hpp>

#include <boost/geometry/algorithms/detail/equals/point_point.hpp>

// For trivial checks
#include <boost/geometry/algorithms/area.hpp>
#include <boost/geometry/algorithms/length.hpp>
#include <boost/geometry/util/math.hpp>
#include <boost/geometry/util/select_coordinate_type.hpp>
#include <boost/geometry/util/select_most_precise.hpp>

#include <boost/geometry/algorithms/detail/equals/collect_vectors.hpp>
#include <boost/geometry/algorithms/detail/equals/interface.hpp>
#include <boost/geometry/algorithms/detail/relate/relate_impl.hpp>
#include <boost/geometry/algorithms/relate.hpp>

#include <boost/geometry/views/detail/indexed_point_view.hpp>

namespace boost { namespace geometry
{

#ifndef DOXYGEN_NO_DETAIL
namespace detail { namespace equals
{

template
<
    std::size_t Dimension,
    std::size_t DimensionCount
>
struct point_point
{
    template <typename Point1, typename Point2, typename Strategy>
    static inline bool apply(Point1 const& point1, Point2 const& point2,
                             Strategy const& strategy)
    {
        typedef decltype(strategy.relate(point1, point2)) strategy_type;
        return strategy_type::apply(point1, point2);
    }
};

template
<
    std::size_t Dimension,
    std::size_t DimensionCount
>
struct box_box
{
    template <typename Box1, typename Box2, typename Strategy>
    static inline bool apply(Box1 const& box1, Box2 const& box2, Strategy const& strategy)
    {
        if (!geometry::math::equals(get<min_corner, Dimension>(box1), get<min_corner, Dimension>(box2))
            || !geometry::math::equals(get<max_corner, Dimension>(box1), get<max_corner, Dimension>(box2)))
        {
            return false;
        }
        return box_box<Dimension + 1, DimensionCount>::apply(box1, box2, strategy);
    }
};

template <std::size_t DimensionCount>
struct box_box<DimensionCount, DimensionCount>
{
    template <typename Box1, typename Box2, typename Strategy>
    static inline bool apply(Box1 const& , Box2 const& , Strategy const& )
    {
        return true;
    }
};

struct segment_segment
{
    template <typename Segment1, typename Segment2, typename Strategy>
    static inline bool apply(Segment1 const& segment1, Segment2 const& segment2,
                             Strategy const& strategy)
    {
        return equals::equals_point_point(
                    indexed_point_view<Segment1 const, 0>(segment1),
                    indexed_point_view<Segment2 const, 0>(segment2),
                    strategy)
                ? equals::equals_point_point(
                    indexed_point_view<Segment1 const, 1>(segment1),
                    indexed_point_view<Segment2 const, 1>(segment2),
                    strategy)
                : ( equals::equals_point_point(
                        indexed_point_view<Segment1 const, 0>(segment1),
                        indexed_point_view<Segment2 const, 1>(segment2),
                        strategy)
                 && equals::equals_point_point(
                        indexed_point_view<Segment1 const, 1>(segment1),
                        indexed_point_view<Segment2 const, 0>(segment2),
                        strategy)
                  );
    }
};

struct area_check
{
    template <typename Geometry1, typename Geometry2, typename Strategy>
    static inline bool apply(Geometry1 const& geometry1,
                             Geometry2 const& geometry2,
                             Strategy const& strategy)
    {
        return geometry::math::equals(geometry::area(geometry1, strategy),
                                      geometry::area(geometry2, strategy));
    }
};

/*
struct length_check
{
    template <typename Geometry1, typename Geometry2, typename Strategy>
    static inline bool apply(Geometry1 const& geometry1,
                             Geometry2 const& geometry2,
                             Strategy const& strategy)
    {
        return geometry::math::equals(geometry::length(geometry1, strategy),
                                      geometry::length(geometry2, strategy));
    }
};
*/

template <typename Geometry1, typename Geometry2, typename Strategy>
struct collected_vector
{
    typedef typename geometry::select_most_precise
        <
            typename select_coordinate_type
                <
                    Geometry1, Geometry2
                >::type,
            double
        >::type calculation_type;

    typedef geometry::collected_vector
        <
            calculation_type,
            Geometry1,
            decltype(std::declval<Strategy>().side())
        > type;
};

template <typename TrivialCheck>
struct equals_by_collection
{
    template <typename Geometry1, typename Geometry2, typename Strategy>
    static inline bool apply(Geometry1 const& geometry1,
                             Geometry2 const& geometry2,
                             Strategy const& strategy)
    {
        if (! TrivialCheck::apply(geometry1, geometry2, strategy))
        {
            return false;
        }

        typedef typename collected_vector
            <
                Geometry1, Geometry2, Strategy
            >::type collected_vector_type;

        std::vector<collected_vector_type> c1, c2;

        geometry::collect_vectors(c1, geometry1);
        geometry::collect_vectors(c2, geometry2);

        if (boost::size(c1) != boost::size(c2))
        {
            return false;
        }

        std::sort(c1.begin(), c1.end());
        std::sort(c2.begin(), c2.end());

        // Just check if these vectors are equal.
        return std::equal(c1.begin(), c1.end(), c2.begin());
    }
};

template<typename Geometry1, typename Geometry2>
struct equals_by_relate
    : detail::relate::relate_impl
        <
            detail::de9im::static_mask_equals_type,
            Geometry1,
            Geometry2
        >
{};

// If collect_vectors which is a SideStrategy-dispatched optimization
// is implemented in a way consistent with the Intersection/Side Strategy
// then collect_vectors is used, otherwise relate is used.
// NOTE: the result could be conceptually different for invalid
// geometries in different coordinate systems because collect_vectors
// and relate treat invalid geometries differently.
template<typename TrivialCheck>
struct equals_by_collection_or_relate
{
    template <typename Geometry1, typename Geometry2, typename Strategy>
    static inline bool apply(Geometry1 const& geometry1,
                             Geometry2 const& geometry2,
                             Strategy const& strategy)
    {
        typedef std::is_base_of
            <
                nyi::not_implemented_tag,
                typename collected_vector
                    <
                        Geometry1, Geometry2, Strategy
                    >::type
            > enable_relate_type;

        return apply(geometry1, geometry2, strategy, enable_relate_type());
    }

private:
    template <typename Geometry1, typename Geometry2, typename Strategy>
    static inline bool apply(Geometry1 const& geometry1,
                             Geometry2 const& geometry2,
                             Strategy const& strategy,
                             std::false_type /*enable_relate*/)
    {
        return equals_by_collection<TrivialCheck>::apply(geometry1, geometry2, strategy);
    }

    template <typename Geometry1, typename Geometry2, typename Strategy>
    static inline bool apply(Geometry1 const& geometry1,
                             Geometry2 const& geometry2,
                             Strategy const& strategy,
                             std::true_type /*enable_relate*/)
    {
        return equals_by_relate<Geometry1, Geometry2>::apply(geometry1, geometry2, strategy);
    }
};

struct equals_always_false
{
    template <typename Geometry1, typename Geometry2, typename Strategy>
    static inline bool apply(Geometry1 const& , Geometry2 const& , Strategy const& )
    {
        return false;
    }
};

}} // namespace detail::equals
#endif // DOXYGEN_NO_DETAIL

#ifndef DOXYGEN_NO_DISPATCH
namespace dispatch
{

template <typename P1, typename P2, std::size_t DimensionCount, bool Reverse>
struct equals<P1, P2, point_tag, point_tag, pointlike_tag, pointlike_tag, DimensionCount, Reverse>
    : detail::equals::point_point<0, DimensionCount>
{};

template <typename MultiPoint1, typename MultiPoint2, std::size_t DimensionCount, bool Reverse>
struct equals<MultiPoint1, MultiPoint2, multi_point_tag, multi_point_tag, pointlike_tag, pointlike_tag, DimensionCount, Reverse>
    : detail::equals::equals_by_relate<MultiPoint1, MultiPoint2>
{};

template <typename MultiPoint, typename Point, std::size_t DimensionCount, bool Reverse>
struct equals<Point, MultiPoint, point_tag, multi_point_tag, pointlike_tag, pointlike_tag, DimensionCount, Reverse>
    : detail::equals::equals_by_relate<Point, MultiPoint>
{};

template <typename Box1, typename Box2, std::size_t DimensionCount, bool Reverse>
struct equals<Box1, Box2, box_tag, box_tag, areal_tag, areal_tag, DimensionCount, Reverse>
    : detail::equals::box_box<0, DimensionCount>
{};

template <typename Ring1, typename Ring2, bool Reverse>
struct equals<Ring1, Ring2, ring_tag, ring_tag, areal_tag, areal_tag, 2, Reverse>
    : detail::equals::equals_by_collection_or_relate<detail::equals::area_check>
{};

template <typename Polygon1, typename Polygon2, bool Reverse>
struct equals<Polygon1, Polygon2, polygon_tag, polygon_tag, areal_tag, areal_tag, 2, Reverse>
    : detail::equals::equals_by_collection_or_relate<detail::equals::area_check>
{};

template <typename Polygon, typename Ring, bool Reverse>
struct equals<Polygon, Ring, polygon_tag, ring_tag, areal_tag, areal_tag, 2, Reverse>
    : detail::equals::equals_by_collection_or_relate<detail::equals::area_check>
{};

template <typename Ring, typename Box, bool Reverse>
struct equals<Ring, Box, ring_tag, box_tag, areal_tag, areal_tag, 2, Reverse>
    : detail::equals::equals_by_collection<detail::equals::area_check>
{};

template <typename Polygon, typename Box, bool Reverse>
struct equals<Polygon, Box, polygon_tag, box_tag, areal_tag, areal_tag, 2, Reverse>
    : detail::equals::equals_by_collection<detail::equals::area_check>
{};

template <typename Segment1, typename Segment2, std::size_t DimensionCount, bool Reverse>
struct equals<Segment1, Segment2, segment_tag, segment_tag, linear_tag, linear_tag, DimensionCount, Reverse>
    : detail::equals::segment_segment
{};

template <typename LineString1, typename LineString2, bool Reverse>
struct equals<LineString1, LineString2, linestring_tag, linestring_tag, linear_tag, linear_tag, 2, Reverse>
    : detail::equals::equals_by_relate<LineString1, LineString2>
{};

template <typename LineString, typename MultiLineString, bool Reverse>
struct equals<LineString, MultiLineString, linestring_tag, multi_linestring_tag, linear_tag, linear_tag, 2, Reverse>
    : detail::equals::equals_by_relate<LineString, MultiLineString>
{};

template <typename MultiLineString1, typename MultiLineString2, bool Reverse>
struct equals<MultiLineString1, MultiLineString2, multi_linestring_tag, multi_linestring_tag, linear_tag, linear_tag, 2, Reverse>
    : detail::equals::equals_by_relate<MultiLineString1, MultiLineString2>
{};

template <typename LineString, typename Segment, bool Reverse>
struct equals<LineString, Segment, linestring_tag, segment_tag, linear_tag, linear_tag, 2, Reverse>
    : detail::equals::equals_by_relate<LineString, Segment>
{};

template <typename MultiLineString, typename Segment, bool Reverse>
struct equals<MultiLineString, Segment, multi_linestring_tag, segment_tag, linear_tag, linear_tag, 2, Reverse>
    : detail::equals::equals_by_relate<MultiLineString, Segment>
{};

template <typename MultiPolygon1, typename MultiPolygon2, bool Reverse>
struct equals
    <
        MultiPolygon1, MultiPolygon2,
        multi_polygon_tag, multi_polygon_tag,
        areal_tag, areal_tag,
        2,
        Reverse
    >
    : detail::equals::equals_by_collection_or_relate<detail::equals::area_check>
{};

template <typename Polygon, typename MultiPolygon, bool Reverse>
struct equals
    <
        Polygon, MultiPolygon,
        polygon_tag, multi_polygon_tag,
        areal_tag, areal_tag,
        2,
        Reverse
    >
    : detail::equals::equals_by_collection_or_relate<detail::equals::area_check>
{};

template <typename MultiPolygon, typename Ring, bool Reverse>
struct equals
    <
        MultiPolygon, Ring,
        multi_polygon_tag, ring_tag,
        areal_tag, areal_tag,
        2,
        Reverse
    >
    : detail::equals::equals_by_collection_or_relate<detail::equals::area_check>
{};

// NOTE: degenerated linear geometries, e.g. segment or linestring containing
//   2 equal points, are considered to be invalid. Though theoretically
//   degenerated segments and linestrings could be treated as points and
//   multi-linestrings as multi-points.
//   This reasoning could also be applied to boxes.

template <typename Geometry1, typename Geometry2, typename Tag1, typename Tag2, std::size_t DimensionCount>
struct equals<Geometry1, Geometry2, Tag1, Tag2, pointlike_tag, linear_tag, DimensionCount, false>
    : detail::equals::equals_always_false
{};

template <typename Geometry1, typename Geometry2, typename Tag1, typename Tag2, std::size_t DimensionCount>
struct equals<Geometry1, Geometry2, Tag1, Tag2, linear_tag, pointlike_tag, DimensionCount, false>
    : detail::equals::equals_always_false
{};

template <typename Geometry1, typename Geometry2, typename Tag1, typename Tag2, std::size_t DimensionCount>
struct equals<Geometry1, Geometry2, Tag1, Tag2, pointlike_tag, areal_tag, DimensionCount, false>
    : detail::equals::equals_always_false
{};

template <typename Geometry1, typename Geometry2, typename Tag1, typename Tag2, std::size_t DimensionCount>
struct equals<Geometry1, Geometry2, Tag1, Tag2, areal_tag, pointlike_tag, DimensionCount, false>
    : detail::equals::equals_always_false
{};

template <typename Geometry1, typename Geometry2, typename Tag1, typename Tag2, std::size_t DimensionCount>
struct equals<Geometry1, Geometry2, Tag1, Tag2, linear_tag, areal_tag, DimensionCount, false>
    : detail::equals::equals_always_false
{};

template <typename Geometry1, typename Geometry2, typename Tag1, typename Tag2, std::size_t DimensionCount>
struct equals<Geometry1, Geometry2, Tag1, Tag2, areal_tag, linear_tag, DimensionCount, false>
    : detail::equals::equals_always_false
{};

} // namespace dispatch
#endif // DOXYGEN_NO_DISPATCH

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_EQUALS_IMPLEMENTATION_HPP


#ifdef COMPILATION_INSTRUCTIONS
(echo '#include"'$0'"'>$0.cpp)&&$CXX -Wall -Wextra -Wpedantic -Wfatal-errors -D_TEST_MULTI_ADAPTORS_LAPACK_CUDA $0.cpp -o $0x `pkg-config --libs blas` -lcudart -lcublas -lcusolver&&$0x&&rm $0x $0.cpp; exit
#endif
// © Alfredo A. Correa 2020

#ifndef MULTI_ADAPTORS_LAPACK_CUDA_HPP
#define MULTI_ADAPTORS_LAPACK_CUDA_HPP

#include "../../memory/adaptors/cuda/ptr.hpp"
#include "../../memory/adaptors/cuda/managed/ptr.hpp"
#include "../../memory/adaptors/cuda/managed/allocator.hpp"

#include "../../adaptors/cuda.hpp"

//#include<cublas_v2.h>
#include <cusolverDn.h>

//#include<iostream> // debug

#include <boost/log/trivial.hpp>

#include<complex>
#include<memory>

#include "../blas/filling.hpp"

namespace boost{
namespace multi{

namespace cusolver{

enum class status : typename std::underlying_type<cusolverStatus_t>::type{
	success                   = CUSOLVER_STATUS_SUCCESS, // "The operation completed successfully."
	not_initialized           = CUSOLVER_STATUS_NOT_INITIALIZED, // "The cuSolver library was not initialized. This is usually caused by the lack of a prior call, an error in the CUDA Runtime API called by the cuSolver routine, or an error in the hardware setup. To correct: call cusolverCreate() prior to the function call; and check that the hardware, an appropriate version of the driver, and the cuSolver library are correctly installed."
	allocation_failed         = CUSOLVER_STATUS_ALLOC_FAILED, // "Resource allocation failed inside the cuSolver library. This is usually caused by a cudaMalloc() failure. To correct: prior to the function call, deallocate previously allocated memory as much as possible."
	invalid_value             = CUSOLVER_STATUS_INVALID_VALUE, // "An unsupported value or parameter was passed to the function (a negative vector size, for example). To correct: ensure that all the parameters being passed have valid values."
	architecture_mismatch     = CUSOLVER_STATUS_ARCH_MISMATCH, // "The function requires a feature absent from the device architecture; usually caused by the lack of support for atomic operations or double precision. To correct: compile and run the application on a device with compute capability 2.0 or above."
	execution_failed          = CUSOLVER_STATUS_EXECUTION_FAILED, // "The GPU program failed to execute. This is often caused by a launch failure of the kernel on the GPU, which can be caused by multiple reasons. To correct: check that the hardware, an appropriate version of the driver, and the cuSolver library are correctly installed."
	internal_error            = CUSOLVER_STATUS_INTERNAL_ERROR, // "An internal cuSolver operation failed. This error is usually caused by a cudaMemcpyAsync() failure. To correct: check that the hardware, an appropriate version of the driver, and the cuSolver library are correctly installed. Also, check that the memory passed as a parameter to the routine is not being deallocated prior to the routine’s completion."
	matrix_type_not_supported = CUSOLVER_STATUS_MATRIX_TYPE_NOT_SUPPORTED // "The matrix type is not supported by this function. This is usually caused by passing an invalid matrix descriptor to the function. To correct: check that the fields in descrA were set correctly."
};

std::string inline status_string(enum status s){ //https://stackoverflow.com/questions/13041399/equivalent-of-cudageterrorstring-for-cublas
	switch(s){
		case status::success                   : return "The operation completed successfully.";
		case status::not_initialized           : return "The cuSolver library was not initialized. This is usually caused by the lack of a prior call, an error in the CUDA Runtime API called by the cuSolver routine, or an error in the hardware setup. To correct: call cusolverCreate() prior to the function call; and check that the hardware, an appropriate version of the driver, and the cuSolver library are correctly installed.";
		case status::allocation_failed         : return "Resource allocation failed inside the cuSolver library. This is usually caused by a cudaMalloc() failure. To correct: prior to the function call, deallocate previously allocated memory as much as possible.";
		case status::invalid_value             : return "An unsupported value or parameter was passed to the function (a negative vector size, for example). To correct: ensure that all the parameters being passed have valid values.";
		case status::architecture_mismatch     : return "The function requires a feature absent from the device architecture; usually caused by the lack of support for atomic operations or double precision. To correct: compile and run the application on a device with compute capability 2.0 or above.";
		case status::execution_failed          : return "The GPU program failed to execute. This is often caused by a launch failure of the kernel on the GPU, which can be caused by multiple reasons. To correct: check that the hardware, an appropriate version of the driver, and the cuSolver library are correctly installed.";
		case status::internal_error            : return "An internal cuSolver operation failed. This error is usually caused by a cudaMemcpyAsync() failure. To correct: check that the hardware, an appropriate version of the driver, and the cuSolver library are correctly installed. Also, check that the memory passed as a parameter to the routine is not being deallocated prior to the routine’s completion.";
		case status::matrix_type_not_supported : return "The matrix type is not supported by this function. This is usually caused by passing an invalid matrix descriptor to the function. To correct: check that the fields in descrA were set correctly.";
	}
	return "cublas status <unknown>";
}
struct error_category : std::error_category{
	char const* name() const noexcept override{return "cusolver wrapper";}
	std::string message(int err) const override{return cusolver::status_string(static_cast<enum cusolver::status>(err));}
	static error_category& instance(){static cusolver::error_category instance; return instance;}
};
inline std::error_code make_error_code(cusolver::status s) noexcept{
	return std::error_code(int(s), cusolver::error_category::instance());
}

struct version_t{
	int major = -1, minor =-1, patch=-1;
	friend std::ostream& operator<<(std::ostream& os, version_t const& self){
		return os<< self.major <<'.'<< self.minor <<'.'<<self.patch <<'\n';
	}
};

auto version(){
	version_t ret;
	cusolverGetProperty(MAJOR_VERSION, &ret.major);
	cusolverGetProperty(MINOR_VERSION, &ret.minor);
	cusolverGetProperty(PATCH_LEVEL, &ret.patch);
	return ret;
}

namespace dense{

using blas::filling;

struct context : std::unique_ptr<std::decay_t<decltype(*cusolverDnHandle_t{})>, decltype(&cusolverDnDestroy)>{
	context()  : std::unique_ptr<std::decay_t<decltype(*cusolverDnHandle_t{})>, decltype(&cusolverDnDestroy)>{
		[]{
			cusolverDnHandle_t h;
			auto s=cusolverDnCreate(&h); assert(CUSOLVER_STATUS_SUCCESS==s and h);
			return h;
		}(), &cusolverDnDestroy
	}{}
	template<class A> int potrf_buffer_size(filling uplo, A const& a);
};

template<typename T>
struct cusolverDn;//{
//	template<class... ArgsA3, class... ArgsB2>
//	static auto potrf_bufferSize(ArgsA3... argsa3, T* ptr, ArgsB2... argsb2);
//};

template<>
struct cusolverDn<float>{
	template<class... A3, class... B2>
	static auto potrf_bufferSize(A3... a3, double* ptr, B2... b2)
	->decltype(cusolverDnSpotrf_bufferSize(a3..., ptr, b2...)){
		return cusolverDnSpotrf_bufferSize(a3..., ptr, b2...);}
	template<class... A> static auto syev(A... a)
	->decltype(cusolverDnSsyevd_bufferSize(a...)){
		return cusolverDnSsyevd_bufferSize(a...);}
	template<class... A> static auto syev(A... a)
	->decltype(cusolverDnSsyevd(a...)){
		return cusolverDnSsyevd(a...);}
};

template<>
struct cusolverDn<double>{
	template<class... A3, class... B2>
	static auto potrf_bufferSize(A3... a3, double* ptr, B2... b2)
	->decltype(cusolverDnDpotrf_bufferSize(a3..., ptr, b2...)){
		return cusolverDnDpotrf_bufferSize(a3..., ptr, b2...);}
	template<class... A> static auto syevd_bufferSize(A... a)
//	->decltype(cusolverDnDsyevd_bufferSize(a...)){
	{	return cusolverDnDsyevd_bufferSize(a...);}
	template<class... A> static auto syevd(A... a)
	->decltype(cusolverDnDsyevd(a...))
	{	return cusolverDnDsyevd(a...);}
};

template<>
struct cusolverDn<std::complex<float>>{
	template<class... A3, class... B2>
	static auto potrf_bufferSize(A3... a3, std::complex<double>* ptr, B2... b2)
	->decltype(cusolverDnCpotrf_bufferSize(a3..., reinterpret_cast<cuComplex*>(ptr), b2...)){
		return cusolverDnCpotrf_bufferSize(a3..., reinterpret_cast<cuComplex*>(ptr), b2...);}
};

template<>
struct cusolverDn<std::complex<double>>{
	static auto translate(std::complex<double>* p){return reinterpret_cast<cuDoubleComplex*>(p);}
	template<class T> static auto translate(T t){return t;}
	template<class... A> static auto potrf_bufferSize(A... a)
	->decltype(cusolverDnZpotrf_bufferSize(translate(a)...)){
		return cusolverDnZpotrf_bufferSize(translate(a)...);}
	template<class... A> static auto potrf(A... a)
	->decltype(cusolverDnZpotrf(translate(a)...)){
		return cusolverDnZpotrf(translate(a)...);}
};

}
}

namespace memory{
namespace cuda{

template<class UL, class S, class PtrT, typename T = typename std::pointer_traits<PtrT>::element_type>
void potrf(UL ul, S n, PtrT A, S incx, int& info){
	boost::multi::cusolver::dense::context ctx; //BOOST_LOG_TRIVIAL(trace)<<"cuda::potrf called on size/stride "<< n <<' '<< incx <<'\n';
	int lwork = -1;
	{
		auto s = cusolver::dense::cusolverDn<T>::potrf_bufferSize(ctx.get(), ul=='U'?CUBLAS_FILL_MODE_UPPER:CUBLAS_FILL_MODE_LOWER, n, raw_pointer_cast(A), incx, &lwork);
		assert(s == CUSOLVER_STATUS_SUCCESS); assert(lwork >= 0);
	}
	multi::cuda::array<T, 1> work(lwork);
	multi::cuda::static_array<int, 0> devInfo;
	auto s = cusolver::dense::cusolverDn<T>::potrf(ctx.get(), ul=='U'?CUBLAS_FILL_MODE_UPPER:CUBLAS_FILL_MODE_LOWER, n, raw_pointer_cast(A), incx, raw_pointer_cast(base(work)), lwork, raw_pointer_cast(base(devInfo)) );
	assert(s == CUSOLVER_STATUS_SUCCESS);
	cudaDeviceSynchronize();
	info = devInfo();
}

// https://docs.nvidia.com/cuda/cusolver/index.html#cuds-lt-t-gt-syevd
template<class S, class PtrT, typename T = typename std::pointer_traits<PtrT>::element_type>
void syev(char jobz, char uplo, S n, PtrT a, S lda, PtrT w, PtrT /*work*/, S /*lwork*/, int& info){
	boost::multi::cusolver::dense::context ctx;
	int lwork_needed = -1;
	{
		auto s = cusolver::dense::cusolverDn<T>::syevd_bufferSize(
			ctx.get(), jobz=='V'?CUSOLVER_EIG_MODE_VECTOR:CUSOLVER_EIG_MODE_NOVECTOR,
			uplo=='U'?CUBLAS_FILL_MODE_UPPER:CUBLAS_FILL_MODE_LOWER,
			n,
			raw_pointer_cast(a),
			lda,
			raw_pointer_cast(w),
			&lwork_needed
		);
		assert(s == CUSOLVER_STATUS_SUCCESS); assert(lwork_needed >= 0);
	}
	multi::cuda::array<T, 1> tmp_work(lwork_needed); // buffers needs no-managed memory!
	multi::cuda::static_array<int, 0> devInfo;
	auto s = cusolver::dense::cusolverDn<T>::syevd(
		ctx.get(), jobz=='V'?CUSOLVER_EIG_MODE_VECTOR:CUSOLVER_EIG_MODE_NOVECTOR,
		uplo=='U'?CUBLAS_FILL_MODE_UPPER:CUBLAS_FILL_MODE_LOWER,
		n,
		raw_pointer_cast(a),
		lda,
		raw_pointer_cast(w),
		raw_pointer_cast(tmp_work.data_elements()),
		tmp_work.size(),
		raw_pointer_cast(base(devInfo))
	);
	if( s != CUSOLVER_STATUS_SUCCESS ) throw std::system_error{cusolver::make_error_code(static_cast<cusolver::status>(s)), "cannot call cusolver function "};
//	cudaDeviceSynchronize();
	info = devInfo();
}

namespace managed{
	template<class UL, class S, class PtrT, typename T = typename std::pointer_traits<PtrT>::element_type>
	auto potrf(UL ul, S n, PtrT A, S incx, int& info)
	->decltype(cuda::potrf(ul, n, cuda::ptr<T>(A), incx, info)){
		return cuda::potrf(ul, n, cuda::ptr<T>(A), incx, info);}

	template<class S, class PtrT, class P2, typename T = typename std::pointer_traits<PtrT>::element_type>
	auto syev(char jobz, char uplo, S n, PtrT a, S lda, PtrT w, P2 work, S lwork, int& info)
	->decltype(cuda::syev(jobz, uplo, n, cuda::ptr<T>(a), lda, cuda::ptr<T>(w), cuda::ptr<T>(work), lwork, info)){
		return cuda::syev(jobz, uplo, n, cuda::ptr<T>(a), lda, cuda::ptr<T>(w), cuda::ptr<T>(work), lwork, info);}

}

}
}

}}

namespace std{template<> struct is_error_code_enum<::boost::multi::cusolver::status> : true_type{};}

///////////////////////////////////////////////////////////////////////////////

#if _TEST_MULTI_ADAPTORS_LAPACK_CUDA

#include "../../array.hpp"
#include "../../utility.hpp"
#include<cassert>

namespace multi = boost::multi;

int main(){
	std::cout << "cusolver version " << multi::cusolver::version() << std::endl;
	multi::cusolver::dense::context c;
//	multi::cublas_context c;
//	assert( c.version() >= 10100 );
}

#endif
#endif


//---------------------------------------------------------------------------//
//!
//! \file   Utility_StructuredHexMesh.hpp
//! \author Philip Britt
//! \brief  Hexahedral mesh class declaration
//!
//---------------------------------------------------------------------------//

#ifndef UTILITY_STRUCTURED_HEX_MESH_HPP
#define UTILITY_STRUCTURED_HEX_MESH_HPP

// Std Lib Includes
#include <utility>

// Boost Includes
#include <boost/serialization/vector.hpp>

// FRENSIE Includes
#include "Utility_Mesh.hpp"
#include "Utility_List.hpp"
#include "Utility_Tuple.hpp"
#include "Utility_Vector.hpp"
#include "Utility_Map.hpp"

namespace Utility{

/*! The structured hexahedral mesh class
 * \details This class stores the mesh itself and can be used to acquire
 * important information from the mesh (e.g. intersections of a line segment
 * with mesh elements).
 */
class StructuredHexMesh : public Mesh
{

public:

  //! The mesh element handle
  typedef Mesh::ElementHandle ElementHandle;

  //! The plane index
  typedef size_t PlaneIndex;

  //! Iterator over all mesh elements
  typedef Mesh::ElementHandleIterator ElementHandleIterator;

  //! The mesh element handle, primary intersection point, track length tuple array
  typedef Mesh::ElementHandleTrackLengthArray ElementHandleTrackLengthArray;

  //! The mesh element handle, volume map
  typedef Mesh::ElementHandleVolumeMap ElementHandleVolumeMap;

  //! The mesh data tag name set
  typedef Mesh::TagNameSet TagNameSet;

  //! The mesh element handle, data map
  typedef Mesh::MeshElementHandleDataMap MeshElementHandleDataMap;

  //! Constructor
  StructuredHexMesh( const std::vector<double>& x_planes,
                     const std::vector<double>& y_planes,
                     const std::vector<double>& z_planes );
  //! Destructor
  ~StructuredHexMesh()
  { /* ... */ }

  //! Get the mesh type name
  std::string getMeshTypeName() const final override;

  //! Get the mesh element type name
  std::string getMeshElementTypeName() const final override;

  //! Get the start iterator of the hex element list.
  ElementHandleIterator getStartElementHandleIterator() const final override;

  //! Get the end iterator of the hex element list.
  ElementHandleIterator getEndElementHandleIterator() const final override;

  //! Get the number of mesh elements
  size_t getNumberOfElements() const final override;

  //! Get the number of planes on the X axis.
  size_t getNumberOfXPlanes() const;

  //! Get the number of planes on the Y axis.
  size_t getNumberOfYPlanes() const;

  //! Get the number of planes on the Z axis.
  size_t getNumberOfZPlanes() const;

  //! Get the location of a specific plane on the x axis.
  double getXPlaneLocation( PlaneIndex i ) const;

  //! Get the location of a specific plane on the y axis.
  double getYPlaneLocation( PlaneIndex i ) const;

  //! Get the location of a specific plane on the z axis.
  double getZPlaneLocation( PlaneIndex i ) const;

  //! Deconstruct a hex index into indices of planes on each dimension.
  void getHexPlaneIndices( const ElementHandle h,
                           size_t hex_parameter_indices[3] ) const;

  //! Returns the volumes of the hex elements for the estimator class.
  void getElementVolumes( ElementHandleVolumeMap& element_volumes ) const final override;

  //! Returns a bool that says whether or not a point is in the mesh.
  bool isPointInMesh( const double point[3] ) const final override;

  //! Returns the index of the hex that contains a given point.
  ElementHandle whichElementIsPointIn( const double point[3] ) const final override;

  //! Returns an array of pairs of hex IDs and partial track lengths along a given line segment.
  void computeTrackLengths( const double start_point[3],
                            const double end_point[3],
                            ElementHandleTrackLengthArray&
                            hex_element_track_lengths ) const final override;

  //! Export the mesh to a file (type determined by suffix - e.g. mesh.vtk)
  void exportData( const std::string& output_file_name,
                   const TagNameSet& tag_root_names,
                   const MeshElementHandleDataMap& mesh_tag_data ) const final override;

  //! Export the mesh to a file
  using Mesh::exportData;

private:

  // Default constructor
  StructuredHexMesh();

  // Enumeration type converting dimensions to integers
  enum Dimension: size_t{ X_DIMENSION = 0,
                          Y_DIMENSION = 1,
                          Z_DIMENSION = 2 };

  // Returns a pair where the first member is true if the ray intersects with the mesh and the second is the distance to the intersection point
  void doesRayIntersectMesh(
                  const double point[3],
                  const double direction[3],
                  const double track_length,
                  std::tuple<bool,Dimension,double>& intersection_data ) const;

  // Trace particle path through mesh until it dies or leaves mesh
  void traceThroughMesh(
              double point[3],
              const double direction[3],
              const double track_length,
              PlaneIndex hex_plane_indices[3],
              ElementHandleTrackLengthArray& hex_element_track_lengths ) const;

  // Find the interaction planes that a particle will interact with
  void findInteractionPlanes( const double point[3],
                              const double direction[3],
                              const PlaneIndex hex_plane_indices[3],
                              std::vector<std::pair<Dimension,PlaneIndex> >&
                              interaction_planes ) const;

  // Find the intersection distance along the path of the particle with the next hex
  void findIntersectionDistance(
                    const double point[3],
                    const double direction[3],
                    const std::vector<std::pair<Dimension,PlaneIndex> >&
                    interaction_planes,
                    std::pair<Dimension,double>& intersection_distance ) const;

  // Calculate distance to a plane from a ray along its direction
  double findDistanceToInteractionPlane(
                                      const double interaction_plane_location,
                                      const double position_component,
                                      const double direction_component ) const;

  // Form the incrementer so that the particle increments over the planes in the correct direction
  void setIncrementer( size_t incrementer[3],
                       const double direction[3] ) const;

  // set the plane indices that make up the hex element index
  void setHexPlaneIndices( const double current_point[3],
                           PlaneIndex hex_plane_indices[3] )const;

  // overloaded function for setting member indices
  void setHexPlaneIndices( const Dimension intersection_dimension,
                           const double current_point[3],
                           PlaneIndex hex_plane_indices[3] )const;

  // Set individual hex plane indicies for particle.
  PlaneIndex setHexPlaneIndex( const double position_component,
                               const std::vector<double>& plane_set,
                               const Dimension plane_dimension  ) const;

  // Returns a set of distances to up to 3 planes that bound the mesh which the particle may interact with
  void findBoundingInteractionPlaneDistances(
                                    const double point[3],
                                    const double direction[3],
                                    std::vector<std::pair<Dimension, double> >&
                                    bounding_intersection_data  ) const;

  // Checks an individual set of planes for interaction plane in that dimension
  void checkPlaneSet( const std::vector<double>& plane_set,
                      const double position_component,
                      const double direction_component,
                      std::vector<std::pair<Dimension, double> >& boundary_planes,
                      const Dimension plane_dimension ) const;

  // Returns whether or not a particle actually hit the mesh surface
  bool isPointOnMeshSurface( const double point[3],
                             const Dimension dimension_intersected ) const;

  // function for checking each individual plane to see if it is within bounds
  bool checkWithinBoundingPlane( const double position_component,
                                 const std::vector<double>& plane_set )const;

  // determines if the particle has exited the mesh
  bool didParticleLeaveMesh(
                           const Dimension intersection_dimension,
                           const std::vector<std::pair<Dimension,PlaneIndex> >&
                           interaction_planes ) const;

  // sets the incrementer for mesh iteration
  void setIncrementer( const double direction[3],
                       int incrementer[3] ) const;

  // pushes point along direction to new intersection point
  void pushPoint( double point[3],
                  const double direction[3],
                  const double push_distance ) const;

  // Compute hex index from plane indices
  size_t findIndex( const size_t i, const size_t j, const size_t k ) const;

  // Compute hex index from plane indices
  size_t findIndex( const size_t indices[3] ) const;

  // Save the data to an archive
  template<typename Archive>
  void save( Archive& ar, const unsigned version ) const;

  // Load the data from an archive
  template<typename Archive>
  void load( Archive& ar, const unsigned version );

  BOOST_SERIALIZATION_SPLIT_MEMBER();

  // Declare the boost serialization access object as a friend
  friend class boost::serialization::access;

  // The tolerance used for geometric tests
  static const double s_tol;

  // The plane location member data
  std::vector<double> d_x_planes;
  std::vector<double> d_y_planes;
  std::vector<double> d_z_planes;

  // The hex elements (ids)
  std::vector<ElementHandle> d_hex_elements;
};

// Save the data to an archive
template<typename Archive>
void StructuredHexMesh::save( Archive& ar, const unsigned version ) const
{
  // Save the base class data
  ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP( Mesh );

  // Save the local data
  ar & BOOST_SERIALIZATION_NVP( d_x_planes );
  ar & BOOST_SERIALIZATION_NVP( d_y_planes );
  ar & BOOST_SERIALIZATION_NVP( d_z_planes );
  ar & BOOST_SERIALIZATION_NVP( d_hex_elements );
}

// Load the data from an archive
template<typename Archive>
void StructuredHexMesh::load( Archive& ar, const unsigned version )
{
  // Load the base class data
  ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP( Mesh );

  // Load the local data
  ar & BOOST_SERIALIZATION_NVP( d_x_planes );
  ar & BOOST_SERIALIZATION_NVP( d_y_planes );
  ar & BOOST_SERIALIZATION_NVP( d_z_planes );
  ar & BOOST_SERIALIZATION_NVP( d_hex_elements );
}

} // end Utility namespace

BOOST_SERIALIZATION_CLASS_VERSION( StructuredHexMesh, Utility, 0 );
BOOST_SERIALIZATION_CLASS_EXPORT_STANDARD_KEY( StructuredHexMesh, Utility );
EXTERN_EXPLICIT_CLASS_SAVE_LOAD_INST( Utility, StructuredHexMesh );

#endif // end UTILITY_STRUCTURED_HEX_MESH_HPP

//---------------------------------------------------------------------------//
// end Utility_StructuredHexMesh.hpp
//---------------------------------------------------------------------------//


// Boost.Geometry (aka GGL, Generic Geometry Library)

// Copyright (c) 2007-2012 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2008-2012 Bruno Lalande, Paris, France.
// Copyright (c) 2009-2012 Mateusz Loskot, London, UK.

// Parts of Boost.Geometry are redesigned from Geodan's Geographic Library
// (geolib/GGL), copyright (c) 1995-2010 Geodan, Amsterdam, the Netherlands.

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_GEOMETRY_ALGORITHMS_DISJOINT_HPP
#define BOOST_GEOMETRY_ALGORITHMS_DISJOINT_HPP

#include <cstddef>
#include <deque>

#include <boost/mpl/if.hpp>
#include <boost/range.hpp>

#include <boost/static_assert.hpp>

#include <boost/geometry/core/access.hpp>
#include <boost/geometry/core/coordinate_dimension.hpp>
#include <boost/geometry/core/reverse_dispatch.hpp>

#include <boost/geometry/algorithms/detail/disjoint.hpp>
#include <boost/geometry/algorithms/detail/for_each_range.hpp>
#include <boost/geometry/algorithms/detail/point_on_border.hpp>
#include <boost/geometry/algorithms/detail/overlay/get_turns.hpp>
#include <boost/geometry/algorithms/within.hpp>

#include <boost/geometry/geometries/concepts/check.hpp>

#include <boost/geometry/util/math.hpp>

namespace boost { namespace geometry
{

#ifndef DOXYGEN_NO_DETAIL
namespace detail { namespace disjoint
{

template<typename Geometry>
struct check_each_ring_for_within
{
    bool has_within;
    Geometry const& m_geometry;

    inline check_each_ring_for_within(Geometry const& g)
        : has_within(false)
        , m_geometry(g)
    {}

    template <typename Range>
    inline void apply(Range const& range)
    {
        typename geometry::point_type<Range>::type p;
        geometry::point_on_border(p, range);
        if (geometry::within(p, m_geometry))
        {
            has_within = true;
        }
    }
};

template <typename FirstGeometry, typename SecondGeometry>
inline bool rings_containing(FirstGeometry const& geometry1,
                SecondGeometry const& geometry2)
{
    check_each_ring_for_within<FirstGeometry> checker(geometry1);
    geometry::detail::for_each_range(geometry2, checker);
    return checker.has_within;
}

struct assign_disjoint_policy
{
    // We want to include all points:
    static bool const include_no_turn = true;
    static bool const include_degenerate = true;
    static bool const include_opposite = true;

    // We don't assign extra info:
    template
	<
		typename Info,
		typename Point1,
		typename Point2,
		typename IntersectionInfo,
		typename DirInfo
	>
    static inline void apply(Info& , Point1 const& , Point2 const&,
                IntersectionInfo const&, DirInfo const&)
    {}
};

template <typename Geometry1, typename Geometry2>
struct disjoint_linear
{
    static inline bool apply(Geometry1 const& geometry1, Geometry2 const& geometry2)
    {
        typedef typename geometry::point_type<Geometry1>::type point_type;

        typedef overlay::turn_info<point_type> turn_info;
        std::deque<turn_info> turns;

        // Specify two policies:
        // 1) Stop at any intersection
        // 2) In assignment, include also degenerate points (which are normally skipped)
        disjoint_interrupt_policy policy;
        geometry::get_turns
            <
                false, false,
                assign_disjoint_policy
            >(geometry1, geometry2, turns, policy);
        if (policy.has_intersections)
        {
            return false;
        }

        return true;
    }
};

template <typename Segment1, typename Segment2>
struct disjoint_segment
{
    static inline bool apply(Segment1 const& segment1, Segment2 const& segment2)
    {
        typedef typename point_type<Segment1>::type point_type;

        segment_intersection_points<point_type> is
            = strategy::intersection::relate_cartesian_segments
            <
                policies::relate::segments_intersection_points
                    <
                        Segment1,
                        Segment2,
                        segment_intersection_points<point_type>
                    >
            >::apply(segment1, segment2);

        return is.count == 0;
    }
};

template <typename Geometry1, typename Geometry2>
struct general_areal
{
    static inline bool apply(Geometry1 const& geometry1, Geometry2 const& geometry2)
    {
        if (! disjoint_linear<Geometry1, Geometry2>::apply(geometry1, geometry2))
        {
            return false;
        }

        // If there is no intersection of segments, they might located
        // inside each other
        if (rings_containing(geometry1, geometry2)
            || rings_containing(geometry2, geometry1))
        {
            return false;
        }

        return true;
    }
};

}} // namespace detail::disjoint
#endif // DOXYGEN_NO_DETAIL

#ifndef DOXYGEN_NO_DISPATCH
namespace dispatch
{

template
<
    typename GeometryTag1, typename GeometryTag2,
    typename Geometry1, typename Geometry2,
    std::size_t DimensionCount
>
struct disjoint
    : detail::disjoint::general_areal<Geometry1, Geometry2>
{};

template <typename Point1, typename Point2, std::size_t DimensionCount>
struct disjoint<point_tag, point_tag, Point1, Point2, DimensionCount>
    : detail::disjoint::point_point<Point1, Point2, 0, DimensionCount>
{};

template <typename Box1, typename Box2, std::size_t DimensionCount>
struct disjoint<box_tag, box_tag, Box1, Box2, DimensionCount>
    : detail::disjoint::box_box<Box1, Box2, 0, DimensionCount>
{};

template <typename Point, typename Box, std::size_t DimensionCount>
struct disjoint<point_tag, box_tag, Point, Box, DimensionCount>
    : detail::disjoint::point_box<Point, Box, 0, DimensionCount>
{};

template <typename Linestring1, typename Linestring2>
struct disjoint<linestring_tag, linestring_tag, Linestring1, Linestring2, 2>
    : detail::disjoint::disjoint_linear<Linestring1, Linestring2>
{};

template <typename Linestring1, typename Linestring2>
struct disjoint<segment_tag, segment_tag, Linestring1, Linestring2, 2>
    : detail::disjoint::disjoint_segment<Linestring1, Linestring2>
{};

template <typename Linestring, typename Segment>
struct disjoint<linestring_tag, segment_tag, Linestring, Segment, 2>
    : detail::disjoint::disjoint_linear<Linestring, Segment>
{};

template
<
    typename GeometryTag1, typename GeometryTag2,
    typename Geometry1, typename Geometry2,
    std::size_t DimensionCount
>
struct disjoint_reversed
{
    static inline bool apply(Geometry1 const& g1, Geometry2 const& g2)
    {
        return disjoint
            <
                GeometryTag2, GeometryTag1,
                Geometry2, Geometry1,
                DimensionCount
            >::apply(g2, g1);
    }
};

} // namespace dispatch
#endif // DOXYGEN_NO_DISPATCH

/*!
\brief \brief_check2{are disjoint}
\ingroup disjoint
\tparam Geometry1 \tparam_geometry
\tparam Geometry2 \tparam_geometry
\param geometry1 \param_geometry
\param geometry2 \param_geometry
\return \return_check2{are disjoint}

\qbk{[include reference/algorithms/disjoint.qbk]}
*/
template <typename Geometry1, typename Geometry2>
inline bool disjoint(Geometry1 const& geometry1,
            Geometry2 const& geometry2)
{
    concept::check_concepts_and_equal_dimensions
        <
            Geometry1 const,
            Geometry2 const
        >();

    return boost::mpl::if_c
        <
            reverse_dispatch<Geometry1, Geometry2>::type::value,
            dispatch::disjoint_reversed
            <
                typename tag<Geometry1>::type,
                typename tag<Geometry2>::type,
                Geometry1,
                Geometry2,
                dimension<Geometry1>::type::value
            >,
            dispatch::disjoint
            <
                typename tag<Geometry1>::type,
                typename tag<Geometry2>::type,
                Geometry1,
                Geometry2,
                dimension<Geometry1>::type::value
            >
        >::type::apply(geometry1, geometry2);
}

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_ALGORITHMS_DISJOINT_HPP


// -*- C++ -*-
//
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//                                   Jiao Lin
//                      California Institute of Technology
//                         (C) 2007 All Rights Reserved
//
// {LicenseText}
//
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//

#include <sstream>
#include <boost/python.hpp>
#include "mccomponents/kernels/sample/phonon/PeriodicDispersion_3D.h"

namespace wrap_mccomponents {

  void wrap_PeriodicDispersion_3D()
  {
    using namespace boost::python;
    typedef DANSE::phonon::AbstractDispersion_3D base_t;
    typedef DANSE::phonon::PeriodicDispersion_3D w_t;

    class_<w_t, bases<base_t>, boost::noncopyable>
      ("PeriodicDispersion_3D",
       init<const base_t &, const w_t::ReciprocalCell &>()
       [with_custodian_and_ward<1,2>() ]
       )
      ;

    class_<w_t::ReciprocalCell>
      ("ReciprocalCell", init<>())
      .def_readwrite( "b1", &w_t::ReciprocalCell::b1)
      .def_readwrite( "b2", &w_t::ReciprocalCell::b2)
      .def_readwrite( "b3", &w_t::ReciprocalCell::b3)
      ;
  }

}

// version
// $Id$

// End of file


#include <cassert>

#include <iostream>
#include <boost/filesystem.hpp>

#include <photon_propagator/device.hpp>
#include <photon_propagator/random.hpp>
#include <photon_propagator/geometry.hpp>
#include <photon_propagator/configuration.hpp>
#include <photon_propagator/ice_model.hpp>
#include <photon_propagator/particle.hpp>
#include <photon_propagator/cascades.hpp>
#include <photon_propagator/photons.hpp>
#include <photon_propagator/cascades_to_photons.hpp>

using std::cerr;
using std::endl;

using boost::filesystem::path;
using boost::filesystem::exists;

int main(int argc, char* argv[]){

  if(argc != 3){
    std::cerr<<"Usage: test_geometry <ICE_MODEL_PATH>"<<std::endl;
    std::exit(-1);
  }

  path ice_model_path(argv[1]);
  assert(exists(path(ice_model_path)));

  std::shared_ptr<Device> device(new Device(0));

  Configuration config(ice_model_path);
  float scattering_angle{config.get_configuration().g};
  IceModel ice_model(ice_model_path, scattering_angle);

  size_t photon_buffer_size{1000};
  size_t hit_buffer_size{100};

  particle electron;
  electron.major_id=0;
  electron.minor_id=0;
  electron.ptype=particle_type::e_minus;
  electron.time=0.;
  electron.energy=100.; // in god-given units
  electron.length=0.;
  electron.direction={0,0,0};
  electron.position={0,0,0};

  Cascades cascades(1, device);
  cascades.add(electron);

  unsigned number_of_blocks{64};
  unsigned threads_per_block{64};
  unsigned n_concurrent_threads{number_of_blocks*threads_per_block};
  Photons photons(n_concurrent_threads, device);
  Random rng(device, number_of_blocks, threads_per_block);
  cascades::GeneratePhotons(rng,
			    ice_model,
			    cascades,
			    device,
			    number_of_blocks,
			    threads_per_block,
			    photons);

  photons.pprint();
}


/**
 * franka_panda.cc
 *
 * Copyright 2019. All Rights Reserved.
 * IPRL
 *
 * Created: January 07, 2019
 * Authors: Toki Migimatsu
 */

#include <Eigen/Eigen>

#include <pybind11/pybind11.h>
#include <pybind11/eigen.h>

#include "franka_panda/model.h"

namespace franka_panda {

namespace py = pybind11;
using namespace pybind11::literals;

PYBIND11_MODULE(frankapanda, m) {

  // Articulated body
  py::class_<Model>(m, "Model")
      .def(py::init<>())
      .def_property_readonly("dof", &Model::dof)
      .def_property("q", &Model::q, &Model::set_q)
      .def_property("dq", &Model::dq, &Model::set_dq)
      .def_property("m_load", &Model::m_load, &Model::set_m_load)
      .def_property("com_load", &Model::com_load, &Model::set_com_load)
      .def_property("I_com_load", &Model::I_com_load, &Model::set_I_com_load)
      .def_property("I_com_load_matrix", &Model::I_com_load_matrix, &Model::set_I_com_load_matrix)
      .def("set_load", &Model::set_load)
      .def_property_readonly("g", &Model::g)
      .def_property("inertia_compensation", &Model::inertia_compensation, &Model::set_inertia_compensation)
      .def_property("stiction_coefficients", &Model::stiction_coefficients, &Model::set_stiction_coefficients)
      .def_property("stiction_activations", &Model::stiction_activations, &Model::set_stiction_activations);

  // Dynamics
  m.def("cartesian_pose",
        [](const Model& model, int link) -> Eigen::Matrix4d {
          Eigen::Isometry3d T = CartesianPose(model, link);
          return T.matrix();
        }, "model"_a, "link"_a = -1)
   .def("jacobian", &Jacobian, "model"_a, "link"_a = -1)
   .def("inertia", &Inertia, "model"_a)
   .def("centrifugal_coriolis", &CentrifugalCoriolis, "model"_a)
   .def("gravity", &Gravity, "model"_a)
   .def("friction", &Friction, "model"_a, "tau"_a);

}

}  // namespace franka_panda


//
// Copyright Jason Rice 2016
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//

#include <mpdef/list.hpp>
#include <mpdef/tree_node.hpp>

#include <boost/hana.hpp>

namespace hana = boost::hana;

int main()
{
  {
    constexpr auto xs = mpdef::make_list(
      hana::make_map(
        mpdef::make_tree_node(hana::int_c<1>, hana::int_c<-1>),
        mpdef::make_tree_node(hana::int_c<2>, hana::int_c<-2>)
      ),
      hana::make_map(
        mpdef::make_tree_node(hana::int_c<2>, hana::int_c<-2>),
        mpdef::make_tree_node(hana::int_c<1>, hana::int_c<-1>)
      )
    );
    (void) xs;
    BOOST_HANA_CONSTANT_ASSERT(xs == hana::reverse(xs));
  }
  {
    constexpr auto xs = mpdef::make_list(
      hana::int_c<0>,
      hana::int_c<1>,
      hana::int_c<2>,
      hana::int_c<3>,
      hana::int_c<4>,
      hana::int_c<5>
    );
    (void)xs;
    BOOST_HANA_CONSTANT_ASSERT(hana::at(xs, hana::int_c<3>) == hana::int_c<3>);
  }
  {
    constexpr auto xs = mpdef::make_list(
      hana::int_c<0>,
      hana::int_c<1>,
      hana::int_c<2>,
      hana::int_c<3>,
      hana::int_c<4>,
      hana::int_c<5>
    );
    (void)xs;
    BOOST_HANA_CONSTANT_ASSERT(hana::unpack(xs, hana::make_tuple)
      == hana::make_tuple(
        hana::int_c<0>,
        hana::int_c<1>,
        hana::int_c<2>,
        hana::int_c<3>,
        hana::int_c<4>,
        hana::int_c<5>
      )
    );
  }
  {
    constexpr auto xs1 = mpdef::make_list(
      hana::int_c<0>,
      hana::int_c<1>,
      hana::int_c<2>,
      hana::int_c<3>,
      hana::int_c<4>,
      hana::int_c<5>
    );

    constexpr auto xs = mpdef::make_list(xs1, xs1, xs1);
    (void)xs;

    BOOST_HANA_CONSTANT_ASSERT(hana::flatten(xs)
      == mpdef::make_list(
        hana::int_c<0>,
        hana::int_c<1>,
        hana::int_c<2>,
        hana::int_c<3>,
        hana::int_c<4>,
        hana::int_c<5>,
        hana::int_c<0>,
        hana::int_c<1>,
        hana::int_c<2>,
        hana::int_c<3>,
        hana::int_c<4>,
        hana::int_c<5>,
        hana::int_c<0>,
        hana::int_c<1>,
        hana::int_c<2>,
        hana::int_c<3>,
        hana::int_c<4>,
        hana::int_c<5>
      )
    );
  }
}


#include <grammar.h>

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/support.hpp>

namespace score {

namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;
namespace phx = boost::phoenix;

template <typename Iterator, typename Skipper>
struct Grammar : qi::grammar<Iterator, ast::Statement(), Skipper> {
  Grammar() : Grammar::base_type(statementRule) {
    using namespace qi;
    using namespace boost::gregorian;
    using namespace boost::posix_time;

    statementRule = (assignmentRule | exprRule) >> eoi;

    assignmentRule = varRule >> lit('=') >> exprRule;

    exprRule      = orRule.alias();
    orRule
      = andRule[_val = _1]
        >> *(((lit("or") | lit("||")) >> andRule) [_val = phx::construct<ast::BinaryRel<ast::RelOr>>(_val, _1)]);
    andRule
      = eqRule[_val = _1]
        >> *(((lit("and") | lit("&&")) >> eqRule) [_val = phx::construct<ast::BinaryRel<ast::RelAnd>>(_val, _1)]);
    eqRule
      = inclusionRule[_val = _1]
        >> *( ((lit("eq") | lit("==")) >> inclusionRule) [_val = phx::construct<ast::BinaryRel<ast::RelEq>>(_val, _1)]
            | ((lit("ne") | lit("!=")) >> inclusionRule) [_val = phx::construct<ast::BinaryRel<ast::RelNotEq>>(_val, _1)]);
    inclusionRule // TODO: does this take precedence over `relRule`s? we should think about this
      = relRule[_val = _1]
        >> *( (lit("between") >> lit("[") >> relRule >> lit(",") >> relRule >> lit("]")) [_val = phx::construct<ast::Range>(_val, _1, _2)] // XXX: differs from ANTLR grammar!
            | (lit("in") >> lit("{") >> (relRule % ',') >> lit("}")) [_val = phx::construct<ast::Contains>(_val, _1)]);
    relRule
      = addSubRule[_val = _1]
        >> *( ((lit("<") | lit("lt")) >> addSubRule) [_val = phx::construct<ast::BinaryRel<ast::RelLessThan>>(_val, _1)]
            | ((lit("<=") | lit("le")) >> addSubRule) [_val = phx::construct<ast::BinaryRel<ast::RelLessThanEq>>(_val, _1)]
            | ((lit(">") | lit("gt")) >> addSubRule) [_val = phx::construct<ast::BinaryRel<ast::RelGreaterThan>>(_val, _1)]
            | ((lit(">=") | lit("ge")) >> addSubRule) [_val = phx::construct<ast::BinaryRel<ast::RelGreaterThanEq>>(_val, _1)]);
    addSubRule
      = mulDivRule[_val = _1]
        >> *( (lit('+') >> mulDivRule) [_val = phx::construct<ast::BinaryOp<ast::OpAdd>>(_val, _1)]
            | (lit('-') >> mulDivRule) [_val = phx::construct<ast::BinaryOp<ast::OpSub>>(_val, _1)]);
    mulDivRule
      = unaryRule[_val = _1]
        >> *( (lit('*') >> unaryRule) [_val = phx::construct<ast::BinaryOp<ast::OpMult>>(_val, _1)]
            | (lit('/') >> unaryRule) [_val = phx::construct<ast::BinaryOp<ast::OpDiv>>(_val, _1)]
            | ((lit("mod") | lit("%")) >> unaryRule) [_val = phx::construct<ast::BinaryOp<ast::OpMod>>(_val, _1)]);
    unaryRule // TODO: better to use '>' instead of '>>' ... but throws exception on bad parse
      = (lit('-') >> exprRule) [_val = phx::construct<ast::UnaryOp<ast::OpUnaryMinus>>(_1)]
      | (lit('!') >> exprRule) [_val = phx::construct<ast::UnaryOp<ast::OpLogicalNot>>(_1)]
      | simpleRule            [_val = _1];
    simpleRule
      %= lit('(') >> exprRule >> lit(')')
      |  lit('#') >> datetimeRule >> lit('#')
      |  lit('#') >> timeRule >> lit('#')
      |  lit('#') >> dateRule >> lit('#')
      |  double_ // TODO: allow negative numbers? replace with a parser for fixed-precision
      |  uint_   // had to switch order with double_ (c.f. ANTLR grammar)
      |  lit('\'') >> stringCharRule >> lit('\'')
      |  lit('\"') >> +stringCharRule >> lit('\"')
      |  existsRule
      |  varRule;

    existsRule = lit("exists") >> varRule [_val = phx::construct<ast::Exists>(_1)]; // TODO: consider using '>'

    predRule %= idRule >> lit("==") >> exprRule;
    qualRule %= idRule >> -(lit('[') >> (uint_  | predRule) >> lit(']'));
    varRule   = (-(string("$")|string("^")|string("in.")|string("out.")|string("this."))
                  >> ((qualRule % '.'))) [_val = phx::construct<ast::Variable>(_1, _2)];

    datetimeRule %= dateRule >> timeRule;
    dateRule     %= uint_parser<unsigned short, 10, 4, 4>()
                    >> lit('-') >> uint_parser<unsigned char, 10, 2, 2>()
                    >> lit('-') >> uint_parser<unsigned char, 10, 2, 2>();
    timeRule      =      (lit('T') >> uint_parser<unsigned short, 10, 2, 2>())
                         [_val += phx::construct<hours>(_1)]
                      >> (lit(':') >> uint_parser<unsigned char, 10, 2, 2>())
                         [_val += phx::construct<minutes>(_1)]
                      >> -(lit(':') >> uint_parser<unsigned char, 10, 2, 2>()
                          [_val += phx::construct<seconds>(_1)])
                      >> -(lit('.') >> ulong_
                          [_val += phx::construct<milliseconds>(_1)]);

    idRule = alpha >> *(alnum | char_('_'));

    stringCharRule = ~char_("\\\"")
                   | lit('\\') >> char_("btnfr\"'\\");

    BOOST_SPIRIT_DEBUG_NODE(statementRule);

    BOOST_SPIRIT_DEBUG_NODE(assignmentRule);

    BOOST_SPIRIT_DEBUG_NODE(exprRule);

    BOOST_SPIRIT_DEBUG_NODE(orRule);
    BOOST_SPIRIT_DEBUG_NODE(andRule);
    BOOST_SPIRIT_DEBUG_NODE(eqRule);
    BOOST_SPIRIT_DEBUG_NODE(inclusionRule);
    BOOST_SPIRIT_DEBUG_NODE(relRule);
    BOOST_SPIRIT_DEBUG_NODE(addSubRule);
    BOOST_SPIRIT_DEBUG_NODE(mulDivRule);
    BOOST_SPIRIT_DEBUG_NODE(unaryRule);
    BOOST_SPIRIT_DEBUG_NODE(simpleRule);
    BOOST_SPIRIT_DEBUG_NODE(existsRule);
    BOOST_SPIRIT_DEBUG_NODE(varRule);

    BOOST_SPIRIT_DEBUG_NODE(datetimeRule);
    BOOST_SPIRIT_DEBUG_NODE(timeRule);
    BOOST_SPIRIT_DEBUG_NODE(dateRule);

    BOOST_SPIRIT_DEBUG_NODE(predRule);
    BOOST_SPIRIT_DEBUG_NODE(qualRule);
    BOOST_SPIRIT_DEBUG_NODE(varRule);

    BOOST_SPIRIT_DEBUG_NODE(idRule);
    BOOST_SPIRIT_DEBUG_NODE(stringCharRule);
  }

  qi::rule<Iterator, ast::Statement(),  Skipper> statementRule;

  qi::rule<Iterator, ast::Assignment(), Skipper> assignmentRule;

  qi::rule<Iterator, ast::Expr(),       Skipper> exprRule,
                                                 orRule,
                                                 andRule,
                                                 eqRule,
                                                 inclusionRule,
                                                 relRule,
                                                 addSubRule,
                                                 mulDivRule,
                                                 unaryRule,
                                                 simpleRule,
                                                 existsRule;

  qi::rule<Iterator, boost::posix_time::time_duration(), Skipper> timeRule;
  qi::rule<Iterator, boost::gregorian::date(),           Skipper> dateRule;
  qi::rule<Iterator, boost::posix_time::ptime(),         Skipper> datetimeRule;

  qi::rule<Iterator, ast::Predicate(), Skipper> predRule;
  qi::rule<Iterator, ast::Qualifier(), Skipper> qualRule;
  qi::rule<Iterator, ast::Variable(),  Skipper> varRule;

  qi::rule<Iterator, std::string(),    Skipper> idRule;
  qi::rule<Iterator, char(),           Skipper> stringCharRule;
};

bool parse(ast::Statement &out, const std::string &in) {
  // skipper approach adapted from: https://stackoverflow.com/a/44531686
  using Iterator = std::string::const_iterator;

  qi::rule<Iterator> lineComment, blockComment, skipper;
  {
    using namespace qi;

    lineComment  = lit("//") >> *(char_ - eol) >> (eol|eoi);
    blockComment = lit("/*") >> *(blockComment | char_ - lit("*/")) > lit("*/");
    skipper      = space | lineComment | blockComment;
  }

  score::Grammar<Iterator, decltype(skipper)> grammar;

  const auto &iter = in.cbegin();
  const auto &end = in.cend();
  bool parsed = phrase_parse(iter, end, grammar, skipper, out);

  return parsed;
}
}