cojson 0.6.0

Collaborative JSON (A high performance CRDT)
Documentation
use std::{
    borrow::Borrow,
    cell::{Ref, RefCell, RefMut},
    collections::HashMap,
    rc::Rc,
};

use unicode_segmentation::UnicodeSegmentation;

use crate::{
    io::OpOutput,
    jmbl_state::JMBLState,
    list::list_translator::ListTranslator,
    map::map_translator::MapTranslator,
    object::AnyObjectState,
    ops::ObjID,
    ops::{Op, OpID, OpKind, OpWithTarget},
    text::text_state::TextRoot, Input,
};

use super::{value::Value, ObjView};

pub(crate) struct JMBLView {
    pub(crate) op_output: Option<OpOutput>,
    pub(crate) jmbl_state: JMBLState,
    translator_for: HashMap<ObjID, TranslatorRef, fxhash::FxBuildHasher>,
    edit_ops: Vec<OpWithTarget>,
}

pub(crate) enum TranslatorRef {
    List(Rc<ListTranslator>),
    Map(Rc<MapTranslator>),
}

impl TranslatorRef {
    pub fn clone_ref(&self) -> Self {
        match self {
            TranslatorRef::List(ref list_translator) => {
                TranslatorRef::List(Rc::clone(list_translator))
            }
            TranslatorRef::Map(ref map_translator) => TranslatorRef::Map(Rc::clone(map_translator)),
        }
    }
}

impl JMBLView {
    pub fn new(op_output: Option<OpOutput>, jmbl_state: JMBLState) -> JMBLView {
        JMBLView {
            op_output,
            jmbl_state,
            translator_for: HashMap::with_hasher(fxhash::FxBuildHasher::default()),
            edit_ops: Vec::new(),
        }
    }

    pub fn expect_writable(&mut self) -> &mut OpOutput {
        self.op_output
            .as_mut()
            .expect("Tried to write to read-only JMBL view")
    }

    pub fn apply_edit_op(
        &mut self,
        target_obj: ObjID,
        prev: OpID,
        kind: OpKind,
        val: Option<litl::Val>,
    ) -> Op {
        // TODO(optimization): directly apply to target object
        let op = self
            .expect_writable()
            .write_op_now(target_obj, prev, kind, val);

        self.translator_for.remove(&target_obj);
        let op_with_target = OpWithTarget {
            target_obj_id: target_obj,
            op: op.clone(),
        };

        self.jmbl_state = self.jmbl_state.with_ops_applied(Some(&op_with_target));

        self.edit_ops.push(op_with_target);

        op
    }

    pub fn current_translator_for(&mut self, obj_id: &ObjID) -> Option<TranslatorRef> {
        match self.translator_for.entry(obj_id.clone()) {
            std::collections::hash_map::Entry::Occupied(existing) => Some(existing.get().clone_ref()),
            std::collections::hash_map::Entry::Vacant(vacant) => {
                let new_translator = match self.jmbl_state.objects.get(obj_id) {
                Some(AnyObjectState::List(list)) => Some(TranslatorRef::List(Rc::new(
                    ListTranslator::new(list.clone()),
                ))),
                Some(AnyObjectState::Map(map)) => {
                    Some(TranslatorRef::Map(Rc::new(MapTranslator::new(map.clone()))))
                },
                _ => None
            };

            new_translator.map(|translator| {
                vacant.insert(translator.clone_ref());
                translator
            })
        }
        }
    }

    fn create_list_from<II: IntoIterator<Item = litl::Val>>(&mut self, items: II) -> ObjID {
        let (create_op, obj_id) = self
            .expect_writable()
            .write_create_op(OpKind::ListCreate, None);

        let mut prev = create_op.id;

        let mut ops_to_apply = vec![OpWithTarget {
            target_obj_id: obj_id,
            op: create_op,
        }];

        for item in items {
            let insert_op = self.expect_writable().write_op_now(
                obj_id,
                prev,
                OpKind::ListInsertAfter,
                Some(item),
            );
            prev = insert_op.id;
            ops_to_apply.push(OpWithTarget {
                target_obj_id: obj_id,
                op: insert_op,
            });
        }

        self.jmbl_state = self.jmbl_state.with_ops_applied(&ops_to_apply);

        obj_id
    }

    fn create_map_from<II: IntoIterator<Item = (litl::Val, litl::Val)>>(
        &mut self,
        pairs: II,
    ) -> ObjID {
        let (create_op, obj_id) = self
            .expect_writable()
            .write_create_op(OpKind::MapCreate, None);

        let mut ops_to_apply = vec![OpWithTarget {
            target_obj_id: obj_id,
            op: create_op.clone(),
        }];

        for (key, value) in pairs.into_iter() {
            let set_op = self.expect_writable().write_op_now(
                obj_id,
                create_op.id,
                OpKind::MapSet,
                Some(litl::Val::array(vec![key, value])),
            );
            ops_to_apply.push(OpWithTarget {
                target_obj_id: obj_id,
                op: set_op,
            });
        }

        self.jmbl_state = self.jmbl_state.with_ops_applied(&ops_to_apply);

        obj_id
    }
}

pub struct JMBLViewRef(Rc<RefCell<JMBLView>>);

impl JMBLViewRef {
    pub(crate) fn new(ctx: JMBLView) -> JMBLViewRef {
        JMBLViewRef(Rc::new(RefCell::new(ctx)))
    }

    pub(crate) fn clone_ref(&self) -> JMBLViewRef {
        JMBLViewRef(Rc::clone(&self.0))
    }

    pub(crate) fn get(&self) -> Ref<JMBLView> {
        self.0.as_ref().borrow()
    }

    pub(crate) fn get_mut(&self) -> RefMut<JMBLView> {
        self.0.as_ref().borrow_mut()
    }

    pub fn view_for(&self, obj_id: &ObjID) -> Option<ObjView> {
        self.get()
            .jmbl_state
            .objects
            .get(obj_id)
            .map(|obj| ObjView::new_for(obj, *obj_id, self.clone_ref()))
    }

    pub fn litl_to_value(&self, val: litl::Val) -> Value {
        match litl::from_val(val.clone()) {
            Ok(obj_id) => self.obj_id_to_value(&obj_id),
            _ => Value::plain(val),
        }
    }

    pub fn obj_id_to_value(&self, obj_id: &ObjID) -> Value {
        if let Some(current_view) = self.view_for(obj_id) {
            if self.0.as_ref().borrow().op_output.is_some() {
                Value::write_view(current_view)
            } else {
                Value::read_view(current_view)
            }
        } else {
            Value::plain(litl::Val::null())
        }
    }

    pub fn get_root(&self) -> Value {
        let ctx = self.get();
        let (root_id, _) = ctx
            .jmbl_state
            .root
            .as_ref()
            .expect("Expected root in JMBLView state");
        self.obj_id_to_value(root_id)
    }

    pub fn set_root(&mut self, root: ObjID) {
        let mut self_mut = self.get_mut();

        assert!(self_mut.jmbl_state.root.is_none());

        let set_root_op = OpWithTarget {
            target_obj_id: root,
            op: self_mut
                .op_output
                .as_mut()
                .expect("Expected view to be writable when creating root")
                .write_op_now(
                    root,
                    root.init_op,
                    OpKind::SetRoot,
                    Some(litl::to_val(&root).unwrap()),
                ),
        };

        self_mut.jmbl_state = self_mut.jmbl_state.with_ops_applied([&set_root_op]);
    }

    pub fn create_map<I: Into<Value>, S: AsRef<str>, It: IntoIterator<Item = (S, I)>>(&mut self, items: It) -> Value {
        let map_id = self.get_mut().create_map_from(
            items
                .into_iter()
                .map(|(key, value)| (litl::Val::str(key.as_ref()), value.into().to_litl_or_ref())),
        );
        Value::write_view(self.view_for(&map_id).unwrap())
    }

    pub fn create_list<I: Into<Value>, It: IntoIterator<Item = I>>(&mut self, items: It) -> Value {
        let list_id = self
            .get_mut()
            .create_list_from(items.into_iter().map(|val| Value::to_litl_or_ref(&val.into())));
        Value::write_view(self.view_for(&list_id).unwrap())
    }

    pub fn create_plain_text(&mut self, text: &str) -> Value {
        let ranges = self.get_mut().create_list_from(vec![]);
        let graphemes = self
            .get_mut()
            .create_list_from(text.graphemes(true).map(litl::Val::str));

        let (text_create_op, text_id) = self.get_mut().expect_writable().write_create_op(
            OpKind::TextCreate,
            Some(litl::to_val(TextRoot { ranges, graphemes }).unwrap()),
        );

        let new_state = self.get().jmbl_state.with_ops_applied(&[OpWithTarget {
            op: text_create_op,
            target_obj_id: text_id,
        }]);

        self.get_mut().jmbl_state = new_state;

        Value::write_view(self.view_for(&text_id).unwrap())
    }

    pub fn create_rich_text(&mut self, _input: ()) -> Value {
        unimplemented!()
    }

    pub fn create_input_recursively(&mut self, input: Input) -> Value {
        match input {
            Input::CollabText(text) => self.create_plain_text(&text),
            Input::CollabMap(items) => {
                let items = items.into_iter().map(|(key, value)| (key, self.create_input_recursively(value))).collect::<Vec<_>>();
                self.create_map(items)
            }
            Input::CollabList(items) => {
                let items = items.into_iter().map(|item| self.create_input_recursively(item)).collect::<Vec<_>>();
                self.create_list(items)
            },
            Input::Plain(plain) => Value::Plain(plain),
            Input::Value(val) => val
        }
    }

    pub fn finalize_writing(&mut self) -> (OpOutput, Vec<OpWithTarget>) {
        let mut ctx = self.get_mut();
        let op_output = ctx.op_output.take().unwrap();
        let edit_ops = std::mem::take(&mut ctx.edit_ops);

        (op_output, edit_ops)
    }
}

impl Clone for JMBLViewRef {
    fn clone(&self) -> Self {
        self.clone_ref()
    }
}