//  (C) Copyright Jeremy Siek 2004
//  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)

#ifndef BOOST_PROPERTY_HPP
#define BOOST_PROPERTY_HPP

#include <boost/mpl/bool.hpp>
#include <boost/mpl/if.hpp>
#include <boost/mpl/has_xxx.hpp>
#include <boost/utility/enable_if.hpp>
#include <boost/type_traits.hpp>
#include <boost/static_assert.hpp>

namespace boost
{

struct no_property
{
};

template < class Tag, class T, class Base = no_property > struct property
{
    typedef Base next_type;
    typedef Tag tag_type;
    typedef T value_type;
    property(const T& v = T()) : m_value(v) {}
    property(const T& v, const Base& b) : m_value(v), m_base(b) {}
    // copy constructor and assignment operator will be generated by compiler

    T m_value;
    Base m_base;
};

// Kinds of properties
namespace graph_introspect_detail
{
    BOOST_MPL_HAS_XXX_TRAIT_DEF(kind)
    template < typename T, bool Cond > struct get_kind
    {
        typedef void type;
    };
    template < typename T > struct get_kind< T, true >
    {
        typedef typename T::kind type;
    };
}

// Having a default is to make this trait work for any type, not just valid
// properties, to work around VC++ <= 10 bugs related to SFINAE in
// compressed_sparse_row_graph's get functions and similar
template < class PropertyTag >
struct property_kind
: graph_introspect_detail::get_kind< PropertyTag,
      graph_introspect_detail::has_kind< PropertyTag >::value >
{
};

// Some standard properties defined independently of Boost.Graph:
enum vertex_all_t
{
    vertex_all
};
enum edge_all_t
{
    edge_all
};
enum graph_all_t
{
    graph_all
};
enum vertex_bundle_t
{
    vertex_bundle
};
enum edge_bundle_t
{
    edge_bundle
};
enum graph_bundle_t
{
    graph_bundle
};

// Code to look up one property in a property list:
template < typename PList, typename PropName, typename Enable = void >
struct lookup_one_property_internal
{
    BOOST_STATIC_CONSTANT(bool, found = false);
    typedef void type;
};

// Special-case properties (vertex_all, edge_all, graph_all)
#define BGL_ALL_PROP(tag)                                                 \
    template < typename T > struct lookup_one_property_internal< T, tag > \
    {                                                                     \
        BOOST_STATIC_CONSTANT(bool, found = true);                        \
        typedef T type;                                                   \
        static T& lookup(T& x, tag) { return x; }                         \
        static const T& lookup(const T& x, tag) { return x; }             \
    };                                                                    \
    template < typename Tag, typename T, typename Base >                  \
    struct lookup_one_property_internal< property< Tag, T, Base >, tag >  \
    { /* Avoid ambiguity */                                               \
        BOOST_STATIC_CONSTANT(bool, found = true);                        \
        typedef property< Tag, T, Base > type;                            \
        static type& lookup(type& x, tag) { return x; }                   \
        static const type& lookup(const type& x, tag) { return x; }       \
    };

BGL_ALL_PROP(vertex_all_t)
BGL_ALL_PROP(edge_all_t)
BGL_ALL_PROP(graph_all_t)
#undef BGL_ALL_PROP

// *_bundled; these need to be macros rather than inheritance to resolve
// ambiguities
#define BGL_DO_ONE_BUNDLE_TYPE(kind)                                           \
    template < typename T >                                                    \
    struct lookup_one_property_internal< T, BOOST_JOIN(kind, _bundle_t) >      \
    {                                                                          \
        BOOST_STATIC_CONSTANT(bool, found = true);                             \
        typedef T type;                                                        \
        static T& lookup(T& x, BOOST_JOIN(kind, _bundle_t)) { return x; }      \
        static const T& lookup(const T& x, BOOST_JOIN(kind, _bundle_t))        \
        {                                                                      \
            return x;                                                          \
        }                                                                      \
    };                                                                         \
                                                                               \
    template < typename Tag, typename T, typename Base >                       \
    struct lookup_one_property_internal< property< Tag, T, Base >,             \
        BOOST_JOIN(kind, _bundle_t) >                                          \
    : lookup_one_property_internal< Base, BOOST_JOIN(kind, _bundle_t) >        \
    {                                                                          \
    private:                                                                   \
        typedef lookup_one_property_internal< Base,                            \
            BOOST_JOIN(kind, _bundle_t) >                                      \
            base_type;                                                         \
                                                                               \
    public:                                                                    \
        template < typename BundleTag >                                        \
        static typename lazy_enable_if_c<                                      \
            (base_type::found                                                  \
                && (is_same< BundleTag,                                        \
                    BOOST_JOIN(kind, _bundle_t) >::value)),                    \
            add_reference< typename base_type::type > >::type                  \
        lookup(property< Tag, T, Base >& p, BundleTag)                         \
        {                                                                      \
            return base_type::lookup(p.m_base, BOOST_JOIN(kind, _bundle_t)()); \
        }                                                                      \
        template < typename BundleTag >                                        \
        static typename lazy_enable_if_c<                                      \
            (base_type::found                                                  \
                && (is_same< BundleTag,                                        \
                    BOOST_JOIN(kind, _bundle_t) >::value)),                    \
            add_reference< const typename base_type::type > >::type            \
        lookup(const property< Tag, T, Base >& p, BundleTag)                   \
        {                                                                      \
            return base_type::lookup(p.m_base, BOOST_JOIN(kind, _bundle_t)()); \
        }                                                                      \
    };

BGL_DO_ONE_BUNDLE_TYPE(vertex)
BGL_DO_ONE_BUNDLE_TYPE(edge)
BGL_DO_ONE_BUNDLE_TYPE(graph)
#undef BGL_DO_ONE_BUNDLE_TYPE

// Normal old-style properties; second case also handles chaining of bundled
// property accesses
template < typename Tag, typename T, typename Base >
struct lookup_one_property_internal< boost::property< Tag, T, Base >, Tag >
{
    BOOST_STATIC_CONSTANT(bool, found = true);
    typedef property< Tag, T, Base > prop;
    typedef T type;
    template < typename U >
    static typename enable_if< is_same< prop, U >, T& >::type lookup(
        U& prop, const Tag&)
    {
        return prop.m_value;
    }
    template < typename U >
    static typename enable_if< is_same< prop, U >, const T& >::type lookup(
        const U& prop, const Tag&)
    {
        return prop.m_value;
    }
};

template < typename Tag, typename T, typename Base, typename PropName >
struct lookup_one_property_internal< boost::property< Tag, T, Base >, PropName >
: lookup_one_property_internal< Base, PropName >
{
private:
    typedef lookup_one_property_internal< Base, PropName > base_type;

public:
    template < typename PL >
    static
        typename lazy_enable_if< is_same< PL, boost::property< Tag, T, Base > >,
            add_reference< typename base_type::type > >::type
        lookup(PL& prop, const PropName& tag)
    {
        return base_type::lookup(prop.m_base, tag);
    }
    template < typename PL >
    static
        typename lazy_enable_if< is_same< PL, boost::property< Tag, T, Base > >,
            add_reference< const typename base_type::type > >::type
        lookup(const PL& prop, const PropName& tag)
    {
        return base_type::lookup(prop.m_base, tag);
    }
};

// Pointer-to-member access to bundled properties
#ifndef BOOST_GRAPH_NO_BUNDLED_PROPERTIES
template < typename T, typename TMaybeBase, typename R >
struct lookup_one_property_internal< T, R TMaybeBase::*,
    typename enable_if< is_base_of< TMaybeBase, T > >::type >
{
    BOOST_STATIC_CONSTANT(bool, found = true);
    typedef R type;
    static R& lookup(T& x, R TMaybeBase::*ptr) { return x.*ptr; }
    static const R& lookup(const T& x, R TMaybeBase::*ptr) { return x.*ptr; }
};
#endif

// Version of above handling const property lists properly
template < typename T, typename Tag >
struct lookup_one_property : lookup_one_property_internal< T, Tag >
{
};

template < typename T, typename Tag > struct lookup_one_property< const T, Tag >
{
    BOOST_STATIC_CONSTANT(
        bool, found = (lookup_one_property_internal< T, Tag >::found));
    typedef const typename lookup_one_property_internal< T, Tag >::type type;
    template < typename U >
    static typename lazy_enable_if< is_same< T, U >,
        add_reference< const typename lookup_one_property_internal< T,
            Tag >::type > >::type
    lookup(const U& p, Tag tag)
    {
        return lookup_one_property_internal< T, Tag >::lookup(p, tag);
    }
};

// The BGL properties specialize property_kind and
// property_num, and use enum's for the Property type (see
// graph/properties.hpp), but the user may want to use a class
// instead with a nested kind type and num.  Also, we may want to
// switch BGL back to using class types for properties at some point.

template < class P > struct has_property : boost::mpl::true_
{
};
template <> struct has_property< no_property > : boost::mpl::false_
{
};

} // namespace boost

#include <boost/pending/detail/property.hpp>

namespace boost
{

template < class PropertyList, class Tag >
struct property_value : lookup_one_property< PropertyList, Tag >
{
};

template < class PropertyList, class Tag >
inline typename lookup_one_property< PropertyList, Tag >::type&
get_property_value(PropertyList& p, Tag tag)
{
    return lookup_one_property< PropertyList, Tag >::lookup(p, tag);
}

template < class PropertyList, class Tag >
inline const typename lookup_one_property< PropertyList, Tag >::type&
get_property_value(const PropertyList& p, Tag tag)
{
    return lookup_one_property< PropertyList, Tag >::lookup(p, tag);
}

namespace detail
{

    /** This trait returns true if T is no_property. */
    template < typename T >
    struct is_no_property : mpl::bool_< is_same< T, no_property >::value >
    {
    };

    template < typename PList, typename Tag > class lookup_one_property_f;

    template < typename PList, typename Tag, typename F >
    struct lookup_one_property_f_result;

    template < typename PList, typename Tag >
    struct lookup_one_property_f_result< PList, Tag,
        const lookup_one_property_f< PList, Tag >(PList) >
    {
        typedef typename lookup_one_property< PList, Tag >::type type;
    };

    template < typename PList, typename Tag >
    struct lookup_one_property_f_result< PList, Tag,
        const lookup_one_property_f< PList, Tag >(PList&) >
    {
        typedef typename lookup_one_property< PList, Tag >::type& type;
    };

    template < typename PList, typename Tag >
    struct lookup_one_property_f_result< PList, Tag,
        const lookup_one_property_f< PList, Tag >(const PList&) >
    {
        typedef const typename lookup_one_property< PList, Tag >::type& type;
    };

    template < typename PList, typename Tag > class lookup_one_property_f
    {
        Tag tag;

    public:
        lookup_one_property_f(Tag tag) : tag(tag) {}
        template < typename F >
        struct result : lookup_one_property_f_result< PList, Tag, F >
        {
        };

        typename lookup_one_property_f_result< PList, Tag,
            const lookup_one_property_f(PList&) >::type
        operator()(PList& pl) const
        {
            return lookup_one_property< PList, Tag >::lookup(pl, tag);
        }
    };

} // namespace detail

namespace detail
{
    // Stuff for directed_graph and undirected_graph to skip over their first
    // vertex_index and edge_index properties when providing vertex_all and
    // edge_all; make sure you know the exact structure of your properties if
    // you use there.
    struct remove_first_property
    {
        template < typename F > struct result
        {
            typedef typename boost::function_traits< F >::arg1_type a1;
            typedef typename boost::remove_reference< a1 >::type non_ref;
            typedef typename non_ref::next_type nx;
            typedef typename boost::mpl::if_< boost::is_const< non_ref >,
                boost::add_const< nx >, nx >::type with_const;
            typedef typename boost::add_reference< with_const >::type type;
        };
        template < typename Prop >
        typename Prop::next_type& operator()(Prop& p) const
        {
            return p.m_base;
        }
        template < typename Prop >
        const typename Prop::next_type& operator()(const Prop& p) const
        {
            return p.m_base;
        }
    };
}

} // namesapce boost

#endif /* BOOST_PROPERTY_HPP */
