Documentation
use std::sync::{Arc, atomic::Ordering};

use sdd::{AtomicOwned, Guard};
use tokio::task::futures::TaskLocalFuture;

use crate::{
	ServiceProvider,
	error::ServiceNotFoundError,
	scope::{Scope, ScopeProvider, refresh_atomic_scope},
};

pub type TaskScopeHandle = Arc<AtomicOwned<Arc<Scope>>>;

tokio::task_local! {
	static TASK_SCOPE: TaskScopeHandle;
}

pub struct TaskScopeProvider;

impl TaskScopeProvider {
	pub fn refresh_scope() -> Result<Arc<Scope>, ServiceNotFoundError> {
		let provider = crate::spo().ok_or(ServiceNotFoundError)?;
		Self::refresh_scope_with(&provider)
	}

	pub fn refresh_scope_with(
		provider: &ServiceProvider,
	) -> Result<Arc<Scope>, ServiceNotFoundError> {
		let handle = TASK_SCOPE.try_get().map_err(|_| ServiceNotFoundError)?;
		refresh_atomic_scope(handle.as_ref(), provider)
	}

	pub fn initialize_async<F>(scope: Scope, fut: F) -> TaskLocalFuture<TaskScopeHandle, F>
	where
		F: Future,
	{
		TASK_SCOPE.scope(Arc::new(AtomicOwned::new(Arc::new(scope))), fut)
	}

	pub fn initialize_sync<F, R>(scope: Scope, fun: F) -> R
	where
		F: FnOnce() -> R,
	{
		TASK_SCOPE.sync_scope(Arc::new(AtomicOwned::new(Arc::new(scope))), fun)
	}

	pub fn update_async<F>(scope: Scope, fut: F) -> TaskLocalFuture<TaskScopeHandle, F>
	where
		F: Future,
	{
		TASK_SCOPE.scope(Arc::new(AtomicOwned::new(Arc::new(scope))), fut)
	}

	pub fn update_sync<F, R>(scope: Scope, fun: F) -> R
	where
		F: FnOnce() -> R,
	{
		TASK_SCOPE.sync_scope(Arc::new(AtomicOwned::new(Arc::new(scope))), fun)
	}
}

impl super::ScopeDefault for TaskScopeProvider {
	fn initialize(_scope: Arc<Scope>) { unreachable!() }

	fn update(_scope: Arc<Scope>) { unreachable!() }
}

impl ScopeProvider for TaskScopeProvider {
	fn provide_scope(&self) -> Result<Arc<Scope>, ServiceNotFoundError> {
		let handle = TASK_SCOPE.try_get().map_err(|_| ServiceNotFoundError)?;
		let guard = Guard::new();
		handle
			.load(Ordering::SeqCst, &guard)
			.as_ref()
			.map(Arc::clone)
			.ok_or(ServiceNotFoundError)
	}
}