// Boost.TypeErasure library
//
// Copyright 2011 Steven Watanabe
//
// 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)
//
// $Id$

#ifndef BOOST_TYPE_ERASURE_BINDING_HPP_INCLUDED
#define BOOST_TYPE_ERASURE_BINDING_HPP_INCLUDED

#include <boost/config.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include <boost/utility/enable_if.hpp>
#include <boost/mpl/transform.hpp>
#include <boost/mpl/find_if.hpp>
#include <boost/mpl/and.hpp>
#include <boost/mpl/not.hpp>
#include <boost/mpl/end.hpp>
#include <boost/mpl/bool.hpp>
#include <boost/mpl/pair.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/type_erasure/static_binding.hpp>
#include <boost/type_erasure/is_subconcept.hpp>
#include <boost/type_erasure/detail/adapt_to_vtable.hpp>
#include <boost/type_erasure/detail/null.hpp>
#include <boost/type_erasure/detail/rebind_placeholders.hpp>
#include <boost/type_erasure/detail/vtable.hpp>
#include <boost/type_erasure/detail/normalize.hpp>
#include <boost/type_erasure/detail/instantiate.hpp>
#include <boost/type_erasure/detail/check_map.hpp>

namespace boost {
namespace type_erasure {

namespace detail {

template<class Source, class Dest, class Map>
struct can_optimize_conversion : ::boost::mpl::and_<
    ::boost::is_same<Source, Dest>,
        ::boost::is_same<
            typename ::boost::mpl::find_if<
                Map,
                ::boost::mpl::not_<
                    ::boost::is_same<
                        ::boost::mpl::first< ::boost::mpl::_1>,
                        ::boost::mpl::second< ::boost::mpl::_1>
                    >
                >
            >::type,
            typename ::boost::mpl::end<Map>::type
        >
    >::type
{};

}

/**
 * Stores the binding of a @c Concept to a set of actual types.
 * @c Concept is interpreted in the same way as with @ref any.
 */
template<class Concept>
class binding
{
    typedef typename ::boost::type_erasure::detail::normalize_concept<
        Concept>::type normalized;
    typedef typename ::boost::mpl::transform<normalized,
        ::boost::type_erasure::detail::maybe_adapt_to_vtable< ::boost::mpl::_1>
    >::type actual_concept;
    typedef typename ::boost::type_erasure::detail::make_vtable<
        actual_concept>::type table_type;
    typedef typename ::boost::type_erasure::detail::get_placeholder_normalization_map<
        Concept
    >::type placeholder_subs;
public:

    /**
     * \pre @ref relaxed must be in @c Concept.
     *
     * \throws Nothing.
     */
    binding() { BOOST_MPL_ASSERT((::boost::type_erasure::is_relaxed<Concept>)); }
    
    /**
     * \pre @c Map must be an MPL map with an entry for each placeholder
     *      referred to by @c Concept.
     *
     * \throws Nothing.
     */
    template<class Map>
    explicit binding(const Map&)
      : impl((
            BOOST_TYPE_ERASURE_INSTANTIATE(Concept, Map),
            static_binding<Map>()
        ))
    {}
    
    /**
     * \pre @c Map must be an MPL map with an entry for each placeholder
     *      referred to by @c Concept.
     *
     * \throws Nothing.
     */
    template<class Map>
    binding(const static_binding<Map>&)
      : impl((
            BOOST_TYPE_ERASURE_INSTANTIATE(Concept, Map),
            static_binding<Map>()
        ))
    {}

    /**
     * Converts from another set of bindings.
     *
     * \pre Map must be an MPL map with an entry for each placeholder
     *      referred to by @c Concept.  The mapped type should be the
     *      corresponding placeholder in Concept2.
     *
     * \throws std::bad_alloc
     */
    template<class Concept2, class Map>
    binding(const binding<Concept2>& other, const Map&
#ifndef BOOST_TYPE_ERASURE_DOXYGEN
        , typename ::boost::enable_if<
            ::boost::mpl::and_<
                ::boost::type_erasure::detail::check_map<Concept, Map>,
                ::boost::type_erasure::is_subconcept<Concept, Concept2, Map>
            >
        >::type* = 0
#endif
        )
      : impl(
            other,
            static_binding<Map>(),
            ::boost::type_erasure::detail::can_optimize_conversion<Concept2, Concept, Map>()
        )
    {}

    /**
     * Converts from another set of bindings.
     *
     * \pre Map must be an MPL map with an entry for each placeholder
     *      referred to by @c Concept.  The mapped type should be the
     *      corresponding placeholder in Concept2.
     *
     * \throws std::bad_alloc
     */
    template<class Concept2, class Map>
    binding(const binding<Concept2>& other, const static_binding<Map>&
#ifndef BOOST_TYPE_ERASURE_DOXYGEN
        , typename ::boost::enable_if<
            ::boost::mpl::and_<
                ::boost::type_erasure::detail::check_map<Concept, Map>,
                ::boost::type_erasure::is_subconcept<Concept, Concept2, Map>
            >
        >::type* = 0
#endif
        )
      : impl(
            other,
            static_binding<Map>(),
            ::boost::type_erasure::detail::can_optimize_conversion<Concept2, Concept, Map>()
        )
    {}

    /**
     * \return true iff the sets of types that the placeholders
     *         bind to are the same for both arguments.
     *
     * \throws Nothing.
     */
    friend bool operator==(const binding& lhs, const binding& rhs)
    { return *lhs.impl.table == *rhs.impl.table; }
    
    /**
     * \return true iff the arguments do not map to identical
     *         sets of types.
     *
     * \throws Nothing.
     */
    friend bool operator!=(const binding& lhs, const binding& rhs)
    { return !(lhs == rhs); }

    /** INTERNAL ONLY */
    template<class T>
    typename T::type find() const { return impl.table->lookup((T*)0); }
private:
    template<class C2>
    friend class binding;
    /** INTERNAL ONLY */
    struct impl_type
    {
        impl_type() {
            table = &::boost::type_erasure::detail::make_vtable_init<
                typename ::boost::mpl::transform<
                    actual_concept,
                    ::boost::type_erasure::detail::get_null_vtable_entry<
                        ::boost::mpl::_1
                    >
                >::type,
                table_type
            >::type::value;
        }
        template<class Map>
        impl_type(const static_binding<Map>&)
        {
            table = &::boost::type_erasure::detail::make_vtable_init<
                typename ::boost::mpl::transform<
                    actual_concept,
                    ::boost::type_erasure::detail::rebind_placeholders<
                        ::boost::mpl::_1,
                        typename ::boost::type_erasure::detail::add_deductions<
                            Map,
                            placeholder_subs
                        >::type
                    >
                >::type,
                table_type
            >::type::value;
        }
        template<class Concept2, class Map>
        impl_type(const binding<Concept2>& other, const static_binding<Map>&, boost::mpl::false_)
          : manager(new table_type)
        {
            manager->template convert_from<
                typename ::boost::type_erasure::detail::convert_deductions<
                    Map,
                    placeholder_subs,
                    typename binding<Concept2>::placeholder_subs
                >::type
            >(*other.impl.table);
            table = manager.get();
        }
        template<class Concept2, class Map>
        impl_type(const binding<Concept2>& other, const static_binding<Map>&, boost::mpl::true_)
          : table(other.impl.table),
            manager(other.impl.manager)
        {}
        const table_type* table;
        ::boost::shared_ptr<table_type> manager;
    } impl;
};

}
}

#endif
