    // Copyright (C) 2006  Davis E. King (davis@dlib.net)
    // License: Boost Software License   See LICENSE.txt for the full license.

    #ifndef DLIB_MATH_DETAIL_BESSEL
    #define DLIB_MATH_DETAIL_BESSEL

    #include <cmath>
    #include <limits>
    #include <stdexcept>
    #include <type_traits>
    #include "../../numeric_constants.h"
    #include "../../assert.h"

    namespace dlib
    {
        namespace detail
        {
    #if __cpp_lib_math_special_functions
            using std::cyl_bessel_i;
            using std::cyl_bessel_j;
    #else
            constexpr unsigned int BESSEL_NITERATIONS = 250;

            template<typename R>
            R cyl_bessel_i(R nu, R x)
            {
                static_assert(std::is_floating_point<R>::value, "template parameter must be a floating point type");
                DLIB_ASSERT(nu >= R{0} && x >= R{0}, "bad arguments. Contract preconditions are : nu >= 0 and x >= 0");

                if (std::isnan(nu) || std::isnan(x))
                    return std::numeric_limits<R>::quiet_NaN();

                if(x == 0)
                {
                    return (nu == R{0}) ? R{1} : R{0};
                }
                else if(nu == R(0.5))
                {
                    // common special case
                    return std::sqrt(2 / (x * pi)) * std::sinh(x);
                }
                else
                {
                    // Compute sum in log-domain to avoid overflow issues
                    const R fact = nu == R{0} ? R{1} : std::pow(R(0.5)*x, nu); //factorize (x/2)^nu
                    R a{};
                    R b{};
                    R c{};
                    R sum{0};

                    for (unsigned int k=0; k < BESSEL_NITERATIONS; ++k)
                    {
                        a = 2 * k * std::log(R(0.5)*x); // log((x/2)^(2k))
                        b = std::lgamma(R(k) + R{1});   // log(k!) = log(gamma(k+1)). Recall gamma(k) = (k-1)!
                        c = std::lgamma(nu + k + 1);
                        sum += std::exp( a - b - c );
                    }

                    return fact * sum;
                }
            }

            template<typename R>
            R cyl_bessel_j(R nu, R x)
            {
                static_assert(std::is_floating_point<R>::value, "template parameter must be a floating point type");
                DLIB_ASSERT(nu >= R{0} && x >= R{0}, "bad arguments. Contract preconditions are : nu >= 0 and x >= 0");

                if (std::isnan(nu) || std::isnan(x))
                    return std::numeric_limits<R>::quiet_NaN();

                /*! Special case !*/
                else if (x == R{0})
                {
                    return (nu == R{0}) ? R{1} : R{0};
                }
                else
                {
                    // Compute sum in log-domain to avoid overflow issues
                    const R fact = nu == R{0} ? R{1} : std::pow(R(0.5)*x, nu); //factorize (x/2)^nu
                    R a{};
                    R b{};
                    R c{};
                    R sum{0};

                    for (unsigned int k=0; k < BESSEL_NITERATIONS; ++k)
                    {
                        a = 2 * k * std::log(R(0.5)*x); // log((x/2)^(2k))
                        b = std::lgamma(R(k) + R{1});   // log(k!) = log(gamma(k+1)). Recall gamma(k) = (k-1)!
                        c = std::lgamma(nu + k + 1);
                        if (k&1)
                            sum -= std::exp(a-b-c);
                        else
                            sum += std::exp(a-b-c);
                    }

                    return fact * sum;
                }
            }
    #endif
        }
    }

    #endif //DLIB_MATH_DETAIL_BESSEL