// ----------------------------------------------------------------
// IMPORTS
// ----------------------------------------------------------------

use rstest::fixture;
use rstest::rstest;
use std::fmt::Debug;
use std::hash::Hash;
use std::panic::catch_unwind;
use std::panic::RefUnwindSafe;

use ads2::assert_length;
use ads2::assert_length_ne;
use ads2::assert_length_lt;
use ads2::assert_length_le;
use ads2::assert_length_gt;
use ads2::assert_length_ge;
use ads2::assert_length_unique;
use ads2::assert_length_unique_ne;
use ads2::assert_length_unique_lt;
use ads2::assert_length_unique_le;
use ads2::assert_length_unique_gt;
use ads2::assert_length_unique_ge;
use ads2::assert_contains;
use ads2::assert_not_contains;
use ads2::assert_eq_contents;
use ads2::assert_ne_contents;
use ads2::assert_subset;
use ads2::assert_not_subset;

// ----------------------------------------------------------------
// Fixtures
// ----------------------------------------------------------------

#[fixture]
fn list1() -> Vec<i32> {
    return vec![1,2,4,5,13,13,13,78,78];
}

#[fixture]
fn list2() -> Vec<i32> {
    return vec![78,13,1,4,13,78,78,5,4,2];
}

#[fixture]
fn list3() -> Vec<i32> {
    return vec![1,2,78,78];
}

#[fixture]
fn list4() -> Vec<i32> {
    return vec![1,2,78,78,100,45];
}

// ----------------------------------------------------------------
// Test length
// ----------------------------------------------------------------

#[rstest]
#[case(list1(),9)]
#[case(list2(),10)]
#[case(list3(),4)]
#[case(list4(),6)]
fn test_rules_length<T>(#[case] list: Vec<T>, #[case] n: usize)
    where T: RefUnwindSafe
{
    assert!(catch_unwind(|| {
        assert_length!(list, n);
    }).is_ok());
    assert!(catch_unwind(|| {
        assert_length_ne!(list, n, "Length should not equal n");
    }).is_err());
}

#[rstest]
#[case(list1(),6)]
#[case(list2(),6)]
#[case(list3(),3)]
#[case(list4(),5)]
fn test_rules_length_ne<T>(#[case] list: Vec<T>, #[case] n: usize)
    where T: RefUnwindSafe
{
    assert!(catch_unwind(|| {
        assert_length!(list, n, "Length should be n!");
    }).is_err());
    assert!(catch_unwind(|| {
        assert_length_ne!(list, n);
    }).is_ok());
}

#[rstest]
#[case(0)]
#[case(1)]
#[case(2)]
#[case(3)]
#[case(4)]
fn test_rules_length_ge<T>(list3: Vec<T>, #[case] n: usize)
    where T: RefUnwindSafe
{
    assert!(catch_unwind(|| {
        assert_length_ge!(list3, n);
    }).is_ok());
}

#[rstest]
#[case(5)]
#[case(100)]
fn test_rules_length_ge_error<T>(list3: Vec<T>, #[case] n: usize)
    where T: RefUnwindSafe
{
    assert!(catch_unwind(|| {
        assert_length_ge!(list3, n, "Length should be >= n!");
    }).is_err());
}

#[rstest]
#[case(0)]
#[case(1)]
#[case(2)]
#[case(3)]
fn test_rules_length_gt<T>(list3: Vec<T>, #[case] n: usize)
    where T: RefUnwindSafe
{
    assert!(catch_unwind(|| {
        assert_length_gt!(list3, n);
    }).is_ok());
}

#[rstest]
#[case(4)]
#[case(5)]
#[case(100)]
fn test_rules_length_gt_error<T>(list3: Vec<T>, #[case] n: usize)
    where T: RefUnwindSafe
{
    assert!(catch_unwind(|| {
        assert_length_gt!(list3, n, "Length should be > n!");
    }).is_err());
}

#[rstest]
#[case(100)]
#[case(5)]
#[case(4)]
fn test_rules_length_le<T>(list3: Vec<T>, #[case] n: usize)
    where T: RefUnwindSafe
{
    assert!(catch_unwind(|| {
        assert_length_le!(list3, n);
    }).is_ok());
}

#[rstest]
#[case(3)]
#[case(2)]
#[case(1)]
#[case(0)]
fn test_rules_length_le_error<T>(list3: Vec<T>, #[case] n: usize)
    where T: RefUnwindSafe
{
    assert!(catch_unwind(|| {
        assert_length_le!(list3, n, "Length should be <= n!");
    }).is_err());
}

#[rstest]
#[case(100)]
#[case(5)]
fn test_rules_length_lt<T>(list3: Vec<T>, #[case] n: usize)
    where T: RefUnwindSafe
{
    assert!(catch_unwind(|| {
        assert_length_lt!(list3, n);
    }).is_ok());
}

#[rstest]
#[case(4)]
#[case(3)]
#[case(2)]
#[case(1)]
#[case(0)]
fn test_rules_length_lt_error<T>(list3: Vec<T>, #[case] n: usize)
    where T: RefUnwindSafe
{
    assert!(catch_unwind(|| {
        assert_length_lt!(list3, n, "Length should be < n!");
    }).is_err());
}

// ----------------------------------------------------------------
// Test length unique
// ----------------------------------------------------------------

#[rstest]
#[case(list1(),6)]
#[case(list2(),6)]
#[case(list3(),3)]
#[case(list4(),5)]
fn test_rules_length_unique<T>(#[case] list: Vec<T>, #[case] n: usize)
    where T: Eq + Hash + Clone + RefUnwindSafe
{
    assert!(catch_unwind(|| {
        assert_length_unique!(list, n);
    }).is_ok());
    assert!(catch_unwind(|| {
        assert_length_unique_ne!(list, n, "Should not have n unique elements!");
    }).is_err());
}

#[rstest]
#[case(list1(),9)]
#[case(list2(),10)]
#[case(list3(),4)]
#[case(list4(),6)]
fn test_rules_length_unique_ne<T>(#[case] list: Vec<T>, #[case] n: usize)
    where T: Eq + Hash + Clone + RefUnwindSafe
{
    assert!(catch_unwind(|| {
        assert_length_unique!(list, n, "Should have n unique elements!");
    }).is_err());
    assert!(catch_unwind(|| {
        assert_length_unique_ne!(list, n);
    }).is_ok());
}

#[rstest]
#[case(0)]
#[case(1)]
#[case(2)]
#[case(5)]
#[case(6)]
fn test_rules_length_unique_ge<T>(list1: Vec<T>, #[case] n: usize)
    where T: Eq + Hash + Clone + RefUnwindSafe
{
    assert!(catch_unwind(|| {
        assert_length_unique_ge!(list1, n);
    }).is_ok());
}

#[rstest]
#[case(7)]
#[case(100)]
fn test_rules_length_unique_ge_error<T>(list1: Vec<T>, #[case] n: usize)
    where T: Eq + Hash + Clone + RefUnwindSafe
{
    assert!(catch_unwind(|| {
        assert_length_unique_ge!(list1, n, "Should have >= n unique elements!");
    }).is_err());
}

#[rstest]
#[case(0)]
#[case(1)]
#[case(2)]
#[case(5)]
fn test_rules_length_unique_gt<T>(list1: Vec<T>, #[case] n: usize)
    where T: Eq + Hash + Clone + RefUnwindSafe
{
    assert!(catch_unwind(|| {
        assert_length_unique_gt!(list1, n);
    }).is_ok());
}

#[rstest]
#[case(6)]
#[case(7)]
#[case(100)]
fn test_rules_length_unique_gt_error<T>(list1: Vec<T>, #[case] n: usize)
    where T: Eq + Hash + Clone + RefUnwindSafe
{
    assert!(catch_unwind(|| {
        assert_length_unique_gt!(list1, n, "Should have > n unique elements!");
    }).is_err());
}

#[rstest]
#[case(100)]
#[case(7)]
#[case(6)]
fn test_rules_length_unique_le<T>(list1: Vec<T>, #[case] n: usize)
    where T: Eq + Hash + Clone + RefUnwindSafe
{
    assert!(catch_unwind(|| {
        assert_length_unique_le!(list1, n);
    }).is_ok());
}

#[rstest]
#[case(5)]
#[case(2)]
#[case(1)]
#[case(0)]
fn test_rules_length_unique_le_error<T>(list1: Vec<T>, #[case] n: usize)
    where T: Eq + Hash + Clone + RefUnwindSafe
{
    assert!(catch_unwind(|| {
        assert_length_unique_le!(list1, n, "Should have <= n unique elements!");
    }).is_err());
}

#[rstest]
#[case(100)]
#[case(7)]
fn test_rules_length_unique_lt<T>(list1: Vec<T>, #[case] n: usize)
    where T: Eq + Hash + Clone + RefUnwindSafe
{
    assert!(catch_unwind(|| {
        assert_length_unique_lt!(list1, n);
    }).is_ok());
}

#[rstest]
#[case(6)]
#[case(5)]
#[case(2)]
#[case(1)]
#[case(0)]
fn test_rules_length_unique_lt_error<T>(list1: Vec<T>, #[case] n: usize)
    where T: Eq + Hash + Clone + RefUnwindSafe
{
    assert!(catch_unwind(|| {
        assert_length_unique_lt!(list1, n, "Should have < n unique elements!");
    }).is_err());
}

// ----------------------------------------------------------------
// Test contains
// ----------------------------------------------------------------

#[rstest]
#[case(5)]
#[case(13)]
#[case(78)]
fn test_rules_contains<T>(list1: Vec<T>, #[case] element: T)
    where T: Eq + RefUnwindSafe
{
    assert!(catch_unwind(|| {
        assert_contains!(list1, element);
    }).is_ok());
    assert!(catch_unwind(|| {
        assert_not_contains!(list1, element, "Should not contain element!");
    }).is_err());
}

#[rstest]
#[case(100)]
#[case(32)]
fn test_rules_not_contains<T>(list1: Vec<T>, #[case] element: T)
    where T: Eq + RefUnwindSafe
{
    assert!(catch_unwind(|| {
        assert_contains!(list1, element, "Should contain element!");
    }).is_err());
    assert!(catch_unwind(|| {
        assert_not_contains!(list1, element);
    }).is_ok());
}

// ----------------------------------------------------------------
// Test comparison of lists by contents
// ----------------------------------------------------------------

#[rstest]
fn test_rules_eq_contents<T>(
    list1: Vec<T>,
    list2: Vec<T>,
    list3: Vec<T>,
    list4: Vec<T>,
)
    where T: Eq + Clone + Hash + Debug + RefUnwindSafe
{
    assert!(catch_unwind(|| {
        assert_eq_contents!(list1, list1);
        assert_eq_contents!(list1, list2);
    }).is_ok());

    assert!(catch_unwind(|| { assert_eq_contents!(list1, list3, "Should be equal as sets!"); }).is_err());
    assert!(catch_unwind(|| { assert_eq_contents!(list1, list4, "Should be equal as sets!"); }).is_err());
    assert!(catch_unwind(|| { assert_eq_contents!(list2, list3, "Should be equal as sets!"); }).is_err());
    assert!(catch_unwind(|| { assert_eq_contents!(list2, list4, "Should be equal as sets!"); }).is_err());
    assert!(catch_unwind(|| { assert_eq_contents!(list3, list4, "Should be equal as sets!"); }).is_err());

    assert!(catch_unwind(|| { assert_ne_contents!(list1, list1, "Should be unequal as sets!"); }).is_err());
    assert!(catch_unwind(|| {assert_ne_contents!(list1, list2, "Should be unequal as sets!"); }).is_err());

    assert!(catch_unwind(|| {
        assert_ne_contents!(list1, list3);
        assert_ne_contents!(list1, list4);
        assert_ne_contents!(list2, list3);
        assert_ne_contents!(list2, list4);
        assert_ne_contents!(list3, list4);
    }).is_ok());
}

#[rstest]
fn test_rules_subset_contents<T>(
    list1: Vec<T>,
    list2: Vec<T>,
    list3: Vec<T>,
    list4: Vec<T>,
)
    where T: Eq + Clone + Hash + Debug + RefUnwindSafe
{
    assert!(catch_unwind(|| {
        assert_subset!(list1, list1);
        assert_subset!(list1, list2);
        assert_subset!(list3, list1);
        assert_subset!(list3, list2);
        assert_subset!(list3, list4);
    }).is_ok());

    assert!(catch_unwind(|| { assert_subset!(list1, list3, "Should be a subset!"); }).is_err());
    assert!(catch_unwind(|| { assert_subset!(list2, list3, "Should be a subset!"); }).is_err());
    assert!(catch_unwind(|| { assert_subset!(list4, list3, "Should be a subset!"); }).is_err());
    assert!(catch_unwind(|| { assert_subset!(list4, list1, "Should be a subset!"); }).is_err());
    assert!(catch_unwind(|| { assert_subset!(list4, list2, "Should be a subset!"); }).is_err());

    assert!(catch_unwind(|| { assert_not_subset!(list1, list1, "Should not be a subset!"); }).is_err());
    assert!(catch_unwind(|| { assert_not_subset!(list1, list2, "Should not be a subset!"); }).is_err());
    assert!(catch_unwind(|| { assert_not_subset!(list3, list1, "Should not be a subset!"); }).is_err());
    assert!(catch_unwind(|| { assert_not_subset!(list3, list2, "Should not be a subset!"); }).is_err());
    assert!(catch_unwind(|| { assert_not_subset!(list3, list4, "Should not be a subset!"); }).is_err());

    assert!(catch_unwind(|| {
        assert_not_subset!(list1, list3);
        assert_not_subset!(list2, list3);
        assert_not_subset!(list4, list3);
        assert_not_subset!(list4, list1);
        assert_not_subset!(list4, list2);
    }).is_ok());
}