#![cfg(feature = "loom")]

use bimodal_array::BimodalArrayError;
use bimodal_array::try_bimodal_array;
use loom::sync::Arc;
use loom::sync::atomic::AtomicBool;
use loom::sync::atomic::AtomicUsize;
use loom::sync::atomic::Ordering;
use loom::thread;

#[test]
fn element_guard_excludes_array_guard() {
    loom::model(|| {
        let (mut array_handle, mut element_handles) = try_bimodal_array(vec![0usize]).unwrap();
        let mut element = element_handles.pop().unwrap();

        let ready = Arc::new(AtomicBool::new(false));
        let release = Arc::new(AtomicBool::new(false));

        let worker = {
            let ready = ready.clone();
            let release = release.clone();
            thread::spawn(move || {
                let _guard = element.lock().unwrap();
                ready.store(true, Ordering::Release);

                while !release.load(Ordering::Acquire) {
                    thread::yield_now();
                }
            })
        };

        while !ready.load(Ordering::Acquire) {
            thread::yield_now();
        }

        assert!(matches!(
            array_handle.lock(),
            Err(BimodalArrayError::CouldNotAcquireArrayLock)
        ));

        release.store(true, Ordering::Release);
        worker.join().unwrap();

        assert!(array_handle.lock().is_ok());
    });
}

#[test]
fn array_guard_excludes_element_guard() {
    loom::model(|| {
        let (mut array_handle, mut element_handles) = try_bimodal_array(vec![0usize]).unwrap();
        let mut element = element_handles.pop().unwrap();

        let _array_guard = array_handle.lock().unwrap();
        let worker = thread::spawn(move || {
            assert!(matches!(
                element.lock(),
                Err(BimodalArrayError::CouldNotAcquireElementLock)
            ));
        });

        worker.join().unwrap();
    });
}

struct LoomDropItem {
    drops: Arc<AtomicUsize>,
}

impl Drop for LoomDropItem {
    fn drop(&mut self) {
        self.drops.fetch_add(1, Ordering::Relaxed);
    }
}

#[test]
fn handles_dropped_from_different_threads_free_data_once() {
    loom::model(|| {
        let drops = Arc::new(AtomicUsize::new(0));
        let data = vec![
            LoomDropItem {
                drops: drops.clone(),
            },
            LoomDropItem {
                drops: drops.clone(),
            },
        ];
        let (array_handle, element_handles) = try_bimodal_array(data).unwrap();

        let drop_array = thread::spawn(move || drop(array_handle));
        let drop_elements = thread::spawn(move || drop(element_handles));

        drop_array.join().unwrap();
        drop_elements.join().unwrap();

        assert_eq!(drops.load(Ordering::Relaxed), 2);
    });
}
