//! [Criterion]'s statistics library.
//!
//! [Criterion]: https://github.com/criterion-rs/criterion.rs
//!
//! **WARNING** This library is criterion's implementation detail and there no plans to stabilize
//! it. In other words, the API may break at any time without notice.

#[cfg(test)]
mod test;

pub mod bivariate;
pub mod tuple;
pub mod univariate;

mod float;
mod rand_util;

use std::{mem, ops::Deref};

use crate::stats::{float::Float, univariate::Sample};

/// The bootstrap distribution of some parameter
#[derive(Clone)]
pub struct Distribution<A>(Box<[A]>);

impl<A> Distribution<A>
where
    A: Float,
{
    /// Create a distribution from the given values
    pub fn from(values: Box<[A]>) -> Distribution<A> {
        Distribution(values)
    }

    /// Computes the confidence interval of the population parameter using percentiles
    ///
    /// # Panics
    ///
    /// Panics if the `confidence_level` is not in the `(0, 1)` range.
    pub fn confidence_interval(&self, confidence_level: A) -> (A, A)
    where
        usize: cast::From<A, Output = Result<usize, cast::Error>>,
    {
        let _0 = A::cast(0);
        let _1 = A::cast(1);
        let _50 = A::cast(50);

        assert!(confidence_level > _0 && confidence_level < _1);

        let percentiles = self.percentiles();

        // FIXME(privacy) this should use the `at_unchecked()` method
        (
            percentiles.at(_50 * (_1 - confidence_level)),
            percentiles.at(_50 * (_1 + confidence_level)),
        )
    }

    /// Computes the "likelihood" of seeing the value `t` or "more extreme" values in the
    /// distribution.
    pub fn p_value(&self, t: A, tails: &Tails) -> A {
        use std::cmp;

        let n = self.0.len();
        let hits = self.0.iter().filter(|&&x| x < t).count();

        let tails = A::cast(match *tails {
            Tails::One => 1,
            Tails::Two => 2,
        });

        A::cast(cmp::min(hits, n - hits)) / A::cast(n) * tails
    }
}

impl<A> Deref for Distribution<A> {
    type Target = Sample<A>;

    fn deref(&self) -> &Sample<A> {
        let slice: &[_] = &self.0;

        unsafe { mem::transmute(slice) }
    }
}

/// Number of tails for significance testing
pub enum Tails {
    /// One tailed test
    One,
    /// Two tailed test
    Two,
}

fn dot<A>(xs: &[A], ys: &[A]) -> A
where
    A: Float,
{
    xs.iter()
        .zip(ys)
        .fold(A::cast(0), |acc, (&x, &y)| acc + x * y)
}

fn sum<A>(xs: &[A]) -> A
where
    A: Float,
{
    use std::ops::Add;

    xs.iter().cloned().fold(A::cast(0), Add::add)
}
