use malachite_base::num::arithmetic::traits::{
    CeilingLogBase2, CheckedLogBase2, FloorLogBase2, IsPowerOf2, PowerOf2,
};
use malachite_base::num::basic::traits::Zero;
use malachite_base::num::conversion::traits::ExactFrom;
use malachite_nz::test_util::generators::natural_gen_var_2;
use malachite_q::test_util::generators::rational_gen_var_2;
use malachite_q::Rational;
use std::str::FromStr;

#[test]
fn test_floor_log_base_2() {
    let test = |n, out| {
        assert_eq!(Rational::from_str(n).unwrap().floor_log_base_2(), out);
    };
    test("1", 0);
    test("100", 6);
    test("1000000000000", 39);
    test("4294967295", 31);
    test("4294967296", 32);
    test("4294967297", 32);
    test("22/7", 1);
    test("936851431250/1397", 29);
    test("1/1000000000000", -40);
    test("1/4294967295", -32);
    test("1/4294967296", -32);
    test("1/4294967297", -33);
    test("1/2", -1);
    test("1/3", -2);
    test("1/4", -2);
    test("1/5", -3);
    test("1/6", -3);
    test("1/7", -3);
    test("1/8", -3);
    test("1/9", -4);
}

#[test]
#[should_panic]
fn floor_log_base_2_fail() {
    Rational::ZERO.floor_log_base_2();
}

#[test]
fn test_ceiling_log_base_2() {
    let test = |n, out| {
        assert_eq!(Rational::from_str(n).unwrap().ceiling_log_base_2(), out);
    };
    test("1", 0);
    test("100", 7);
    test("1000000000000", 40);
    test("4294967295", 32);
    test("4294967296", 32);
    test("4294967297", 33);
    test("22/7", 2);
    test("936851431250/1397", 30);
    test("1/1000000000000", -39);
    test("1/4294967295", -31);
    test("1/4294967296", -32);
    test("1/4294967297", -32);
    test("1/2", -1);
    test("1/3", -1);
    test("1/4", -2);
    test("1/5", -2);
    test("1/6", -2);
    test("1/7", -2);
    test("1/8", -3);
    test("1/9", -3);
}

#[test]
#[should_panic]
fn ceiling_log_base_2_fail() {
    Rational::ZERO.ceiling_log_base_2();
}

#[test]
fn test_checked_log_base_2() {
    let test = |n, out| {
        assert_eq!(Rational::from_str(n).unwrap().checked_log_base_2(), out);
    };
    test("1", Some(0));
    test("100", None);
    test("1000000000000", None);
    test("4294967295", None);
    test("4294967296", Some(32));
    test("4294967297", None);
    test("22/7", None);
    test("936851431250/1397", None);
    test("1/1000000000000", None);
    test("1/4294967295", None);
    test("1/4294967296", Some(-32));
    test("1/4294967297", None);
    test("1/2", Some(-1));
    test("1/3", None);
    test("1/4", Some(-2));
    test("1/5", None);
    test("1/6", None);
    test("1/7", None);
    test("1/8", Some(-3));
    test("1/9", None);
}

#[test]
#[should_panic]
fn checked_log_base_2_fail() {
    Rational::ZERO.checked_log_base_2();
}

#[test]
fn floor_log_base_2_properties() {
    rational_gen_var_2().test_properties(|x| {
        let floor_log_base_2 = x.floor_log_base_2();
        assert!(Rational::power_of_2(floor_log_base_2) <= x);
        assert!(x < Rational::power_of_2(floor_log_base_2 + 1));
    });

    natural_gen_var_2().test_properties(|n| {
        assert_eq!(
            i64::exact_from((&n).floor_log_base_2()),
            Rational::from(n).floor_log_base_2()
        );
    });
}

#[test]
fn ceiling_log_base_2_properties() {
    rational_gen_var_2().test_properties(|x| {
        let ceiling_log_base_2 = x.ceiling_log_base_2();
        assert!(Rational::power_of_2(ceiling_log_base_2 - 1) < x);
        assert!(x <= Rational::power_of_2(ceiling_log_base_2));
    });

    natural_gen_var_2().test_properties(|n| {
        assert_eq!(
            i64::exact_from((&n).ceiling_log_base_2()),
            Rational::from(n).ceiling_log_base_2()
        );
    });
}

#[test]
fn checked_log_base_2_properties() {
    rational_gen_var_2().test_properties(|x| {
        let checked_log_base_2 = x.checked_log_base_2();
        assert_eq!(checked_log_base_2.is_some(), x.is_power_of_2());
        if let Some(log_base_2) = checked_log_base_2 {
            assert_eq!(x.floor_log_base_2(), log_base_2);
            assert_eq!(x.ceiling_log_base_2(), log_base_2);
            assert_eq!(Rational::power_of_2(log_base_2), x);
        }
    });

    natural_gen_var_2().test_properties(|n| {
        assert_eq!(
            (&n).checked_log_base_2().map(i64::exact_from),
            Rational::from(n).checked_log_base_2()
        );
    });
}
