use self::fluid::core::prelude::*;
use crate as fluid;
use num_traits::Float;
use std::ops::Not;

/// Used to check equality between two float numbers, with a specific precision.
#[derive(Debug, Drop)]
pub struct Precision<L: Debug>
where
    L: Float,
{
    should: ShouldImpl<L>,
    right: L,
    precision: L,
}

impl<L: Debug> AssertionImpl for Precision<L>
where
    L: Float,
{
    type Left = L;

    fn failure_message(&mut self) -> Option<String> {
        let should: &mut ShouldImpl<Self::Left> = (&mut self.should).into();
        let right_dbg = self.right.dbg();
        let left_dbg = should.left_dbg();
        let precision = self.precision.dbg();
        let truthness = should.truthness();
        let left = *(should.left.as_ref()?);

        if ((left - self.right).abs() < self.precision) != should.truthness {
            let message = if let Some(stringified) = should.stringified() {
                format!(
                    "\t{} has the value {}\n\
                     \tbut it should{} be equal to {} within the range {}.",
                    stringified,
                    left_dbg,
                    truthness.str(),
                    right_dbg,
                    precision
                )
            } else {
                format!(
                    "\t{} is{} equal to {} within the range {}.",
                    left_dbg,
                    truthness.not().str(),
                    right_dbg,
                    precision
                )
            };
            Some(message)
        } else {
            None
        }
    }

    fn consume_as_should(mut self) -> ShouldImpl<Self::Left> {
        self.should.take()
    }

    fn should_mut(&mut self) -> &mut ShouldImpl<Self::Left> {
        &mut self.should
    }
}

impl<T> ChainableAssert<super::BeEqualTo<T, T>>
where
    T: Debug + Float,
{
    /// Modifier for an equality assertion:
    /// checks if a float equality pass within the given precision.
    ///
    /// # Example
    ///
    /// ```rust
    /// # use fluid::prelude::*;
    /// (1.).should().be_equal_to(1.01).with_precision(0.1);
    /// ```
    pub fn with_precision(self, precision: T) -> ChainableAssert<Precision<T>> {
        let mut assert = self.0;
        let should = assert.should.take();
        let implem = Precision {
            right: assert.right,
            should,
            precision,
        };
        ChainableAssert(implem)
    }
}

#[cfg(test)]
mod tests {

    use crate::prelude::*;

    #[fact]
    #[should_panic]
    fn failed_precision() {
        (1.).should().be_equal_to(1.01).with_precision(0.001);
    }

}
