/*
 * // Copyright (c) Radzivon Bartoshyk 8/2025. All rights reserved.
 * //
 * // Redistribution and use in source and binary forms, with or without modification,
 * // are permitted provided that the following conditions are met:
 * //
 * // 1.  Redistributions of source code must retain the above copyright notice, this
 * // list of conditions and the following disclaimer.
 * //
 * // 2.  Redistributions in binary form must reproduce the above copyright notice,
 * // this list of conditions and the following disclaimer in the documentation
 * // and/or other materials provided with the distribution.
 * //
 * // 3.  Neither the name of the copyright holder nor the names of its
 * // contributors may be used to endorse or promote products derived from
 * // this software without specific prior written permission.
 * //
 * // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
use crate::double_double::DoubleDouble;
use crate::dyadic_float::{DyadicFloat128, DyadicSign};
use crate::polyeval::f_polyeval9;

/**
Beta series

Generated by SageMath:
```python
#generate b series
def binomial_like(n, m):
    prod = QQ(1)
    z = QQ(4)*(n**2)
    for k in range(1,m + 1):
        prod *= (z - (2*k - 1)**2)
    return prod / (QQ(2)**(2*m) * (ZZ(m).factorial()))

R = LaurentSeriesRing(RealField(300), 'x',default_prec=300)
x = R.gen()

def Pn_asymptotic(n, y, terms=10):
    # now y = 1/x
    return sum( (-1)**m * binomial_like(n, 2*m) / (QQ(2)**(2*m)) * y**(QQ(2)*m) for m in range(terms) )

def Qn_asymptotic(n, y, terms=10):
    return sum( (-1)**m * binomial_like(n, 2*m + 1) / (QQ(2)**(2*m + 1)) * y**(QQ(2)*m + 1) for m in range(terms) )

P = Pn_asymptotic(0, x, 50)
Q = Qn_asymptotic(0, x, 50)

def sqrt_series(s):
    val = S.valuation()
    lc = S[val]  # Leading coefficient
    b = lc.sqrt() * x**(val // 2)

    for _ in range(5):
        b = (b + S / b) / 2
        b = b
    return b

S = (P**2 + Q**2).truncate(50)

b_series = sqrt_series(S).truncate(30)
#see the series
print(b_series)
```
**/
#[inline]
pub(crate) fn bessel_0_asympt_beta(recip: DoubleDouble) -> DoubleDouble {
    const C: [(u64, u64); 10] = [
        (0x0000000000000000, 0x3ff0000000000000),
        (0x0000000000000000, 0xbfb0000000000000),
        (0x0000000000000000, 0x3fba800000000000),
        (0x0000000000000000, 0xbfe15f0000000000),
        (0x0000000000000000, 0x4017651180000000),
        (0x0000000000000000, 0xc05ab8c13b800000),
        (0x0000000000000000, 0x40a730492f262000),
        (0x0000000000000000, 0xc0fc73a7acd696f0),
        (0xbdf3a00000000000, 0x41577458dd9fce68),
        (0xbe4ba6b000000000, 0xc1b903ab9b27e18f),
    ];

    // Doing (1/x)*(1/x) instead (1/(x*x)) to avoid spurious overflow/underflow
    let x2 = DoubleDouble::quick_mult(recip, recip);

    let mut p = DoubleDouble::mul_add(
        x2,
        DoubleDouble::from_bit_pair(C[9]),
        DoubleDouble::from_bit_pair(C[8]),
    );

    p = DoubleDouble::mul_add_f64(x2, p, f64::from_bits(C[7].1));
    p = DoubleDouble::mul_add_f64(x2, p, f64::from_bits(C[6].1));
    p = DoubleDouble::mul_add_f64(x2, p, f64::from_bits(C[5].1));
    p = DoubleDouble::mul_add_f64(x2, p, f64::from_bits(C[4].1));
    p = DoubleDouble::mul_add_f64(x2, p, f64::from_bits(C[3].1));
    p = DoubleDouble::mul_add_f64(x2, p, f64::from_bits(C[2].1));
    p = DoubleDouble::mul_add_f64(x2, p, f64::from_bits(C[1].1));
    p = DoubleDouble::mul_add_f64(x2, p, f64::from_bits(C[0].1));
    p
}

/**
Beta series

Generated by SageMath:
```python
#generate b series
def binomial_like(n, m):
    prod = QQ(1)
    z = QQ(4)*(n**2)
    for k in range(1,m + 1):
        prod *= (z - (2*k - 1)**2)
    return prod / (QQ(2)**(2*m) * (ZZ(m).factorial()))

R = LaurentSeriesRing(RealField(300), 'x',default_prec=300)
x = R.gen()

def Pn_asymptotic(n, y, terms=10):
    # now y = 1/x
    return sum( (-1)**m * binomial_like(n, 2*m) / (QQ(2)**(2*m)) * y**(QQ(2)*m) for m in range(terms) )

def Qn_asymptotic(n, y, terms=10):
    return sum( (-1)**m * binomial_like(n, 2*m + 1) / (QQ(2)**(2*m + 1)) * y**(QQ(2)*m + 1) for m in range(terms) )

P = Pn_asymptotic(0, x, 50)
Q = Qn_asymptotic(0, x, 50)

def sqrt_series(s):
    val = S.valuation()
    lc = S[val]  # Leading coefficient
    b = lc.sqrt() * x**(val // 2)

    for _ in range(5):
        b = (b + S / b) / 2
        b = b
    return b

S = (P**2 + Q**2).truncate(50)

b_series = sqrt_series(S).truncate(30)
#see the series
print(b_series)
```
**/
#[inline]
pub(crate) fn bessel_0_asympt_beta_fast(recip: DoubleDouble) -> DoubleDouble {
    const C: [u64; 10] = [
        0x3ff0000000000000,
        0xbfb0000000000000,
        0x3fba800000000000,
        0xbfe15f0000000000,
        0x4017651180000000,
        0xc05ab8c13b800000,
        0x40a730492f262000,
        0xc0fc73a7acd696f0,
        0x41577458dd9fce68,
        0xc1b903ab9b27e18f,
    ];

    // Doing (1/x)*(1/x) instead (1/(x*x)) to avoid spurious overflow/underflow
    let x2 = DoubleDouble::quick_mult(recip, recip);

    let p = f_polyeval9(
        x2.hi,
        f64::from_bits(C[1]),
        f64::from_bits(C[2]),
        f64::from_bits(C[3]),
        f64::from_bits(C[4]),
        f64::from_bits(C[5]),
        f64::from_bits(C[6]),
        f64::from_bits(C[7]),
        f64::from_bits(C[8]),
        f64::from_bits(C[9]),
    );

    DoubleDouble::mul_f64_add_f64(x2, p, f64::from_bits(C[0]))
}

/// see [bessel_0_asympt_beta] for more info
pub(crate) fn bessel_0_asympt_beta_hard(recip: DyadicFloat128) -> DyadicFloat128 {
    static C: [DyadicFloat128; 12] = [
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -127,
            mantissa: 0x80000000_00000000_00000000_00000000_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -131,
            mantissa: 0x80000000_00000000_00000000_00000000_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -131,
            mantissa: 0xd4000000_00000000_00000000_00000000_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -128,
            mantissa: 0x8af80000_00000000_00000000_00000000_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -125,
            mantissa: 0xbb288c00_00000000_00000000_00000000_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -121,
            mantissa: 0xd5c609dc_00000000_00000000_00000000_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -116,
            mantissa: 0xb9824979_31000000_00000000_00000000_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -111,
            mantissa: 0xe39d3d66_b4b78000_00000000_00000000_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -105,
            mantissa: 0xbba2c6ec_fe733d8c_00000000_00000000_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -99,
            mantissa: 0xc81d5cd9_3f0c79ba_6b000000_00000000_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -92,
            mantissa: 0x86118ddf_c1ffc100_0ee1b000_00000000_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -86,
            mantissa: 0xdc7ccfa9_930b874d_52df3464_00000000_u128,
        },
    ];

    let x2 = recip * recip;

    let mut p = C[11];
    for i in (0..11).rev() {
        p = x2 * p + C[i];
    }
    p
}
