use malachite_base::num::arithmetic::traits::{
    CeilingLogBase, CeilingLogBase2, CeilingLogBasePowerOf2, CheckedLogBase, CheckedLogBase2,
    CheckedLogBasePowerOf2, DivisibleBy, FloorLogBase, FloorLogBase2, FloorLogBasePowerOf2,
    IsPowerOf2, PowerOf2, Reciprocal,
};
use malachite_base::num::basic::traits::{NegativeOne, One, Zero};
use malachite_base::num::conversion::traits::ExactFrom;
use malachite_base::test_util::generators::signed_gen_var_12;
use malachite_nz::test_util::generators::natural_unsigned_pair_gen_var_8;
use malachite_q::test_util::generators::{rational_gen_var_2, rational_signed_pair_gen_var_5};
use malachite_q::Rational;
use std::str::FromStr;

#[test]
fn test_floor_log_base_power_of_2() {
    let test = |n, pow, out| {
        assert_eq!(
            Rational::from_str(n)
                .unwrap()
                .floor_log_base_power_of_2(pow),
            out
        );
    };
    test("1", 1, 0);
    test("1", 2, 0);
    test("1", 5, 0);
    test("100", 1, 6);
    test("100", 2, 3);
    test("100", 5, 1);
    test("1000000000000", 1, 39);
    test("1000000000000", 2, 19);
    test("1000000000000", 5, 7);
    test("1000000000000", 5, 7);
    test("1", -1, 0);
    test("1", -2, 0);
    test("1", -5, 0);
    test("100", -1, -7);
    test("100", -2, -4);
    test("100", -5, -2);
    test("1000000000000", -1, -40);
    test("1000000000000", -2, -20);
    test("1000000000000", -5, -8);
    test("1000000000000", -5, -8);
    test("1/2", 1, -1);
    test("1/3", 1, -2);
    test("1/4", 1, -2);
    test("1/5", 1, -3);
    test("1/6", 1, -3);
    test("1/7", 1, -3);
    test("1/8", 1, -3);
    test("1/9", 1, -4);
    test("1/2", 2, -1);
    test("1/3", 2, -1);
    test("1/4", 2, -1);
    test("1/5", 2, -2);
    test("1/6", 2, -2);
    test("1/7", 2, -2);
    test("1/8", 2, -2);
    test("1/9", 2, -2);
    test("1/2", -1, 1);
    test("1/3", -1, 1);
    test("1/4", -1, 2);
    test("1/5", -1, 2);
    test("1/6", -1, 2);
    test("1/7", -1, 2);
    test("1/8", -1, 3);
    test("1/9", -1, 3);
    test("1/2", -2, 0);
    test("1/3", -2, 0);
    test("1/4", -2, 1);
    test("1/5", -2, 1);
    test("1/6", -2, 1);
    test("1/7", -2, 1);
    test("1/8", -2, 1);
    test("1/9", -2, 1);
}

#[test]
#[should_panic]
fn floor_log_base_power_of_2_fail_1() {
    Rational::ZERO.floor_log_base_power_of_2(1);
}

#[test]
#[should_panic]
fn floor_log_base_power_of_2_fail_2() {
    Rational::ONE.floor_log_base_power_of_2(0);
}

#[test]
#[should_panic]
fn floor_log_base_power_of_2_fail_3() {
    Rational::NEGATIVE_ONE.floor_log_base_power_of_2(1);
}

#[test]
fn test_ceiling_log_base_power_of_2() {
    let test = |n, pow, out| {
        assert_eq!(
            Rational::from_str(n)
                .unwrap()
                .ceiling_log_base_power_of_2(pow),
            out
        );
    };
    test("1", 1, 0);
    test("1", 2, 0);
    test("1", 5, 0);
    test("100", 1, 7);
    test("100", 2, 4);
    test("100", 5, 2);
    test("1000000000000", 1, 40);
    test("1000000000000", 2, 20);
    test("1000000000000", 5, 8);
    test("1000000000000", 5, 8);
    test("1", -1, 0);
    test("1", -2, 0);
    test("1", -5, 0);
    test("100", -1, -6);
    test("100", -2, -3);
    test("100", -5, -1);
    test("1000000000000", -1, -39);
    test("1000000000000", -2, -19);
    test("1000000000000", -5, -7);
    test("1000000000000", -5, -7);
    test("1/2", 1, -1);
    test("1/3", 1, -1);
    test("1/4", 1, -2);
    test("1/5", 1, -2);
    test("1/6", 1, -2);
    test("1/7", 1, -2);
    test("1/8", 1, -3);
    test("1/9", 1, -3);
    test("1/2", 2, 0);
    test("1/3", 2, 0);
    test("1/4", 2, -1);
    test("1/5", 2, -1);
    test("1/6", 2, -1);
    test("1/7", 2, -1);
    test("1/8", 2, -1);
    test("1/9", 2, -1);
    test("1/2", -1, 1);
    test("1/3", -1, 2);
    test("1/4", -1, 2);
    test("1/5", -1, 3);
    test("1/6", -1, 3);
    test("1/7", -1, 3);
    test("1/8", -1, 3);
    test("1/9", -1, 4);
    test("1/2", -2, 1);
    test("1/3", -2, 1);
    test("1/4", -2, 1);
    test("1/5", -2, 2);
    test("1/6", -2, 2);
    test("1/7", -2, 2);
    test("1/8", -2, 2);
    test("1/9", -2, 2);
}

#[test]
#[should_panic]
fn ceiling_log_base_power_of_2_fail_1() {
    Rational::ZERO.ceiling_log_base_power_of_2(1);
}

#[test]
#[should_panic]
fn ceiling_log_base_power_of_2_fail_2() {
    Rational::ONE.ceiling_log_base_power_of_2(0);
}

#[test]
#[should_panic]
fn ceiling_log_base_power_of_2_fail_3() {
    Rational::NEGATIVE_ONE.ceiling_log_base_power_of_2(1);
}

#[test]
fn test_checked_log_base_power_of_2() {
    let test = |n, pow, out| {
        assert_eq!(
            Rational::from_str(n)
                .unwrap()
                .checked_log_base_power_of_2(pow),
            out
        );
    };
    test("1", 1, Some(0));
    test("1", 2, Some(0));
    test("1", 5, Some(0));
    test("100", 1, None);
    test("100", 2, None);
    test("100", 5, None);
    test("1000000000000", 1, None);
    test("1000000000000", 2, None);
    test("1000000000000", 5, None);
    test("1000000000000", 5, None);
    test("1", -1, Some(0));
    test("1", -2, Some(0));
    test("1", -5, Some(0));
    test("100", -1, None);
    test("100", -2, None);
    test("100", -5, None);
    test("1000000000000", -1, None);
    test("1000000000000", -2, None);
    test("1000000000000", -5, None);
    test("1000000000000", -5, None);
    test("1/2", 1, Some(-1));
    test("1/3", 1, None);
    test("1/4", 1, Some(-2));
    test("1/5", 1, None);
    test("1/6", 1, None);
    test("1/7", 1, None);
    test("1/8", 1, Some(-3));
    test("1/9", 1, None);
    test("1/2", 2, None);
    test("1/3", 2, None);
    test("1/4", 2, Some(-1));
    test("1/5", 2, None);
    test("1/6", 2, None);
    test("1/7", 2, None);
    test("1/8", 2, None);
    test("1/9", 2, None);
    test("1/2", -1, Some(1));
    test("1/3", -1, None);
    test("1/4", -1, Some(2));
    test("1/5", -1, None);
    test("1/6", -1, None);
    test("1/7", -1, None);
    test("1/8", -1, Some(3));
    test("1/9", -1, None);
    test("1/2", -2, None);
    test("1/3", -2, None);
    test("1/4", -2, Some(1));
    test("1/5", -2, None);
    test("1/6", -2, None);
    test("1/7", -2, None);
    test("1/8", -2, None);
    test("1/9", -2, None);
}

#[test]
#[should_panic]
fn checked_log_base_power_of_2_fail_1() {
    Rational::ZERO.checked_log_base_power_of_2(1);
}

#[test]
#[should_panic]
fn checked_log_base_power_of_2_fail_2() {
    Rational::ONE.checked_log_base_power_of_2(0);
}

#[test]
#[should_panic]
fn checked_log_base_power_of_2_fail_3() {
    Rational::NEGATIVE_ONE.checked_log_base_power_of_2(1);
}

#[test]
fn floor_log_base_power_of_2_properties() {
    rational_signed_pair_gen_var_5().test_properties(|(n, pow)| {
        let floor_log = n.floor_log_base_power_of_2(pow);
        assert_eq!(n.floor_log_base(&Rational::power_of_2(pow)), floor_log);

        let product = floor_log * pow;
        if pow >= 0 {
            assert!(Rational::power_of_2(product) <= n);
            assert!(Rational::power_of_2(product + pow) > n);
        } else {
            assert!(Rational::power_of_2(product) >= n);
            assert!(Rational::power_of_2(product + pow) < n);
        }

        let ceiling_log = n.ceiling_log_base_power_of_2(pow);
        if n.is_power_of_2() && n.checked_log_base_2().unwrap().divisible_by(pow) {
            assert_eq!(ceiling_log, floor_log);
        } else {
            assert_eq!(ceiling_log, floor_log + 1);
        }

        assert_eq!(n.floor_log_base_power_of_2(-pow), -ceiling_log);
        assert_eq!(n.reciprocal().floor_log_base_power_of_2(pow), -ceiling_log);
    });

    rational_gen_var_2().test_properties(|n| {
        assert_eq!(n.floor_log_base_power_of_2(1), n.floor_log_base_2());
    });

    signed_gen_var_12().test_properties(|pow| {
        assert_eq!(Rational::ONE.floor_log_base_power_of_2(pow), 0);
    });

    natural_unsigned_pair_gen_var_8().test_properties(|(n, pow)| {
        assert_eq!(
            i64::exact_from(n.floor_log_base_power_of_2(pow)),
            Rational::from(n).floor_log_base_power_of_2(i64::exact_from(pow))
        );
    });
}

#[test]
fn ceiling_log_base_power_of_2_properties() {
    rational_signed_pair_gen_var_5().test_properties(|(n, pow)| {
        let ceiling_log = n.ceiling_log_base_power_of_2(pow);
        assert_eq!(n.ceiling_log_base(&Rational::power_of_2(pow)), ceiling_log);

        let product = ceiling_log * pow;
        if pow >= 0 {
            assert!(Rational::power_of_2(product) >= n);
            assert!(Rational::power_of_2(product - pow) < n);
        } else {
            assert!(Rational::power_of_2(product) <= n);
            assert!(Rational::power_of_2(product - pow) > n);
        }

        let floor_log = n.floor_log_base_power_of_2(pow);
        if n.is_power_of_2() && n.checked_log_base_2().unwrap().divisible_by(pow) {
            assert_eq!(floor_log, ceiling_log);
        } else {
            assert_eq!(floor_log, ceiling_log - 1);
        }

        assert_eq!(n.ceiling_log_base_power_of_2(-pow), -floor_log);
        assert_eq!(n.reciprocal().ceiling_log_base_power_of_2(pow), -floor_log);
    });

    rational_gen_var_2().test_properties(|n| {
        assert_eq!(n.ceiling_log_base_power_of_2(1), n.ceiling_log_base_2());
    });

    signed_gen_var_12().test_properties(|pow| {
        assert_eq!(Rational::ONE.ceiling_log_base_power_of_2(pow), 0);
    });

    natural_unsigned_pair_gen_var_8().test_properties(|(n, pow)| {
        assert_eq!(
            i64::exact_from(n.ceiling_log_base_power_of_2(pow)),
            Rational::from(n).ceiling_log_base_power_of_2(i64::exact_from(pow))
        );
    });
}

#[test]
fn checked_log_base_power_of_2_properties() {
    rational_signed_pair_gen_var_5().test_properties(|(n, pow)| {
        let checked_log = n.checked_log_base_power_of_2(pow);
        assert_eq!(n.checked_log_base(&Rational::power_of_2(pow)), checked_log);
        assert_eq!(
            checked_log.is_some(),
            n.is_power_of_2() && n.checked_log_base_2().unwrap().divisible_by(pow)
        );
        if let Some(log) = checked_log {
            assert_eq!(Rational::power_of_2(log * pow), n);
            assert_eq!(log == 0, n == Rational::ONE);
            assert_eq!(n.floor_log_base_power_of_2(pow), log);
            assert_eq!(n.ceiling_log_base_power_of_2(pow), log);
            assert_eq!(n.checked_log_base_power_of_2(-pow), Some(-log));
            assert_eq!(n.reciprocal().checked_log_base_power_of_2(pow), Some(-log));
        }
    });

    rational_gen_var_2().test_properties(|n| {
        assert_eq!(n.checked_log_base_power_of_2(1), n.checked_log_base_2());
    });

    signed_gen_var_12().test_properties(|pow| {
        assert_eq!(Rational::ONE.checked_log_base_power_of_2(pow), Some(0));
    });

    natural_unsigned_pair_gen_var_8().test_properties(|(n, pow)| {
        assert_eq!(
            n.checked_log_base_power_of_2(pow).map(i64::exact_from),
            Rational::from(n).checked_log_base_power_of_2(i64::exact_from(pow))
        );
    });
}
