///|
/// Geometric measurements for 4-connected foreground components.
///
/// A foreground pixel is one whose Rec.601 luma is strictly greater than the
/// supplied threshold. Components use the same row-major, 4-connectivity
/// semantics as `Image::label_components`.

///|
/// Inclusive pixel bounds for one connected component.
pub(all) struct BoundingBox {
  min_x : Int
  min_y : Int
  max_x : Int
  max_y : Int
}

///|
/// Measurements for one connected foreground component.
///
/// `centroid_x` and `centroid_y` are arithmetic means of the foreground pixel
/// coordinates (pixel centres are represented by their integer coordinates).
/// The bounding box is inclusive: a one-pixel component has equal minimum and
/// maximum coordinates on both axes.
pub(all) struct ComponentStats {
  label : Int
  area : Int
  bbox : BoundingBox
  centroid : (Double, Double)
  // Flattened aliases keep the common scalar access pattern convenient.
  min_x : Int
  min_y : Int
  max_x : Int
  max_y : Int
  centroid_x : Double
  centroid_y : Double
}

///|
/// Computes area, inclusive bounds and centroid for every foreground
/// component. Results are ordered by increasing label, which is the same
/// row-major discovery order returned by `label_components`.
///
/// The threshold comparison is strict (`luma > threshold`) and connectivity
/// is four-way (up, down, left and right). An image with no foreground pixels
/// returns an empty array.
pub fn Image::regionprops(self : Image, threshold : Int) -> Array[ComponentStats] {
  // Validate the public image invariant before indexing through the label map.
  let n = self.pixel_count()
  let (labels, count) = self.label_components(threshold)
  if count == 0 {
    return []
  }
  let areas : Array[Int] = Array::make(count + 1, 0)
  let min_xs : Array[Int] = Array::make(count + 1, self.width)
  let min_ys : Array[Int] = Array::make(count + 1, self.height)
  let max_xs : Array[Int] = Array::make(count + 1, -1)
  let max_ys : Array[Int] = Array::make(count + 1, -1)
  let sum_xs : Array[Int64] = Array::make(count + 1, 0L)
  let sum_ys : Array[Int64] = Array::make(count + 1, 0L)
  for p in 0.. max_xs[label] {
      max_xs[label] = x
    }
    if y > max_ys[label] {
      max_ys[label] = y
    }
    sum_xs[label] = sum_xs[label] + x.to_int64()
    sum_ys[label] = sum_ys[label] + y.to_int64()
  }
  let result : Array[ComponentStats] = []
  for label in 1..<(count + 1) {
    let area = areas[label]
    let cx = sum_xs[label].to_double() / area.to_double()
    let cy = sum_ys[label].to_double() / area.to_double()
    let bbox = {
      min_x: min_xs[label],
      min_y: min_ys[label],
      max_x: max_xs[label],
      max_y: max_ys[label],
    }
    result.push({
      label,
      area,
      bbox,
      centroid: (cx, cy),
      min_x: min_xs[label],
      min_y: min_ys[label],
      max_x: max_xs[label],
      max_y: max_ys[label],
      centroid_x: cx,
      centroid_y: cy,
    })
  }
  result
}