///|
/// A same-size replacement of one data shard in a consistent old codeword.
pub(all) struct DataShardUpdate {
  index : Int32
  old_data : Bytes
  new_data : Bytes
}

///|
/// Apply old XOR new deltas to parity, without reading unchanged data shards.
/// Returns detached parity; neither inputs nor caller data slots are modified.
/// All shapes and indices are validated before calculation. The caller must
/// supply old data and parity from the same valid generation and persist the
/// new data plus parity together. This operation cannot verify that premise.
pub fn Codec::update_parity(
  self : Codec,
  parity : Array[Bytes],
  changes : Array[DataShardUpdate],
) -> Array[Bytes] raise ErasureError {
  if parity.length() != self.parity_count {
    raise InvalidShardCount(expected=self.parity_count, actual=parity.length())
  }
  let length = parity[0].length()
  self.check_length(length)
  for row = 1; row < parity.length(); row = row + 1 {
    if parity[row].length() != length {
      raise InvalidShardLength(
        index=self.data_count + row,
        expected=length,
        actual=parity[row].length(),
      )
    }
  }
  if changes.length() > self.data_count {
    raise ResourceLimit("data update count exceeds codec dimensions")
  }
  let seen = Array::make(self.data_count, false)
  for change in changes {
    if change.index < 0 || change.index >= self.data_count {
      raise InvalidIndex(change.index)
    }
    if seen[change.index] {
      raise DuplicateIndex(change.index)
    }
    seen[change.index] = true
    if change.old_data.length() != length {
      raise InvalidShardLength(
        index=change.index,
        expected=length,
        actual=change.old_data.length(),
      )
    }
    if change.new_data.length() != length {
      raise InvalidShardLength(
        index=change.index,
        expected=length,
        actual=change.new_data.length(),
      )
    }
  }
  let output : Array[Bytes] = []
  for row = 0; row < self.parity_count; row = row + 1 {
    let bytes = parity[row].to_array()
    for change in changes {
      let coefficient = self.generator.cells[self.data_count + row][change.index]
      if coefficient != 0 {
        for position = 0; position < length; position = position + 1 {
          let delta = change.old_data[position].to_int() ^
            change.new_data[position].to_int()
          if delta != 0 {
            bytes[position] = (bytes[position].to_int() ^
            self.field.multiply(coefficient, delta)).to_byte()
          }
        }
      }
    }
    output.push(Bytes::from_array(bytes))
  }
  output
}