///|
pub enum BorderMode {
  Clamp
  Reflect
  Constant(Gray)
} derive(Debug, Eq)

///|
pub struct Kernel {
  width : Int
  height : Int
  values : Array[Int]
} derive(Debug, Eq)

///|
pub fn Kernel::new(width~ : Int, height~ : Int, values~ : Array[Int]) -> Kernel {
  let expected = max_i(0, width * height)
  let normalized = Array::make(expected, 0)
  for i in 0.. Int {
  if x >= 0 && y >= 0 && x < self.width && y < self.height {
    self.values[y * self.width + x]
  } else {
    0
  }
}

///|
pub fn Kernel::sum(self : Kernel) -> Int {
  let mut result = 0
  for value in self.values {
    result += value
  }
  result
}

///|
pub fn Kernel::scale(
  self : Kernel,
  numerator : Int,
  denominator : Int,
) -> Kernel {
  let den = if denominator == 0 { 1 } else { denominator }
  let values : Array[Int] = []
  for value in self.values {
    values.push(value * numerator / den)
  }
  Kernel::new(width=self.width, height=self.height, values~)
}

///|
pub fn Kernel::box_filter(width : Int, height : Int) -> Kernel {
  let w = max_i(1, width)
  let h = max_i(1, height)
  Kernel::new(width=w, height=h, values=Array::make(w * h, 1))
}

///|
pub fn Kernel::sharpen() -> Kernel {
  Kernel::new(width=3, height=3, values=[0, -1, 0, -1, 5, -1, 0, -1, 0])
}

///|
pub fn Kernel::sobel_x() -> Kernel {
  Kernel::new(width=3, height=3, values=[-1, 0, 1, -2, 0, 2, -1, 0, 1])
}

///|
pub fn Kernel::sobel_y() -> Kernel {
  Kernel::new(width=3, height=3, values=[-1, -2, -1, 0, 0, 0, 1, 2, 1])
}

///|
fn reflect_index(index : Int, length : Int) -> Int {
  if length <= 1 {
    0
  } else {
    let period = length * 2 - 2
    let wrapped = (index % period + period) % period
    if wrapped < length {
      wrapped
    } else {
      period - wrapped
    }
  }
}

///|
fn sample_with_border(
  image : GrayImage,
  x : Int,
  y : Int,
  border : BorderMode,
) -> Int {
  if image.in_bounds(x, y) {
    image.get(x, y).value
  } else {
    match border {
      Clamp =>
        image.get(
          clamp(x, low=0, high=max_i(0, image.width - 1)),
          clamp(y, low=0, high=max_i(0, image.height - 1)),
        ).value
      Reflect =>
        image.get(reflect_index(x, image.width), reflect_index(y, image.height)).value
      Constant(gray) => gray.value
    }
  }
}

///|
fn convolve_at(
  image : GrayImage,
  kernel : Kernel,
  x : Int,
  y : Int,
  border : BorderMode,
) -> Int {
  let cx = kernel.width / 2
  let cy = kernel.height / 2
  let mut sum = 0
  for ky in 0.. GrayImage {
  let out = GrayImage::new(width=self.width, height=self.height)
  for y in 0.. GrayImage {
  let total = kernel.sum()
  if total == 0 {
    self.convolve(kernel, border~)
  } else {
    let out = GrayImage::new(width=self.width, height=self.height)
    for y in 0.. GrayImage {
  let kernel = Kernel::box_filter(width, height)
  let total = max_i(1, kernel.sum())
  let out = GrayImage::new(width=self.width, height=self.height)
  for y in 0.. GrayImage {
  let kernel = Kernel::box_filter(width, height)
  let total = max_i(1, kernel.sum())
  let out = GrayImage::new(width=self.width, height=self.height)
  for y in 0.. GrayImage {
  let kernel = Kernel::box_filter(width, height)
  let total = max_i(1, kernel.sum())
  let out = GrayImage::new(width=self.width, height=self.height)
  for y in 0.. Unit {
  values.push(value)
  let mut i = values.length() - 1
  while i > 0 && values[i - 1] > value {
    values[i] = values[i - 1]
    i -= 1
  }
  values[i] = value
}

///|
pub fn GrayImage::median_filter(
  self : GrayImage,
  radius : Int,
  border? : BorderMode = Clamp,
) -> GrayImage {
  let r = max_i(0, radius)
  let out = GrayImage::new(width=self.width, height=self.height)
  for y in 0.. GrayImage {
  let blurred = self.mean_filter(radius * 2 + 1, radius * 2 + 1)
  let amount = clamp(amount_per_100, low=0, high=500)
  let out = GrayImage::new(width=self.width, height=self.height)
  for y in 0.. GrayImage {
  let window = max_i(1, radius * 2 + 1)
  let neighborhood = self.mean_filter(window, window)
  let out = GrayImage::new(width=self.width, height=self.height)
  for y in 0..= threshold {
          Gray::white()
        } else {
          Gray::black()
        },
      )
    }
  }
  out
}

///|
pub fn GrayImage::local_contrast(self : GrayImage, radius : Int) -> GrayImage {
  let blurred = self.mean_filter(radius * 2 + 1, radius * 2 + 1)
  let out = GrayImage::new(width=self.width, height=self.height)
  for y in 0.. GrayImage {
  let out = GrayImage::new(width=self.width, height=self.height)
  for y in 0.. GrayImage {
  let weight = clamp(weight_per_1000, low=0, high=1000)
  let out = GrayImage::new(width=self.width, height=self.height)
  for y in 0..