///|
/// A validated COCO mask RLE. Runs alternate background and foreground
/// pixels in column-major image order, starting with background.
pub struct CocoRle {
  height : Int32
  width : Int32
  counts : Array[Int32]
  foreground_area : Int32
} derive(Debug, Eq)

///|
pub extend CocoRle with Debug::{to_repr}

///|
pub extend CocoRle with Eq::{equal, not_equal}

///|
fn rle_pixel_count(height : Int32, width : Int32) -> Int64 raise EvalError {
  if height <= 0 || width <= 0 {
    raise InvalidInput("mask RLE height and width must be positive")
  }
  let pixels = height.to_int64() * width.to_int64()
  if pixels > 2147483647L {
    raise InvalidInput("mask RLE image exceeds supported pixel count")
  }
  pixels
}

///|
/// Creates an RLE without expanding it into a pixel array. The run lengths
/// must sum to height * width; an initial zero run represents a mask whose
/// first pixel is foreground. Dimensions are limited to Int-sized images.
pub fn CocoRle::new(
  height : Int32,
  width : Int32,
  counts : Array[Int32],
) -> CocoRle raise EvalError {
  let pixels = rle_pixel_count(height, width)
  if counts.is_empty() {
    raise InvalidInput("mask RLE counts must not be empty")
  }
  let mut total = 0L
  let mut area = 0L
  for index, count in counts {
    if count < 0 || count.to_int64() > pixels - total {
      raise InvalidInput(
        "mask RLE has negative or excess run at index \{index}",
      )
    }
    total += count.to_int64()
    if index % 2 == 1 {
      area += count.to_int64()
    }
  }
  if total != pixels {
    raise InvalidInput("mask RLE run lengths do not cover the image")
  }
  { height, width, counts: counts.copy(), foreground_area: area.to_int(), }
}

///|
pub fn CocoRle::height(self : CocoRle) -> Int32 {
  self.height
}

///|
pub fn CocoRle::width(self : CocoRle) -> Int32 {
  self.width
}

///|
pub fn CocoRle::area(self : CocoRle) -> Int32 {
  self.foreground_area
}

///|
fn CocoRle::intersection(self : CocoRle, other : CocoRle) -> Int32 {
  let mut left_index = 0
  let mut right_index = 0
  let mut left_remaining = self.counts[0]
  let mut right_remaining = other.counts[0]
  let mut common = 0
  while left_index < self.counts.length() && right_index < other.counts.length() {
    if left_remaining == 0 {
      left_index += 1
      if left_index < self.counts.length() {
        left_remaining = self.counts[left_index]
      }
      continue
    }
    if right_remaining == 0 {
      right_index += 1
      if right_index < other.counts.length() {
        right_remaining = other.counts[right_index]
      }
      continue
    }
    let span = left_remaining.min(right_remaining)
    if left_index % 2 == 1 && right_index % 2 == 1 {
      common += span
    }
    left_remaining -= span
    right_remaining -= span
  }
  common
}

///|
/// Returns the mask IoU for equal-sized images. Empty masks have IoU zero.
pub fn CocoRle::iou(self : CocoRle, other : CocoRle) -> Double raise EvalError {
  if self.height != other.height || self.width != other.width {
    raise InvalidInput("mask RLE image dimensions differ")
  }
  let common = self.intersection(other).to_double()
  let union = self.foreground_area.to_double() +
    other.foreground_area.to_double() -
    common
  if union == 0.0 {
    0.0
  } else {
    common / union
  }
}

///|
/// COCO crowd overlap: self is the detection, other is a crowd ground truth.
/// The denominator is the detection area, not the union.
pub fn CocoRle::crowd_iou(
  self : CocoRle,
  other : CocoRle,
) -> Double raise EvalError {
  if self.height != other.height || self.width != other.width {
    raise InvalidInput("mask RLE image dimensions differ")
  }
  if self.foreground_area == 0 {
    0.0
  } else {
    self.intersection(other).to_double() / self.foreground_area.to_double()
  }
}