// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

//! Test utilities, primarily targeted to custom LiteMap stores.

use crate::store::*;
use crate::LiteMap;
use alloc::vec::Vec;
use core::fmt::Debug;

// Test code
#[allow(clippy::expect_used)]
fn check_equivalence<'a, K, V, S0, S1>(mut a: S0, mut b: S1)
where
    K: Ord + Debug + PartialEq + 'a,
    V: Debug + PartialEq + 'a,
    S0: StoreMut<K, V> + StoreIterable<'a, K, V>,
    S1: StoreMut<K, V> + StoreIterable<'a, K, V>,
{
    let len = a.lm_len();
    assert_eq!(len, b.lm_len());
    if len == 0 {
        assert!(a.lm_is_empty());
        assert!(b.lm_is_empty());
    }
    for i in 0..len {
        let a_kv = a.lm_get(i);
        let b_kv = b.lm_get(i);
        assert!(a_kv.is_some());
        assert_eq!(a_kv, b_kv);
        let a_kv_mut = a.lm_get_mut(i);
        let b_kv_mut = b.lm_get_mut(i);
        assert!(a_kv_mut.is_some());
        assert_eq!(a_kv_mut, b_kv_mut);
    }
    for j in 0..len {
        let needle = a.lm_get(j).expect("j is in range").0;
        let a_binary = a.lm_binary_search_by(|k| k.cmp(needle));
        let b_binary = a.lm_binary_search_by(|k| k.cmp(needle));
        assert_eq!(Ok(j), a_binary);
        assert_eq!(Ok(j), b_binary);
    }
    assert!(a.lm_get(len).is_none());
    assert!(b.lm_get(len).is_none());
    assert_eq!(a.lm_last(), b.lm_last());
}

// Test code
#[allow(clippy::expect_used)]
fn check_into_iter_equivalence<K, V, S0, S1>(a: S0, b: S1)
where
    K: Ord + Debug + PartialEq,
    V: Debug + PartialEq,
    S0: StoreIntoIterator<K, V>,
    S1: StoreIntoIterator<K, V>,
{
    let a_vec = a.lm_into_iter().collect::<Vec<_>>();
    let b_vec = b.lm_into_iter().collect::<Vec<_>>();
    assert_eq!(a_vec, b_vec);
}

const SORTED_DATA: &[(u32, u64)] = &[
    (106, 4816),
    (147, 9864),
    (188, 8588),
    (252, 6031),
    (434, 2518),
    (574, 8500),
    (607, 3756),
    (619, 4965),
    (663, 2669),
    (724, 9211),
];

const RANDOM_DATA: &[(u32, u64)] = &[
    (546, 7490),
    (273, 4999),
    (167, 8078),
    (176, 2101),
    (373, 1304),
    (339, 9613),
    (561, 3620),
    (301, 1214),
    (483, 4453),
    (704, 5359),
];

// Test code
#[allow(clippy::expect_used)]
#[allow(clippy::panic)]
fn populate_litemap<S>(map: &mut LiteMap<u32, u64, S>)
where
    S: StoreMut<u32, u64> + Debug,
{
    assert_eq!(0, map.len());
    assert!(map.is_empty());
    for (k, v) in SORTED_DATA.iter() {
        #[allow(clippy::single_match)] // for clarity
        match map.try_append(*k, *v) {
            Some(_) => panic!("appending sorted data: {k:?} to {map:?}"),
            None => (), // OK
        };
    }
    assert_eq!(10, map.len());
    for (k, v) in RANDOM_DATA.iter() {
        #[allow(clippy::single_match)] // for clarity
        match map.try_append(*k, *v) {
            Some(_) => (), // OK
            None => panic!("cannot append random data: {k:?} to{map:?}"),
        };
    }
    assert_eq!(10, map.len());
    for (k, v) in RANDOM_DATA.iter() {
        map.insert(*k, *v);
    }
    assert_eq!(20, map.len());
}

/// Tests that a litemap that uses the given store as backend has behavior consistent with the
/// reference impl.
///
/// Call this function in a test with the store impl to test as a valid backend for LiteMap.
// Test code
#[allow(clippy::expect_used)]
pub fn check_store<'a, S>()
where
    S: StoreConstEmpty<u32, u64>
        + StoreMut<u32, u64>
        + StoreIterable<'a, u32, u64>
        + StoreFromIterator<u32, u64>
        + Clone
        + Debug
        + PartialEq
        + 'a,
{
    let mut litemap_test: LiteMap<u32, u64, S> = LiteMap::new();
    assert!(litemap_test.is_empty());
    let mut litemap_std = LiteMap::<u32, u64>::new();
    populate_litemap(&mut litemap_test);
    populate_litemap(&mut litemap_std);
    check_equivalence(litemap_test.clone().values, litemap_std.clone().values);

    litemap_test.retain(|_, v| v % 2 == 0);
    litemap_std.retain(|_, v| v % 2 == 0);
    assert_eq!(11, litemap_test.len());
    assert_eq!(11, litemap_std.len());
    check_equivalence(litemap_test.clone().values, litemap_std.clone().values);

    litemap_test
        .remove(&175)
        .ok_or(())
        .expect_err("does not exist");
    litemap_test.remove(&147).ok_or(()).expect("exists");
    litemap_std
        .remove(&175)
        .ok_or(())
        .expect_err("does not exist");
    litemap_std.remove(&147).ok_or(()).expect("exists");

    assert_eq!(10, litemap_test.len());
    assert_eq!(10, litemap_std.len());
    check_equivalence(litemap_test.clone().values, litemap_std.clone().values);

    litemap_test.clear();
    litemap_std.clear();
    check_equivalence(litemap_test.values, litemap_std.values);
}

/// Similar to [`check_store`] function, but also checks the validitiy of [`StoreIterableMut`]
/// trait.
// Test code
#[allow(clippy::expect_used)]
pub fn check_store_full<'a, S>()
where
    S: StoreConstEmpty<u32, u64>
        + StoreIterableMut<'a, u32, u64>
        + StoreIntoIterator<u32, u64>
        + StoreFromIterator<u32, u64>
        + Clone
        + Debug
        + PartialEq
        + 'a,
{
    let mut litemap_test: LiteMap<u32, u64, S> = LiteMap::new();
    assert!(litemap_test.is_empty());
    let mut litemap_std = LiteMap::<u32, u64>::new();
    populate_litemap(&mut litemap_test);
    populate_litemap(&mut litemap_std);
    check_equivalence(litemap_test.clone().values, litemap_std.clone().values);
    check_into_iter_equivalence(litemap_test.clone().values, litemap_std.clone().values);

    let extras_test = litemap_test.clone();
    let extras_test = litemap_test
        .extend_from_litemap(extras_test)
        .expect("duplicates");
    assert_eq!(extras_test, litemap_test);
    let extras_std = litemap_std.clone();
    check_equivalence(litemap_test.clone().values, litemap_std.clone().values);
    check_into_iter_equivalence(litemap_test.clone().values, litemap_std.clone().values);

    litemap_test.retain(|_, v| v % 2 == 0);
    litemap_std.retain(|_, v| v % 2 == 0);
    assert_eq!(11, litemap_test.len());
    assert_eq!(11, litemap_std.len());
    check_equivalence(litemap_test.clone().values, litemap_std.clone().values);
    check_into_iter_equivalence(litemap_test.clone().values, litemap_std.clone().values);

    let extras_test = litemap_test
        .extend_from_litemap(extras_test)
        .expect("duplicates");
    let extras_std = litemap_std
        .extend_from_litemap(extras_std)
        .expect("duplicates");
    assert_eq!(11, extras_test.len());
    assert_eq!(11, extras_std.len());
    assert_eq!(20, litemap_test.len());
    assert_eq!(20, litemap_std.len());
    check_equivalence(litemap_test.clone().values, litemap_std.clone().values);
    check_into_iter_equivalence(litemap_test.clone().values, litemap_std.clone().values);

    litemap_test
        .remove(&175)
        .ok_or(())
        .expect_err("does not exist");
    litemap_test.remove(&176).ok_or(()).expect("exists");
    litemap_std
        .remove(&175)
        .ok_or(())
        .expect_err("does not exist");
    litemap_std.remove(&176).ok_or(()).expect("exists");
    assert_eq!(19, litemap_test.len());
    assert_eq!(19, litemap_std.len());
    check_equivalence(litemap_test.clone().values, litemap_std.clone().values);
    check_into_iter_equivalence(litemap_test.clone().values, litemap_std.clone().values);

    litemap_test.clear();
    litemap_std.clear();
    check_equivalence(litemap_test.clone().values, litemap_std.clone().values);
    check_into_iter_equivalence(litemap_test.values, litemap_std.values);
}
