#![allow(clippy::unit_arg)]

use std::panic::{catch_unwind, AssertUnwindSafe};

use proptest::{arbitrary::any, collection::vec, prelude::*, proptest};
use proptest_derive::Arbitrary;

use crate::inline_array::InlineArray;

type TestType = [usize; 16];

#[derive(Arbitrary, Debug)]
enum Action<A>
where
    A: Arbitrary,
    <A as Arbitrary>::Strategy: 'static,
{
    Push(A),
    Pop,
    Set((usize, A)),
    Insert(usize, A),
    Remove(usize),
    SplitOff(usize),
    Drain,
    Clear,
}

proptest! {
    #[test]
    fn test_actions(actions in vec(any::<Action<u32>>(), 0..super::action_count())) {
        let capacity = InlineArray::<u32, TestType>::CAPACITY;
        let mut chunk = InlineArray::<u32, TestType>::new();
        let mut guide: Vec<_> = chunk.iter().cloned().collect();
        for action in actions {
            match action {
                 Action::Push(value) => {
                    if chunk.is_full() {
                        assert!(catch_unwind(AssertUnwindSafe(|| chunk.push(value))).is_err());
                    } else {
                        chunk.push(value);
                        guide.push(value);
                    }
                }
                Action::Pop => {
                    assert_eq!(chunk.pop(), guide.pop());
                }
                Action::Set((index, value)) => {
                    if index >= chunk.len() {
                        assert!(catch_unwind(AssertUnwindSafe(|| chunk[index] = value)).is_err());
                    } else {
                        chunk[index] = value;
                        guide[index] = value;
                    }
                }
                Action::Insert(index, value) => {
                    if index >= chunk.len() || chunk.is_full() {
                        assert!(catch_unwind(AssertUnwindSafe(|| chunk.insert(index, value))).is_err());
                    } else {
                        chunk.insert(index, value);
                        guide.insert(index, value);
                    }
                }
                Action::Remove(index) => {
                    if index >= chunk.len() {
                        assert_eq!(None, chunk.remove(index));
                    } else {
                        assert_eq!(chunk.remove(index), Some(guide.remove(index)));
                    }
                }
                Action::SplitOff(index) => {
                    if index >= chunk.len() {
                        assert!(catch_unwind(AssertUnwindSafe(|| chunk.split_off(index))).is_err());
                    } else {
                        let chunk_off = chunk.split_off(index);
                        let guide_off = guide.split_off(index);
                        assert_eq!(chunk_off, guide_off);
                    }
                }
                Action::Drain => {
                    let drained: Vec<_> = chunk.drain().collect();
                    let drained_guide: Vec<_> = guide.drain(..).collect();
                    assert_eq!(drained, drained_guide);
                }
                Action::Clear => {
                    chunk.clear();
                    guide.clear();
                }
            }
            assert_eq!(chunk, guide);
            assert!(guide.len() <= capacity);
        }
    }
}
