///|
pub struct HistogramSummary {
  bins : Array[Int]
  total : Int
  min : Int
  max : Int
} derive(Debug, Eq)

///|
pub fn HistogramSummary::from_image(
  image : GrayImage,
  bin_count? : Int = 256,
) -> HistogramSummary {
  let count = max_i(1, bin_count)
  let bins = Array::make(count, 0)
  let mut low = 255
  let mut high = 0
  let mut total = 0
  for value in image.pixels {
    let index = min_i(count - 1, value * count / 256)
    bins[index] += 1
    low = min_i(low, value)
    high = max_i(high, value)
    total += 1
  }
  {
    bins,
    total,
    min: if total == 0 {
      0
    } else {
      low
    },
    max: if total == 0 {
      0
    } else {
      high
    },
  }
}

///|
pub fn HistogramSummary::count_at(self : HistogramSummary, bin : Int) -> Int {
  if bin < 0 || bin >= self.bins.length() {
    0
  } else {
    self.bins[bin]
  }
}

///|
pub fn HistogramSummary::cumulative_at(
  self : HistogramSummary,
  bin : Int,
) -> Int {
  let mut total = 0
  for i in 0.. Int {
  if self.total == 0 {
    0
  } else {
    let target = self.total *
      clamp(percentile_per_1000, low=0, high=1000) /
      1000
    let mut cumulative = 0
    let mut result = 0
    for i, count in self.bins {
      cumulative += count
      if cumulative >= max_i(1, target) {
        result = i * 256 / self.bins.length()
        break
      }
    }
    result
  }
}

///|
pub fn HistogramSummary::entropy_x1000(self : HistogramSummary) -> Int {
  if self.total == 0 {
    0
  } else {
    let mut entropy = 0
    for count in self.bins {
      if count > 0 {
        let probability = count * 1000 / self.total
        entropy += probability * (1000 - probability) / 1000
      }
    }
    entropy
  }
}

///|
pub fn GrayImage::histogram_summary(
  self : GrayImage,
  bin_count? : Int = 256,
) -> HistogramSummary {
  HistogramSummary::from_image(self, bin_count~)
}

///|
pub fn GrayImage::percentile(
  self : GrayImage,
  percentile_per_1000 : Int,
) -> Int {
  self.histogram_summary().percentile(percentile_per_1000)
}

///|
pub fn GrayImage::otsu_threshold(self : GrayImage) -> Int {
  let histogram = self.histogram_summary()
  if histogram.total == 0 {
    0
  } else {
    let mut total_sum = 0
    for i, count in histogram.bins {
      total_sum += i * count
    }
    let mut background_weight = 0
    let mut background_sum = 0
    let mut best_threshold = 0
    let mut best_score = -1
    for i, count in histogram.bins {
      background_weight += count
      background_sum += i * count
      let foreground_weight = histogram.total - background_weight
      if background_weight > 0 && foreground_weight > 0 {
        let delta = background_sum * foreground_weight -
          (total_sum - background_sum) * background_weight
        let score = delta * delta / (background_weight * foreground_weight)
        if score > best_score {
          best_score = score
          best_threshold = i
        }
      }
    }
    best_threshold
  }
}

///|
pub fn GrayImage::equalize_histogram(self : GrayImage) -> GrayImage {
  let histogram = self.histogram_summary()
  let out = GrayImage::new(width=self.width, height=self.height)
  if histogram.total == 0 {
    out
  } else {
    for y in 0.. RegionSummary {
  let clipped = clamp_rect(rect, image.size())
  let mut count = 0
  let mut total = 0
  let mut low = 255
  let mut high = 0
  for y in clipped.y.. Int {
  self.max - self.min
}

///|
pub fn compare_regions(
  image : GrayImage,
  expected : Rect,
  actual : Rect,
) -> Int {
  let a = region_summary(image, expected)
  let b = region_summary(image, actual)
  abs_i(a.mean_x1000 - b.mean_x1000) / 1000
}