///|
// Changes group contiguous ops with shared metadata.
pub struct Change {
  id : @types.ID
  lamport : @types.Lamport
  deps : @types.Frontiers
  timestamp : Int64
  commit_msg : String?
  origin : String?
  ops : Array[@op.Op]
} derive(Show)

///|
pub fn Change::new(
  ops : Array[@op.Op],
  deps : @types.Frontiers,
  id : @types.ID,
  lamport : @types.Lamport,
  timestamp : Int64,
) -> Change {
  Change::new_with_meta(ops, deps, id, lamport, timestamp, None, None)
}

///|
pub fn Change::new_with_meta(
  ops : Array[@op.Op],
  deps : @types.Frontiers,
  id : @types.ID,
  lamport : @types.Lamport,
  timestamp : Int64,
  commit_msg : String?,
  origin : String?,
) -> Change {
  Change::{ id, lamport, deps, timestamp, commit_msg, origin, ops }
}

///|
pub fn Change::ops(self : Change) -> Array[@op.Op] {
  self.ops
}

///|
pub fn Change::deps(self : Change) -> @types.Frontiers {
  self.deps
}

///|
pub fn Change::peer(self : Change) -> @types.PeerID {
  self.id.peer
}

///|
pub fn Change::lamport(self : Change) -> @types.Lamport {
  self.lamport
}

///|
pub fn Change::timestamp(self : Change) -> Int64 {
  self.timestamp
}

///|
pub fn Change::commit_msg(self : Change) -> String? {
  self.commit_msg
}

///|
pub fn Change::origin(self : Change) -> String? {
  self.origin
}

///|
pub fn Change::id(self : Change) -> @types.ID {
  self.id
}

///|
pub fn Change::deps_on_self(self : Change) -> Bool {
  match self.deps.as_single() {
    Some(id) => id.peer == self.id.peer
    None => false
  }
}

///|
pub fn Change::len(self : Change) -> Int {
  let mut total = 0
  for op in self.ops {
    total = total + op.content_len()
  }
  total
}

///|
pub fn Change::is_empty(self : Change) -> Bool {
  self.len() == 0
}

///|
pub fn Change::ctr_span(self : Change) -> @types.CounterSpan {
  let start = self.id.counter
  let end = start + self.len()
  @types.CounterSpan::new(start, end)
}

///|
pub fn Change::id_span(self : Change) -> @types.IdSpan {
  let start = self.id.counter
  let end = start + self.len()
  @types.IdSpan::new(self.id.peer, start, end)
}

///|
pub fn Change::id_end(self : Change) -> @types.ID {
  let end = self.id.counter + self.len()
  @types.ID::new(self.id.peer, end)
}

///|
pub fn Change::id_last(self : Change) -> @types.ID {
  let last = self.id.counter + self.len() - 1
  @types.ID::new(self.id.peer, last)
}

///|
pub fn Change::slice(self : Change, from : Int, to : Int) -> Change {
  if from >= to {
    Change::new_with_meta(
      [],
      self.deps,
      self.id,
      self.lamport,
      self.timestamp,
      self.commit_msg,
      self.origin,
    )
  } else {
    let ops : Array[@op.Op] = []
    let mut offset = 0
    for op in self.ops {
      let op_len = op.content_len()
      let op_start = offset
      let op_end = offset + op_len
      if op_end <= from {
        offset = op_end
        continue
      }
      if op_start >= to {
        break
      }
      let slice_start = if from > op_start { from - op_start } else { 0 }
      let slice_end = if to < op_end { to - op_start } else { op_len }
      ops.push(op.slice(slice_start, slice_end))
      offset = op_end
    }
    let new_id = self.id.inc(from)
    let deps = if from > 0 {
      @types.Frontiers::from_id(self.id.inc(from - 1))
    } else {
      self.deps
    }
    Change::{
      id: new_id,
      lamport: self.lamport + from.reinterpret_as_uint(),
      deps,
      timestamp: self.timestamp,
      commit_msg: self.commit_msg,
      origin: self.origin,
      ops,
    }
  }
}