//! Deferred commands: queue spawn/despawn/insert/remove and apply them to the Universe after systems run.

use std::sync::Mutex;

use crate::ecs::archetype::ComponentBundle;
use crate::ecs::entity::Entity;
use crate::ecs::universe::Universe;

/// A single command to be applied later.
pub enum Command {
    SpawnEmpty,
    /// Box is internal; the inner type implements [`ComponentBundle`](crate::ecs::archetype::ComponentBundle).
    SpawnWith(Box<dyn ComponentBundleSend>),
    Despawn(Entity),
}

/// Trait for boxed component bundles used in commands. Required to be public because it appears in `Command::SpawnWith`.
pub trait ComponentBundleSend: Send + Sync {
    fn spawn_into(self: Box<Self>, universe: &mut Universe);
}

impl<B: ComponentBundle + Send + Sync + 'static> ComponentBundleSend for B {
    fn spawn_into(self: Box<Self>, universe: &mut Universe) {
        universe.spawn(*self);
    }
}

/// Thread-safe command queue. Apply to the universe after systems run.
pub struct Commands {
    queue: Mutex<Vec<Command>>,
}

impl Commands {
    pub fn new() -> Self {
        Self {
            queue: Mutex::new(Vec::new()),
        }
    }

    /// Queue spawning an entity with no components. The entity is created when you call `apply`.
    pub fn spawn_empty(&self) {
        self.queue
            .lock()
            .expect("commands lock")
            .push(Command::SpawnEmpty);
    }

    /// Queue spawning an entity with the given bundle. The entity is created when you call `apply`.
    pub fn spawn<B: ComponentBundle + Send + Sync + 'static>(&self, bundle: B) {
        self.queue
            .lock()
            .expect("commands lock")
            .push(Command::SpawnWith(Box::new(bundle)));
    }

    /// Queue despawning an entity.
    pub fn despawn(&self, entity: Entity) {
        self.queue
            .lock()
            .expect("commands lock")
            .push(Command::Despawn(entity));
    }

    /// Apply all queued commands to the universe. Call after all systems have run.
    pub fn apply(&self, universe: &mut Universe) {
        let mut queue = self.queue.lock().expect("commands lock");
        for cmd in queue.drain(..) {
            match cmd {
                Command::SpawnEmpty => {
                    universe.spawn_empty();
                }
                Command::SpawnWith(bundle) => {
                    bundle.spawn_into(universe);
                }
                Command::Despawn(entity) => {
                    universe.despawn(entity);
                }
            }
        }
    }
}

impl Default for Commands {
    fn default() -> Self {
        Self::new()
    }
}

unsafe impl Send for Commands {}
unsafe impl Sync for Commands {}
