///|
pub struct ConsistencyProof {
  priv first : UInt64
  priv second : UInt64
  priv path : Array[Hash32]
} derive(Eq, Debug)

///|
pub extend ConsistencyProof with Eq::{equal, not_equal}

///|
pub extend ConsistencyProof with @debug.Debug::{to_repr}

///|
pub fn ConsistencyProof::new(
  old_size : UInt64,
  new_size : UInt64,
  nodes : Array[Hash32],
) -> ConsistencyProof raise AppendError {
  if old_size > new_size || new_size > MAX_TREE_SIZE {
    raise InvalidSize
  }
  if nodes.length() > 128 {
    raise InvalidProofLength
  }
  { first: old_size, second: new_size, path: nodes[:].to_owned(), }
}

///|
pub fn ConsistencyProof::old_size(self : ConsistencyProof) -> UInt64 {
  self.first
}

///|
pub fn ConsistencyProof::new_size(self : ConsistencyProof) -> UInt64 {
  self.second
}

///|
pub fn ConsistencyProof::nodes(self : ConsistencyProof) -> Array[Hash32] {
  self.path[:].to_owned()
}

///|
fn MemoryLog::consistency_path(
  self : MemoryLog,
  start : Int,
  old_size : Int,
  new_size : Int,
  known_root : Bool,
  path : Array[Hash32],
) -> Unit {
  if old_size == new_size {
    if !known_root {
      path.push(self.range_root(start, new_size))
    }
    return
  }
  let k = split_size(new_size)
  if old_size <= k {
    self.consistency_path(start, old_size, k, known_root, path)
    path.push(self.range_root(start + k, new_size - k))
  } else {
    self.consistency_path(start + k, old_size - k, new_size - k, false, path)
    path.push(self.range_root(start, k))
  }
}

///|
pub fn MemoryLog::prove_consistency(
  self : MemoryLog,
  old_size : UInt64,
  new_size : UInt64,
) -> ConsistencyProof raise AppendError {
  if old_size > new_size || new_size > self.size() {
    raise InvalidSize
  }
  let path = []
  if old_size != 0UL && old_size != new_size {
    self.consistency_path(0, old_size.to_int(), new_size.to_int(), true, path)
  }
  { first: old_size, second: new_size, path, }
}