/// A scoped thread-local memory place.
///
/// ```
/// sound::thread_context! { pub context Context: u32 };
///
/// Context::scoped(&mut value, || {
///   Context::with(|value| {
///     assert_eq!(*value, 52);
///
///     *value = 42;
///   });
///
///   Context::with(|value| assert_eq!(*value, 42));
/// });
/// ```
///
/// ## Safety
#[macro_export]
macro_rules! thread_context {
  ($($vis:vis static $name:ident: $ty:ty;)*) => {
    $(thread_context! {$vis $name $ty})*
  };

  ($vis:vis $name:ident $ty:ty) => {
    pub struct $name {
      data: std::cell::RefCell<Option<*mut $ty>>,
    }

    #[allow(unused)]
    impl $name {
      #[doc(hidden)]
      fn thread_local() -> &'static std::thread::LocalKey<$name> {
        std::thread_local! {
          static CTX: $name = $name { data: Default::default() };
        }

        &CTX
      }

      pub fn with<U>(func: impl FnOnce(&mut $ty) -> U) -> U {
        Self::thread_local().with(|context| {
          let borrow = context.data.borrow().expect("attempted to access context while it was vacant");
          let value = unsafe { &mut *borrow };

          func(value)
        })
      }

      pub fn scoped<U>(value: &mut $ty, func: impl FnOnce() -> U) -> U {
        Self::thread_local().with(|context| {
          assert_eq!(context.data.replace(Some(value)), None);
        });

        let retval = func();

        Self::thread_local().with(|context| context.data.replace(None));

        retval
      }
    }
  };
}

use std::marker::PhantomData;

#[doc(hidden)]
#[derive(Default)]
pub struct ContextMut<T>(PhantomData<T>);

unsafe impl<T> Sync for ContextMut<T> {}

#[allow(unused)]
impl<T> ContextMut<T> {
  pub const fn new() -> ContextMut<T> {
    ContextMut(PhantomData)
  }

  fn thread_local() -> &'static std::thread::LocalKey<std::cell::RefCell<Option<*mut std::ffi::c_void>>> {
    std::thread_local! {
      static CTX: std::cell::RefCell<Option<*mut std::ffi::c_void>> = Default::default();
    }

    &CTX
  }

  pub fn with<U>(&'static self, func: impl FnOnce(&mut T) -> U) -> U {
    Self::thread_local().with_borrow(|borrow| {
      let value = unsafe { &mut *(borrow.expect("attempted to access context while it was vacant") as *mut _) };

      func(value)
    })
  }

  pub fn scoped<'a, U>(&'a self, value: &'a mut T, func: impl 'a + FnOnce() -> U) -> U {
    Self::thread_local().with_borrow_mut(|context| {
      let ptr: *mut T = value;
      assert_eq!(context.replace(ptr as *mut _), None);
    });

    let retval = func();

    Self::thread_local().with_borrow_mut(|context| context.take());

    retval
  }
}

#[doc(hidden)]
#[derive(Default)]
pub struct Context<T>(PhantomData<T>);

unsafe impl<T> Sync for Context<T> {}

#[allow(unused)]
impl<T> Context<T> {
  pub const fn new() -> Context<T> {
    Context(PhantomData)
  }

  fn thread_local() -> &'static std::thread::LocalKey<std::cell::RefCell<Option<*const std::ffi::c_void>>> {
    std::thread_local! {
      static CTX: std::cell::RefCell<Option<*const std::ffi::c_void>> = Default::default();
    }

    &CTX
  }

  pub fn with<'a, U>(&'static self, func: impl FnOnce(&'a T) -> U) -> U {
    Self::thread_local().with_borrow(|borrow| {
      let value = unsafe { &mut *(borrow.expect("attempted to access context while it was vacant") as *mut _) };

      func(value)
    })
  }

  pub fn scoped<'a, U>(&'a self, value: &'a T, func: impl FnOnce() -> U) -> U {
    let ptr: *const T = value;
    Self::thread_local().with_borrow_mut(|context| {
      assert_eq!(context.replace(ptr as *const _), None);
    });

    let retval = func();

    Self::thread_local().with_borrow_mut(|context| context.take());

    retval
  }
}
