// Test with `cargo +nightly miri test` to check sanity!

#![allow(clippy::redundant_clone)]
#![allow(clippy::disallowed_names)]

use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt::Write;
use std::mem;
use std::sync::atomic::{AtomicUsize, Ordering::*};

use ecow::string::ToEcoString;
use ecow::{eco_vec, EcoString, EcoVec};

const ALPH: &str = "abcdefghijklmnopqrstuvwxyz";
const LIMIT: usize = EcoString::INLINE_LIMIT;

fn v<T>(value: T) -> Box<T> {
    Box::new(value)
}

#[test]
fn test_mem_size() {
    let word = mem::size_of::<usize>();
    assert_eq!(mem::size_of::<EcoVec<u8>>(), 2 * word);
    assert_eq!(mem::size_of::<Option<EcoVec<u8>>>(), 2 * word);

    if cfg!(target_endian = "little") {
        if cfg!(target_pointer_width = "32") {
            // Inline length still should be reasonable on 32-bit systems
            assert_eq!(mem::size_of::<EcoString>(), 4 * word);
            // No niche :(
            assert_eq!(mem::size_of::<Option<EcoString>>(), 5 * word);
        } else if cfg!(target_pointer_width = "64") {
            assert_eq!(mem::size_of::<EcoString>(), 2 * word);
            // No niche :(
            assert_eq!(mem::size_of::<Option<EcoString>>(), 3 * word);
        }
    }
}

#[test]
fn test_vec_macro() {
    assert_eq!(eco_vec![Box::new(1); 3], vec![v(1); 3]);
}

#[test]
fn test_vec_construction() {
    assert_eq!(EcoVec::<()>::default(), &[]);
    assert_eq!(EcoVec::from(vec![(); 100]), vec![(); 100]);
}

#[test]
fn test_from_vec_rc() {
    use std::rc::Rc;

    let x = Rc::new(());
    let v = vec![x.clone()];
    assert_eq!(Rc::strong_count(&x), 2);

    let vec = EcoVec::from(v);
    assert_eq!(Rc::strong_count(&x), 2, "Rc count should not change");
    assert_eq!(vec.len(), 1);
    assert!(Rc::ptr_eq(&x, &vec[0]));

    std::mem::drop(vec);
    assert_eq!(Rc::strong_count(&x), 1);
}

#[test]
fn test_vec_with_capacity() {
    let mut vec = EcoVec::with_capacity(3);
    assert_eq!(vec.capacity(), 3);
    let ptr = vec.as_ptr();
    vec.push(1);
    vec.push(2);
    vec.push(3);
    assert_eq!(ptr, vec.as_ptr());
    vec.push(4);
    assert_eq!(vec, [1, 2, 3, 4]);
}

#[test]
#[should_panic(expected = "capacity overflow")]
fn test_vec_with_capacity_fail() {
    EcoVec::<u8>::with_capacity(usize::MAX);
}

#[test]
fn test_vec_empty() {
    let mut first = EcoVec::with_capacity(3);
    assert!(first.is_empty());
    assert_eq!(first.len(), 0);
    first.push("hi".to_string());
    assert!(!first.is_empty());
    assert_eq!(first.len(), 1);
    let second = first.clone();
    first.clear();
    assert!(first.is_empty());
    first.clear();
    assert!(first.is_empty());
    assert_eq!(second.len(), 1);
    assert_eq!(second, ["hi".to_string()]);
}

#[test]
fn test_vec_make_mut() {
    let mut first = eco_vec![4, -3, 11, 6, 10];
    let ptr = first.as_ptr();
    first.make_mut()[1] -= 1;
    assert_eq!(ptr, first.as_ptr());
    let second = first.clone();
    first.make_mut().sort();
    assert_eq!(first, [-4, 4, 6, 10, 11]);
    assert_eq!(second, [4, -4, 11, 6, 10]);
}

#[test]
fn test_vec_push() {
    let mut first = EcoVec::new();
    first.push(1);
    first.push(2);
    first.push(3);
    assert_eq!(first.len(), 3);
    let mut second = first.clone();
    let third = second.clone();
    let _ = third.clone();
    second.push(4);
    assert_eq!(second.len(), 4);
    assert_eq!(first, [1, 2, 3]);
    assert_eq!(second, [1, 2, 3, 4]);
    assert_eq!(third, [1, 2, 3]);
    assert_ne!(first.as_ptr(), second.as_ptr());
    assert_eq!(first.as_ptr(), third.as_ptr());
}

#[test]
fn test_vec_pop() {
    let mut first = EcoVec::new();
    assert_eq!(first.pop(), None);
    first.push(v("a"));
    let ptr = first.as_ptr();
    assert_eq!(first.pop(), Some(v("a")));
    first.push(v("b"));
    assert_eq!(ptr, first.as_ptr());
    let second = first.clone();
    assert_eq!(first[0], v("b"));
    assert_eq!(ptr, first.as_ptr());
    assert_eq!(first.pop(), Some(v("b")));
    assert_eq!(first, []);
    assert_eq!(second, [v("b")]);
}

#[test]
fn test_vec_insert() {
    let mut first = EcoVec::new();
    first.insert(0, "okay");
    let ptr = first.as_ptr();
    first.insert(0, "reverse");
    let mut second = first.clone();
    first.insert(2, "a");
    first.insert(1, "b");
    second.insert(2, "last");
    assert_eq!(first, ["reverse", "b", "okay", "a"]);
    assert_eq!(second, ["reverse", "okay", "last"]);
    assert_ne!(ptr, first.as_ptr());
    assert_eq!(ptr, second.as_ptr());
}

#[test]
#[should_panic(expected = "index is out bounds (index: 4, len: 3)")]
fn test_vec_insert_fail() {
    EcoVec::from([1, 2, 3]).insert(4, 0);
}

#[test]
fn test_vec_remove() {
    let mut first = EcoVec::with_capacity(4);
    let ptr = first.as_ptr();
    first.extend_from_slice(&[v(2), v(4), v(1)]);
    let second = first.clone();
    assert_eq!(first.remove(1), v(4));
    assert_eq!(first, [v(2), v(1)]);
    assert_eq!(second, [v(2), v(4), v(1)]);
    assert_ne!(ptr, first.as_ptr());
    assert_eq!(ptr, second.as_ptr());
}

#[test]
#[should_panic(expected = "index is out bounds (index: 4, len: 3)")]
fn test_vec_remove_fail() {
    EcoVec::from([1, 2, 3]).remove(4);
}

#[test]
fn test_vec_truncate() {
    let mut vec = eco_vec!["ok"; 10];
    vec.truncate(13);
    vec.truncate(3);
    assert_eq!(vec, ["ok"; 3]);

    let mut cloned = vec.clone();
    cloned.truncate(2);
    assert_eq!(cloned, ["ok"; 2]);
}

#[test]
fn test_vec_drain() {
    let mut vec = EcoVec::from_elem(v("a"), 10);
    vec.drain(3..6);
    assert_eq!(vec.len(), 7);

    // Manually pull an item, before dropping the drain.
    let mut drain = vec.drain(5..);
    drain.next();
    drop(drain);
    assert_eq!(vec.len(), 5);

    let mut cloned = vec.clone();
    cloned.drain(..2);
    assert_eq!(cloned.len(), 3);

    let drained = cloned.drain(..).collect::<Vec<_>>();
    assert_eq!(drained.len(), 3);
    assert_eq!(cloned, []);
}

#[test]
fn test_vec_splice() {
    let mut vec = eco_vec!["a"; 6];
    // Inserted iterator is smaller.
    vec.splice(2..4, ["b"; 1]);
    assert_eq!(vec, ["a", "a", "b", "a", "a"]);

    // Inserted iterator is larger.
    let mut cloned = vec.clone();
    cloned.splice(3..4, ["c"; 3]);
    assert_eq!(cloned, ["a", "a", "b", "c", "c", "c", "a"]);

    // Inserted iterator is the same size.
    cloned.splice(2..6, ["d"; 4]);
    assert_eq!(cloned, ["a", "a", "d", "d", "d", "d", "a"]);

    // Insert an interator that doesn't have exact size hints.
    cloned.splice(1..6, ["e"; 3].into_iter().filter(|_| true));
    assert_eq!(cloned, ["a", "e", "e", "e", "a"]);
}

#[test]
fn test_vec_extend() {
    let mut vec = EcoVec::new();
    vec.extend_from_slice(&[]);
    vec.extend_from_slice(&[2, 3, 4]);
    assert_eq!(vec, [2, 3, 4]);
}

#[test]
fn test_vec_into_iter() {
    let first = eco_vec![v(2), v(4), v(5)];
    let mut second = first.clone();
    assert_eq!(first.clone().into_iter().count(), 3);
    assert_eq!(second.clone().into_iter().rev().collect::<Vec<_>>(), [v(5), v(4), v(2)]);
    second.clear();
    assert_eq!(second.into_iter().collect::<Vec<_>>(), []);
    assert_eq!(first.clone().into_iter().collect::<Vec<_>>(), [v(2), v(4), v(5)]);
    let mut iter = first.into_iter();
    assert_eq!(iter.len(), 3);
    assert_eq!(iter.next(), Some(v(2)));
    assert_eq!(iter.next_back(), Some(v(5)));
    assert_eq!(iter.as_slice(), [v(4)]);
    drop(iter);
}

#[test]
fn test_vec_zst() {
    static COUNTER: AtomicUsize = AtomicUsize::new(0);

    #[derive(Clone)]
    struct Potato;
    impl Drop for Potato {
        fn drop(&mut self) {
            COUNTER.fetch_add(1, SeqCst);
        }
    }

    let mut vec = EcoVec::new();
    for _ in 0..1000 {
        vec.push(Potato);
    }
    assert_eq!(vec.len(), 1000);
    drop(vec);

    assert_eq!(COUNTER.load(SeqCst), 1000);
}

#[test]
fn test_vec_huge_alignment() {
    #[derive(Debug, PartialEq, Clone)]
    #[repr(align(128))]
    struct B(&'static str);
    let mut vec: EcoVec<B> =
        "hello, world! what's going on?".split_whitespace().map(B).collect();

    assert_eq!(vec.len(), 5);
    assert_eq!(vec.capacity(), 8);
    assert_eq!(vec, [B("hello,"), B("world!"), B("what's"), B("going"), B("on?")]);
    assert_eq!(vec.pop(), Some(B("on?")));
    assert_eq!(vec.len(), 4);
    assert_eq!(vec.last(), Some(&B("going")));
    assert_eq!(vec.remove(1), B("world!"));
    assert_eq!(vec.len(), 3);
    assert_eq!(vec, [B("hello,"), B("what's"), B("going")]);
    assert_eq!(vec[1], B("what's"));
    vec.push(B("where?"));
    vec.insert(1, B("wonder!"));
    assert_eq!(vec, [B("hello,"), B("wonder!"), B("what's"), B("going"), B("where?")]);
    vec.retain(|b| b.0.starts_with('w'));
    assert_eq!(vec, [B("wonder!"), B("what's"), B("where?")]);
    vec.truncate(1);
    assert_eq!(vec.last(), vec.first());

    let empty: EcoVec<B> = EcoVec::new();
    assert_eq!(empty, &[]);
}

#[test]
#[should_panic(expected = "dropped the hot potato!")]
#[allow(unused_must_use)]
fn test_vec_drop_panic() {
    #[derive(Clone)]
    struct Potato;
    impl Drop for Potato {
        fn drop(&mut self) {
            panic!("dropped the hot potato!");
        }
    }

    eco_vec![Potato];
}

#[test]
#[should_panic(expected = "dropped the hot potato!")]
fn test_vec_clear_drop_panic() {
    #[derive(Clone)]
    struct Potato;
    impl Drop for Potato {
        fn drop(&mut self) {
            panic!("dropped the hot potato!");
        }
    }

    let mut vec = eco_vec![Potato];
    vec.clear();
}

#[test]
fn test_array_from_vec() {
    let array = [String::from("foo"), String::from("bar")];
    let a = EcoVec::from(array.clone());
    let b = a.clone();
    let c: [String; 2] = a.try_into().unwrap();
    assert_eq!(b, c);
    let d = b.clone();
    assert_eq!(c, array);
    assert_eq!(d, array);
    drop(b);
    assert_eq!(c, array);
    drop(d);
    assert_eq!(c, array);

    assert_eq!(<[String; 0]>::try_from(EcoVec::new()).unwrap(), <[String; 0]>::default());
}

#[test]
fn test_str_new() {
    // Test inline strings.
    assert_eq!(EcoString::new(), "");
    assert_eq!(EcoString::from('a'), "a");
    assert_eq!(EcoString::from('😀'), "😀");
    assert_eq!(EcoString::from("abc"), "abc");

    // Test around the inline limit.
    assert_eq!(EcoString::from(&ALPH[..LIMIT - 1]), ALPH[..LIMIT - 1]);
    assert_eq!(EcoString::from(&ALPH[..LIMIT]), ALPH[..LIMIT]);
    assert_eq!(EcoString::from(&ALPH[..LIMIT + 1]), ALPH[..LIMIT + 1]);

    // Test heap string.
    assert_eq!(EcoString::from(ALPH), ALPH);
}

#[test]
fn test_str_push() {
    let mut v = EcoString::new();
    v.push('a');
    v.push('b');
    v.push_str("cd😀");
    assert_eq!(v, "abcd😀");
    assert_eq!(v.len(), 8);

    // Test fully filling the inline storage.
    v.push_str("efghijk");
    assert_eq!(v.len(), 15);

    // Test spilling with `push`.
    let mut a = v.clone();
    assert_eq!(a, "abcd😀efghijk");
    a.push('l');
    assert_eq!(a, "abcd😀efghijkl");
    assert_eq!(a.len(), 16);

    // Test spilling with `push_str`.
    let mut b = v.clone();
    b.push_str("lmn");
    assert_eq!(b, "abcd😀efghijklmn");
    assert_eq!(b.len(), 18);

    // v should be unchanged.
    assert_eq!(v.len(), 15);
}

#[test]
fn test_str_insert() {
    let mut v = EcoString::new();
    v.insert(0, 'a');
    v.insert(1, '😀');
    v.insert_str(1, "bcd");
    assert_eq!(v, "abcd😀");
    assert_eq!(v.len(), 8);

    // Test fully filling the inline storage.
    v.insert_str(8, "efghijk");
    assert_eq!(v.len(), 15);

    // Test spilling with `insert`.
    let mut a = v.clone();
    assert_eq!(a, "abcd😀efghijk");
    a.insert(8, '_');
    assert_eq!(a, "abcd😀_efghijk");
    assert_eq!(a.len(), 16);

    // Test spilling with `insert_str`.
    let mut b = v.clone();
    b.insert_str(2, "._.");
    assert_eq!(b, "ab._.cd😀efghijk");
    assert_eq!(b.len(), 18);

    // v should be unchanged.
    assert_eq!(v.len(), 15);
}

#[test]
fn test_str_pop() {
    // Test with inline string.
    let mut v = EcoString::from("Hello World!");
    assert_eq!(v.pop(), Some('!'));
    assert_eq!(v, "Hello World");

    // Remove one-by-one.
    for _ in 0..10 {
        v.pop();
    }

    assert_eq!(v, "H");
    assert_eq!(v.pop(), Some('H'));
    assert_eq!(v, "");
    assert!(v.is_empty());

    // Test with large string.
    let mut v = EcoString::from(ALPH);
    assert_eq!(v.pop(), Some('z'));
    assert_eq!(v.len(), 25);
}

#[test]
fn test_str_remove() {
    // Test with inline string.
    let mut v = EcoString::from("Hello World!");
    assert_eq!(v.remove(0), 'H');
    assert_eq!(v, "ello World!");

    // Remove one-by-one.
    for i in (4..10).rev() {
        v.remove(i);
    }
    assert_eq!(v, "ello!");

    for _ in 0..5 {
        v.remove(0);
    }
    assert!(v.is_empty());

    // Test with large string.
    let mut v = EcoString::from(ALPH);
    assert_eq!(v.remove(21), 'v');
    assert_eq!(v.len(), 25);
}

#[test]
fn test_str_index() {
    // Test that we can use the index syntax.
    let v = EcoString::from("abc");
    assert_eq!(&v[..2], "ab");
}

#[test]
fn test_str_case() {
    assert_eq!(EcoString::new().to_uppercase(), "");
    assert_eq!(EcoString::from("abc").to_uppercase(), "ABC");
    assert_eq!(EcoString::from("AΣ").to_lowercase(), "aς");
    assert_eq!(
        EcoString::from("a").repeat(100).to_uppercase(),
        EcoString::from("A").repeat(100)
    );
    assert_eq!(
        EcoString::from("Ö").repeat(20).to_lowercase(),
        EcoString::from("ö").repeat(20)
    );
}

#[test]
fn test_str_repeat() {
    // Test with empty string.
    assert_eq!(EcoString::new().repeat(0), "");
    assert_eq!(EcoString::new().repeat(100), "");

    // Test non-spilling and spilling case.
    let v = EcoString::from("abc");
    assert_eq!(v.repeat(0), "");
    assert_eq!(v.repeat(3), "abcabcabc");
    assert_eq!(v.repeat(5), "abcabcabcabcabc");
}

#[test]
fn test_str_inline_okay() {
    assert_eq!(EcoString::inline("hello"), "hello");
}

#[test]
#[should_panic(expected = "exceeded inline capacity")]
fn test_str_inline_capacity_exceeded() {
    EcoString::inline("this is a pretty long string");
}

#[test]
fn test_str_clear() {
    let mut inline_clear = EcoString::from("foo");
    inline_clear.clear();
    assert_eq!(inline_clear, "");

    let mut spilled_clear: EcoString = std::iter::repeat('a').take(100).collect();
    let cloned = spilled_clear.clone();
    spilled_clear.clear();
    assert_eq!(spilled_clear, "");
    assert_eq!(cloned.len(), 100);
}

#[test]
fn test_str_construction() {
    let from_cow = EcoString::from(Cow::Borrowed("foo"));
    let from_char_iter: EcoString = "foo".chars().collect();
    let from_eco_string_iter: EcoString =
        [EcoString::from("f"), EcoString::from("oo")].into_iter().collect();

    assert_eq!(from_cow, from_char_iter);
    assert_eq!(from_char_iter, from_eco_string_iter);
    assert_eq!(from_eco_string_iter, "foo");

    let str_from_eco_string_ref = String::from(&from_cow);
    let str_from_eco_string = String::from(from_cow);

    assert_eq!(str_from_eco_string, str_from_eco_string_ref);
    assert_eq!(str_from_eco_string_ref, "foo");

    let from_string_iter: EcoString = ["foo", " ", "bar"].into_iter().collect();

    assert_eq!(from_string_iter, "foo bar");
}

#[test]
fn test_str_extend() {
    let mut s = EcoString::from("Hello, ");
    s.extend("world!".chars());

    s.extend([" How ", "are ", "you", "?"]);

    assert_eq!(s, "Hello, world! How are you?");
}

#[test]
fn test_str_add() {
    let hello = EcoString::from("Hello, ");
    let world = EcoString::from("world!");

    let add = hello.clone() + world.clone();
    let mut add_assign = hello.clone();
    add_assign += world;

    assert_eq!(add, add_assign);
    assert_eq!(add, "Hello, world!");

    let add_str = hello.clone() + "world!";
    let mut add_assign_str = hello.clone();
    add_assign_str += "world!";

    assert_eq!(add_str, add_assign_str);
    assert_eq!(add_str, "Hello, world!");
}

#[test]
fn test_str_complex() {
    let mut foo = EcoString::default();
    foo.write_char('f').unwrap();
    foo.write_str("oo").unwrap();

    let bar = EcoString::from("bar");

    let mut hash_map: HashMap<_, EcoVec<_>> = [
        (foo.clone(), eco_vec![foo.clone(), bar.clone(), foo]),
        (bar.clone(), eco_vec![bar; 1]),
    ]
    .into_iter()
    .collect();

    hash_map.get_mut("foo").unwrap().make_mut().sort();

    assert_eq!(hash_map.get("foo").unwrap(), &["bar".into(), "foo".into(), "foo".into()]);
    assert_eq!(hash_map.get("bar").unwrap(), &["bar".into()]);
}

#[test]
fn test_str_to_eco_string() {
    let num = 123456789;
    assert_eq!(num.to_eco_string(), "123456789");
}
