use core::fmt::Debug;
use core::mem;
use std::sync::Arc;

use arc_swap::{ArcSwap, ArcSwapOption, Guard};

#[derive(Debug, Default)]
pub struct ConcurrentCell<T: Debug + Default> {
	active: ArcSwap<T>,
	inactive: ArcSwapOption<T>,
}

impl<T: Debug + Default> ConcurrentCell<T> {
	pub fn new(value: T) -> Self {
		let active = ArcSwap::new(Arc::new(value));
		let inactive = ArcSwapOption::new(Some(Arc::new(T::default())));

		Self { active, inactive }
	}

	#[inline]
	/// Only one writer is allowed at a time; allowing multiple is more likely to result in unintended race conditions than it is to enable intended behavior.
	pub fn writer(&self) -> Result<Writer<'_, T>, WriterAlreadyActive> {
		let inner = self.inactive.swap(None).ok_or(WriterAlreadyActive)?;

		Ok(Writer { cell: self, inner })
	}

	#[inline]
	pub fn get(&self) -> Guard<Arc<T>> {
		self.active.load()
	}
}

pub struct Writer<'cell, T: Debug + Default> {
	cell: &'cell ConcurrentCell<T>,
	inner: Arc<T>,
}

impl<T: Debug + Default> Writer<'_, T> {
	#[inline]
	pub fn get_mut(&mut self) -> &mut T {
		// SAFETY:  It's only ever possible for a single clone of this Arc to exist.  It only lives in an Arc because that's required for ArcSwap.
		Arc::get_mut(&mut self.inner).unwrap()
	}
}

impl<T: Debug + Default> Drop for Writer<'_, T> {
	fn drop(&mut self) {
		let swap = mem::take(&mut self.inner);
		let swap = self.cell.active.swap(swap);
		self.cell.inactive.swap(Some(swap));
	}
}

#[derive(Debug, thiserror::Error)]
#[error("There is already a writer active for this ConcurrentCell")]
pub struct WriterAlreadyActive;

#[cfg(test)]
mod tests {
	use super::*;

	#[test]
	fn test_single_threaded() {
		let cell = ConcurrentCell::new(1u8);
		let first_ref = cell.get();
		assert_eq!(**first_ref, 1);
		assert_eq!(**cell.inactive.load().as_ref().unwrap(), 0);

		*cell.writer().unwrap().get_mut() = 2;
		let second_ref = cell.get();
		assert_eq!(**second_ref, 2);
		assert_eq!(**cell.inactive.load().as_ref().unwrap(), 1);

		let _writer = cell.writer().unwrap();
		assert!(cell.writer().is_err());
	}
}
