#[macro_use]
extern crate proptest;
extern crate strength_reduce;

use proptest::test_runner::Config;
use strength_reduce::{StrengthReducedU8, StrengthReducedU16, StrengthReducedU32, StrengthReducedU64, StrengthReducedUsize, StrengthReducedU128};


macro_rules! reduction_proptest {
    ($test_name:ident, $struct_name:ident, $primitive_type:ident) => (
        mod $test_name {
            use super::*;
            use proptest::sample::select;

            fn assert_div_rem_equivalence(divisor: $primitive_type, numerator: $primitive_type) {
                let reduced_divisor = $struct_name::new(divisor);
                let expected_div = numerator / divisor;
                let expected_rem = numerator % divisor;
                let reduced_div = numerator / reduced_divisor;
                let reduced_rem = numerator % reduced_divisor;
                assert_eq!(expected_div, reduced_div, "Divide failed with numerator: {}, divisor: {}", numerator, divisor);
                assert_eq!(expected_rem, reduced_rem, "Modulo failed with numerator: {}, divisor: {}", numerator, divisor);
                let (reduced_combined_div, reduced_combined_rem) = $struct_name::div_rem(numerator, reduced_divisor);
                assert_eq!(expected_div, reduced_combined_div, "div_rem divide failed with numerator: {}, divisor: {}", numerator, divisor);
                assert_eq!(expected_rem, reduced_combined_rem, "div_rem modulo failed with numerator: {}, divisor: {}", numerator, divisor);
            }



            proptest! {
                #![proptest_config(Config::with_cases(100_000))]

                #[test]
                fn fully_generated_inputs_are_div_rem_equivalent(divisor in 1..core::$primitive_type::MAX, numerator in 0..core::$primitive_type::MAX) {
                    assert_div_rem_equivalence(divisor, numerator);
                }

                #[test]
                fn generated_divisors_with_edge_case_numerators_are_div_rem_equivalent(
                        divisor in 1..core::$primitive_type::MAX,
                        numerator in select(vec![0 as $primitive_type, 1 as $primitive_type, core::$primitive_type::MAX - 1, core::$primitive_type::MAX])) {
                    assert_div_rem_equivalence(divisor, numerator);
                }

                #[test]
                fn generated_numerators_with_edge_case_divisors_are_div_rem_equivalent(
                        divisor in select(vec![1 as $primitive_type, 2 as $primitive_type, core::$primitive_type::MAX - 1, core::$primitive_type::MAX]),
                        numerator in 0..core::$primitive_type::MAX) {
                    assert_div_rem_equivalence(divisor, numerator);
                }
            }
        }
    )
}
reduction_proptest!(strength_reduced_u08, StrengthReducedU8, u8);
reduction_proptest!(strength_reduced_u16, StrengthReducedU16, u16);
reduction_proptest!(strength_reduced_u32, StrengthReducedU32, u32);
reduction_proptest!(strength_reduced_u64, StrengthReducedU64, u64);
reduction_proptest!(strength_reduced_usize, StrengthReducedUsize, usize);
reduction_proptest!(strength_reduced_u128, StrengthReducedU128, u128);

macro_rules! exhaustive_test {
    ($test_name:ident, $struct_name:ident, $primitive_type:ident) => (
    	#[test]
    	#[ignore]
    	fn $test_name() {
    		for divisor in 1..=std::$primitive_type::MAX {
    			let reduced_divisor = $struct_name::new(divisor);

    			for numerator in 0..=std::$primitive_type::MAX {
    				let expected_div = numerator / divisor;
	                let expected_rem = numerator % divisor;

	                let reduced_div = numerator / reduced_divisor;
	                assert_eq!(expected_div, reduced_div, "Divide failed with numerator: {}, divisor: {}", numerator, divisor);

	                let reduced_rem = numerator % reduced_divisor;
	                assert_eq!(expected_rem, reduced_rem, "Modulo failed with numerator: {}, divisor: {}", numerator, divisor);

	                let (reduced_combined_div, reduced_combined_rem) = $struct_name::div_rem(numerator, reduced_divisor);
	                assert_eq!(expected_div, reduced_combined_div, "div_rem divide failed with numerator: {}, divisor: {}", numerator, divisor);
	                assert_eq!(expected_rem, reduced_combined_rem, "div_rem modulo failed with numerator: {}, divisor: {}", numerator, divisor);
    			}
    		}
    	}
    )
}

exhaustive_test!(test_strength_reduced_u08_exhaustive, StrengthReducedU8, u8);
exhaustive_test!(test_strength_reduced_u16_exhaustive, StrengthReducedU16, u16);
