// Copyright Daniel Wallin 2006.
// 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 <boost/parameter/preprocessor.hpp>
#include <boost/parameter/binding.hpp>
#include <boost/parameter/config.hpp>
#include "basics.hpp"

#if defined(BOOST_PARAMETER_CAN_USE_MP11)
#include <type_traits>
#else
#include <boost/mpl/bool.hpp>
#include <boost/mpl/if.hpp>
#include <boost/type_traits/is_same.hpp>
#endif

namespace test {

    BOOST_PARAMETER_BASIC_FUNCTION((int), f, test::tag,
        (required
            (tester, *)
            (name, *)
        )
        (optional
            (value, *)
            (index, (int))
        )
    )
    {
        typedef typename boost::parameter::binding<
            Args,test::tag::index,int&
        >::type index_type;

#if defined(BOOST_PARAMETER_CAN_USE_MP11)
        static_assert(
            std::is_same<index_type,int&>::value
          , "index_type == int&"
        );
#else
        BOOST_MPL_ASSERT((
            typename boost::mpl::if_<
                boost::is_same<index_type,int&>
              , boost::mpl::true_
              , boost::mpl::false_
            >::type
        ));
#endif

        args[test::_tester](
            args[test::_name]
          , args[test::_value | 1.f]
          , args[test::_index | 2]
        );

        return 1;
    }
} // namespace test

#include <boost/parameter/value_type.hpp>

#if !defined(BOOST_PARAMETER_CAN_USE_MP11)
#include <boost/type_traits/remove_const.hpp>
#endif

namespace test {

    BOOST_PARAMETER_BASIC_FUNCTION((int), g, test::tag,
        (required
            (tester, *)
            (name, *)
        )
        (optional
            (value, *)
            (index, (int))
        )
    )
    {
        typedef typename boost::parameter::value_type<
            Args,test::tag::index,int
        >::type index_type;

#if defined(BOOST_PARAMETER_CAN_USE_MP11)
        static_assert(
            std::is_same<
                typename std::remove_const<index_type>::type
              , int
            >::value
          , "remove_const<index_type>::type == int"
        );
#else
        BOOST_MPL_ASSERT((
            typename boost::mpl::if_<
                boost::is_same<
                    typename boost::remove_const<index_type>::type
                  , int
                >
              , boost::mpl::true_
              , boost::mpl::false_
            >::type
        ));
#endif

        args[test::_tester](
            args[test::_name]
          , args[test::_value | 1.f]
          , args[test::_index | 2]
        );

        return 1;
    }
} // namespace test

#if !defined(BOOST_PARAMETER_CAN_USE_MP11)
#include <boost/type_traits/remove_reference.hpp>
#endif

namespace test {

    BOOST_PARAMETER_FUNCTION((int), h, test::tag,
        (required
            (tester, *)
            (name, *)
        )
        (optional
            (value, *, 1.f)
            (index, (int), 2)
        )
    )
    {
#if defined(BOOST_PARAMETER_CAN_USE_MP11)
        static_assert(
            std::is_same<
                typename std::remove_const<
                    typename std::remove_reference<index_type>::type
                >::type
              , int
            >::value
          , "remove_cref<index_type>::type == int"
        );
#elif !BOOST_WORKAROUND(BOOST_BORLANDC, BOOST_TESTED_AT(0x564)) && \
    !BOOST_WORKAROUND(BOOST_MSVC, <= 1300)
        BOOST_MPL_ASSERT((
            typename boost::mpl::if_<
                boost::is_same<
                    typename boost::remove_const<
                        typename boost::remove_reference<index_type>::type
                    >::type
                  , int
                >
              , boost::mpl::true_
              , boost::mpl::false_
            >::type
        ));
#endif  // BOOST_PARAMETER_CAN_USE_MP11 || Borland/MSVC workarounds not needed

        tester(name, value, index);

        return 1;
    }

    BOOST_PARAMETER_FUNCTION((int), h2, test::tag,
        (required
            (tester, *)
            (name, *)
        )
        (optional
            (value, *, 1.f)
            (index, (int), static_cast<int>(value * 2))
        )
    )
    {
#if defined(BOOST_PARAMETER_CAN_USE_MP11)
        static_assert(
            std::is_same<
                typename std::remove_const<
                    typename std::remove_reference<index_type>::type
                >::type
              , int
            >::value
          , "remove_cref<index_type>::type == int"
        );
#elif !BOOST_WORKAROUND(BOOST_BORLANDC, BOOST_TESTED_AT(0x564)) && \
    !BOOST_WORKAROUND(BOOST_MSVC, <= 1300)
        BOOST_MPL_ASSERT((
            typename boost::mpl::if_<
                boost::is_same<
                    typename boost::remove_const<
                        typename boost::remove_reference<index_type>::type
                    >::type
                  , int
                >
              , boost::mpl::true_
              , boost::mpl::false_
            >::type
        ));
#endif  // BOOST_PARAMETER_CAN_USE_MP11 || Borland/MSVC workarounds not needed

        tester(name, value, index);

        return 1;
    }
} // namespace test

#include <string>

#if !defined(BOOST_NO_SFINAE)
#include <boost/parameter/aux_/preprocessor/nullptr.hpp>
#include <boost/core/enable_if.hpp>
#if !defined(BOOST_PARAMETER_CAN_USE_MP11)
#include <boost/type_traits/is_base_of.hpp>
#include <boost/type_traits/is_convertible.hpp>
#endif
#endif

namespace test {

    struct base_0
    {
        float f;
        int i;

        template <typename Args>
        explicit base_0(
            Args const& args
#if !defined(BOOST_NO_SFINAE)
          , typename boost::disable_if<
                typename boost::mpl::if_<
                    boost::is_base_of<base_0,Args>
                  , boost::mpl::true_
                  , boost::mpl::false_
                >::type
            >::type* = BOOST_PARAMETER_AUX_PP_NULLPTR
#endif  // BOOST_NO_SFINAE
        ) : f(args[test::_value | 1.f]), i(args[test::_index | 2])
        {
        }
    };

    struct class_0 : test::base_0
    {
        BOOST_PARAMETER_CONSTRUCTOR(class_0, (test::base_0), test::tag,
            (optional
                (value, *)
                (index, *)
            )
        )

        BOOST_PARAMETER_BASIC_FUNCTION_CALL_OPERATOR((int), test::tag,
            (optional
                (value, *)
                (index, *)
            )
        )
        {
            this->f = args[test::_value | 2.f];
            this->i = args[test::_index | 1];
            return 1;
        }
    };

    struct base_1
    {
        template <typename Args>
        explicit base_1(
            Args const& args
#if !defined(BOOST_NO_SFINAE)
          , typename boost::disable_if<
#if defined(BOOST_PARAMETER_CAN_USE_MP11)
                std::is_base_of<base_1,Args>
#else
                typename boost::mpl::if_<
                    boost::is_base_of<base_1,Args>
                  , boost::mpl::true_
                  , boost::mpl::false_
                >::type
#endif
            >::type* = BOOST_PARAMETER_AUX_PP_NULLPTR
#endif  // BOOST_NO_SFINAE
        )
        {
            args[test::_tester](
                args[test::_name]
              , args[test::_value | 1.f]
              , args[test::_index | 2]
            );
        }
    };

    struct class_1 : test::base_1
    {
        BOOST_PARAMETER_CONSTRUCTOR(class_1, (test::base_1), test::tag,
            (required
                (tester, *)
                (name, *)
            )
            (optional
                (value, *)
                (index, *)
            )
        )

        BOOST_PARAMETER_BASIC_MEMBER_FUNCTION((int), f, test::tag,
            (required
                (tester, *)
                (name, *)
            )
            (optional
                (value, *)
                (index, *)
            )
        )
        {
            args[test::_tester](
                args[test::_name]
              , args[test::_value | 1.f]
              , args[test::_index | 2]
            );

            return 1;
        }

        BOOST_PARAMETER_BASIC_CONST_MEMBER_FUNCTION((int), f, test::tag,
            (required
                (tester, *)
                (name, *)
            )
            (optional
                (value, *)
                (index, *)
            )
        )
        {
            args[test::_tester](
                args[test::_name]
              , args[test::_value | 1.f]
              , args[test::_index | 2]
            );

            return 1;
        }

        BOOST_PARAMETER_MEMBER_FUNCTION((int), f2, test::tag,
            (required
                (tester, *)
                (name, *)
            )
            (optional
                (value, *, 1.f)
                (index, *, 2)
            )
        )
        {
            tester(name, value, index);
            return 1;
        }

        BOOST_PARAMETER_CONST_MEMBER_FUNCTION((int), f2, test::tag,
            (required
                (tester, *)
                (name, *)
            )
            (optional
                (value, *, 1.f)
                (index, *, 2)
            )
        )
        {
            tester(name, value, index);
            return 1;
        }

        BOOST_PARAMETER_MEMBER_FUNCTION((int), static f_static, test::tag,
            (required
                (tester, *)
                (name, *)
            )
            (optional
                (value, *, 1.f)
                (index, *, 2)
            )
        )
        {
            tester(name, value, index);
            return 1;
        }

        BOOST_PARAMETER_FUNCTION_CALL_OPERATOR((int), test::tag,
            (required
                (tester, *)
                (name, *)
            )
            (optional
                (value, *, 1.f)
                (index, *, 2)
            )
        )
        {
            tester(name, value, index);
            return 1;
        }

        BOOST_PARAMETER_CONST_FUNCTION_CALL_OPERATOR((int), test::tag,
            (required
                (tester, *)
                (name, *)
            )
            (optional
                (value, *, 1.f)
                (index, *, 2)
            )
        )
        {
            tester(name, value, index);
            return 1;
        }
    };

    BOOST_PARAMETER_FUNCTION((int), sfinae, test::tag,
        (required
            (name, (std::string))
        )
    )
    {
        return 1;
    }

#if !defined(BOOST_NO_SFINAE)
    // On compilers that actually support SFINAE, add another overload
    // that is an equally good match and can only be in the overload set
    // when the others are not.  This tests that the SFINAE is actually
    // working.  On all other compilers we're just checking that everything
    // about SFINAE-enabled code will work, except of course the SFINAE.
    template <typename A0>
    typename boost::enable_if<
#if defined(BOOST_PARAMETER_CAN_USE_MP11)
        std::is_same<int,A0>
#else
        typename boost::mpl::if_<
            boost::is_same<int,A0>
          , boost::mpl::true_
          , boost::mpl::false_
        >::type
#endif
      , int
    >::type
        sfinae(A0 const& a0)
    {
        return 0;
    }
#endif  // BOOST_NO_SFINAE

    struct predicate
    {
        template <typename T, typename Args>
#if defined(BOOST_PARAMETER_CAN_USE_MP11)
        using fn = std::is_convertible<T,std::string>;
#else
        struct apply
          : boost::mpl::if_<
                boost::is_convertible<T,std::string>
              , boost::mpl::true_
              , boost::mpl::false_
            >
        {
        };
#endif

        BOOST_PARAMETER_BASIC_CONST_FUNCTION_CALL_OPERATOR((bool), test::tag,
            (required
                (value, *)
                (index, *)
            )
        )
        {
            return args[test::_value] < args[test::_index];
        }
    };

    BOOST_PARAMETER_FUNCTION((int), sfinae1, test::tag,
        (required
            (name, *(test::predicate))
        )
    )
    {
        return 1;
    }

#if !defined(BOOST_NO_SFINAE)
    // On compilers that actually support SFINAE, add another overload
    // that is an equally good match and can only be in the overload set
    // when the others are not.  This tests that the SFINAE is actually
    // working.  On all other compilers we're just checking that everything
    // about SFINAE-enabled code will work, except of course the SFINAE.
    template <typename A0>
    typename boost::enable_if<
#if defined(BOOST_PARAMETER_CAN_USE_MP11)
        std::is_same<int,A0>
#else
        typename boost::mpl::if_<
            boost::is_same<int,A0>
          , boost::mpl::true_
          , boost::mpl::false_
        >::type
#endif
      , int
    >::type
        sfinae1(A0 const& a0)
    {
        return 0;
    }
#endif  // BOOST_NO_SFINAE

    struct udt
    {
        udt(int foo_, int bar_) : foo(foo_), bar(bar_)
        {
        }

        int foo;
        int bar;
    };

    BOOST_PARAMETER_FUNCTION((int), lazy_defaults, test::tag,
        (required
            (name, *)
        )
        (optional
            (value, *, name.foo)
            (index, *, name.bar)
        )
    )
    {
        return 0;
    }
} // namespace test

#if BOOST_WORKAROUND(BOOST_MSVC, == 1300)
#include <boost/parameter/aux_/as_lvalue.hpp>
#endif

#include <boost/core/lightweight_test.hpp>

int main()
{
    test::f(
        test::values(std::string("foo"), 1.f, 2)
      , std::string("foo")
    );
    test::f(
        test::_tester = test::values(std::string("foo"), 1.f, 2)
      , test::_name = std::string("foo")
    );

    int index_lvalue = 2;

    test::f(
        test::_tester = test::values(std::string("foo"), 1.f, 2)
      , test::_name = std::string("foo")
      , test::_value = 1.f
      , test::_index = index_lvalue
    );

    test::f(
        test::values(std::string("foo"), 1.f, 2)
      , std::string("foo")
      , 1.f
      , index_lvalue
    );

    test::g(
        test::values(std::string("foo"), 1.f, 2)
      , std::string("foo")
      , 1.f
#if BOOST_WORKAROUND(BOOST_MSVC, == 1300)
      , boost::parameter::aux::as_lvalue(2)
#else
      , 2
#endif
    );

    test::h(
        test::values(std::string("foo"), 1.f, 2)
      , std::string("foo")
      , 1.f
#if BOOST_WORKAROUND(BOOST_MSVC, == 1300)
      , boost::parameter::aux::as_lvalue(2)
#else
      , 2
#endif
    );

    test::h2(
        test::_tester = test::values(std::string("foo"), 1.f, 2)
      , test::_name = std::string("foo")
      , test::_value = 1.f
    );

    test::class_0 u;

    BOOST_TEST(2 == u.i);
    BOOST_TEST(1.f == u.f);

    u();

    BOOST_TEST(1 == u.i);
    BOOST_TEST(2.f == u.f);

    test::class_1 x(
        test::values(std::string("foo"), 1.f, 2)
      , std::string("foo")
      , test::_index = 2
    );

    x.f(test::values(std::string("foo"), 1.f, 2), std::string("foo"));
    x.f(
        test::_tester = test::values(std::string("foo"), 1.f, 2)
      , test::_name = std::string("foo")
    );
    x.f2(test::values(std::string("foo"), 1.f, 2), std::string("foo"));
    x.f2(
        test::_tester = test::values(std::string("foo"), 1.f, 2)
      , test::_name = std::string("foo")
    );
    x(test::values(std::string("foo"), 1.f, 2), std::string("foo"));
    x(
        test::_tester = test::values(std::string("foo"), 1.f, 2)
      , test::_name = std::string("foo")
    );

    test::class_1 const& x_const = x;

    x_const.f(test::values(std::string("foo"), 1.f, 2), std::string("foo"));
    x_const.f(
        test::_tester = test::values(std::string("foo"), 1.f, 2)
      , test::_name = std::string("foo")
    );
    x_const.f2(test::values(std::string("foo"), 1.f, 2), std::string("foo"));
    x_const.f2(
        test::_tester = test::values(std::string("foo"), 1.f, 2)
      , test::_name = std::string("foo")
    );
    test::class_1::f_static(
        test::values(std::string("foo"), 1.f, 2)
      , std::string("foo")
    );
    test::class_1::f_static(
        test::_tester = test::values(std::string("foo"), 1.f, 2)
      , test::_name = std::string("foo")
    );
    x_const(test::values(std::string("foo"), 1.f, 2), std::string("foo"));
    x_const(
        test::_tester = test::values(std::string("foo"), 1.f, 2)
      , test::_name = std::string("foo")
    );

    test::predicate p;
    test::predicate const& p_const = p;

    BOOST_TEST(p_const(3, 4));
    BOOST_TEST(!p_const(4, 3));
    BOOST_TEST(!p_const(test::_index = 3, test::_value = 4));

#if !defined(BOOST_NO_SFINAE) && \
    !BOOST_WORKAROUND(BOOST_BORLANDC, BOOST_TESTED_AT(0x592))
    // GCC 3- tries to bind string literals
    // to non-const references to char const*.
    // BOOST_TEST(test::sfinae("foo") == 1);
    char const* foo_str = "foo";
    BOOST_TEST(test::sfinae(foo_str) == 1);
    BOOST_TEST(test::sfinae(1) == 0);

#if !BOOST_WORKAROUND(__SUNPRO_CC, BOOST_TESTED_AT(0x580))
    // Sun actually eliminates the desired overload for some reason.
    // Disabling this part of the test because SFINAE abilities are
    // not the point of this test.
    BOOST_TEST(test::sfinae1(foo_str) == 1);
#endif

    BOOST_TEST(test::sfinae1(1) == 0);
#endif

    test::lazy_defaults(test::_name = test::udt(0, 1));
    test::lazy_defaults(test::_name = 0, test::_value = 1, test::_index = 2);

    return boost::report_errors();
}

