simi 0.2.1

A framework for building wasm front-end web application in Rust
//! This module, together with `crate::dom` defines the Simi framework
use crate::dom::NodeList;
use std::cell::{Cell, RefCell};
use std::rc::{Rc, Weak};

/// Indicate if we should update the ui or not
pub type UpdateView = bool;

/// Required method for the app context
pub trait AppContext<A: Application> {
    /// Create a new app context
    fn init(main: WeakMain<A>, parent: A::Parent) -> Self;
}

impl<A: Application> AppContext<A> for () {
    fn init(_: WeakMain<A>, _: A::Parent) -> Self {}
}

/// This trait will turn your struct/enum into a simi-app's data state
pub trait Application: Sized + 'static {
    /// Message manage by the app
    type Message;
    /// App context
    type Context: AppContext<Self>;
    /// Parent app that this app is belong to and render in
    type Parent;
    /// Init the app state
    fn init() -> Self;
    /// Create context
    //fn init_context(main: WeakMain<Self>, parent: Self::Parent) -> Self::Context;
    /// Update the app state
    fn update(&mut self, m: Self::Message, cp: ContextPlus<Self>) -> UpdateView;
    /// Render a simi app using self as the app state.
    /// Note that the parameter must be named `context`.
    /// You should not rename its name, otherwise, it will fail to compile.
    fn render(&self, context: RenderContext<Self>);
    /// Raise when mount done
    #[allow(unused_variables)]
    fn mounted(&self, cp: ContextPlus<Self>) {}
}

/// Trait for a simi's component
pub trait Component<A: Application> {
    /// A method for rendering the component
    /// Note that the parameter must be named `context`.
    /// You should not rename its name, otherwise, it will fail to compile.
    fn render(&self, context: RenderContext<A>);
}

/// The center of a simi app
pub struct Main<A: Application> {
    app: A,
    root_list: NodeList,
    real_root: web_sys::Element,
    context: Option<A::Context>,
    weak: Option<WeakMain<A>>,
    sub_apps: SubAppCollection,
}

/// Manage a Simi app
pub struct RcMain<A: Application> {
    rc: Rc<RefCell<Main<A>>>,
}

/// Contains a weak link to the Main struct
pub struct WeakMain<A: Application> {
    weak: Weak<RefCell<Main<A>>>,
}

impl<A: Application> std::clone::Clone for WeakMain<A> {
    fn clone(&self) -> Self {
        Self {
            weak: Weak::clone(&self.weak),
        }
    }
}

impl<A: Application> Main<A> {
    fn update(&mut self, m: A::Message) {
        let context = self
            .context
            .as_mut()
            .unwrap_or_else(|| panic!("Update called when the app is not started yet"));
        let cp = ContextPlus {
            context,
            sub_apps: &self.sub_apps,
        };
        let update_view = self.app.update(m, cp);
        if update_view {
            self.render()
        }
    }
    fn render(&mut self) {
        let Main {
            ref app,
            ref mut root_list,
            real_root,
            weak,
            ref mut sub_apps,
            ..
        } = self;
        sub_apps.map.clear();
        //let context = context.unwrap_or_else(|| panic!("Context is not available yet"));
        let weak = weak.as_ref().map_or_else(
            || panic!("Update called when the app is not started yet"),
            std::clone::Clone::clone,
        );
        let rc = RenderContext::with_sub_apps(root_list, real_root.as_ref(), weak, None, sub_apps);
        app.render(rc);
    }
    fn mounted(&self, context: &mut A::Context) {
        let cp = ContextPlus {
            context,
            sub_apps: &self.sub_apps,
        };
        self.app.mounted(cp);
    }

    /// Get a mutable reference to the app's context instance
    pub fn get_app_context_mut(&mut self) -> &mut A::Context {
        match self.context {
            None => panic!("App not started yet"),
            Some(ref mut context) => context,
        }
    }
}

impl<A: Application> RcMain<A> {
    pub(crate) fn to_weak(&self) -> WeakMain<A> {
        WeakMain {
            weak: Rc::downgrade(&self.rc),
        }
    }
    /// Create the main instance from the app instance
    pub fn start_in(real_root: web_sys::Element, parent: A::Parent) -> Self {
        crate::set_panic_hook_once();
        real_root.set_inner_html("");
        let main = Self {
            rc: Rc::new(RefCell::new(Main {
                app: A::init(),
                root_list: NodeList::new(),
                real_root,
                context: None,
                weak: None,
                sub_apps: SubAppCollection::new(),
            })),
        };
        let weak = main.to_weak();
        let mut context = A::Context::init(weak.clone(), parent);
        match main.rc.try_borrow_mut() {
            Err(e) => log::error!("Error borrowing: {}", e),
            Ok(mut main) => {
                main.weak = Some(weak.clone());
                main.render();
                main.mounted(&mut context);
                main.context = Some(context);
            }
        };
        main
    }

    /// Start the simi app inside an element with the given id
    pub fn start_in_element_id(element_id: &str, parent: A::Parent) -> Self {
        let real_root = crate::interop::document()
            .get_element_by_id(element_id)
            .unwrap_or_else(|| panic!("No element with id = '{}'", element_id));
        Self::start_in(real_root, parent)
    }

    /// Start the simi app in the document body
    pub fn start_in_body(parent: A::Parent) -> Self {
        let real_root = crate::interop::document()
            .body()
            .expect("document body")
            .into();
        Self::start_in(real_root, parent)
    }
    /// Update the app with the given message. Mainly use for testing.
    pub fn send_message(&self, m: A::Message) {
        match self.rc.try_borrow_mut() {
            Err(e) => log::error!("Error borrowing: {}", e),
            Ok(mut main) => main.update(m),
        }
    }
}

impl<A: Application> WeakMain<A> {
    /// Update the app with the given message
    pub fn send_message(&self, m: A::Message) {
        match self.weak.upgrade() {
            None => log::error!("Upgrade WeakMain to Rc"),
            Some(main) => match main.try_borrow_mut() {
                Err(e) => log::error!("Error borrowing: {}", e),
                Ok(mut main) => main.update(m),
            },
        }
    }

    /// Get a copy of inner value
    pub fn get_inner(&self) -> Weak<RefCell<Main<A>>> {
        self.weak.clone()
    }
}

/// What is the best name for this?
pub struct ContextPlus<'a, A: Application> {
    /// The instance of A::Context
    pub context: &'a mut A::Context,
    /// Collection of all sub apps
    pub sub_apps: &'a SubAppCollection,
}

/// Context use for Application::render and Component::render
pub struct RenderContext<'a, A: 'a + Application> {
    /// List to store root-elements of render!
    pub root_list: Cell<Option<&'a mut NodeList>>,
    /// Real element
    pub real_root: &'a web_sys::Node,
    /// Link to the Main
    pub main: WeakMain<A>,
    /// The render must insert new not before this node
    pub next_sibling: Option<&'a web_sys::Node>,
    /// Any sub app that is rendered will be collected into this
    pub sub_apps: Cell<Option<&'a mut SubAppCollection>>,
}

impl<'a, A: Application> RenderContext<'a, A> {
    /// New RenderContext
    pub fn new(
        root_list: &'a mut NodeList,
        real_root: &'a web_sys::Node,
        main: WeakMain<A>,
        next_sibling: Option<&'a web_sys::Node>,
    ) -> Self {
        Self {
            root_list: Cell::new(Some(root_list)),
            real_root,
            main,
            next_sibling,
            sub_apps: Cell::new(None),
        }
    }
    fn with_sub_apps(
        root_list: &'a mut NodeList,
        real_root: &'a web_sys::Node,
        main: WeakMain<A>,
        next_sibling: Option<&'a web_sys::Node>,
        sub_apps: &'a mut SubAppCollection,
    ) -> Self {
        Self {
            root_list: Cell::new(Some(root_list)),
            real_root,
            main,
            next_sibling,
            sub_apps: Cell::new(Some(sub_apps)),
        }
    }
    /// Take the root_list
    pub fn take_root_list(&self) -> &'a mut NodeList {
        self.root_list
            .replace(None)
            .expect("Root list is already taken")
    }
    /// Take the sub_apps
    pub fn take_sup_apps_collection(&self) -> &'a mut SubAppCollection {
        self.sub_apps
            .replace(None)
            .expect("Should only call this in `application!`")
    }
}

/// A collection of all sub apps of the current app
pub struct SubAppCollection {
    map: std::collections::HashMap<u8, Box<std::any::Any>>,
}

impl SubAppCollection {
    fn new() -> Self {
        Self {
            map: std::collections::HashMap::new(),
        }
    }

    pub(crate) fn set<S: Application>(&mut self, id: u8, weak: WeakMain<S>) {
        self.map.insert(id, Box::new(weak));
    }

    /// Get the expected app with the given id
    pub fn get<S: Application>(&self, id: u8) -> Option<WeakMain<S>> {
        self.map
            .get(&id)
            .map(|any| any.downcast_ref::<WeakMain<S>>().cloned())
            .unwrap_or_else(|| None)
    }
}