//  (C) Copyright Gennadiy Rozental 2001.
//  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)

//  See http://www.boost.org/libs/test for the library home page.
//
//!@file
//!@brief test case family based on data generator
// ***************************************************************************

#ifndef BOOST_TEST_DATA_TEST_CASE_HPP_102211GER
#define BOOST_TEST_DATA_TEST_CASE_HPP_102211GER

// Boost.Test
#include <boost/test/data/config.hpp>
#include <boost/test/data/dataset.hpp>
#include <boost/test/data/for_each_sample.hpp>

// Boost
#include <boost/preprocessor/repetition/enum_params.hpp>
#include <boost/preprocessor/repetition/enum_binary_params.hpp>
#include <boost/preprocessor/repetition/repeat_from_to.hpp>

#include <boost/preprocessor/variadic/to_seq.hpp>
#include <boost/preprocessor/variadic/size.hpp>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/seq/for_each.hpp>
#include <boost/preprocessor/seq/enum.hpp>
#include <boost/preprocessor/control/iif.hpp>
#include <boost/preprocessor/comparison/equal.hpp>

#include <boost/bind.hpp>

#include <boost/test/detail/suppress_warnings.hpp>
#include <boost/test/tools/detail/print_helper.hpp>

//____________________________________________________________________________//

namespace boost {
namespace unit_test {
namespace data {

namespace ds_detail {

// ************************************************************************** //
// **************                     seed                     ************** //
// ************************************************************************** //

struct seed {
    template<typename DataSet>
    typename data::result_of::make<DataSet>::type
    operator->*( DataSet&& ds ) const
    {
        return data::make( std::forward<DataSet>( ds ) );
    }
};

// ************************************************************************** //
// **************                 test_case_gen                ************** //
// ************************************************************************** //

template<typename TestCase,typename DataSet>
class test_case_gen : public test_unit_generator {
public:
    // Constructor
#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES
    test_case_gen( const_string tc_name, const_string tc_file, std::size_t tc_line, DataSet&& ds )
    : m_tc_name( ut_detail::normalize_test_case_name( tc_name ) )
    {
        data::for_each_sample( std::forward<DataSet>( ds ), *this );
    }
    test_case_gen( test_case_gen&& gen )
    : m_tc_name( gen.m_tc_name )
    , m_test_cases( std::move(gen.m_test_cases) )
    {}
#else
    test_case_gen( const_string tc_name, const_string tc_file, std::size_t tc_line, DataSet const& ds )
    : m_tc_name( ut_detail::normalize_test_case_name( tc_name ) )
    {
        data::for_each_sample( ds, *this );
    }
#endif

    virtual test_unit* next() const
    {
        if( m_test_cases.empty() )
            return 0;

        test_unit* res = m_test_cases.front();
        m_test_cases.pop_front();

        return res;
    }

    // !! ?? variadics based implementation
#define TC_MAKE(z,arity,_)                                                          \
    template<BOOST_PP_ENUM_PARAMS(arity, typename Arg)>                             \
    void    operator()( BOOST_PP_ENUM_BINARY_PARAMS(arity, Arg, const& arg) ) const \
    {                                                                               \
        m_test_cases.push_back( new test_case( m_tc_name, m_tc_file, m_tc_line,     \
         boost::bind( &TestCase::template test_method<BOOST_PP_ENUM_PARAMS(arity,Arg)>, \
         BOOST_PP_ENUM_PARAMS(arity, arg) ) ) );                                    \
    }                                                                               \

    BOOST_PP_REPEAT_FROM_TO(1, 4, TC_MAKE, _)

private:
    // Data members
    std::string                     m_tc_name;
    const_string                    m_tc_file;
    std::size_t                     m_tc_line;
    mutable std::list<test_unit*>   m_test_cases;
};

//____________________________________________________________________________//

#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES
template<typename TestCase,typename DataSet>
test_case_gen<TestCase,DataSet>
make_test_case_gen( const_string tc_name, const_string tc_file, std::size_t tc_line, DataSet&& ds )
{
    return test_case_gen<TestCase,DataSet>( tc_name, tc_file, tc_line, std::forward<DataSet>(ds) );
}
#else
template<typename TestCase,typename DataSet>
test_case_gen<TestCase,DataSet>
make_test_case_gen( const_string tc_name, const_string tc_file, std::size_t tc_line, DataSet const& ds )
{
    return test_case_gen<TestCase,DataSet>( tc_name, tc_file, tc_line, ds );
}
#endif

//____________________________________________________________________________//

} // namespace ds_detail

// ************************************************************************** //
// **************             BOOST_DATA_TEST_CASE             ************** //
// ************************************************************************** //

#define BOOST_DATA_TEST_CASE_PARAM(r, _, i, param)  (BOOST_PP_CAT(Arg, i) const& param)
#define BOOST_DATA_TEST_CONTEXT(r, _, param)  << BOOST_STRINGIZE(param) << " = " << boost::test_tools::tt_detail::print_helper(param) << "; "

#define BOOST_DATA_TEST_CASE_PARAMS( params )                           \
    BOOST_PP_SEQ_ENUM(                                                  \
        BOOST_PP_SEQ_FOR_EACH_I(BOOST_DATA_TEST_CASE_PARAM, _, params)) \
/**/

#define BOOST_DATA_TEST_CASE_IMPL( arity, test_name, dataset, params )  \
struct test_name {                                                      \
    template<BOOST_PP_ENUM_PARAMS(arity, typename Arg)>                 \
    static void test_method( BOOST_DATA_TEST_CASE_PARAMS( params ) )    \
    {                                                                   \
        BOOST_TEST_CONTEXT( ""                                          \
            BOOST_PP_SEQ_FOR_EACH(BOOST_DATA_TEST_CONTEXT, _, params))  \
            _impl(BOOST_PP_SEQ_ENUM(params));                           \
    }                                                                   \
private:                                                                \
    template<BOOST_PP_ENUM_PARAMS(arity, typename Arg)>                 \
    static void _impl(BOOST_DATA_TEST_CASE_PARAMS( params ));           \
};                                                                      \
                                                                        \
BOOST_AUTO_TU_REGISTRAR( test_name )(                                   \
    boost::unit_test::data::ds_detail::make_test_case_gen<test_name>(   \
          BOOST_STRINGIZE( test_name ),                                 \
          __FILE__, __LINE__,                                           \
          boost::unit_test::data::ds_detail::seed{} ->* dataset ),      \
    boost::unit_test::decorator::collector::instance() );               \
                                                                        \
    template<BOOST_PP_ENUM_PARAMS(arity, typename Arg)>                 \
    void test_name::_impl( BOOST_DATA_TEST_CASE_PARAMS( params ) )      \
/**/

#define BOOST_DATA_TEST_CASE_WITH_PARAMS( test_name, dataset, ... )     \
    BOOST_DATA_TEST_CASE_IMPL( BOOST_PP_VARIADIC_SIZE(__VA_ARGS__),     \
                               test_name, dataset,                      \
                               BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__) )  \
/**/
#define BOOST_DATA_TEST_CASE_NO_PARAMS( test_name, dataset )            \
    BOOST_DATA_TEST_CASE_WITH_PARAMS( test_name, dataset, sample )      \
/**/

#if BOOST_PP_VARIADICS_MSVC

#define BOOST_DATA_TEST_CASE( ... )                                     \
    BOOST_PP_CAT(                                                       \
    BOOST_PP_IIF(BOOST_PP_EQUAL(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__),2), \
                     BOOST_DATA_TEST_CASE_NO_PARAMS,                    \
                     BOOST_DATA_TEST_CASE_WITH_PARAMS) (__VA_ARGS__), ) \
/**/
#else

#define BOOST_DATA_TEST_CASE( ... )                                     \
    BOOST_PP_IIF(BOOST_PP_EQUAL(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__),2), \
                     BOOST_DATA_TEST_CASE_NO_PARAMS,                    \
                     BOOST_DATA_TEST_CASE_WITH_PARAMS) (__VA_ARGS__)    \
/**/
#endif

} // namespace data
} // namespace unit_test
} // namespace boost

#include <boost/test/detail/enable_warnings.hpp>

#endif // BOOST_TEST_DATA_TEST_CASE_HPP_102211GER

