use std::fmt;
use std::ops::{Add, AddAssign, Sub, SubAssign};
use automerge::{ChangeHash, ObjId, Prop, ReadDoc, ScalarValue, Value, transaction::Transactable};
use crate::{Automorph, Error, PrimitiveChanged, Result, ScalarCursor};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Counter {
value: i64,
pending_delta: i64,
}
impl Counter {
#[must_use]
pub fn new(value: i64) -> Self {
Self {
value,
pending_delta: 0,
}
}
#[must_use]
pub fn value(&self) -> i64 {
self.value + self.pending_delta
}
pub fn increment(&mut self, amount: i64) {
self.pending_delta += amount;
}
pub fn decrement(&mut self, amount: i64) {
self.pending_delta -= amount;
}
pub fn set(&mut self, value: i64) {
self.value = value;
self.pending_delta = 0;
}
#[must_use]
pub fn has_pending_changes(&self) -> bool {
self.pending_delta != 0
}
}
impl fmt::Debug for Counter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Counter({})", self.value())
}
}
impl fmt::Display for Counter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.value())
}
}
impl From<i64> for Counter {
fn from(value: i64) -> Self {
Self::new(value)
}
}
impl From<Counter> for i64 {
fn from(counter: Counter) -> Self {
counter.value()
}
}
impl Add<i64> for Counter {
type Output = Self;
fn add(mut self, rhs: i64) -> Self::Output {
self.increment(rhs);
self
}
}
impl AddAssign<i64> for Counter {
fn add_assign(&mut self, rhs: i64) {
self.increment(rhs);
}
}
impl Sub<i64> for Counter {
type Output = Self;
fn sub(mut self, rhs: i64) -> Self::Output {
self.decrement(rhs);
self
}
}
impl SubAssign<i64> for Counter {
fn sub_assign(&mut self, rhs: i64) {
self.decrement(rhs);
}
}
impl Automorph for Counter {
type Changes = PrimitiveChanged;
type Cursor = ScalarCursor;
fn save<D: Transactable + ReadDoc>(
&self,
doc: &mut D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<()> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get(obj, prop.clone())? {
Some((Value::Scalar(s), _)) if s.is_counter() => {
if self.pending_delta != 0 {
doc.increment(obj, prop, self.pending_delta)?;
}
}
_ => {
doc.put(obj, prop, ScalarValue::counter(self.value()))?;
}
}
Ok(())
}
fn load<D: ReadDoc>(doc: &D, obj: impl AsRef<ObjId>, prop: impl Into<Prop>) -> Result<Self> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get(obj, prop)? {
Some((Value::Scalar(s), _)) => {
if let Some(int_val) = s.to_i64() {
Ok(Self::new(int_val))
} else {
Err(Error::type_mismatch("Counter", Some(format!("{:?}", s))))
}
}
Some((v, _)) => Err(Error::type_mismatch("Counter", Some(format!("{:?}", v)))),
None => Err(Error::missing_value()),
}
}
fn load_at<D: ReadDoc>(
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get_at(obj, prop, heads)? {
Some((Value::Scalar(s), _)) => {
if let Some(int_val) = s.to_i64() {
Ok(Self::new(int_val))
} else {
Err(Error::type_mismatch("Counter", Some(format!("{:?}", s))))
}
}
Some((v, _)) => Err(Error::type_mismatch("Counter", Some(format!("{:?}", v)))),
None => Err(Error::missing_value()),
}
}
fn diff<D: ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<Self::Changes> {
let loaded = Self::load(doc, obj, prop)?;
Ok(PrimitiveChanged::new(self.value() != loaded.value()))
}
fn diff_at<D: ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self::Changes> {
let loaded = Self::load_at(doc, obj, prop, heads)?;
Ok(PrimitiveChanged::new(self.value() != loaded.value()))
}
fn update<D: ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<Self::Changes> {
let loaded = Self::load(doc, obj, prop)?;
let changed = self.value() != loaded.value();
self.value = loaded.value;
self.pending_delta = 0;
Ok(PrimitiveChanged::new(changed))
}
fn update_at<D: ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self::Changes> {
let loaded = Self::load_at(doc, obj, prop, heads)?;
let changed = self.value() != loaded.value();
self.value = loaded.value;
self.pending_delta = 0;
Ok(PrimitiveChanged::new(changed))
}
}
#[cfg(test)]
mod tests {
use super::*;
use automerge::{ActorId, AutoCommit, ROOT};
#[test]
fn test_counter_basic_operations() {
let mut counter = Counter::new(0);
assert_eq!(counter.value(), 0);
counter.increment(5);
assert_eq!(counter.value(), 5);
counter.decrement(2);
assert_eq!(counter.value(), 3);
}
#[test]
fn test_counter_roundtrip() {
let mut doc = AutoCommit::new();
let counter = Counter::new(42);
counter.save(&mut doc, &ROOT, "count").unwrap();
let loaded = Counter::load(&doc, &ROOT, "count").unwrap();
assert_eq!(loaded.value(), 42);
}
#[test]
fn test_counter_increment_merges() {
let mut doc1 = AutoCommit::new();
let counter = Counter::new(0);
counter.save(&mut doc1, &ROOT, "count").unwrap();
let mut doc2 = doc1.fork().with_actor(ActorId::random());
let mut counter1 = Counter::load(&doc1, &ROOT, "count").unwrap();
counter1.increment(5);
counter1.save(&mut doc1, &ROOT, "count").unwrap();
let mut counter2 = Counter::load(&doc2, &ROOT, "count").unwrap();
counter2.increment(3);
counter2.save(&mut doc2, &ROOT, "count").unwrap();
doc1.merge(&mut doc2).unwrap();
let merged = Counter::load(&doc1, &ROOT, "count").unwrap();
assert_eq!(
merged.value(),
8,
"Both increments should merge: 0 + 5 + 3 = 8"
);
}
#[test]
fn test_counter_arithmetic_operators() {
let mut counter = Counter::new(10);
counter += 5;
assert_eq!(counter.value(), 15);
counter -= 3;
assert_eq!(counter.value(), 12);
let counter2 = counter + 8;
assert_eq!(counter2.value(), 20);
}
}