///|
// Coefficients expressing one output row directly in the selected input basis.
fn Codec::selected_coefficients(
  self : Codec,
  index : Int32,
  decoder : Matrix?,
) -> Array[Int32] {
  match decoder {
    None => self.generator.cells[index]
    Some(inverse) => {
      if index < self.data_count {
        return inverse.cells[index]
      }
      let coefficients = Array::make(self.data_count, 0)
      for inner = 0; inner < self.data_count; inner = inner + 1 {
        let factor = self.generator.cells[index][inner]
        if factor != 0 {
          for column = 0; column < self.data_count; column = column + 1 {
            coefficients[column] = coefficients[column] ^
              self.field.multiply(factor, inverse.cells[inner][column])
          }
        }
      }
      coefficients
    }
  }
}

///|
fn Codec::selected_byte(
  self : Codec,
  coefficients : Array[Int32],
  selected : Array[Bytes],
  position : Int32,
) -> Byte {
  let mut value = 0
  for column = 0; column < self.data_count; column = column + 1 {
    if coefficients[column] != 0 {
      value = value ^
        self.field.multiply(
          coefficients[column],
          selected[column][position].to_int(),
        )
    }
  }
  value.to_byte()
}

///|
/// Return only the requested shards, detached and in request order. Both data
/// and parity indices are supported. Unrequested missing shards are not built.
/// For a nonempty request, at least k known-intact shards must be supplied.
/// Extra supplied shards are checked for consistency with the selected k.
/// An empty request validates shapes and indices but does no consistency work.
pub fn Codec::reconstruct_selected(
  self : Codec,
  shards : Array[Bytes?],
  requested : Array[Int32],
) -> Array[Bytes] raise ErasureError {
  if shards.length() != self.total_count() {
    raise InvalidShardCount(expected=self.total_count(), actual=shards.length())
  }
  if requested.length() > self.total_count() {
    raise ResourceLimit("requested shard count exceeds codec dimensions")
  }
  let wanted = Array::make(self.total_count(), false)
  for index in requested {
    if index < 0 || index >= self.total_count() {
      raise InvalidIndex(index)
    }
    if wanted[index] {
      raise DuplicateIndex(index)
    }
    wanted[index] = true
  }
  let flags = Array::make(self.total_count(), false)
  let mut length = -1
  for index = 0; index < shards.length(); index = index + 1 {
    if shards[index] is Some(shard) {
      if length == -1 {
        length = shard.length()
        self.check_length(length)
      } else if shard.length() != length {
        raise InvalidShardLength(index~, expected=length, actual=shard.length())
      }
      flags[index] = true
    }
  }
  if requested.is_empty() {
    return []
  }
  let plan = self.plan(flags)
  if !plan.recoverable() {
    raise NotEnoughShards(required=self.data_count, available=plan.available())
  }
  let indices = plan.selected_indices()
  let selected_flags = Array::make(self.total_count(), false)
  let selected : Array[Bytes] = []
  for index in indices {
    selected_flags[index] = true
    match shards[index] {
      Some(bytes) => selected.push(bytes)
      None =>
        raise NotEnoughShards(
          required=self.data_count,
          available=selected.length(),
        )
    }
  }
  let decoder = if plan.needs_decode() {
    Some(self.generator.selected_rows(indices).inverse(self.field))
  } else {
    None
  }
  // Validate surplus evidence without allocating every missing data/parity row.
  for index = 0; index < shards.length(); index = index + 1 {
    if !selected_flags[index] {
      if shards[index] is Some(bytes) {
        let coefficients = self.selected_coefficients(index, decoder)
        for position = 0; position < length; position = position + 1 {
          if self.selected_byte(coefficients, selected, position) !=
            bytes[position] {
            raise InconsistentShard(index)
          }
        }
      }
    }
  }
  let output : Array[Bytes] = []
  for index in requested {
    match shards[index] {
      Some(bytes) => output.push(copy_shard(bytes))
      None => {
        let coefficients = self.selected_coefficients(index, decoder)
        let bytes = Array::make(length, b'\x00')
        for position = 0; position < length; position = position + 1 {
          bytes[position] = self.selected_byte(coefficients, selected, position)
        }
        output.push(Bytes::from_array(bytes))
      }
    }
  }
  output
}