//! Abstractions for spawning and managing actors in an asynchronous environment.
//! Currently hannibal supports both `tokio` and `smol` `async-global-executore`.

use std::any::type_name;

use crate::{Addr, StreamHandler, addr::OwningAddr, event_loop::EventLoop};

use super::{Actor, ActorHandle};

/// Enables an actor to spawn itself.
pub trait Spawnable: Actor {
    /// Spawns the actor and returns an `Addr` to it.
    /// # Defaults
    /// - `unbounded channel`
    /// - `recreate_from_default`: `false`
    fn spawn(self) -> Addr<Self> {
        self.spawn_owning().detach()
    }

    /// Spawns the actor and returns an [`OwningAddr`] to it.
    fn spawn_owning(self) -> OwningAddr<Self> {
        let event_loop = EventLoop::unbounded();
        Self::spawn_owning_in(self, event_loop)
    }

    /// Spawns an actor in a specific event-loop and returns an [`OwningAddr`] to it.
    #[doc(hidden)]
    fn spawn_owning_in(self, event_loop: EventLoop<Self>) -> OwningAddr<Self> {
        log::trace!("spawn {}", type_name::<Self>());
        let (event_loop, addr) = event_loop.create(self);
        let handle = ActorHandle::spawn(event_loop);
        OwningAddr::new(addr, handle)
    }
}

/// An actor that can handle a stream of messages.
pub trait StreamSpawnable<T>: Actor + StreamHandler<T::Item>
where
    T: futures::Stream + Unpin + Send + 'static,
    T::Item: 'static + Send,
    Self: StreamHandler<T::Item>,
{
    /// Spawns the actor on the provided stream and returns an `Addr` handle.
    fn spawn_on_stream(self, stream: T) -> Addr<Self> {
        self.spawn_owning_on_stream(stream).detach()
    }

    /// Spawns the actor on the provided stream and returns an `OwningAddr` handle.
    fn spawn_owning_on_stream(self, stream: T) -> OwningAddr<Self> {
        log::trace!("spawn on stream {}", type_name::<Self>());
        let (event_loop, addr) = EventLoop::unbounded().create_on_stream(self, stream);
        let handle = ActorHandle::spawn(event_loop);
        OwningAddr::new(addr, handle)
    }
}

/// An Actor that implements [`Default`].
pub trait DefaultSpawnable: Actor + Default {
    /// Spawns a new actor with default configuration.
    fn spawn_default() -> Addr<Self> {
        Self::spawn_default_owning().detach()
    }

    /// Spawns a new actor with default configuration.
    fn spawn_default_owning() -> OwningAddr<Self> {
        log::trace!("spawn defauwning {}", type_name::<Self>());
        let (event_loop, addr) = EventLoop::unbounded().create(Self::default());
        let handle = ActorHandle::spawn(event_loop);
        OwningAddr::new(addr, handle)
    }
}

/// Every actor that can be spawned.
impl<A: Actor> Spawnable for A {}

/// Every actor that implements `Default`.
impl<A: Actor + Default> DefaultSpawnable for A {}

/// Actors that implement `StreamHandler`.
impl<A, T> StreamSpawnable<T> for A
where
    A: Actor + StreamHandler<T::Item>,
    T: futures::Stream + Unpin + Send + 'static,
    T::Item: 'static + Send,
{
}

#[cfg(test)]
mod tests {
    #![allow(clippy::unwrap_used)]
    mod spawned_with_tokio {
        use crate::{
            actor::tests::{Ping, TokioActor},
            spawnable::{DefaultSpawnable, Spawnable},
        };

        #[tokio::test]
        async fn spawn() {
            let tokio_actor = TokioActor::<()>::default();
            let mut addr = tokio_actor.spawn();
            assert!(!addr.stopped());

            addr.call(Ping).await.unwrap();
            addr.stop().unwrap();
            addr.await.unwrap()
        }

        #[tokio::test]
        async fn spawn_default() {
            let mut addr = TokioActor::<()>::spawn_default();
            assert!(!addr.stopped());

            addr.call(Ping).await.unwrap();
            addr.stop().unwrap();
            addr.await.unwrap()
        }
    }
}
