///|
pub enum Connectivity {
  Four
  Eight
} derive(Debug, Eq)

///|
pub struct Component {
  id : Int
  area : Int
  bounds : Rect
  pixels : Array[Point]
} derive(Debug, Eq)

///|
pub fn Component::new(id~ : Int, pixels~ : Array[Point]) -> Component {
  let mut left = 0
  let mut top = 0
  let mut right = 0
  let mut bottom = 0
  if pixels.length() > 0 {
    left = pixels[0].x
    top = pixels[0].y
    right = pixels[0].x + 1
    bottom = pixels[0].y + 1
    for point in pixels {
      left = min_i(left, point.x)
      top = min_i(top, point.y)
      right = max_i(right, point.x + 1)
      bottom = max_i(bottom, point.y + 1)
    }
  }
  {
    id,
    area: pixels.length(),
    bounds: Rect::new(x=left, y=top, width=right - left, height=bottom - top),
    pixels,
  }
}

///|
pub fn Component::centroid_x1000(self : Component) -> Point {
  if self.area == 0 {
    Point::new(x=0, y=0)
  } else {
    let mut x = 0
    let mut y = 0
    for point in self.pixels {
      x += point.x
      y += point.y
    }
    Point::new(x=x * 1000 / self.area, y=y * 1000 / self.area)
  }
}

///|
pub fn Component::perimeter(
  self : Component,
  connectivity? : Connectivity = Four,
) -> Int {
  let mut total = 0
  let directions = [
    Point::new(x=1, y=0),
    Point::new(x=-1, y=0),
    Point::new(x=0, y=1),
    Point::new(x=0, y=-1),
  ]
  for point in self.pixels {
    let mut exposed = 0
    for direction in directions {
      let found = self.contains(
        Point::new(x=point.x + direction.x, y=point.y + direction.y),
      )
      if !found {
        exposed += 1
      }
    }
    if connectivity == Eight {
      let diagonals = [
        Point::new(x=1, y=1),
        Point::new(x=1, y=-1),
        Point::new(x=-1, y=1),
        Point::new(x=-1, y=-1),
      ]
      for direction in diagonals {
        if !self.contains(
            Point::new(x=point.x + direction.x, y=point.y + direction.y),
          ) {
          exposed += 1
        }
      }
    }
    total += exposed
  }
  total
}

///|
pub fn Component::contains(self : Component, point : Point) -> Bool {
  for candidate in self.pixels {
    if candidate == point {
      return true
    }
  }
  false
}

///|
pub fn Component::to_mask(self : Component, size : Size) -> Mask {
  let mask = Mask::new(width=size.width, height=size.height)
  for point in self.pixels {
    mask.set(point.x, point.y, self.id)
  }
  mask
}

///|
pub fn connected_components(
  mask : Mask,
  foreground : Int,
  connectivity? : Connectivity = Four,
) -> Array[Component] {
  let labels = Array::make(mask.values.length(), 0)
  let components : Array[Component] = []
  let width = max_i(0, mask.width)
  let height = max_i(0, mask.height)
  let cardinal = [
    Point::new(x=1, y=0),
    Point::new(x=-1, y=0),
    Point::new(x=0, y=1),
    Point::new(x=0, y=-1),
  ]
  let diagonal = [
    Point::new(x=1, y=1),
    Point::new(x=1, y=-1),
    Point::new(x=-1, y=1),
    Point::new(x=-1, y=-1),
  ]
  let mut next_id = 1
  for y in 0..= 0 && ny >= 0 && nx < width && ny < height {
              let ni = ny * width + nx
              if mask.values[ni] == foreground && labels[ni] == 0 {
                labels[ni] = next_id
                queue.push(Point::new(x=nx, y=ny))
              }
            }
          }
          if connectivity == Eight {
            for direction in diagonal {
              let nx = point.x + direction.x
              let ny = point.y + direction.y
              if nx >= 0 && ny >= 0 && nx < width && ny < height {
                let ni = ny * width + nx
                if mask.values[ni] == foreground && labels[ni] == 0 {
                  labels[ni] = next_id
                  queue.push(Point::new(x=nx, y=ny))
                }
              }
            }
          }
        }
        components.push(Component::new(id=next_id, pixels~))
        next_id += 1
      }
    }
  }
  components
}

///|
pub fn component_labels(mask : Mask, foreground : Int) -> Mask {
  let out = Mask::new(width=mask.width, height=mask.height)
  let components = connected_components(mask, foreground)
  for component in components {
    for point in component.pixels {
      out.set(point.x, point.y, component.id)
    }
  }
  out
}

///|
pub fn remove_small_components(
  mask : Mask,
  foreground : Int,
  minimum_area : Int,
  connectivity? : Connectivity = Four,
) -> Mask {
  let out = Mask::new(width=mask.width, height=mask.height)
  let threshold = max_i(0, minimum_area)
  for component in connected_components(mask, foreground, connectivity~) {
    if component.area >= threshold {
      for point in component.pixels {
        out.set(point.x, point.y, foreground)
      }
    }
  }
  out
}

///|
pub fn keep_largest_component(
  mask : Mask,
  foreground : Int,
  connectivity? : Connectivity = Four,
) -> Mask {
  let out = Mask::new(width=mask.width, height=mask.height)
  let mut largest : Component? = None
  for component in connected_components(mask, foreground, connectivity~) {
    match largest {
      None => largest = Some(component)
      Some(previous) =>
        if component.area > previous.area {
          largest = Some(component)
        }
    }
  }
  match largest {
    None => out
    Some(component) => {
      for point in component.pixels {
        out.set(point.x, point.y, foreground)
      }
      out
    }
  }
}

///|
pub fn binary_fill_holes(mask : Mask, foreground : Int) -> Mask {
  let out = mask
  let background = Mask::new(width=mask.width, height=mask.height)
  let queue : Array[Point] = []
  if mask.width > 0 && mask.height > 0 {
    for x in 0.. Mask {
  let out = Mask::new(width=mask.width, height=mask.height)
  for y in 0..