use malachite_base::num::arithmetic::traits::{Reciprocal, RoundToMultiple};
use malachite_base::num::basic::traits::{One, Zero};
use malachite_base::num::comparison::traits::PartialOrdAbs;
use malachite_base::num::conversion::traits::RoundingFrom;
use malachite_base::rounding_modes::RoundingMode;
use malachite_nz::integer::Integer;
use malachite_nz::natural::Natural;
use malachite_q::arithmetic::traits::{Approximate, ApproximateAssign};
use malachite_q::test_util::arithmetic::approximate::approximate_naive;
use malachite_q::test_util::generators::{
    rational_gen, rational_natural_natural_triple_gen_var_1, rational_natural_pair_gen_var_3,
    rational_natural_pair_gen_var_4,
};
use malachite_q::Rational;
use std::str::FromStr;

#[test]
fn test_approximate() {
    let test = |x, d, out| {
        let mut x = Rational::from_str(x).unwrap();
        let d = Natural::from_str(d).unwrap();
        let a = x.clone().approximate(&d);
        assert!(a.is_valid());
        assert_eq!(a.to_string(), out);
        let a = (&x).approximate(&d);
        assert!(a.is_valid());
        assert_eq!(a.to_string(), out);

        x.approximate_assign(&d);
        assert!(x.is_valid());
        assert_eq!(x.to_string(), out);
    };
    test("3", "1", "3");
    test("3", "10", "3");
    test("27/32", "1", "1");
    test("27/32", "3", "1");
    test("27/32", "4", "3/4");
    test("27/32", "5", "4/5");
    test("27/32", "6", "5/6");
    test("27/32", "12", "5/6");
    test("27/32", "13", "11/13");
    test("27/32", "18", "11/13");
    test("27/32", "19", "16/19");
    test("27/32", "31", "16/19");
    test("27/32", "32", "27/32");
    test("27/32", "100", "27/32");

    test("6369051672525773/4503599627370496", "10", "7/5");
    test("6369051672525773/4503599627370496", "100", "140/99");
    test("6369051672525773/4503599627370496", "1000", "1393/985");
    test(
        "6369051672525773/4503599627370496",
        "1000000",
        "665857/470832",
    );

    test("884279719003555/281474976710656", "10", "22/7");
    test("884279719003555/281474976710656", "100", "311/99");
    test("884279719003555/281474976710656", "1000", "355/113");
    test(
        "884279719003555/281474976710656",
        "1000000",
        "3126535/995207",
    );

    test("6121026514868073/2251799813685248", "10", "19/7");
    test("6121026514868073/2251799813685248", "100", "193/71");
    test("6121026514868073/2251799813685248", "1000", "1457/536");
    test(
        "6121026514868073/2251799813685248",
        "1000000",
        "1084483/398959",
    );

    test("7/9", "2", "1");
    test("-7/9", "2", "-1");
    test("1/2", "1", "0");
    test("-1/2", "1", "0");
    test("3/2", "1", "2");
    test("-3/2", "1", "-2");
    test("1/4", "2", "0");
    test("-1/4", "2", "0");
}

#[test]
#[should_panic]
fn approximate_assign_fail() {
    let mut x = Rational::ONE;
    x.approximate_assign(&Natural::ZERO);
}

#[test]
#[should_panic]
fn approximate_fail() {
    Rational::ONE.approximate(&Natural::ZERO);
}

#[test]
#[should_panic]
fn approximate_ref_fail() {
    (&Rational::ONE).approximate(&Natural::ZERO);
}

#[test]
fn approximate_properties() {
    rational_natural_pair_gen_var_3().test_properties(|(x, max_denominator)| {
        let a = x.clone().approximate(&max_denominator);
        assert!(a.is_valid());

        let a_alt = (&x).approximate(&max_denominator);
        assert!(a_alt.is_valid());
        assert_eq!(a, a_alt);

        let mut a_alt = x.clone();
        a_alt.approximate_assign(&max_denominator);
        assert!(a_alt.is_valid());
        assert_eq!(a_alt, a);

        assert_eq!(
            (&x).round_to_multiple(
                Rational::from(a.denominator_ref()).reciprocal(),
                RoundingMode::Nearest
            ),
            a
        );
        assert_eq!((-x).approximate(&max_denominator), -a);
    });

    rational_natural_pair_gen_var_4().test_properties(|(x, max_denominator)| {
        assert_eq!(
            (&x).approximate(&max_denominator),
            approximate_naive(&x, &max_denominator)
        );
    });

    rational_natural_natural_triple_gen_var_1().test_properties(|(x, d, max_d)| {
        let a = (&x).approximate(&max_d);
        let a_alt = (&x).round_to_multiple(Rational::from(&d).reciprocal(), RoundingMode::Nearest);
        assert!((&x - a_alt).ge_abs(&(&x - &a)));

        let a_worse = x.approximate(&d);
        assert!(a_worse.denominator_ref() <= a.denominator_ref());
    });

    rational_gen().test_properties(|x| {
        assert_eq!((&x).approximate(x.denominator_ref()), x);
        assert_eq!(
            (&x).approximate(&Natural::ONE),
            Integer::rounding_from(x, RoundingMode::Nearest)
        );
    });
}
