///////////////////////////////////////////////////////////////////////////////
// extractor.hpp
//
//  Copyright 2005 Eric Niebler. 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_ACCUMULATORS_FRAMEWORK_EXTRACTOR_HPP_EAN_28_10_2005
#define BOOST_ACCUMULATORS_FRAMEWORK_EXTRACTOR_HPP_EAN_28_10_2005

#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/tuple/rem.hpp>
#include <boost/preprocessor/array/size.hpp>
#include <boost/preprocessor/array/data.hpp>
#include <boost/preprocessor/array/elem.hpp>
#include <boost/preprocessor/seq/to_array.hpp>
#include <boost/preprocessor/seq/transform.hpp>
#include <boost/preprocessor/repetition/enum_params.hpp>
#include <boost/preprocessor/repetition/enum_trailing.hpp>
#include <boost/preprocessor/repetition/enum_trailing_params.hpp>
#include <boost/preprocessor/repetition/enum_trailing_binary_params.hpp>
#include <boost/preprocessor/repetition/repeat.hpp>
#include <boost/preprocessor/repetition/repeat_from_to.hpp>
#include <boost/parameter/binding.hpp>
#include <boost/mpl/bool.hpp>
#include <boost/mpl/if.hpp>
#include <boost/mpl/eval_if.hpp>
#include <boost/mpl/apply.hpp>
#include <boost/type_traits/remove_const.hpp>
#include <boost/type_traits/remove_reference.hpp>
#include <boost/accumulators/accumulators_fwd.hpp>
#include <boost/accumulators/framework/parameters/accumulator.hpp>

namespace boost { namespace accumulators
{

namespace detail
{
    template<typename AccumulatorSet, typename Feature>
    struct accumulator_set_result
    {
        typedef typename as_feature<Feature>::type feature_type;
        typedef typename mpl::apply<
            typename boost::remove_const<
                typename boost::remove_reference<AccumulatorSet>::type
            >::type
          , feature_type
        >::type::result_type type;
    };

    template<typename Args, typename Feature>
    struct argument_pack_result
      : accumulator_set_result<
            typename boost::remove_reference<
                typename parameter::binding<
                    typename boost::remove_const<
                        typename boost::remove_reference<Args>::type
                    >::type
                  , tag::accumulator
                >::type
            >::type
          , Feature
        >
    {
    };

    template<typename A, typename Feature>
    struct extractor_result
      : mpl::eval_if<
            detail::is_accumulator_set<A>
          , accumulator_set_result<A, Feature>
          , argument_pack_result<A, Feature>
        >
    {
    };

    template<typename Feature, typename AccumulatorSet>
    typename extractor_result<AccumulatorSet, Feature>::type
    do_extract(AccumulatorSet const &acc, mpl::true_)
    {
        typedef typename as_feature<Feature>::type feature_type;
        return extract_result<feature_type>(acc);
    }

    template<typename Feature, typename Args>
    typename extractor_result<Args, Feature>::type
    do_extract(Args const &args, mpl::false_)
    {
        typedef typename as_feature<Feature>::type feature_type;
        return find_accumulator<feature_type>(args[accumulator]).result(args);
    }

} // namespace detail


///////////////////////////////////////////////////////////////////////////////
/// Extracts the result associated with Feature from the specified accumulator_set.
template<typename Feature>
struct extractor
{
    typedef extractor<Feature> this_type;

    /// The result meta-function for determining the return type of the extractor
    template<typename F>
    struct result;

    template<typename A1>
    struct result<this_type(A1)>
      : detail::extractor_result<A1, Feature>
    {
    };

    /// Extract the result associated with Feature from the accumulator set
    /// \param acc The accumulator set object from which to extract the result
    template<typename Arg1>
    typename detail::extractor_result<Arg1, Feature>::type
    operator ()(Arg1 const &arg1) const
    {
        // Arg1 could be an accumulator_set or an argument pack containing
        // an accumulator_set. Dispatch accordingly.
        return detail::do_extract<Feature>(arg1, detail::is_accumulator_set<Arg1>());
    }

    /// \overload
    ///
    /// \param a1 Optional named parameter to be passed to the accumulator's result() function.
    template<typename AccumulatorSet, typename A1>
    typename detail::extractor_result<AccumulatorSet, Feature>::type
    operator ()(AccumulatorSet const &acc, A1 const &a1) const
    {
        BOOST_MPL_ASSERT((detail::is_accumulator_set<AccumulatorSet>));
        typedef typename as_feature<Feature>::type feature_type;
        return extract_result<feature_type>(acc, a1);
    }

    // ... other overloads generated by Boost.Preprocessor:

    /// INTERNAL ONLY
    ///
#define BOOST_ACCUMULATORS_EXTRACTOR_FUN_OP(z, n, _)                                    \
    template<BOOST_PP_ENUM_PARAMS_Z(z, n, typename A)>                                  \
    struct result<this_type(BOOST_PP_ENUM_PARAMS_Z(z, n, A))>                           \
      : detail::extractor_result<A1, Feature>                                           \
    {};                                                                                 \
    template<                                                                           \
        typename AccumulatorSet                                                         \
        BOOST_PP_ENUM_TRAILING_PARAMS_Z(z, n, typename A)                               \
    >                                                                                   \
    typename detail::extractor_result<AccumulatorSet, Feature>::type                    \
    operator ()(                                                                        \
        AccumulatorSet const &acc                                                       \
        BOOST_PP_ENUM_TRAILING_BINARY_PARAMS_Z(z, n, A, const &a)                       \
    ) const                                                                             \
    {                                                                                   \
        BOOST_MPL_ASSERT((detail::is_accumulator_set<AccumulatorSet>));                 \
        typedef typename as_feature<Feature>::type feature_type;                        \
        return extract_result<feature_type>(acc BOOST_PP_ENUM_TRAILING_PARAMS_Z(z, n, a));\
    }

    BOOST_PP_REPEAT_FROM_TO(
        2
      , BOOST_PP_INC(BOOST_ACCUMULATORS_MAX_ARGS)
      , BOOST_ACCUMULATORS_EXTRACTOR_FUN_OP
      , _
    )

#undef BOOST_ACCUMULATORS_EXTRACTOR_FUN_OP

    #ifdef BOOST_ACCUMULATORS_DOXYGEN_INVOKED
    /// \overload
    ///
    template<typename AccumulatorSet, typename A1, typename A2, ...>
    typename detail::extractor_result<AccumulatorSet, Feature>::type
    operator ()(AccumulatorSet const &acc, A1 const &a1, A2 const &a2, ...);
    #endif
};

}} // namespace boost::accumulators

/// INTERNAL ONLY
///
#define BOOST_ACCUMULATORS_ARRAY_REM(Array)                                                         \
    BOOST_PP_TUPLE_REM_CTOR(BOOST_PP_ARRAY_SIZE(Array), BOOST_PP_ARRAY_DATA(Array))

/// INTERNAL ONLY
///
#define BOOST_ACCUMULATORS_SEQ_REM(Seq)                                                             \
    BOOST_ACCUMULATORS_ARRAY_REM(BOOST_PP_SEQ_TO_ARRAY(Seq))

/// INTERNAL ONLY
///
#define BOOST_ACCUMULATORS_ARGS_OP(s, data, elem)                                                   \
    T ## s

/// INTERNAL ONLY
///
#define BOOST_ACCUMULATORS_PARAMS_OP(s, data, elem)                                                 \
    elem T ## s

/// INTERNAL ONLY
///
#define BOOST_ACCUMULATORS_MAKE_FEATURE(Tag, Feature, ParamsSeq)                                    \
    Tag::Feature<                                                                                   \
        BOOST_ACCUMULATORS_SEQ_REM(                                                                 \
            BOOST_PP_SEQ_TRANSFORM(BOOST_ACCUMULATORS_ARGS_OP, ~, ParamsSeq)                        \
        )                                                                                           \
    >

/// INTERNAL ONLY
///
#define BOOST_ACCUMULATORS_DEFINE_EXTRACTOR_FUN_IMPL(z, n, Tag, Feature, ParamsSeq)                 \
    template<                                                                                       \
        BOOST_ACCUMULATORS_SEQ_REM(                                                                 \
            BOOST_PP_SEQ_TRANSFORM(BOOST_ACCUMULATORS_PARAMS_OP, ~, ParamsSeq)                      \
        )                                                                                           \
      , typename Arg1                                                                               \
        BOOST_PP_ENUM_TRAILING_PARAMS_Z(z, n, typename A)                                           \
    >                                                                                               \
    typename boost::accumulators::detail::extractor_result<                                         \
        Arg1                                                                                        \
      , BOOST_ACCUMULATORS_MAKE_FEATURE(Tag, Feature, ParamsSeq)                                    \
    >::type                                                                                         \
    Feature(Arg1 const &arg1 BOOST_PP_ENUM_TRAILING_BINARY_PARAMS_Z(z, n, A, const &a) )            \
    {                                                                                               \
        typedef BOOST_ACCUMULATORS_MAKE_FEATURE(Tag, Feature, ParamsSeq) feature_type;              \
        return boost::accumulators::extractor<feature_type>()(                                      \
            arg1 BOOST_PP_ENUM_TRAILING_PARAMS_Z(z, n, a));                                         \
    }

/// INTERNAL ONLY
///
#define BOOST_ACCUMULATORS_DEFINE_EXTRACTOR_FUN(z, n, _)                                            \
    BOOST_ACCUMULATORS_DEFINE_EXTRACTOR_FUN_IMPL(                                                   \
        z                                                                                           \
      , n                                                                                           \
      , BOOST_PP_ARRAY_ELEM(0, _)                                                                   \
      , BOOST_PP_ARRAY_ELEM(1, _)                                                                   \
      , BOOST_PP_ARRAY_ELEM(2, _)                                                                   \
    )

#define BOOST_ACCUMULATORS_DEFINE_EXTRACTOR(Tag, Feature, ParamSeq)                                 \
    BOOST_PP_REPEAT(                                                                                \
        BOOST_PP_INC(BOOST_ACCUMULATORS_MAX_ARGS)                                                   \
      , BOOST_ACCUMULATORS_DEFINE_EXTRACTOR_FUN                                                     \
      , (3, (Tag, Feature, ParamSeq))                                                               \
    )

#endif
