/// Port of Java's `java.util.Random` linear congruential generator.
///
/// Produces the same sequence as `new java.util.Random(seed)` in Java/Spark.
/// Used by `histogram_numeric` for deterministic tie-breaking when merging bins.
#[derive(Debug, Clone)]
pub struct JavaRandom {
    state: u64,
}

impl JavaRandom {
    /// Creates a new RNG matching `new java.util.Random(seed)`.
    pub fn new(seed: u64) -> Self {
        let initial = (seed ^ 0x5DEECE66D) & ((1u64 << 48) - 1);
        Self { state: initial }
    }

    /// Returns the next random `f64` in [0.0, 1.0), matching `Random.nextDouble()`.
    pub fn next_f64(&mut self) -> f64 {
        let bits = self.next_bits(26) as u64;
        let frac = self.next_bits(27) as u64;
        (bits * (1u64 << 27) + frac) as f64 / ((1u64 << 53) as f64)
    }

    fn next_bits(&mut self, bits: u32) -> i32 {
        self.state = self.state.wrapping_mul(0x5DEECE66D).wrapping_add(0xB) & ((1u64 << 48) - 1);
        (self.state >> (48 - bits)) as i32
    }
}

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

    // Reference values were generated by running the following Java program:
    //
    // ```java
    // import java.util.Random;
    //
    // public class JavaRandomRef {
    //     public static void main(String[] args) {
    //         long[] seeds = {31183, 0, 12345};
    //         for (long seed : seeds) {
    //             System.out.println("Seed: " + seed);
    //             Random rng = new Random(seed);
    //             for (int i = 0; i < 10; i++) {
    //                 System.out.printf("  %.20f%n", rng.nextDouble());
    //             }
    //         }
    //     }
    // }
    // ```

    /// Expected values from `new java.util.Random(31183)` — the seed used by NumericHistogram.
    const JAVA_RANDOM_SEED_31183: [f64; 10] = [
        0.10748527202486546,
        0.893630159548549,
        0.1375869077362396,
        0.8115751632634917,
        0.9246709013541556,
        0.27791639012635216,
        0.8238514900074336,
        0.8821874826735107,
        0.46783564785789034,
        0.4220303659572687,
    ];

    /// Expected values from `new java.util.Random(0)`.
    const JAVA_RANDOM_SEED_0: [f64; 10] = [
        0.730967787376657,
        0.24053641567148587,
        0.6374174253501083,
        0.5504370051176339,
        0.5975452777972018,
        0.3332183994766498,
        0.3851891847407185,
        0.984841540199809,
        0.8791825178724801,
        0.9412491794821144,
    ];

    /// Expected values from `new java.util.Random(12345)`.
    const JAVA_RANDOM_SEED_12345: [f64; 10] = [
        0.3618031071604718,
        0.932993485288541,
        0.8330913489710237,
        0.32647575623792624,
        0.2355237906476252,
        0.34911535662488336,
        0.4480776326931518,
        0.6381529437838686,
        0.1582665432952023,
        0.768888060192009,
    ];

    #[test]
    fn test_java_random_seed_31183() {
        let mut rng = JavaRandom::new(31183);
        for expected in JAVA_RANDOM_SEED_31183 {
            let actual = rng.next_f64();
            assert!(
                (actual - expected).abs() < 1e-15,
                "Expected {}, got {}",
                expected,
                actual
            );
        }
    }

    #[test]
    fn test_java_random_seed_0() {
        let mut rng = JavaRandom::new(0);
        for expected in JAVA_RANDOM_SEED_0 {
            let actual = rng.next_f64();
            assert!(
                (actual - expected).abs() < 1e-15,
                "Expected {}, got {}",
                expected,
                actual
            );
        }
    }

    #[test]
    fn test_java_random_seed_12345() {
        let mut rng = JavaRandom::new(12345);
        for expected in JAVA_RANDOM_SEED_12345 {
            let actual = rng.next_f64();
            assert!(
                (actual - expected).abs() < 1e-15,
                "Expected {}, got {}",
                expected,
                actual
            );
        }
    }
}
