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 {
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()
}
}