//! # Weibull
//!
//! $$
//! f(x)=\frac{k}{\lambda}\left(\frac{x}{\lambda}\right)^{k-1}e^{-(x/\lambda)^k},\ x\ge0
//! $$
//!
use std::cell::UnsafeCell;

use rand::Rng;
use rand_distr::Distribution;

use super::SimdFloatExt;
use super::exp::SimdExpZig;
use crate::simd_rng::SimdRng;

pub struct SimdWeibull<T: SimdFloatExt> {
  lambda: T,
  inv_k: T,
  exp1: SimdExpZig<T>,
  simd_rng: UnsafeCell<SimdRng>,
}

impl<T: SimdFloatExt> SimdWeibull<T> {
  #[inline]
  pub fn new(lambda: T, k: T) -> Self {
    Self::from_seed_source(lambda, k, &mut crate::simd_rng::Unseeded)
  }

  /// Creates a Weibull distribution with a deterministic seed.
  #[inline]
  pub fn with_seed(lambda: T, k: T, seed: u64) -> Self {
    Self::from_seed_source(lambda, k, &mut crate::simd_rng::Deterministic(seed))
  }

  /// Creates a Weibull distribution with RNGs from a [`SeedExt`](crate::simd_rng::SeedExt) source.
  /// Each sub-component (exp, main rng) gets an independent stream.
  pub(crate) fn from_seed_source(lambda: T, k: T, seed: &mut impl crate::simd_rng::SeedExt) -> Self {
    assert!(lambda > T::zero() && k > T::zero());
    Self {
      lambda,
      inv_k: T::one() / k,
      exp1: SimdExpZig::from_seed_source(T::one(), seed),
      simd_rng: UnsafeCell::new(seed.rng()),
    }
  }

  /// Returns a single sample using the internal SIMD RNG.
  #[inline]
  pub fn sample_fast(&self) -> T {
    let rng = unsafe { &mut *self.simd_rng.get() };
    let u = T::sample_uniform_simd(rng).max(T::min_positive_val());
    self.lambda * (-u.ln()).powf(self.inv_k)
  }

  pub fn fill_slice<R: Rng + ?Sized>(&self, _rng: &mut R, out: &mut [T]) {
    let rng = unsafe { &mut *self.simd_rng.get() };
    self.exp1.fill_slice(rng, out);
    let lambda = T::splat(self.lambda);
    let inv_k = self.inv_k;
    let mut chunks = out.chunks_exact_mut(8);
    for chunk in &mut chunks {
      let mut tmp = [T::zero(); 8];
      tmp.copy_from_slice(chunk);
      let x = T::simd_from_array(tmp);
      let y = lambda * T::simd_powf(x, inv_k);
      chunk.copy_from_slice(&T::simd_to_array(y));
    }
    for x in chunks.into_remainder().iter_mut() {
      *x = self.lambda * (*x).powf(inv_k);
    }
  }
}

impl<T: SimdFloatExt> Clone for SimdWeibull<T> {
  fn clone(&self) -> Self {
    Self::new(self.lambda, T::one() / self.inv_k)
  }
}

impl<T: SimdFloatExt> Distribution<T> for SimdWeibull<T> {
  #[inline(always)]
  fn sample<R: Rng + ?Sized>(&self, _rng: &mut R) -> T {
    let rng = unsafe { &mut *self.simd_rng.get() };
    let u = T::sample_uniform_simd(rng).max(T::min_positive_val());
    self.lambda * (-u.ln()).powf(self.inv_k)
  }
}

py_distribution!(PyWeibull, SimdWeibull,
  sig: (lambda_, k, dtype=None),
  params: (lambda_: f64, k: f64)
);
