
use super::*;

use crate::{
    test_utils::{
        check_formatting_equivalence,
    },
    nonexhaustive_enum::{
        GetEnumInfo,
        examples::{
            command_a,
            command_b,
            command_c,
            command_h_mismatched_discriminant,
            command_serde,
            generic_a,
            many_ranges_a,
            many_ranges_b,
        },
    },
};

use core_extensions::SelfOps;


use std::{
    cmp::{Ord, Ordering, PartialEq, PartialOrd},
    collections::hash_map::DefaultHasher,
    hash::{Hash,Hasher},
    sync::Arc,
};




#[test]
fn construct_deconstruct(){
    {
        use self::command_a::{Foo as FooA};
        let mut variant_a=NonExhaustive::new(FooA::A);
        let mut variant_b=NonExhaustive::new(FooA::B(11));

        assert_eq!(variant_a.as_enum(), Ok(&FooA::A));
        assert_eq!(variant_b.as_enum(), Ok(&FooA::B(11)));

        assert_eq!(variant_a.as_enum_mut(), Ok(&mut FooA::A));
        assert_eq!(variant_b.as_enum_mut(), Ok(&mut FooA::B(11)));

        assert_eq!(variant_a.into_enum(), Ok(FooA::A));
        assert_eq!(variant_b.into_enum(), Ok(FooA::B(11)));
    }
    {
        use self::command_b::{Foo as FooB};

        let mut variant_a=NonExhaustive::new(FooB::A);
        let mut variant_b=NonExhaustive::new(FooB::B(11));
        let mut variant_c=NonExhaustive::new(FooB::C);

        assert_eq!(variant_a.as_enum(), Ok(&FooB::A));
        assert_eq!(variant_b.as_enum(), Ok(&FooB::B(11)));
        assert_eq!(variant_c.as_enum(), Ok(&FooB::C));

        assert_eq!(variant_a.as_enum_mut(), Ok(&mut FooB::A));
        assert_eq!(variant_b.as_enum_mut(), Ok(&mut FooB::B(11)));
        assert_eq!(variant_c.as_enum_mut(), Ok(&mut FooB::C));

        assert_eq!(variant_a.into_enum(), Ok(FooB::A));
        assert_eq!(variant_b.into_enum(), Ok(FooB::B(11)));
        assert_eq!(variant_c.into_enum(), Ok(FooB::C));
    }
}


#[test]
fn get_discriminant(){
    {
        use self::command_c::Foo as FooC;
        let wrapped_a=NonExhaustive::new(FooC::A);
        let wrapped_b=NonExhaustive::new(FooC::B(11));
        let wrapped_c=NonExhaustive::new(FooC::C);
        let wrapped_d=NonExhaustive::new(FooC::D{name:"what".into()});

        assert_eq!(wrapped_a.get_discriminant(),0);
        assert_eq!(wrapped_b.get_discriminant(),1);
        assert_eq!(wrapped_c.get_discriminant(),2);
        assert_eq!(wrapped_d.get_discriminant(),3);
    }
    {
        use self::command_h_mismatched_discriminant::Foo;
        let wrapped_a=NonExhaustive::new(Foo::A);
        let wrapped_b=NonExhaustive::new(Foo::B);
        let wrapped_c=NonExhaustive::new(Foo::C);

        assert_eq!(wrapped_a.get_discriminant(),40);
        assert_eq!(wrapped_b.get_discriminant(),41);
        assert_eq!(wrapped_c.get_discriminant(),42);
    }
}


#[test]
fn is_valid_discriminant(){
    {
        use self::command_c::Foo as FooC;
        assert_eq!(FooC::is_valid_discriminant(0),true);
        assert_eq!(FooC::is_valid_discriminant(1),true);
        assert_eq!(FooC::is_valid_discriminant(2),true);
        assert_eq!(FooC::is_valid_discriminant(3),true);
        assert_eq!(FooC::is_valid_discriminant(4),false);
        assert_eq!(FooC::is_valid_discriminant(5),false);
    }
    {
        use self::command_h_mismatched_discriminant::Foo;
        assert_eq!(Foo::is_valid_discriminant(0),false);
        assert_eq!(Foo::is_valid_discriminant(39),false);
        assert_eq!(Foo::is_valid_discriminant(40),true);
        assert_eq!(Foo::is_valid_discriminant(41),true);
        assert_eq!(Foo::is_valid_discriminant(42),true);
        assert_eq!(Foo::is_valid_discriminant(43),false);
        assert_eq!(Foo::is_valid_discriminant(44),false);
    }
    {
        use self::many_ranges_a::Foo;
        assert_eq!(Foo::is_valid_discriminant(0),true);
        assert_eq!(Foo::is_valid_discriminant(1),false);
        assert_eq!(Foo::is_valid_discriminant(2),false);
        assert_eq!(Foo::is_valid_discriminant(39),false);
        assert_eq!(Foo::is_valid_discriminant(40),true);
        assert_eq!(Foo::is_valid_discriminant(41),true);
        assert_eq!(Foo::is_valid_discriminant(42),true);
        assert_eq!(Foo::is_valid_discriminant(43),false);
        assert_eq!(Foo::is_valid_discriminant(44),false);
        assert_eq!(Foo::is_valid_discriminant(58),false);
        assert_eq!(Foo::is_valid_discriminant(59),false);
        assert_eq!(Foo::is_valid_discriminant(60),true);
        assert_eq!(Foo::is_valid_discriminant(61),true);
        assert_eq!(Foo::is_valid_discriminant(62),false);
        assert_eq!(Foo::is_valid_discriminant(63),false);
    }
    {
        use self::many_ranges_b::Foo;
        assert_eq!(Foo::is_valid_discriminant(0),true);
        assert_eq!(Foo::is_valid_discriminant(1),false);
        assert_eq!(Foo::is_valid_discriminant(2),false);
        assert_eq!(Foo::is_valid_discriminant(39),false);
        assert_eq!(Foo::is_valid_discriminant(40),true);
        assert_eq!(Foo::is_valid_discriminant(41),true);
        assert_eq!(Foo::is_valid_discriminant(42),false);
        assert_eq!(Foo::is_valid_discriminant(43),false);
        assert_eq!(Foo::is_valid_discriminant(58),false);
        assert_eq!(Foo::is_valid_discriminant(59),false);
        assert_eq!(Foo::is_valid_discriminant(60),true);
        assert_eq!(Foo::is_valid_discriminant(62),false);
        assert_eq!(Foo::is_valid_discriminant(63),false);
    }
}


// This also tests what happens between dynamic libraries.
#[test]
fn transmuting_enums(){
    unsafe{
        use self::command_a::{Foo as FooA};
        use self::command_c::{Foo as FooC};

        let mut variant_a=NonExhaustive::new(FooC::A).transmute_enum::<FooA>();
        let mut variant_b=NonExhaustive::new(FooC::B(11)).transmute_enum::<FooA>();
        let mut variant_c=NonExhaustive::new(FooC::C).transmute_enum::<FooA>();
        let mut variant_d=FooC::D{name:"what".into()}
            .piped(NonExhaustive::new)
            .transmute_enum::<FooA>();

        assert_eq!(variant_c.is_valid_discriminant(), false);
        assert_eq!(variant_d.is_valid_discriminant(), false);

        assert_eq!(variant_a.as_enum(), Ok(&FooA::A));
        assert_eq!(variant_b.as_enum(), Ok(&FooA::B(11)));
        assert_eq!(variant_c.as_enum().ok(), None);
        assert_eq!(variant_d.as_enum().ok(), None);

        assert_eq!(variant_a.as_enum_mut(), Ok(&mut FooA::A));
        assert_eq!(variant_b.as_enum_mut(), Ok(&mut FooA::B(11)));
        assert_eq!(variant_c.as_enum_mut().ok(), None);
        assert_eq!(variant_d.as_enum_mut().ok(), None);

        assert_eq!(variant_a.into_enum(), Ok(FooA::A));
        assert_eq!(variant_b.into_enum(), Ok(FooA::B(11)));
        assert_eq!(variant_c.into_enum().ok(), None);
        assert_eq!(variant_d.into_enum().ok(), None);
    }
}


#[test]
fn clone_test(){
    use self::generic_a::Foo;

    let arc=Arc::new(100);
    assert_eq!(Arc::strong_count(&arc), 1);

    let variant_a=NonExhaustive::new(Foo::<Arc<i32>>::A);
    let variant_b=NonExhaustive::new(Foo::<Arc<i32>>::B);
    let variant_c=NonExhaustive::new(Foo::<Arc<i32>>::C(arc.clone()));

    assert_eq!(Arc::strong_count(&arc), 2);
    
    assert_eq!(variant_a.clone(), variant_a);
    assert_eq!(variant_b.clone(), variant_b);
    {
        let clone_c=variant_c.clone();
        assert_eq!(Arc::strong_count(&arc), 3);
        assert_eq!(clone_c, variant_c);
    }
    assert_eq!(Arc::strong_count(&arc), 2);


    assert_eq!(variant_a.clone(), Foo::A);
    assert_eq!(variant_b.clone(), Foo::B);
    {
        let clone_c=variant_c.clone();
        assert_eq!(Arc::strong_count(&arc), 3);
        assert_eq!(clone_c.clone(), Foo::C(arc.clone()));
    }
    assert_eq!(Arc::strong_count(&arc), 2);


    drop(variant_c);
    assert_eq!(Arc::strong_count(&arc), 1);
}


#[test]
fn fmt_test(){
    use self::command_serde::{Foo as FooC};

    let variant_a=FooC::A;
    let wrapped_a=NonExhaustive::new(variant_a.clone());
    
    let variant_b=FooC::B(11);
    let wrapped_b=NonExhaustive::new(variant_b.clone());
    
    let variant_c=FooC::C;
    let wrapped_c=NonExhaustive::new(variant_c.clone());
    
    let variant_d=FooC::D{name:"what".into()};
    let wrapped_d=NonExhaustive::new(variant_d.clone());

    check_formatting_equivalence(&variant_a,&wrapped_a);
    check_formatting_equivalence(&variant_b,&wrapped_b);
    check_formatting_equivalence(&variant_c,&wrapped_c);
    check_formatting_equivalence(&variant_d,&wrapped_d);
}



#[test]
fn cmp_test(){
    use self::generic_a::Foo;

    let variant_a=Foo::<String>::A;
    let wrapped_a=NonExhaustive::new(variant_a.clone());
    
    let variant_b=Foo::<String>::B;
    let wrapped_b=NonExhaustive::new(variant_b.clone());
    
    let variant_c=Foo::<String>::C("what".into());
    let wrapped_c=NonExhaustive::new(variant_c.clone());

    for wrapped in vec![&wrapped_a,&wrapped_b,&wrapped_c] {
        assert_eq!(wrapped.cmp(&wrapped), Ordering::Equal);
    }
    assert_eq!(wrapped_a.cmp(&wrapped_b), Ordering::Less);
    assert_eq!(wrapped_b.cmp(&wrapped_c), Ordering::Less);

    macro_rules! cmp_tests {
        (
            loop_variables=$variant:ident,$wrapped:ident,$which_one:ident;
            var_b=$var_b:ident;
            var_c=$var_c:ident;
        ) => (
            #[allow(unused_variables)]
            for ($variant,$wrapped) in 
                vec![(&variant_a,&wrapped_a),(&variant_b,&wrapped_b),(&variant_c,&wrapped_c)] 
            {
                assert_eq!($wrapped == $which_one, true);
                assert_eq!($wrapped <= $which_one, true);
                assert_eq!($wrapped >= $which_one, true );
                assert_eq!($wrapped < $which_one, false);
                assert_eq!($wrapped > $which_one, false);
                assert_eq!($wrapped != $which_one, false);
                assert_eq!($wrapped.partial_cmp($which_one), Some(Ordering::Equal));
                assert_eq!($wrapped.eq($which_one), true);
                assert_eq!($wrapped.ne($which_one), false);
            }

            assert_eq!(wrapped_a == $var_b, false);
            assert_eq!(wrapped_a <= $var_b, true);
            assert_eq!(wrapped_a >= $var_b, false);
            assert_eq!(wrapped_a < $var_b, true);
            assert_eq!(wrapped_a > $var_b, false);
            assert_eq!(wrapped_a != $var_b, true);
            assert_eq!(wrapped_a.partial_cmp(&$var_b), Some(Ordering::Less));
            assert_eq!(wrapped_a.eq(&$var_b), false);
            assert_eq!(wrapped_a.ne(&$var_b), true);

            assert_eq!(wrapped_b == $var_c, false);
            assert_eq!(wrapped_b <= $var_c, true);
            assert_eq!(wrapped_b >= $var_c, false);
            assert_eq!(wrapped_b < $var_c, true);
            assert_eq!(wrapped_b > $var_c, false);
            assert_eq!(wrapped_b != $var_c, true);
            assert_eq!(wrapped_b.partial_cmp(&$var_c), Some(Ordering::Less));
            assert_eq!(wrapped_b.eq(&$var_c), false);
            assert_eq!(wrapped_b.ne(&$var_c), true);

        )
    }

    cmp_tests!{
        loop_variables=variant,wrapped,variant;
        var_b=variant_b;
        var_c=variant_c;
    }

    cmp_tests!{
        loop_variables=variant,wrapped,wrapped;
        var_b=wrapped_b;
        var_c=wrapped_c;
    }
}


#[test]
fn hash_test(){
    use self::generic_a::Foo;

    fn hash_value<H:Hash>(v:&H)->u64{
        let mut hasher=DefaultHasher::new();
        v.hash(&mut hasher);
        hasher.finish()
    }


    let variant_a=Foo::<String>::A;
    let wrapped_a=NonExhaustive::new(variant_a.clone());
    
    let variant_b=Foo::<String>::B;
    let wrapped_b=NonExhaustive::new(variant_b.clone());
    
    let variant_c=Foo::<String>::C("what".into());
    let wrapped_c=NonExhaustive::new(variant_c.clone());    
    
    for (variant,wrapped) in 
        vec![(&variant_a,&wrapped_a),(&variant_b,&wrapped_b),(&variant_c,&wrapped_c)] 
    {
        assert_eq!(hash_value(variant),hash_value(wrapped));
    }
}



#[test]
fn serde_test() {
    use self::command_serde::{Foo as FooC};

    let variant_a=FooC::A;
    let variant_b=FooC::B(10);
    let variant_c=FooC::C;
    let variant_d=FooC::D{name:"what".into()};

    let expected_a=NonExhaustive::new(variant_a.clone());
    let expected_b=NonExhaustive::new(variant_b.clone());
    let expected_c=NonExhaustive::new(variant_c.clone());
    let expected_d=NonExhaustive::new(variant_d.clone());

    let json_a=r#""A""#;
    let json_dd_a=serde_json::to_string(&json_a).unwrap();

    let json_b=r#"{"B":10}"#;
    let json_dd_b=serde_json::to_string(&json_b).unwrap();

    let json_c=r#""C""#;
    let json_dd_c=serde_json::to_string(&json_c).unwrap();

    let json_d=r#"{"D":{"name":"what"}}"#;
    let json_dd_d=serde_json::to_string(&json_d).unwrap();


    assert_eq!(
        serde_json::from_str::<NonExhaustiveFor<FooC>>(r#" "oinoiasnd" "#).map_err(drop),
        Err(()),
    );

    assert_eq!(
        NonExhaustiveFor::<FooC>::deserialize_from_proxy(
            r#"oinoiasnd"#.into()
        ).map_err(drop),
        Err(()),
    );

    for (json_dd,json,expected,variant) in 
        vec![
            (&*json_dd_a,json_a,&expected_a,&variant_a),
            (&*json_dd_b,json_b,&expected_b,&variant_b),
            (&*json_dd_c,json_c,&expected_c,&variant_c),
            (&*json_dd_d,json_d,&expected_d,&variant_d),
        ]
    {
        {
            let deserialized=serde_json::from_str::<NonExhaustiveFor<FooC>>(json_dd).unwrap();
            assert_eq!(deserialized,*expected);
            assert_eq!(deserialized,*variant);
        }
        {
            let deserialized=
                NonExhaustiveFor::<FooC>::deserialize_from_proxy(json.into()).unwrap();
            assert_eq!(deserialized,*expected);
            assert_eq!(deserialized,*variant);
        }

        assert_eq!(&*serde_json::to_string(&expected).unwrap(), json_dd);
        assert_eq!(&*expected.serialize_into_proxy().unwrap(), json);
        assert_eq!(&*serde_json::to_string(&variant).unwrap(), json);
    }
}


