use once_cell::sync::Lazy;
use rand::{distributions::Alphanumeric, Rng};
use rand_seeder::Seeder;
use std::{cell::RefCell, io::Write, path::Path};

// This RNG is used in the standard rand crate.
// See https://github.com/rust-random/rand/issues/932 for a discussion about it.
// We use it because it is Serializable.
pub type TestRng = rand_chacha::ChaCha12Rng;

static SEED: Lazy<String> = Lazy::new(|| {
    if let Ok(seed) = std::env::var("TEST_RNG_SEED") {
        println!("EnvVar test seed is \"{seed}\".");
        seed
    } else {
        let mut rng = rand::thread_rng();
        let seed: String = (&mut rng)
            .sample_iter(Alphanumeric)
            .take(7)
            .map(char::from)
            .collect();
        println!("Generated test seed is \"{seed}\".");
        seed
    }
});
static RNG: Lazy<TestRng> = Lazy::new(|| {
    let seed = SEED.clone();
    Seeder::from(seed).make_rng()
});

thread_local! {
    /// Indicates whether the RNG was already used.
    /// Each test uses its own thread, so this will be false at the start of each test.
    static RNG_ALREADY_USED: RefCell<bool> = const { RefCell::new(false) };
}

/// Returns a seeded RNG, usable for simulations.
/// This function should only be called once per test.
pub fn get() -> TestRng {
    if RNG_ALREADY_USED.with_borrow(|b| *b) {
        panic!("RNG should only be got once per test.");
    }
    RNG_ALREADY_USED.with_borrow_mut(|b| *b = true);
    println!("The seed is {}.", SEED.as_str());
    RNG.clone()
}

pub fn save_to_file(rng: &TestRng, path: impl AsRef<Path>) {
    let data = bincode::serialize(rng).unwrap();
    let mut file = std::fs::File::create(path).unwrap();
    file.write_all(&data).unwrap();
}
pub fn load_from_file(path: impl AsRef<Path>) -> TestRng {
    let data = std::fs::read(path).unwrap();
    bincode::deserialize(&data).unwrap()
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    #[should_panic]
    fn cannot_be_called_twice() {
        get();
        get();
    }
}
