extern crate mocktopus;

mod mocking_fns;
mod mocking_methods;
mod mocking_methods_async;
mod mocking_trait_defaults;
mod mocking_traits;

use mocktopus::macros::*;
use mocktopus::mocking::*;
use mocktopus::mocking_utils::*;
use std::fmt::Display;

mod mock_safe {
    use super::*;

    #[mockable]
    pub fn no_args_returns_str() -> &'static str {
        "not mocked"
    }

    #[test]
    fn when_not_mocked_then_returns_not_mocked() {
        assert_eq!("not mocked", no_args_returns_str());
    }

    #[test]
    fn when_mocked_then_returns_mocked() {
        no_args_returns_str.mock_safe(|| MockResult::Return("mocked"));

        assert_eq!("mocked", no_args_returns_str());
    }
}

mod mocks_do_not_leak_between_tests {
    use super::*;

    #[mockable]
    pub fn no_args_returns_str() -> &'static str {
        "not mocked"
    }

    macro_rules! generate_tests {
        ($($fn_name:ident),+) => {
            $(
                #[test]
                fn $fn_name() {
                    assert_eq!("not mocked", no_args_returns_str(), "function was mocked before mocking");

                    unsafe {
                        no_args_returns_str.mock_raw(|| MockResult::Return(stringify!($fn_name)));
                    }

                    assert_eq!(stringify!($fn_name), no_args_returns_str(), "mocking failed");
                }
            )+
        }
    }

    generate_tests!(t00, t01, t02, t03, t04, t05, t06, t07, t08, t09);
    generate_tests!(t10, t11, t12, t13, t14, t15, t16, t17, t18, t19);
    generate_tests!(t20, t21, t22, t23, t24, t25, t26, t27, t28, t29);
    generate_tests!(t30, t31, t32, t33, t34, t35, t36, t37, t38, t39);
    generate_tests!(t40, t41, t42, t43, t44, t45, t46, t47, t48, t49);
    generate_tests!(t50, t51, t52, t53, t54, t55, t56, t57, t58, t59);
    generate_tests!(t60, t61, t62, t63, t64, t65, t66, t67, t68, t69);
    generate_tests!(t70, t71, t72, t73, t74, t75, t76, t77, t78, t79);
    generate_tests!(t80, t81, t82, t83, t84, t85, t86, t87, t88, t89);
    generate_tests!(t90, t91, t92, t93, t94, t95, t96, t97, t98, t99);
}

mod panicking_inside_mock_is_safe {
    use super::*;

    #[mockable]
    fn function(_has_drop: String) {}

    #[test]
    #[should_panic]
    fn uninitialised_string_is_not_dropped() {
        function.mock_safe(|_| panic!("inside mock"));

        function("initialised".to_string());
    }
}

mod mocking_generic_over_a_type_with_lifetime_mocks_all_lifetime_variants {
    use super::*;
    use std::fmt::Display;

    #[mockable]
    fn function<T: Display>(generic: T) -> String {
        format!("not mocked {}", generic)
    }

    static STATIC_CHAR: char = 'S';

    #[test]
    fn all_lifetime_variants_get_mocked() {
        unsafe {
            function::<&char>.mock_raw(|c| MockResult::Return(format!("mocked {}", c)));
        }
        let local_char = 'L';

        assert_eq!("mocked L", function(&local_char));
        assert_eq!("mocked S", function(&STATIC_CHAR));
        assert_eq!("not mocked 3", function(&3));
    }
}

mod mocking_generic_over_a_reference_does_not_mock_opposite_mutability_variant {
    use super::*;
    use std::fmt::Display;

    #[mockable]
    fn function<T: Display>(generic: T) -> String {
        format!("not mocked {}", generic)
    }

    #[test]
    fn mocking_for_ref_does_not_mock_for_mut_ref() {
        unsafe {
            function::<&char>.mock_raw(|c| MockResult::Return(format!("mocked {}", c)));
        }

        assert_eq!("mocked R", function(&'R'));
        assert_eq!("not mocked M", function(&mut 'M'));
    }

    #[test]
    fn mocking_for_mut_ref_does_not_mock_for_ref() {
        unsafe {
            function::<&mut char>.mock_raw(|c| MockResult::Return(format!("mocked {}", c)));
        }

        assert_eq!("not mocked R", function(&'R'));
        assert_eq!("mocked M", function(&mut 'M'));
    }
}

mod mocking_trait_default_for_struct_does_not_mock_same_default_for_another_struct {
    use super::*;

    #[mockable]
    trait Trait {
        fn function() -> &'static str {
            "not mocked"
        }
    }

    struct Struct1;

    impl Trait for Struct1 {}

    struct Struct2;

    impl Trait for Struct2 {}

    #[test]
    fn when_not_mocked_then_both_run_normally() {
        assert_eq!("not mocked", Struct1::function());
        assert_eq!("not mocked", Struct2::function());
    }

    #[test]
    fn when_mocked_for_one_then_runs_mock_for_it_and_runs_normally_for_other() {
        unsafe { Struct1::function.mock_raw(|| MockResult::Return("mocked")) }

        assert_eq!("mocked", Struct1::function());
        assert_eq!("not mocked", Struct2::function());
    }
}

mod mock_closures_can_mutate_their_state {
    use super::*;

    #[mockable]
    fn function() -> &'static str {
        "not mocked"
    }

    #[test]
    fn when_not_mocked_then_runs_normally() {
        assert_eq!("not mocked", function());
    }

    #[test]
    fn when_mocked_then_runs_mocks_with_state() {
        let mut x = 0;
        function.mock_safe(move || {
            x += 1;
            match x {
                1 => MockResult::Return("first mock"),
                _ => MockResult::Return("other mock"),
            }
        });

        assert_eq!("first mock", function());
        assert_eq!("other mock", function());
    }
}

mod calling_mocked_function_inside_mock_closure {
    use super::*;

    #[mockable]
    fn mockable_1() -> String {
        "not mocked 1".to_string()
    }

    #[mockable]
    fn mockable_2() -> String {
        "not mocked 2".to_string()
    }

    fn not_mockable() -> String {
        "not mockable".to_string()
    }

    #[test]
    fn when_calling_itself_then_does_not_run_mock() {
        let mut first_call = true;
        mockable_1.mock_safe(move || match first_call {
            true => {
                first_call = false;
                MockResult::Return(format!("mocked 1 {}", mockable_1()))
            }
            false => panic!("Mock called second time"),
        });

        assert_eq!("mocked 1 not mocked 1", mockable_1());
    }

    #[test]
    fn when_calling_not_mocked_function_then_runs_it_normally() {
        mockable_1.mock_safe(|| MockResult::Return(format!("mocked 1 {}", mockable_2())));

        assert_eq!("mocked 1 not mocked 2", mockable_1());
    }

    #[test]
    fn when_calling_mocked_function_then_runs_its_mock() {
        mockable_1.mock_safe(|| MockResult::Return(format!("mocked 1 {}", mockable_2())));
        mockable_2.mock_safe(|| MockResult::Return("mocked 2".to_string()));

        assert_eq!("mocked 1 mocked 2", mockable_1());
    }

    #[test]
    fn when_calling_not_mockable_function_then_runs_it_normally() {
        mockable_1.mock_safe(|| MockResult::Return(format!("mocked 1 {}", not_mockable())));

        assert_eq!("mocked 1 not mockable", mockable_1());
    }
}

mod creating_mock_inside_mock_closure {
    use super::*;

    #[mockable]
    fn mockable_1() -> String {
        "not mocked 1".to_string()
    }

    #[mockable]
    fn mockable_2() -> String {
        "not mocked 2".to_string()
    }

    #[test]
    fn when_creating_mock_of_itself_then_registers_mock() {
        mockable_1.mock_safe(|| {
            mockable_1.mock_safe(|| MockResult::Return("mocked 1B".to_string()));
            MockResult::Return("mocked 1A".to_string())
        });

        assert_eq!("mocked 1A", mockable_1());
        assert_eq!("mocked 1B", mockable_1());
    }

    #[test]
    fn when_creating_mock_of_itself_and_calling_itself_then_runs_new_mock() {
        mockable_1.mock_safe(|| {
            mockable_1.mock_safe(|| MockResult::Return("mocked 1B".to_string()));
            MockResult::Return(format!("mocked 1A {}", mockable_1()))
        });

        assert_eq!("mocked 1A mocked 1B", mockable_1());
    }

    #[test]
    fn when_creating_mock_of_other_function_then_registers_mock() {
        mockable_1.mock_safe(|| {
            mockable_2.mock_safe(|| MockResult::Return("mocked 2".to_string()));
            MockResult::Return("mocked 1".to_string())
        });

        assert_eq!("not mocked 2", mockable_2());
        assert_eq!("mocked 1", mockable_1());
        assert_eq!("mocked 2", mockable_2());
    }

    #[test]
    fn when_creating_mock_of_other_function_and_calling_it_then_runs_new_mock() {
        mockable_1.mock_safe(|| {
            mockable_2.mock_safe(|| MockResult::Return("mocked 2".to_string()));
            MockResult::Return(format!("mocked 1 {}", mockable_2()))
        });

        assert_eq!("mocked 1 mocked 2", mockable_1());
    }
}

mod clear_mocks {
    use super::*;

    #[mockable]
    fn mockable_1() -> &'static str {
        "not mocked 1"
    }

    #[mockable]
    fn mockable_2() -> &'static str {
        "not mocked 2"
    }

    #[test]
    fn when_clearing_mocks_original_function_operations_return() {
        mockable_1.mock_safe(|| MockResult::Return("mocked 1"));
        assert_eq!("mocked 1", mockable_1());
        assert_eq!("not mocked 2", mockable_2());

        clear_mocks();

        assert_eq!("not mocked 1", mockable_1());
        assert_eq!("not mocked 2", mockable_2());
    }

    #[test]
    fn clearing_mocks_inside_context_clears_all_mocks() {
        assert_eq!("not mocked 1", mockable_1());
        mockable_1.mock_safe(|| MockResult::Return("mocked 1"));
        assert_eq!("mocked 1", mockable_1());
        MockContext::new()
            .mock_safe(mockable_1, || MockResult::Return("mocked 1 context"))
            .run(|| {
                assert_eq!("mocked 1 context", mockable_1());
                clear_mocks();
                assert_eq!("not mocked 1", mockable_1());
            });
        assert_eq!("not mocked 1", mockable_1());
    }

    #[test]
    fn clearing_mocks_inside_context_does_not_disrupt_context_cleanup() {
        assert_eq!("not mocked 1", mockable_1());
        mockable_1.mock_safe(|| MockResult::Return("mocked 1 pre"));
        assert_eq!("mocked 1 pre", mockable_1());
        MockContext::new()
            .mock_safe(mockable_1, || MockResult::Return("mocked 1 context"))
            .run(|| {
                assert_eq!("mocked 1 context", mockable_1());
                clear_mocks();
                assert_eq!("not mocked 1", mockable_1());
                mockable_1.mock_safe(|| MockResult::Return("mocked 1 post"));
                assert_eq!("mocked 1 post", mockable_1());
            });
        assert_eq!("mocked 1 post", mockable_1());
    }
}

mod clear_mock {
    use super::*;

    #[mockable]
    fn mockable_1() -> &'static str {
        "not mocked 1"
    }

    #[mockable]
    fn mockable_2() -> &'static str {
        "not mocked 2"
    }

    #[test]
    fn clearing_mocked_deregisters_the_mock() {
        mockable_1.mock_safe(|| MockResult::Return("mocked 1"));
        mockable_2.mock_safe(|| MockResult::Return("mocked 2"));
        assert_eq!("mocked 1", mockable_1());
        assert_eq!("mocked 2", mockable_2());

        mockable_1.clear_mock();

        assert_eq!("not mocked 1", mockable_1());
        assert_eq!("mocked 2", mockable_2());
    }

    #[test]
    fn clearing_not_mocked_does_nothing() {
        mockable_2.mock_safe(|| MockResult::Return("mocked 2"));
        assert_eq!("not mocked 1", mockable_1());
        assert_eq!("mocked 2", mockable_2());

        mockable_1.clear_mock();

        assert_eq!("not mocked 1", mockable_1());
        assert_eq!("mocked 2", mockable_2());
    }

    #[test]
    fn clearing_mocks_inside_context_clears_mocks_in_all_contexts() {
        assert_eq!("not mocked 1", mockable_1());
        mockable_1.mock_safe(|| MockResult::Return("mocked 1"));
        assert_eq!("mocked 1", mockable_1());
        MockContext::new()
            .mock_safe(mockable_1, || MockResult::Return("mocked 1 context"))
            .run(|| {
                assert_eq!("mocked 1 context", mockable_1());
                mockable_1.clear_mock();
                assert_eq!("not mocked 1", mockable_1());
            });
        assert_eq!("not mocked 1", mockable_1());
    }
}

mod mock_context {
    use super::*;

    #[mockable]
    fn mockable_1() -> &'static str {
        "not mocked 1"
    }

    #[mockable]
    fn mockable_2() -> &'static str {
        "not mocked 2"
    }

    #[mockable]
    fn mockable_string() -> String {
        "not mocked".to_string()
    }

    #[test]
    fn context_mocks_mock_only_inside_run_closure() {
        assert_eq!("not mocked 1", mockable_1());
        assert_eq!("not mocked 2", mockable_2());
        MockContext::new()
            .mock_safe(mockable_1, || MockResult::Return("mocked 1"))
            .run(|| {
                assert_eq!("mocked 1", mockable_1());
                assert_eq!("not mocked 2", mockable_2());
            });
        assert_eq!("not mocked 1", mockable_1());
        assert_eq!("not mocked 2", mockable_2());
    }

    #[test]
    fn context_mocks_with_no_mocks_have_no_effect() {
        assert_eq!("not mocked 1", mockable_1());
        MockContext::new().run(|| {
            assert_eq!("not mocked 1", mockable_1());
        });
        assert_eq!("not mocked 1", mockable_1());
    }

    #[test]
    fn context_mocks_with_multiple_mocks_of_the_same_function_use_only_last_mock() {
        assert_eq!("not mocked 1", mockable_1());
        MockContext::new()
            .mock_safe(mockable_1, || MockResult::Return("mocked 1 A"))
            .mock_safe(mockable_1, || MockResult::Return("mocked 1 B"))
            .mock_safe(mockable_1, || MockResult::Return("mocked 1 C"))
            .run(|| {
                assert_eq!("mocked 1 C", mockable_1());
            });
        assert_eq!("not mocked 1", mockable_1());
    }

    #[test]
    fn nested_context_mocks_shadow_outside_mocks_only_inside_run_closure() {
        assert_eq!("not mocked 1", mockable_1());
        assert_eq!("not mocked 2", mockable_2());
        mockable_1.mock_safe(|| MockResult::Return("mocked 1"));
        assert_eq!("mocked 1", mockable_1());
        assert_eq!("not mocked 2", mockable_2());
        MockContext::new()
            .mock_safe(mockable_1, || MockResult::Return("mocked 1 context 1"))
            .mock_safe(mockable_2, || MockResult::Return("mocked 2 context 1"))
            .run(|| {
                assert_eq!("mocked 1 context 1", mockable_1());
                assert_eq!("mocked 2 context 1", mockable_2());
                MockContext::new()
                    .mock_safe(mockable_1, || MockResult::Return("mocked 1 context 2"))
                    .run(|| {
                        assert_eq!("mocked 1 context 2", mockable_1());
                        assert_eq!("mocked 2 context 1", mockable_2());
                    });
                assert_eq!("mocked 1 context 1", mockable_1());
                assert_eq!("mocked 2 context 1", mockable_2());
            });
        assert_eq!("mocked 1", mockable_1());
        assert_eq!("not mocked 2", mockable_2());
    }

    #[test]
    fn calling_function_with_shadowed_mock_from_inside_mock_closure_calls_shadowed_mock() {
        assert_eq!("not mocked", mockable_string());
        mockable_string.mock_safe(|| MockResult::Return(format!("{}, mocked", mockable_string())));
        assert_eq!("not mocked, mocked", mockable_string());
        MockContext::new()
            .mock_safe(mockable_string, || {
                MockResult::Return(format!("{}, mocked context", mockable_string()))
            })
            .run(|| {
                assert_eq!("not mocked, mocked, mocked context", mockable_string());
            });
        assert_eq!("not mocked, mocked", mockable_string());
    }
}
