// Boost.Geometry (aka GGL, Generic Geometry Library)
// Unit Test

// Copyright (c) 2014, Oracle and/or its affiliates.

// Contributed and/or modified by Menelaos Karavelas, on behalf of Oracle

// Licensed under the Boost Software License version 1.0.
// http://www.boost.org/users/license.html

#ifndef BOOST_TEST_MODULE
#define BOOST_TEST_MODULE test_math_sqrt
#endif

#include <cmath>
#include <iostream>

#include <boost/test/included/unit_test.hpp>

#include <boost/config.hpp>
#include <boost/type_traits/is_fundamental.hpp>

#include "number_types.hpp"

// important: the include above must precede the include below,
// otherwise the test will fail for the custom number type:
// custom_with_global_sqrt

#include <boost/geometry/util/math.hpp>
#include <boost/geometry/algorithms/not_implemented.hpp>

#ifdef HAVE_TTMATH
#  include <boost/geometry/extensions/contrib/ttmath_stub.hpp>
#endif

namespace bg = boost::geometry;




// call BOOST_CHECK
template <typename Argument, bool IsFundamental /* true */>
struct check
{
    template <typename Result>
    static inline void apply(Argument const& arg, Result const& result)
    {
        BOOST_CHECK_CLOSE(static_cast<double>(bg::math::sqrt(arg)),
                          static_cast<double>(result),
                          0.00001);        
    }
};


template <typename Argument>
struct check<Argument, false>
{
    template <typename Result>
    static inline void apply(Argument const& arg, Result const& result)
    {
        Result const tol(0.00001);
        BOOST_CHECK( bg::math::abs(bg::math::sqrt(arg) - result) < tol );
    }
};






// test sqrt return type and value
template
<
    typename Argument,
    typename ExpectedResult,
    typename Result = typename bg::math::detail::square_root
        <
            Argument
        >::return_type,
    bool IsFundamental = boost::is_fundamental<Argument>::value
>
struct check_sqrt
    : bg::not_implemented<Argument, Result, ExpectedResult>
{};


template <typename Argument, typename Result, bool IsFundamental>
struct check_sqrt<Argument, Result, Result, IsFundamental>
{
    static inline void apply(Argument const& arg, Result const& result)
    {
#ifdef BOOST_GEOMETRY_TEST_DEBUG
        std::cout << "testing: " << typeid(Result).name()
                  << " sqrt(" << typeid(Argument).name()
                  << ")" << std::endl;
#endif
        check<Argument, IsFundamental>::apply(arg, result);
    }
};






// test cases
BOOST_AUTO_TEST_CASE( test_math_sqrt_fundamental )
{
    static const double sqrt2 = std::sqrt(2.0);
    static const long double sqrt2L = std::sqrt(2.0L);
    static const float sqrt2F = std::sqrt(2.0F);

    check_sqrt<float, float>::apply(2.0F, sqrt2F);
    check_sqrt<double, double>::apply(2.0, sqrt2);
    check_sqrt<long double, long double>::apply(2.0L, sqrt2L);

    check_sqrt<char, double>::apply(2, sqrt2);
    check_sqrt<signed char, double>::apply(2, sqrt2);
    check_sqrt<short, double>::apply(2, sqrt2);
    check_sqrt<int, double>::apply(2, sqrt2);
    check_sqrt<long, double>::apply(2L, sqrt2);
#if !defined(BOOST_NO_LONG_LONG)
    check_sqrt<long long, double>::apply(2LL, sqrt2);
#endif
#ifdef BOOST_HAS_LONG_LONG
    check_sqrt
        <
            boost::long_long_type, double
        >::apply(boost::long_long_type(2), sqrt2);
#endif
}


BOOST_AUTO_TEST_CASE( test_math_sqrt_custom )
{
    typedef number_types::custom<double> custom1;
    typedef custom_global<double> custom2;
    typedef number_types::custom_with_global_sqrt<double> custom3;

    static const double sqrt2 = std::sqrt(2.0);

    check_sqrt<custom1, custom1>::apply(custom1(2.0), custom1(sqrt2));
    check_sqrt<custom2, custom2>::apply(custom2(2.0), custom2(sqrt2));
    check_sqrt<custom3, custom3>::apply(custom3(2.0), custom3(sqrt2));

#ifdef HAVE_TTMATH
    typedef ttmath_big custom4;
    typedef ttmath::Big<1, 4> custom5;

    check_sqrt<custom4, custom4>::apply(custom4(2.0), custom4(sqrt2));
    check_sqrt<custom5, custom5>::apply(custom5(2.0), custom5(sqrt2));
#endif
}
