// Copyright 2025-2026 Gabriel Bjørnager Jensen.
//
// SPDX: MIT OR Apache-2.0

//! Tests for [`NVec`].

#![cfg(test)]

use core::iter;
use nvec::{NVec, n_vec};

#[cfg(target_has_atomic = "8")]
use core::sync::atomic::{AtomicU8, Ordering};

#[cfg(target_has_atomic = "8")]
#[test]
fn test_n_vec_clone() {
	static CLONE_COUNTER: AtomicU8 = AtomicU8::new(0);

	struct Foo;

	impl Clone for Foo {
		fn clone(&self) -> Self {
			CLONE_COUNTER.fetch_add(1, Ordering::Release);
			Self
		}
	}

	assert_eq!(
		CLONE_COUNTER.load(Ordering::Acquire),
		0,
	);

	let _ = NVec::<Foo, 256>::try_from_elem(Foo, 129).unwrap();

	assert_eq!(
		CLONE_COUNTER.load(Ordering::Acquire),
		128,
	);
}

#[cfg(target_has_atomic = "8")]
#[test]
fn test_n_vec_drop() {
	static DROP_COUNTER: AtomicU8 = AtomicU8::new(0);

	#[derive(Clone)]
	struct Foo;

	impl Drop for Foo {
		fn drop(&mut self) {
			DROP_COUNTER.fetch_add(1, Ordering::Release);
		}
	}

	assert_eq!(
		DROP_COUNTER.load(Ordering::Acquire),
		0,
	);

	let _ = NVec::<Foo, 256>::try_from_elem(Foo, 254).unwrap();

	assert_eq!(
		DROP_COUNTER.load(Ordering::Acquire),
		254,
	);

	DROP_COUNTER.store(0, Ordering::Release);

	let mut iter = NVec::<Foo, 256>::try_from_elem(Foo, 69).unwrap().into_iter();

	assert_eq!(
		DROP_COUNTER.load(Ordering::Acquire),
		0,
	);

	iter.next();

	assert_eq!(
		DROP_COUNTER.load(Ordering::Acquire),
		1,
	);

	drop(iter);

	assert_eq!(
		DROP_COUNTER.load(Ordering::Acquire),
		69,
	);
}

#[should_panic]
#[test]
fn test_n_vec_extend_out_of_bounds() {
	let mut v = NVec::<u128, 2>::new();
	v.extend([u128::MAX; 3]);
}

#[test]
fn test_n_vec_from_array() {
	assert_eq!(
		NVec::from_array([69_u8; 8]).as_slice(),
		[69; 8],
	);

	assert_eq!(
		NVec::from_array([u8::MAX; 16]).as_mut_slice(),
		[u8::MAX; 16],
	);
}

#[test]
fn test_n_vec_into_iter() {
	let mut iter = NVec::<i32, 16>::new().into_iter();

	assert_eq!(iter.as_slice(),  []);
	assert_eq!(iter.size_hint(), (0, Some(0)));
	assert_eq!(iter.next(),      None);
	assert_eq!(iter.as_slice(),  []);
	assert_eq!(iter.next_back(), None);
	assert_eq!(iter.as_slice(),  []);

	let mut iter = NVec::<bool, 64>::try_from_elem(true, 4).unwrap().into_iter();

	assert_eq!(iter.as_slice(),  [true; 4]);
	assert_eq!(iter.size_hint(), (4, Some(4)));
	assert_eq!(iter.next(),      Some(true));
	assert_eq!(iter.as_slice(),  [true; 3]);
	assert_eq!(iter.next(),      Some(true));
	assert_eq!(iter.as_slice(),  [true; 2]);
	assert_eq!(iter.next(),      Some(true));
	assert_eq!(iter.as_slice(),  [true; 1]);
	assert_eq!(iter.next(),      Some(true));
	assert_eq!(iter.as_slice(),  []);
	assert_eq!(iter.next(),      None);
	assert_eq!(iter.next_back(), None);
	assert_eq!(iter.as_slice(),  []);

	let mut iter = NVec::<i8, 3>::from_array([i8::MIN, 0, i8::MAX]).into_iter();

	assert_eq!(iter.as_slice(),  [i8::MIN, 0, i8::MAX]);
	assert_eq!(iter.size_hint(), (3, Some(3)));
	assert_eq!(iter.next_back(), Some(i8::MAX));
	assert_eq!(iter.as_slice(),  [i8::MIN, 0]);
	assert_eq!(iter.next(),      Some(i8::MIN));
	assert_eq!(iter.as_slice(),  [0]);
	assert_eq!(iter.next_back(), Some(0));
	assert_eq!(iter.as_slice(),  []);
	assert_eq!(iter.next(),      None);
	assert_eq!(iter.as_slice(),  []);
}

#[cfg(feature = "alloc")]
#[test]
fn test_n_vec_into_vec_boxed_slice() {
	let data = [
		'I',
		'L',
		'O',
		'V',
		'E',
		'Y',
		'O',
		'U',
	];

	let v = NVec::<char, 8>::from_array(data);

	assert_eq!(
		v.copied().into_vec(),
		data,
	);

	assert_eq!(
		*v.copied().into_boxed_slice(),
		data,
	);
}

#[test]
fn test_n_vec_macro() {
	let v: NVec<i32, 4> = n_vec![];

	assert_eq!(
		v,
		[],
	);

	let v: NVec<i32, 4> = n_vec![i32::MAX / 1, i32::MAX / 2, i32::MAX / 3, i32::MAX / 4];

	assert_eq!(
		v,
		NVec::<i32, 4>::from_array([i32::MAX / 1, i32::MAX / 2, i32::MAX / 3, i32::MAX / 4]),
	);
}

#[test]
fn test_n_vec_push_unchecked_mut() {
	let mut v = NVec::<u128, 1>::new();

	assert_eq!(v.capacity(),           1);
	assert_eq!(v.len(),                0);
	assert_eq!(v.remaining_capacity(), 1);
	assert_eq!(v.as_slice(),           []);

	// SAFETY: The vector has a capacity of `1` and
	// this is the only element that we are pushing
	// into it.
	unsafe { v.push_unchecked_mut(u128::from_be_bytes([67; 16])) };

	assert_eq!(v.capacity(),           1);
	assert_eq!(v.len(),                1);
	assert_eq!(v.remaining_capacity(), 0);
	assert_eq!(v.as_slice(),           [0x43434343434343434343434343434343]);
}

#[cfg(miri)]
#[test]
#[should_panic]
fn test_n_vec_push_unchecked_mut_out_of_bounds() {
	let mut v = NVec::<u8, 0>::new();

	unsafe { v.push_unchecked_mut(0) };
}

#[test]
fn test_n_vec_spare_capacity_mut() {
	let mut v = NVec::<i32, 12>::new();

	assert_eq!(v.len(),      00);
	assert_eq!(v.capacity(), 12);

	assert_eq!(v.spare_capacity_mut().len(), 12);

	v.try_push(0).unwrap();

	assert_eq!(v.len(),      01);
	assert_eq!(v.capacity(), 12);

	assert_eq!(v.spare_capacity_mut().len(), 11);

	v.try_resize(12, 0).unwrap();

	assert_eq!(v.len(),      12);
	assert_eq!(v.capacity(), 12);

	assert_eq!(v.spare_capacity_mut().len(), 0);
}

#[cfg(target_has_atomic = "8")]
#[test]
fn test_n_vec_truncate_clear() {
	static DROP_COUNTER: AtomicU8 = AtomicU8::new(0);

	#[derive(Clone)]
	struct Foo;

	impl Drop for Foo {
		fn drop(&mut self) {
			DROP_COUNTER.fetch_add(1, Ordering::Release);
		}
	}

	let mut v = NVec::<Foo, 256>::try_from_elem(Foo, 192).unwrap();

	assert_eq!(
		DROP_COUNTER.load(Ordering::Acquire),
		0,
	);

	v.truncate(128);

	assert_eq!(
		DROP_COUNTER.load(Ordering::Acquire),
		64,
	);

	v.truncate(96);

	assert_eq!(
		DROP_COUNTER.load(Ordering::Acquire),
		96,
	);

	v.clear();

	assert_eq!(
		DROP_COUNTER.load(Ordering::Acquire),
		192,
	);
}

#[test]
fn test_n_vec_try_extend_extend() {
	let mut v = NVec::<char, 6>::new();

	assert_eq!(v.capacity(),           6);
	assert_eq!(v.len(),                0);
	assert_eq!(v.remaining_capacity(), 6);
	assert_eq!(v.as_slice(),           []);

	v.extend(iter::once('A'));

	assert_eq!(v.capacity(),           6);
	assert_eq!(v.len(),                1);
	assert_eq!(v.remaining_capacity(), 5);
	assert_eq!(v.as_slice(),           ['A']);

	v.try_extend_from_slice(&['m', 'o', 'g']).unwrap();

	assert_eq!(v.capacity(),           6);
	assert_eq!(v.len(),                4);
	assert_eq!(v.remaining_capacity(), 2);
	assert_eq!(v.as_slice(),           ['A', 'm', 'o', 'g']);

	v.try_extend(['U', 'S', '6', '7']).unwrap_err();

	assert_eq!(v.capacity(),           6);
	assert_eq!(v.len(),                6);
	assert_eq!(v.remaining_capacity(), 0);
	assert_eq!(v.as_slice(),           ['A', 'm', 'o', 'g', 'U', 'S']);
}


#[test]
fn test_n_vec_try_push() {
	let mut v = NVec::<i8, 5>::new();

	assert_eq!(v.len(),      0);
	assert_eq!(v.capacity(), 5);
	assert_eq!(v,            []);

	v.try_push(i8::MAX).unwrap();

	assert_eq!(v.len(),      1);
	assert_eq!(v.capacity(), 5);
	assert_eq!(v,            [127]);

	v.try_push(i8::MAX / 2).unwrap();

	assert_eq!(v.len(),      2);
	assert_eq!(v.capacity(), 5);
	assert_eq!(v,            [127, 63]);

	v.try_push(0).unwrap();

	assert_eq!(v.len(),      3);
	assert_eq!(v.capacity(), 5);
	assert_eq!(v,            [127, 63, 0]);

	v.try_push(i8::MIN / 2).unwrap();

	assert_eq!(v.len(),      4);
	assert_eq!(v.capacity(), 5);
	assert_eq!(v,            [127, 63, 0, -64]);

	v.try_push(i8::MIN).unwrap();

	assert_eq!(v.len(),      5);
	assert_eq!(v.capacity(), 5);
	assert_eq!(v,            [127, 63, 0, -64, -128]);
}

#[test]
fn test_n_vec_try_reserve() {
	let mut v = NVec::<(), 48>::new();

	assert_eq!(v.len(), 0);

	v.try_reserve(48).unwrap();
	v.try_reserve(49).unwrap_err();

	assert_eq!(v.len(), 0);

	v.try_resize(47, ()).unwrap();

	assert_eq!(v.len(), 47);

	v.try_reserve(1).unwrap();
	v.try_reserve(2).unwrap_err();

	assert_eq!(v.len(), 47);

	v.try_resize(48, ()).unwrap();

	assert_eq!(v.len(), 48);

	v.try_reserve(0).unwrap();
	v.try_reserve(1).unwrap_err();

	assert_eq!(v.len(), 48);
}

#[cfg(target_has_atomic = "8")]
#[test]
fn test_n_vec_try_resize() {
	static CLONE_COUNTER: AtomicU8 = AtomicU8::new(0);

	#[derive(Debug, Eq, PartialEq)]
	struct Foo(pub u16);

	impl Clone for Foo {
		fn clone(&self) -> Self {
			CLONE_COUNTER.fetch_add(1, Ordering::Release);
			Self(self.0)
		}
	}

	let mut v = NVec::<Foo, 128>::from_array([const { Foo(420) }; 128]);

	assert_eq!(v, [const { Foo(420) }; 128]);

	v.try_resize(69, Foo(69)).unwrap();

	assert_eq!(v, [const { Foo(420) }; 69]);

	assert_eq!(
		CLONE_COUNTER.load(Ordering::Acquire),
		0,
	);

	v.try_resize(112, Foo(69)).unwrap();

	assert_eq!(&v[..69], [const { Foo(420) }; 69]);
	assert_eq!(&v[69..], [const { Foo(69) };  43]);

	assert_eq!(
		CLONE_COUNTER.load(Ordering::Acquire),
		43,
	);
}

#[test]
fn test_n_vec_zst() {
	assert_eq!(size_of::<NVec<(), { usize::MAX }>>(), size_of::<NVec<(), 0>>());

	let mut v = NVec::<(), { usize::MAX }>::new();

	assert_eq!(v.capacity(), usize::MAX);
	assert_eq!(v.len(),      0);

	assert_eq!(v.as_slice(),     []);
	assert_eq!(v.as_mut_slice(), []);

	for _ in 0..255 {
		v.try_push(()).unwrap();
	}

	assert_eq!(v.capacity(), usize::MAX);
	assert_eq!(v.len(),      255);

	assert_eq!(v.as_slice(),     [(); 255]);
	assert_eq!(v.as_mut_slice(), [(); 255]);
}
