cojson 0.6.0

Collaborative JSON (A high performance CRDT)
Documentation
use std::{collections::{HashMap, HashSet}, rc::Rc};

use crate::{
    ops::{ObjID, Op, OpKind},
    view::{
        jmbl_view::{JMBLViewRef, TranslatorRef},
        value::Value,
    }, io::SrcID,
};

use super::map_translator::MapTranslator;

#[derive(Clone)]
pub struct MapView {
    pub(crate) obj_id: ObjID,
    ctx: JMBLViewRef,
}

impl MapView {
    pub(crate) fn new(obj_id: ObjID, ctx: JMBLViewRef) -> MapView {
        MapView { obj_id, ctx }
    }

    fn translator_state(&self) -> Rc<MapTranslator> {
        match self
            .ctx
            .get_mut()
            .current_translator_for(&self.obj_id)
            .expect("Should have translator for map view")
        {
            TranslatorRef::Map(map_translator) => map_translator,
            _ => panic!("Expected Map Translator"),
        }
    }

    pub fn val(&self) -> HashMap<String, Value> {
        self.translator_state()
            .iter_entries()
            .map(|(key, val)| (key, self.ctx.litl_to_value(val)))
            .collect()
    }

    pub fn insert<K: Into<String>, V: Into<Value>>(&mut self, key: K, val: V) {
        let key_val = key.into();

        if let Some(last_op) = self.translator_state().last_op_for_key(&key_val) {
            // TODO(optimization): directly apply ops to correct object
            self.ctx.get_mut().apply_edit_op(
                self.obj_id,
                last_op.id,
                OpKind::MapSet,
                Some(litl::Val::array(vec![litl::Val::string(key_val), val.into().to_litl_or_ref()])),
            );
        } else {
            let create_op_id = self
                .translator_state()
                .create
                .clone()
                .expect("Expected create op on insert")
                .id;
            // TODO(optimization): directly apply ops to correct object
            self.ctx.get_mut().apply_edit_op(
                self.obj_id,
                create_op_id,
                OpKind::MapSet,
                Some(litl::Val::array(vec![litl::Val::string(key_val), val.into().to_litl_or_ref()])),
            );
        }
    }

    pub fn delete<I: Into<String>>(&mut self, key: I) {
        let key_val = key.into();
        if let Some(last_op) = self.translator_state().last_op_for_key(&key_val) {
            // TODO(optimization): directly apply ops to correct object
            self.ctx.get_mut().apply_edit_op(
                self.obj_id,
                last_op.id,
                OpKind::MapSet,
                Some(litl::Val::array(vec![key_val.into(), litl::Value::Null])),
            );
        }
        // else no-op
    }

    pub fn get<I: Into<String>>(&self, key: I) -> Value {
        self.apparent_value_for_key(key.into())
    }

    fn apparent_value_for_key(&self, key: String) -> Value {
        match self.translator_state().last_op_for_key_not_undone(&key) {
            Some(Op {
                kind: OpKind::MapSet,
                val: entry,
                ..
            }) => self.ctx.litl_to_value(entry.unwrap()[1].clone()),
            None => Value::plain(litl::Val::null()),
            Some(_other_op) => unreachable!("Only map ops expected"),
        }
    }

    pub fn val_to_litl(&self) -> litl::Val {
        litl::Val::object(
            self.val()
                .into_iter()
                .map(|(key, val)| (key, val.val_to_litl())),
        )
    }

    pub fn entries(&self) -> Vec<(String, litl::Val)> {
        self.translator_state().iter_entries().collect()
    }

    pub fn affecting_logs_for_key(&self, key: &str) -> HashSet<SrcID> {
        (*self.translator_state()).map.ops_for_key.get(key).map(|ops|
            ops.iter().map(|op| op.id.0).collect()
        ).unwrap_or_default()
    }

    pub fn last_op_for_key_not_undone(&self, key: &str) -> Option<Op> {
        self.translator_state().last_op_for_key_not_undone(key)
    }
}

impl std::fmt::Debug for MapView {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("MapView")
            .field("value", &self.val())
            .finish()
    }
}

impl std::cmp::PartialEq for MapView {
    fn eq(&self, other: &Self) -> bool {
        self.translator_state().map == other.translator_state().map
    }
}

impl std::cmp::Eq for MapView {}

impl std::hash::Hash for MapView {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.translator_state().map.hash(state);
    }
}