/*
 * // Copyright (c) Radzivon Bartoshyk 7/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::bessel::j1_coeffs::{J1_ZEROS, J1_ZEROS_VALUE};
use crate::bessel::j1f_coeffs::J1F_COEFFS;
use crate::bessel::trigo_bessel::sin_small;
use crate::double_double::DoubleDouble;
use crate::polyeval::{f_polyeval7, f_polyeval10, f_polyeval12, f_polyeval14};
use crate::rounding::CpuCeil;
use crate::sincos_reduce::rem2pif_any;

/// Bessel of the first kind of order 1
///
/// Max ULP 0.5
///
/// Note about accuracy:
/// - Close to zero Bessel have tiny values such that testing against MPFR must be done exactly
///   in the same precision, since any nearest representable number have ULP > 0.5.
///   For example `J1(0.000000000000000000000000000000000000023509886)` in single precision
///   have an error at least 0.72 ULP for any number with extended precision,
///   that would be represented in f32.
pub fn f_j1f(x: f32) -> f32 {
    let ux = x.to_bits().wrapping_shl(1);
    if ux >= 0xffu32 << 24 || ux == 0 {
        // |x| == 0, |x| == inf, |x| == NaN
        if ux == 0 {
            // |x| == 0
            return x;
        }
        if x.is_infinite() {
            return 0.;
        }
        return x + f32::NAN; // x == NaN
    }

    let ax = x.to_bits() & 0x7fff_ffff;

    if ax < 0x429533c2u32 {
        // |x| <= 74.60109
        if ax < 0x3e800000u32 {
            // |x| <= 0.25
            if ax <= 0x34000000u32 {
                // |x| <= f32::EPSILON
                // taylor series for J1(x) ~ x/2 + O(x^3)
                return x * 0.5;
            }
            return poly_near_zero(x);
        }
        return small_argument_path(x);
    }

    // Exceptional cases:
    if ax == 0x6ef9be45 {
        return if x.is_sign_negative() {
            f32::from_bits(0x187d8a8f)
        } else {
            -f32::from_bits(0x187d8a8f)
        };
    } else if ax == 0x7f0e5a38 {
        return if x.is_sign_negative() {
            -f32::from_bits(0x131f680b)
        } else {
            f32::from_bits(0x131f680b)
        };
    }

    j1f_asympt(x) as f32
}

#[inline]
fn j1f_rsqrt(x: f64) -> f64 {
    (1. / x) * x.sqrt()
}

/*
   Evaluates:
   J1 = sqrt(2/(PI*x)) * beta(x) * cos(x - 3*PI/4 - alpha(x))
   discarding 1*PI/2 using identities gives:
   J1 = sqrt(2/(PI*x)) * beta(x) * sin(x - PI/4 - alpha(x))

   to avoid squashing small (-PI/4 - alpha(x)) into a large x actual expansion is:

   J1 = sqrt(2/(PI*x)) * beta(x) * sin((x mod 2*PI) - PI/4 - alpha(x))
*/
#[inline]
fn j1f_asympt(x: f32) -> f64 {
    static SGN: [f64; 2] = [1., -1.];
    let sign_scale = SGN[x.is_sign_negative() as usize];
    let x = f32::from_bits(x.to_bits() & 0x7fff_ffff);

    let dx = x as f64;

    let alpha = j1f_asympt_alpha(dx);
    let beta = j1f_asympt_beta(dx);

    let angle = rem2pif_any(x);

    const SQRT_2_OVER_PI: f64 = f64::from_bits(0x3fe9884533d43651);
    const MPI_OVER_4: f64 = f64::from_bits(0xbfe921fb54442d18);

    let x0pi34 = MPI_OVER_4 - alpha;
    let r0 = angle + x0pi34;

    let m_sin = sin_small(r0);

    let z0 = beta * m_sin;
    let scale = SQRT_2_OVER_PI * j1f_rsqrt(dx);

    scale * z0 * sign_scale
}

/**
Note expansion generation below: this is negative series expressed in Sage as positive,
so before any real evaluation `x=1/x` should be applied.

Generated by SageMath:
```python
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(1, x, 50)
Q = Qn_asymptotic(1, x, 50)

R_series = (-Q/P)

# alpha is atan(R_series) so we're doing Taylor series atan expansion on R_series

arctan_series_Z = sum([QQ(-1)**k * x**(QQ(2)*k+1) / RealField(700)(RealField(700)(2)*k+1) for k in range(25)])
alpha_series = arctan_series_Z(R_series)

# see the series
print(alpha_series)
```

See notes/bessel_asympt.ipynb for generation
**/
#[inline]
pub(crate) fn j1f_asympt_alpha(x: f64) -> f64 {
    const C: [u64; 12] = [
        0xbfd8000000000000,
        0x3fc5000000000000,
        0xbfd7bccccccccccd,
        0x4002f486db6db6db,
        0xc03e9fbf40000000,
        0x4084997b55945d17,
        0xc0d4a914195269d9,
        0x412cd1b53816aec1,
        0xc18aa4095d419351,
        0x41ef809305f11b9d,
        0xc2572e6809ed618b,
        0x42c4c5b6057839f9,
    ];
    let recip = 1. / x;
    let x2 = recip * recip;
    let p = f_polyeval12(
        x2,
        f64::from_bits(C[0]),
        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]),
        f64::from_bits(C[10]),
        f64::from_bits(C[11]),
    );
    p * recip
}

/**
Note expansion generation below: this is negative series expressed in Sage as positive,
so before any real evaluation `x=1/x` should be applied

Generated by SageMath:
```python
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(1, x, 50)
Q = Qn_asymptotic(1, 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 beta series
print(b_series)
```

See notes/bessel_asympt.ipynb for generation
**/
#[inline]
pub(crate) fn j1f_asympt_beta(x: f64) -> f64 {
    const C: [u64; 10] = [
        0x3ff0000000000000,
        0x3fc8000000000000,
        0xbfc8c00000000000,
        0x3fe9c50000000000,
        0xc01ef5b680000000,
        0x40609860dd400000,
        0xc0abae9b7a06e000,
        0x41008711d41c1428,
        0xc15ab70164c8be6e,
        0x41bc1055e24f297f,
    ];
    let recip = 1. / x;
    let x2 = recip * recip;
    f_polyeval10(
        x2,
        f64::from_bits(C[0]),
        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]),
    )
}

/**
Generated in Sollya:
```python
pretty = proc(u) {
  return ~(floor(u*1000)/1000);
};

bessel_j1 = library("./cmake-build-release/libbessel_sollya.dylib");

f = bessel_j1(x)/x;
d = [0, 0.921];
w = 1;
pf = fpminimax(f, [|0,2,4,6,8,10,12|], [|D...|], d, absolute, floating);

w = 1;
or_f = bessel_j1(x);
pf1 = pf * x;
err_p = -log2(dirtyinfnorm(pf1*w-or_f, d));
print ("relative error:", pretty(err_p));

for i from 0 to degree(pf) by 2 do {
    print("'", coeff(pf, i), "',");
};
```
See ./notes/bessel_sollya/bessel_j1f_at_zero.sollya
**/
#[inline]
fn poly_near_zero(x: f32) -> f32 {
    let dx = x as f64;
    let x2 = dx * dx;
    let p = f_polyeval7(
        x2,
        f64::from_bits(0x3fe0000000000000),
        f64::from_bits(0xbfaffffffffffffc),
        f64::from_bits(0x3f65555555554089),
        f64::from_bits(0xbf0c71c71c2a74ae),
        f64::from_bits(0x3ea6c16bbd1dc5c1),
        f64::from_bits(0xbe384562afb69e7d),
        f64::from_bits(0x3dc248d0d0221cd0),
    );
    (p * dx) as f32
}

/// This method on small range searches for nearest zero or extremum.
/// Then picks stored series expansion at the point end evaluates the poly at the point.
#[inline]
fn small_argument_path(x: f32) -> f32 {
    static SIGN: [f64; 2] = [1., -1.];
    let sign_scale = SIGN[x.is_sign_negative() as usize];
    let x_abs = f32::from_bits(x.to_bits() & 0x7fff_ffff) as f64;

    // let avg_step = 74.60109 / 47.0;
    // let inv_step = 1.0 / avg_step;

    const INV_STEP: f64 = 0.6300176043004198;

    let fx = x_abs * INV_STEP;
    const J1_ZEROS_COUNT: f64 = (J1_ZEROS.len() - 1) as f64;
    let idx0 = unsafe { fx.min(J1_ZEROS_COUNT).to_int_unchecked::<usize>() };
    let idx1 = unsafe {
        fx.cpu_ceil()
            .min(J1_ZEROS_COUNT)
            .to_int_unchecked::<usize>()
    };

    let found_zero0 = DoubleDouble::from_bit_pair(J1_ZEROS[idx0]);
    let found_zero1 = DoubleDouble::from_bit_pair(J1_ZEROS[idx1]);

    let dist0 = (found_zero0.hi - x_abs).abs();
    let dist1 = (found_zero1.hi - x_abs).abs();

    let (found_zero, idx, dist) = if dist0 < dist1 {
        (found_zero0, idx0, dist0)
    } else {
        (found_zero1, idx1, dist1)
    };

    if idx == 0 {
        return poly_near_zero(x);
    }

    // We hit exact zero, value, better to return it directly
    if dist == 0. {
        return (f64::from_bits(J1_ZEROS_VALUE[idx]) * sign_scale) as f32;
    }

    let c = &J1F_COEFFS[idx - 1];

    let r = (x_abs - found_zero.hi) - found_zero.lo;

    let p = f_polyeval14(
        r,
        f64::from_bits(c[0]),
        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]),
        f64::from_bits(c[10]),
        f64::from_bits(c[11]),
        f64::from_bits(c[12]),
        f64::from_bits(c[13]),
    );

    (p * sign_scale) as f32
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_f_j1f() {
        assert_eq!(
            f_j1f(77.743162408196766932633181568235159),
            0.09049267898021947
        );
        assert_eq!(
            f_j1f(-0.000000000000000000000000000000000000008827127),
            -0.0000000000000000000000000000000000000044135635
        );
        assert_eq!(
            f_j1f(0.000000000000000000000000000000000000008827127),
            0.0000000000000000000000000000000000000044135635
        );
        assert_eq!(f_j1f(5.4), -0.3453447907795863);
        assert_eq!(
            f_j1f(84.027189586293545175976760219782591),
            0.0870430264022591
        );
        assert_eq!(f_j1f(f32::INFINITY), 0.);
        assert_eq!(f_j1f(f32::NEG_INFINITY), 0.);
        assert!(f_j1f(f32::NAN).is_nan());
        assert_eq!(f_j1f(-1.7014118e38), 0.000000000000000000006856925);
    }
}
