///|
pub(all) struct BlockDensity {
  row_start : Int32
  column_start : Int32
  row_stop : Int32
  column_stop : Int32
  recurrent : Int32
  eligible : Int32
  rate : Double?
} derive(Debug, Eq)

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

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

///|
/// Block output is row-major; partial bottom/right blocks are included.
pub fn Recurrence::block_densities(
  self : Recurrence,
  rows_per_block : Int32,
  cols_per_block : Int32,
) -> Result[Array[BlockDensity], RqaError] {
  if rows_per_block < 1 ||
    cols_per_block < 1 ||
    rows_per_block > 4096 ||
    cols_per_block > 4096 {
    return Err(Invalid("block dimensions"))
  }
  let row_blocks = (self.height - 1) / rows_per_block + 1
  let col_blocks = (self.width - 1) / cols_per_block + 1
  let result : Array[BlockDensity] = []
  for bi in 0.. Result[Recurrence, RqaError] {
  if row < 0 ||
    col < 0 ||
    height < 1 ||
    width < 1 ||
    row >= self.height ||
    col >= self.width ||
    height > self.height - row ||
    width > self.width - col {
    return Err(Invalid("recurrence crop"))
  }
  Ok({
    height,
    width,
    bits: Array::makei(height * width, fn(p) {
      self.bits[(row + p / width) * self.width + col + p % width]
    }),
    mask: Array::makei(height * width, fn(p) {
      self.mask[(row + p / width) * self.width + col + p % width]
    }),
  })
}