simi 0.2.1

A framework for building wasm front-end web application in Rust
use super::{Arm, Component, Element, KeyedFor, Node, NonKeyedFor, SubApp, Text, WebSysNode};
use crate::app::{Application, WeakMain};

/// Collection of virtual nodes
pub struct NodeList {
    pub(super) nodes: Vec<Node>,
}

#[allow(clippy::new_without_default)]
impl NodeList {
    /// New NodeList
    pub fn new() -> Self {
        Self { nodes: Vec::new() }
    }
    /// New NodeList with `capacity`
    pub fn with_capacity(capacity: usize) -> Self {
        Self {
            nodes: Vec::with_capacity(capacity),
        }
    }

    /// Make sure that the list is able to store a total `capacity` items without any more allocation
    pub fn new_capacity(&mut self, capacity: usize) {
        if capacity > self.nodes.capacity() {
            let additional = capacity - self.nodes.len();
            self.nodes.reserve(additional);
        }
    }

    /// No node?
    pub fn is_empty(&self) -> bool {
        self.nodes.is_empty()
    }

    /// Node count
    pub fn len(&self) -> usize {
        self.nodes.len()
    }

    /// Mutable reference to self.nodes
    pub fn nodes_mut(&mut self) -> &mut Vec<Node> {
        &mut self.nodes
    }

    /// Create a comment node that neverchange
    pub fn no_update_comment(
        &mut self,
        text: &str,
        real_parent: &web_sys::Node,
        next_sibling: Option<&web_sys::Node>,
    ) {
        self.nodes.push(Node::NeverChange(WebSysNode::new_comment(
            text,
            real_parent,
            next_sibling,
        )));
    }

    /// Create a text node that neverchange
    pub fn no_update_literal_string(
        &mut self,
        text: &str,
        real_parent: &web_sys::Node,
        next_sibling: Option<&web_sys::Node>,
    ) {
        self.nodes.push(Node::NeverChange(WebSysNode::new_text(
            text,
            real_parent,
            next_sibling,
        )));
    }

    /// Create a text node that neverchange
    pub fn no_update_text(
        &mut self,
        text: &impl ToString,
        real_parent: &web_sys::Node,
        next_sibling: Option<&web_sys::Node>,
    ) {
        self.nodes.push(Node::NeverChange(WebSysNode::new_text(
            &text.to_string(),
            real_parent,
            next_sibling,
        )));
    }

    /// Create/update a text node,
    /// return a reference to it if it is new
    pub fn text(&mut self, index: usize, value: &impl ToString) -> Option<&Text> {
        // FIXME: This is a generic method and a little big??
        if index >= self.nodes.len() {
            debug_assert_eq!(index, self.nodes.len());
            self.nodes
                .push(Node::TextExpression(Text::new(value.to_string())));
            match self.nodes.get_mut(index).expect("Last node added") {
                Node::TextExpression(text) => Some(text),
                _ => panic!("Expect a Node::TextExpression"),
            }
        } else {
            // It is safe here because
            //      index < self.nodes.len()
            // is true here
            match unsafe { self.nodes.get_unchecked_mut(index) } {
                Node::TextExpression(text) => {
                    text.update_text(value.to_string());
                    None
                }
                _ => panic!("Expect a Node::TextExpression"),
            }
        }
    }

    /// Create/get an element node
    pub fn element(&mut self, index: usize, tag: &str) -> (&mut Element, bool) {
        let new = if index >= self.nodes.len() {
            self.nodes.push(Node::Element(Element::new(tag)));
            true
        } else {
            false
        };
        match self.nodes.get_mut(index).expect("Node at given index") {
            Node::Element(e) => (e, new),
            _ => panic!("Expect a Node::Element at the given index"),
        }
    }

    /// Create/get a non-keyed-for node
    pub fn non_keyed_for(&mut self, index: usize, capacity: usize) -> &mut NonKeyedFor {
        if index >= self.nodes.len() {
            self.nodes.push(Node::NonKeyedFor(NonKeyedFor::with_capacity(capacity)));
        }
        match self.nodes.get_mut(index).expect("Node at given index") {
            Node::NonKeyedFor(f) => {
                // Why uncomment the next line severely affect the performance of
                // `run lots` but not `run` in https://github.com/krausest/js-framework-benchmark
                // f.node_list_mut().new_capacity(capacity);
                f
            }
            _ => panic!("Expect a Node::NonKeyedFor at the given index"),
        }
    }

    /// Create/get a non-keyed-for node
    pub fn keyed_for(&mut self, index: usize) -> &mut KeyedFor {
        if index >= self.nodes.len() {
            self.nodes.push(Node::KeyedFor(KeyedFor::new()));
        }
        match self.nodes.get_mut(index).expect("Node at given index") {
            Node::KeyedFor(f) => f,
            _ => panic!("Expect a Node::KeyedFor at the given index"),
        }
    }

    /// Create/get an arm for `if-clause` or `match`
    pub fn arm(&mut self, index: usize) -> &mut Arm {
        if index >= self.nodes.len() {
            self.nodes.push(Node::Arm(Arm::new()));
        }
        match self.nodes.get_mut(index).expect("Not at given index") {
            Node::Arm(a) => a,
            _ => panic!("Expect a Node::Arm at the given index"),
        }
    }

    /// Create/get a component node
    pub fn component(&mut self, index: usize) -> (bool, &mut Component) {
        let new = if index >= self.nodes.len() {
            self.nodes.push(Node::Component(Component::new()));
            true
        } else {
            false
        };
        match self.nodes.get_mut(index).expect("Node at given index") {
            Node::Component(a) => (new, a),
            _ => panic!("Expect a Node::Component at the given index"),
        }
    }

    /// Create/get sub app node as the only node of this node list
    pub fn sub_app<A, B>(
        &mut self,
        id: u8,
        real_parent: &web_sys::Node,
        parent_main: WeakMain<A>,
    ) -> &SubApp
    where
        A: Application,
        B: Application<Parent = WeakMain<A>>,
    {
        if self.nodes.is_empty() {
            self.nodes.push(Node::SubApp(SubApp::new::<A, B>(
                id,
                real_parent,
                parent_main,
            )));
        }
        // Its safe because self.nodes will never be empty here
        match unsafe { self.nodes.get_unchecked(0) } {
            Node::SubApp(sa) => sa,
            _ => panic!("Expect a Node::SubApp"),
        }
    }

    /// Create/get sub app node as the only node of this node list
    pub fn tab_sub_app<A, B>(
        &mut self,
        id: u8,
        real_parent: &web_sys::Node,
        parent_main: WeakMain<A>,
    ) -> &SubApp
    where
        A: Application,
        B: Application<Parent = WeakMain<A>>,
    {
        if self.nodes.is_empty() {
            self.nodes.push(Node::SubApp(SubApp::new::<A, B>(
                id,
                real_parent,
                parent_main,
            )));
        } else {
            // Its safe because self.nodes will never be empty here
            let same = match unsafe { self.nodes.get_unchecked(0) } {
                Node::SubApp(sa) => sa.is_same_app::<B>(id),
                _ => panic!("Expect a Node::SubApp"),
            };
            if !same {
                self.remove_real_node(real_parent);
                self.nodes.push(Node::SubApp(SubApp::new::<A, B>(
                    id,
                    real_parent,
                    parent_main,
                )));
            }
        }
        // Its safe because self.nodes will never be empty here
        match unsafe { self.nodes.get_unchecked(0) } {
            Node::SubApp(sa) => sa,
            _ => panic!("Expect a Node::SubApp"),
        }
    }

    /// Remove real nodes from real dom and return the next real sibling of the last real node of this list
    pub(crate) fn remove_and_get_next_sibling(
        &mut self,
        real_parent: &web_sys::Node,
    ) -> Option<web_sys::Node> {
        if self.nodes.is_empty() {
            return None;
        }
        let mut last = self.nodes.pop().expect("Simi bug: Arm must not be empty");
        let next_sibling = last.remove_and_get_next_sibling(real_parent);
        self.remove_real_node(real_parent);
        next_sibling
    }

    /// Remove real node from real dom
    pub(crate) fn remove_real_node(&mut self, real_parent: &web_sys::Node) {
        while let Some(mut node) = self.nodes.pop() {
            node.remove_real_node(real_parent);
        }
    }

    /// Remove all nodes after the given size from both real and virtual dom
    pub fn truncate(&mut self, size_after_removal: usize, real_parent: &web_sys::Node) {
        if size_after_removal == 0 {
            real_parent.set_text_content(None);
            self.nodes.truncate(0);
            return;
        }
        while self.nodes.len() > size_after_removal {
            let mut node = self.nodes.pop().expect("No more node to remove");
            node.remove_real_node(real_parent);
        }
    }

    /// Get next sibling of the last node
    pub fn get_next_sibling(&self) -> Option<web_sys::Node> {
        self.nodes
            .last()
            .map(super::Node::get_next_sibling)
            .unwrap_or_else(|| None)
    }

    /// Get first real node
    pub fn get_first_real_node(&self) -> Option<&web_sys::Node> {
        self.nodes
            .first()
            .map(super::Node::get_first_real_node)
            .unwrap_or_else(|| None)
    }

    /// Clone everything except for tracked attributes, and for-loop content
    pub(super) fn clone(&self, real_parent: &web_sys::Node) -> Self {
        let mut nodes = Vec::with_capacity(self.nodes.capacity());
        for n in self.nodes.iter() {
            nodes.push(n.clone(real_parent));
        }
        Self { nodes }
    }

    /// Start cloning self and all childs
    pub(super) fn start_clone(&self) -> Self {
        let mut nodes = Vec::with_capacity(self.nodes.capacity());
        for n in self.nodes.iter() {
            nodes.push(n.start_clone());
        }
        Self { nodes }
    }
}