use super::platform;
use super::raw_sys;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Mwc64 {
    x: u64,
    c: u64,
    t: u64,
    value: u64,
    index: u8,
}

impl Mwc64 {
    #[inline(always)]
    pub const fn new(seed1: u64, seed2: u64) -> Self {
        Self {
            x: (seed1 << 32).wrapping_add(seed2),
            c: 123_456_123_456_123_456,
            t: 0,
            value: 0,
            index: 2,
        }
    }

    #[inline(always)]
    pub fn from_os_seed() -> raw_sys::Result<Self> {
        let mut buf = [0u8; 16];
        let mut filled = 0usize;
        while filled < buf.len() {
            let read = platform::getrandom(&mut buf[filled..], 0)?;
            if read == 0 {
                return Err(raw_sys::Error(5));
            }
            filled += read;
        }

        let seed1 = u64::from_ne_bytes(buf[0..8].try_into().unwrap());
        let seed2 = u64::from_ne_bytes(buf[8..16].try_into().unwrap());
        Ok(Self::new(seed1.max(1), seed2.max(1)))
    }

    #[inline(always)]
    fn next_block(&mut self) -> u64 {
        self.t = (self.x << 58).wrapping_add(self.c);
        self.c = self.x >> 6;
        self.x = self.x.wrapping_add(self.t);
        self.c = self.c.wrapping_add((self.x < self.t) as u64);
        self.x
    }

    #[inline(always)]
    pub fn next_u32(&mut self) -> u32 {
        if self.index == 2 {
            self.value = self.next_block();
            self.index = 0;
        }

        let shift = (self.index as u32) * 32;
        let value = (self.value >> shift) as u32;
        self.index += 1;
        value
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Mwc {
    inner: Mwc64,
}

impl Mwc {
    #[inline(always)]
    pub const fn new(seed1: u64, seed2: u64) -> Self {
        Self {
            inner: Mwc64::new(seed1, seed2),
        }
    }

    #[inline(always)]
    pub fn from_os_seed() -> raw_sys::Result<Self> {
        Ok(Self {
            inner: Mwc64::from_os_seed()?,
        })
    }

    #[inline(always)]
    pub fn next_u32(&mut self) -> u32 {
        self.inner.next_u32()
    }

    #[inline(always)]
    pub fn in_range(&mut self, min: usize, max: usize) -> usize {
        debug_assert!(min <= max);
        let range = 1 + max - min;
        min + ((((self.next_u32() as u64) * (range as u64)) >> 32) as usize)
    }
}
