use super::*;
use std::convert::TryInto;
use std::fmt::Debug;

const N: usize = 10;
const H: usize = 5;

type Fv<T> = FixedCapacityVec<T, N>;

fn test_api<T: Debug, F: FnMut() -> T>(mut f: F) {
    let f = &mut f;

    let mk_half = |f: &mut F| {
        let mut v = Fv::<T>::new();
        assert_eq!(v.len(), 0);
        assert!(v.is_empty());
        assert!(!v.is_full());

        println!("fill 0..{H}");
        for i in 0..H {
            v.push(f());
            assert_eq!(v.len(), i + 1);
        }
        assert!(!v.is_empty());
        assert!(!v.is_full());
        v
    };

    println!("make (1)");
    let v = mk_half(f);

    println!("fail conversion to boxed array");
    let mut v: Fv<T> = Box::<[T; N]>::try_from(v).unwrap_err();
    assert_eq!(v.len(), H);

    println!("deref");
    let slice = &v[..];
    println!("{slice:?}");

    println!("deref mut");
    let slice = &mut v[..];
    slice[1] = f();
    println!("{slice:?}");

    println!("drop 0..{H}");
    drop(v);

    println!("make empty (2)");
    let mut v = Fv::<T>::new();

    println!("fill 0..{N}");
    for _i in 0..N {
        v.push(f());
    }
    let FullError = v.try_push_or_discard(f()).unwrap_err();

    assert_eq!(v.len(), N);
    assert!(v.is_full());
    assert!(!v.is_empty());

    println!("conversion to boxed array");
    let v: Box<[T; N]> = v.try_into().expect("not into boxed array");

    println!("drop boxed array");
    drop(v);

    println!("conversion from boxed array");
    let ba = Box::new([f(), f()]);
    let v = FixedCapacityVec::from(ba);
    assert!(v.is_full());
    println!("{v:?}");
    drop(v);

    println!("conversion to Vec");
    let v = mk_half(f);
    let v: Vec<T> = v.into();
    println!("{v:?}");
    drop(v);

    println!("failed conversion from Vec");
    let mut v = vec![f(), f()];
    v.shrink_to_fit();
    let mut v: Vec<T> = match Fv::<T>::try_from(v) {
        Err(fail) => fail,
        Ok(_v) if Layout::new::<T>().size() == 0 => {
            // oh dear :-).  Just make a new one, then.
            vec![f(), f()]
        }
        Ok(y) => panic!("{:?}", y),
    };
    println!("successful conversion from Vec");
    v.reserve_exact(
        // If T is a ZST, .capacity() might be 0 or usize::MAX or god knows
        N.saturating_sub(v.capacity()),
    );
    let v: Fv<T> = v.try_into().unwrap();
    println!("{:?}", &v[..]);
    drop(v);

    println!("try_push");
    let mut v = mk_half(f);
    let rejected = loop {
        match v.try_push(f()) {
            Ok(()) => {}
            Err(n) => break n,
        }
    };
    println!("rejected {rejected:?}");
    drop(v);

    println!("pop");
    let mut v = mk_half(f);
    while let Some(item) = v.pop() {
        println!("popped {item:?}");
    }
    drop(v);
}

fn test_api_nopush<T: Debug, const N: usize>() {
    let v = FixedCapacityVec::<T, N>::new();
    assert_eq!(v.len(), 0);
    assert!(v.is_empty());

    if N == 0 {
        assert!(v.is_full());
        println!("successful conversion to boxed array");
        let v = FixedCapacityVec::<T, N>::new();
        let v = Box::<[T; N]>::try_from(v)
            .map_err(|_| ())
            .expect("not into boxed 0 slice");
        assert_eq!(v.len(), 0);
    } else {
        assert!(!v.is_full());
    }
}

#[test]
fn api_i32() {
    test_api(|| 42_i32);
}

use std::cell::RefCell;
use std::rc::Rc;

#[derive(Debug)]
struct HasDrop {
    index: usize,
    counted: Rc<RefCell<usize>>,
}
impl Drop for HasDrop {
    fn drop(&mut self) {
        println!("dropping {}", self.index);
        *self.counted.borrow_mut() -= 1;
    }
}

#[test]
fn zst() {
    test_api(|| ());
    test_api_nopush::<(), 0>();
    test_api_nopush::<(), 1>();
    test_api_nopush::<u32, 0>();

    #[derive(Debug)]
    enum Never {}
    test_api_nopush::<Never, 0>();
    test_api_nopush::<Never, 1>();
}

#[test]
fn api_has_drop() {
    let counted = Rc::new(RefCell::new(0));
    let mut next_index = 0;

    test_api(|| {
        let index = {
            let mut counted = counted.borrow_mut();
            *counted += 1;
            next_index += 1;
            next_index
        };
        println!("creating {}", index);
        HasDrop {
            index,
            counted: counted.clone(),
        }
    });

    assert_eq!(*counted.borrow(), 0);
}
