Documentation
use std::{cell::RefCell, sync::Arc};

#[derive(Default)]
pub struct Engine {
    current_reaction: RefCell<Option<Reaction>>,
    pub(crate) current_update: RefCell<Option<Update>>,
}

impl Engine {
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    pub(crate) fn track(&self, subscriptions: &Arc<RefCell<SubscriptionList>>) {
        let mut reaction = self.current_reaction.borrow_mut();
        let reaction = match reaction.as_mut() {
            Some(reaction) => reaction,
            None => return,
        };
        subscriptions
            .borrow_mut()
            .push(reaction.subscriptions.clone());
    }

    pub fn batch(self: &Arc<Self>) -> Batch {
        let mut current_update = self.current_update.borrow_mut();
        let root = current_update.is_none();
        if !root {
            return Batch { engine: None };
        }

        *current_update = Some(Update::new());
        Batch {
            engine: Some(self.clone()),
        }
    }

    pub fn react(&self, mut f: impl FnMut() + 'static) {
        let mut current_reaction = self.current_reaction.borrow_mut();
        assert!(current_reaction.is_none());
        *current_reaction = Some(Reaction::new());
        drop(current_reaction);

        f();

        let mut current_reaction = self.current_reaction.borrow_mut();
        let reaction = current_reaction.take().unwrap();
        reaction
            .subscriptions
            .borrow_mut()
            .push(Arc::new(RefCell::new(f)));
    }
}

fn slow_pop_front<T>(xs: &mut Vec<T>) -> Option<T> {
    if xs.is_empty() {
        None
    } else {
        Some(xs.remove(0))
    }
}

type SubscriptionList = Vec<Arc<RefCell<Vec<Subscription>>>>;
type Subscription = Arc<RefCell<dyn FnMut()>>;

struct Reaction {
    subscriptions: Arc<RefCell<Vec<Subscription>>>,
}

impl Reaction {
    pub fn new() -> Self {
        Reaction {
            subscriptions: Arc::new(RefCell::new(Vec::new())),
        }
    }
}

pub(crate) struct Update {
    updates: Vec<Box<dyn FnOnce()>>,
}

impl Update {
    pub fn new() -> Self {
        Update {
            updates: Vec::new(),
        }
    }
}

#[must_use]
pub struct Batch {
    engine: Option<Arc<Engine>>,
}

impl Drop for Batch {
    fn drop(&mut self) {
        let engine = match &mut self.engine {
            Some(x) => x,
            None => return,
        };

        loop {
            let head = {
                let mut update = engine.current_update.borrow_mut();
                slow_pop_front(&mut update.as_mut().unwrap().updates)
            };
            let head = match head {
                Some(x) => x,
                None => break,
            };
            head();
        }

        engine.current_update.borrow_mut().take().unwrap();
    }
}

#[cfg(test)]
mod tests {
    use crate::instance::{Atom, Engine};
    use std::{cell::RefCell, sync::Arc};

    #[test]
    fn react_simple() {
        let engine = Arc::new(Engine::new());
        let atom = Atom::new(engine.clone(), 1);
        let sink = Arc::new(RefCell::new(Vec::new()));
        engine.react({
            let atom = atom.clone();
            let sink = sink.clone();
            move || {
                sink.borrow_mut().push(*atom.get());
            }
        });
        assert_eq!(*sink.borrow(), [1]);
        atom.set(2);
        assert_eq!(*sink.borrow(), [1, 2]);
    }
}