extern crate libc;

#[allow(dead_code)]
mod cmath {
    use libc::{c_double, c_int};

    #[link_name = "m"]
    extern "C" {
        pub fn frexp(n: c_double, value: &mut c_int) -> c_double;
        pub fn ldexp(x: c_double, n: c_int) -> c_double;
        pub fn modf(x: c_double, iptr: &mut c_double) -> c_double;
        pub fn ilogb(n: c_double) -> c_int;
        pub fn logb(n: c_double) -> c_double;
        pub fn scalbn(x: c_double, n: c_int) -> c_double;

        pub fn erf(n: c_double) -> c_double;
        pub fn erfc(n: c_double) -> c_double;
        pub fn tgamma(n: c_double) -> c_double;
        pub fn lgamma(n: c_double) -> c_double;

        pub fn fmod(a: c_double, b: c_double) -> c_double;
        pub fn lround(x: c_double) -> c_int;
        // need longlong: llround
        // need fenv.h: rint, lrint, llrint, nearbyint
        pub fn remainder(a: c_double, b: c_double) -> c_double;
        pub fn remquo(x: c_double, y: c_double, quot: &mut c_int) -> c_double;

        pub fn copysign(x: c_double, y: c_double) -> c_double;
        pub fn nextafter(x: c_double, y: c_double) -> c_double;
        // need long_double: nexttoward

        pub fn fdim(a: c_double, b: c_double) -> c_double;
        pub fn fma(x: c_double, y: c_double, z: c_double) -> c_double;
    }
}

pub mod f64 {
    use libc::c_int;
    use super::cmath;

    /// frexp is used to split the number x into a normalized fraction and an exponent
    /// which is stored in exp.
    pub fn frexp(x: f64) -> (f64, isize) {
        let mut n: c_int = 0;
        let f = unsafe { cmath::frexp(x, &mut n) };
        (f, n as isize)
    }
    /// ldexp returns the result of multiplying the floating-point number x by 2 raised to
    /// the power exp.
    pub fn ldexp(x: f64, exp: isize) -> f64 {
        unsafe { cmath::ldexp(x, exp as i32) }
    }
    /// modf breaks the argument x into an integral part and a fractional part, each
    /// of which has the same sign as x.
    pub fn modf(x: f64) -> (f64, f64) {
        let mut i: f64 = 0.;
        let f = unsafe { cmath::modf(x, &mut i) };
        (i, f)
    }
    /// iligb returns the exponent part of their argument as a signed integer.
    pub fn ilogb(n: f64) -> isize {
        (unsafe { cmath::ilogb(n) }) as isize
    }
    /// logb extracts the exponent from the internal floating-point
    /// representation of x and return it as a floating-point value.
    pub fn logb(x: f64) -> f64 {
        unsafe { cmath::logb(x) }
    }
    /// scalbn multiplies their first argument x by FLT_RADIX (probably 2) to the power of
    /// exp, that is:
    /// ```text
    /// x * FLT_RADIX ** exp
    /// ```
    pub fn scalbn(x: f64, exp: isize) -> f64 {
        unsafe { cmath::scalbn(x, exp as c_int) }
    }

    /// erf returns the error function of x, defined as
    /// ```text
    /// erf(x) = 2/sqrt(pi)* integral from 0 to x of exp(-t*t) dt
    /// ```
    pub fn erf(x: f64) -> f64 {
        unsafe { cmath::erf(x) }
    }
    /// erfc returns the complementary error function of x, that is, 1.0 - erf(x).
    pub fn erfc(x: f64) -> f64 {
        unsafe { cmath::erfc(x) }
    }

    /// tgamma calculates the Gamma function of x.
    ///
    /// The Gamma function is defined by
    ///
    /// ```text
    /// Gamma(x) = integral from 0 to infinity of t^(x-1) e^-t dt
    /// ```
    /// It is defined for every real number except for nonpositive integers.  For nonnegative
    /// integral m one has
    ///
    /// ```text
    /// Gamma(m+1) = m!
    /// ```
    /// and, more generally, for all x:
    ///
    /// ```text
    /// Gamma(x+1) = x * Gamma(x)
    /// ```
    ///
    /// Furthermore, the following is valid for all values of x outside the poles:
    ///
    /// ```text
    /// Gamma(x) * Gamma(1 - x) = PI / sin(PI * x)
    /// ```
    pub fn tgamma(x: f64) -> f64 {
        unsafe { cmath::tgamma(x) }
    }
    /// lgamma returns the natural logarithm of the absolute value of the Gamma function.
    pub fn lgamma(x: f64) -> f64 {
        unsafe { cmath::lgamma(x) }
    }

    /// fmod computes the floating-point remainder of dividing x by y.  The return
    /// value is x - n * y, where n is the quotient of x / y, rounded toward zero to an integer.
    pub fn fmod(x: f64, y: f64) -> f64 {
        unsafe { cmath::fmod(x, y) }
    }
    /// lround rounds their argument to the nearest integer value, rounding away from zero.
    pub fn lround(x: f64) -> isize {
        (unsafe { cmath::lround(x) }) as isize
    }
    /// remainder compute the remainder of dividing x by y. The return value is x-n*y, where
    /// n is the value x / y, rounded to the nearest integer. If the absolute value of x-n*y is
    /// 0.5, n is chosen to be even.
    pub fn remainder(x: f64, y: f64) -> f64 {
        unsafe { cmath::remainder(x, y) }
    }
    /// remquo computes the remainder and part of the quotient upon division of x by y.
    pub fn remquo(x: f64, y: f64) -> (isize, f64) {
        let mut quot: c_int = 0;
        let rem = unsafe { cmath::remquo(x, y, &mut quot) };
        (quot as isize, rem)
    }

    /// copysign returns a value whose absolute value matches that of x, but whose sign bit
    /// matches that of y.
    pub fn copysign(x: f64, y: f64) -> f64 {
        unsafe { cmath::copysign(x, y) }
    }
    /// nextafter returns the next representable floating-point value following x in the
    /// direction of y. If y is less than x, these functions will return the largest representable
    /// number less than x.
    pub fn nextafter(x: f64, y: f64) -> f64 {
        unsafe { cmath::nextafter(x, y) }
    }

    /// fdim returns the positive difference, max(x-y,0), between their arguments.
    pub fn fdim(x: f64, y: f64) -> f64 {
        unsafe { cmath::fdim(x, y) }
    }
    /// fma computes x * y + z. The result is rounded as one ternary operation
    /// according to the current rounding mode.
    pub fn fma(x: f64, y: f64, z: f64) -> f64 {
        unsafe { cmath::fma(x, y, z) }
    }
}

#[cfg(test)]
mod tests {
    use super::f64::*;

    #[test]
    fn smoke_test() {
        frexp(0.);
        ldexp(0., 0);
        modf(0.);
        ilogb(0.);
        logb(0.);
        scalbn(0., 0);

        erf(0.);
        erfc(0.);
        tgamma(0.);
        lgamma(0.);
        lround(0.);
        remainder(0., 0.);
        remquo(0., 0.);

        copysign(0., 0.);
        nextafter(0., 0.);

        fdim(0., 0.);
        fma(0., 0., 0.);
    }
}
