///|
/// A report about the solid mask.
pub(all) struct MaskReport {
  total_cells : Int
  solid_cells : Int
  fluid_cells : Int
  solid_fraction : Double
} derive(Debug)

///|
/// A row-major solid-cell mask used by advanced simulations.
pub(all) struct DomainMask {
  size : Size
  solid : Array[Bool]
} derive(Debug)

///|
/// Allocate an empty mask.
pub fn DomainMask::new(size~ : Size) -> DomainMask {
  { size, solid: Array::make(size.width.max(0) * size.height.max(0), false) }
}

///|
/// Set a solid flag when coordinates are inside the mask.
pub fn DomainMask::set(
  self : DomainMask,
  x~ : Int,
  y~ : Int,
  solid~ : Bool,
) -> Unit {
  if x >= 0 && y >= 0 && x < self.size.width && y < self.size.height {
    self.solid[y * self.size.width + x] = solid
  }
}

///|
/// Read a solid flag; outside cells are treated as solid.
pub fn DomainMask::is_solid(self : DomainMask, x : Int, y : Int) -> Bool {
  if x >= 0 && y >= 0 && x < self.size.width && y < self.size.height {
    self.solid[y * self.size.width + x]
  } else {
    true
  }
}

///|
/// Count solid cells.
pub fn DomainMask::solid_count(self : DomainMask) -> Int {
  let mut count = 0
  for solid in self.solid {
    if solid {
      count += 1
    }
  }
  count
}

///|
/// Count fluid cells inside the mask.
pub fn DomainMask::fluid_count(self : DomainMask) -> Int {
  self.size.width.max(0) * self.size.height.max(0) - self.solid_count()
}

///|
/// Return a copy of the mask.
pub fn DomainMask::copy(self : DomainMask) -> DomainMask {
  { size: self.size, solid: self.solid.copy() }
}

///|
/// Read a mask as empty outside its finite raster for morphology operations.
fn mask_solid_or_false(mask : DomainMask, x : Int, y : Int) -> Bool {
  x >= 0 &&
  y >= 0 &&
  x < mask.size.width &&
  y < mask.size.height &&
  mask.solid[y * mask.size.width + x]
}

///|
/// Grow solid cells by a four-neighbor stencil.
pub fn DomainMask::dilate(
  self : DomainMask,
  iterations? : Int = 1,
) -> DomainMask {
  let result = self.copy()
  for _ in 0.. DomainMask {
  let result = self.copy()
  for _ in 0.. Double {
  let mut total = 0
  for y in 0.. Point {
  let mut x = 0.0
  let mut y = 0.0
  let count = self.solid_count()
  if count == 0 {
    Point::new(x=0.0, y=0.0)
  } else {
    for row in 0.. DomainMask {
  let result = DomainMask::new(size=left.size)
  for y in 0.. DomainMask {
  let result = DomainMask::new(size=left.size)
  for y in 0.. String {
  let builder = StringBuilder(
    size_hint=(self.size.width + 1) * self.size.height,
  )
  for y in 0.. Unit {
  if radius >= 0.0 {
    let r2 = radius * radius
    for y in 0.. Unit {
  for y in 0.. MaskReport {
  let total = self.size.width.max(0) * self.size.height.max(0)
  let solid = self.solid_count()
  {
    total_cells: total,
    solid_cells: solid,
    fluid_cells: total - solid,
    solid_fraction: safe_divide(
      solid.to_double(),
      total.to_double(),
      fallback=0.0,
    ),
  }
}

///|
/// Rasterize a shape into a new mask.
pub fn rasterize_shape(size~ : Size, shape~ : Shape) -> DomainMask {
  let mask = DomainMask::new(size~)
  for y in 0.. Array[ConnectedComponent] {
  let visited = Array::make(self.solid.length(), false)
  let components = Array::new()
  for y in 0..= 0 &&
              neighbor.y >= 0 &&
              neighbor.x < self.size.width &&
              neighbor.y < self.size.height {
              let index = neighbor.y * self.size.width + neighbor.x
              if self.solid[index] && !visited[index] {
                visited[index] = true
                queue.push(neighbor)
              }
            }
          }
        }
        components.push({ size: cells.length(), cells })
      }
    }
  }
  components
}