///|
/// Semantic labels for boundary patches.
pub(all) enum BoundaryLabel {
  Interior
  Wall
  Inlet
  Outlet
  Periodic
  Obstacle
} derive(Debug, Eq)

///|
/// Row-major boundary label map.
pub(all) struct BoundaryMap {
  size : Size
  labels : Array[BoundaryLabel]
} derive(Debug)

///|
/// Allocate an interior-filled boundary map.
pub fn BoundaryMap::new(size~ : Size) -> BoundaryMap {
  {
    size,
    labels: Array::make(size.width.max(0) * size.height.max(0), Interior),
  }
}

///|
/// Set a boundary label within the map.
pub fn BoundaryMap::set(
  self : BoundaryMap,
  x~ : Int,
  y~ : Int,
  label~ : BoundaryLabel,
) -> Unit {
  if x >= 0 && y >= 0 && x < self.size.width && y < self.size.height {
    self.labels[y * self.size.width + x] = label
  }
}

///|
/// Get a label, treating out-of-domain coordinates as walls.
pub fn BoundaryMap::get(
  self : BoundaryMap,
  x~ : Int,
  y~ : Int,
) -> BoundaryLabel {
  if x >= 0 && y >= 0 && x < self.size.width && y < self.size.height {
    self.labels[y * self.size.width + x]
  } else {
    Wall
  }
}

///|
/// Count one label.
pub fn BoundaryMap::count(self : BoundaryMap, label : BoundaryLabel) -> Int {
  let mut result = 0
  for current in self.labels {
    if current == label {
      result += 1
    }
  }
  result
}

///|
/// Count all non-interior labels.
pub fn BoundaryMap::boundary_count(self : BoundaryMap) -> Int {
  self.labels.length() - self.count(Interior)
}

///|
/// Convert a label to a stable string.
pub fn boundary_label_name(label : BoundaryLabel) -> String {
  match label {
    Interior => "interior"
    Wall => "wall"
    Inlet => "inlet"
    Outlet => "outlet"
    Periodic => "periodic"
    Obstacle => "obstacle"
  }
}

///|
/// Export labels as CSV.
pub fn BoundaryMap::to_csv(self : BoundaryMap) -> String {
  let builder = StringBuilder(
    size_hint=self.size.width * self.size.height * 12 + 16,
  )
  builder.write_string("x,y,label\n")
  for y in 0.. String {
  let builder = StringBuilder(
    size_hint=(self.size.width + 1) * self.size.height,
  )
  for y in 0.. '.'
        Wall => '#'
        Inlet => 'I'
        Outlet => 'O'
        Periodic => 'P'
        Obstacle => 'X'
      }
      builder.write_char(symbol)
    }
    builder.write_char('\n')
  }
  builder.to_string()
}

///|
/// Set the outer frame to wall labels.
pub fn BoundaryMap::set_outer_walls(self : BoundaryMap) -> Unit {
  for x in 0.. DomainMask {
  let result = DomainMask::new(size=self.size)
  for y in 0..