///|
/// An axis-aligned rectangle in continuous `xyxy` coordinates.
pub struct BoundingBox {
  x1 : Double
  y1 : Double
  x2 : Double
  y2 : Double
} derive(Debug, Eq)

///|
fn is_finite(value : Double) -> Bool {
  !value.is_nan() && !value.is_inf()
}

///|
fn BoundingBox::valid(self : BoundingBox) -> Bool {
  is_finite(self.x1) &&
  is_finite(self.y1) &&
  is_finite(self.x2) &&
  is_finite(self.y2) &&
  self.x2 > self.x1 &&
  self.y2 > self.y1
}

///|
/// Creates a valid continuous-coordinate bounding box.
pub fn BoundingBox::new(
  x1 : Double,
  y1 : Double,
  x2 : Double,
  y2 : Double,
) -> BoundingBox raise TrackerError {
  let bbox = { x1, y1, x2, y2, }
  if !bbox.valid() {
    raise InvalidBoundingBox(
      "coordinates must be finite and satisfy x2 > x1 and y2 > y1",
    )
  }
  bbox
}

///|
/// Creates a box from its top-left corner, width, and height.
pub fn BoundingBox::from_xywh(
  x : Double,
  y : Double,
  width : Double,
  height : Double,
) -> BoundingBox raise TrackerError {
  BoundingBox::new(x, y, x + width, y + height)
}

///|
/// Creates a box from its center, width, and height.
///
/// This is the coordinate order commonly emitted by YOLO-family detectors.
pub fn BoundingBox::from_cxcywh(
  center_x : Double,
  center_y : Double,
  width : Double,
  height : Double,
) -> BoundingBox raise TrackerError {
  let half_width = width / 2.0
  let half_height = height / 2.0
  BoundingBox::new(
    center_x - half_width,
    center_y - half_height,
    center_x + half_width,
    center_y + half_height,
  )
}

///|
/// Returns the left coordinate.
pub fn BoundingBox::x1(self : BoundingBox) -> Double {
  self.x1
}

///|
/// Returns the top coordinate.
pub fn BoundingBox::y1(self : BoundingBox) -> Double {
  self.y1
}

///|
/// Returns the right coordinate.
pub fn BoundingBox::x2(self : BoundingBox) -> Double {
  self.x2
}

///|
/// Returns the bottom coordinate.
pub fn BoundingBox::y2(self : BoundingBox) -> Double {
  self.y2
}

///|
/// Returns the box as its top-left corner, width, and height.
pub fn BoundingBox::to_xywh(
  self : BoundingBox,
) -> (Double, Double, Double, Double) {
  (self.x1, self.y1, self.x2 - self.x1, self.y2 - self.y1)
}

///|
/// Returns the box as its center, width, and height.
pub fn BoundingBox::to_cxcywh(
  self : BoundingBox,
) -> (Double, Double, Double, Double) {
  let width = self.x2 - self.x1
  let height = self.y2 - self.y1
  (self.x1 + width / 2.0, self.y1 + height / 2.0, width, height)
}

///|
fn BoundingBox::area(self : BoundingBox) -> Double {
  if self.valid() {
    (self.x2 - self.x1) * (self.y2 - self.y1)
  } else {
    0.0
  }
}

///|
fn BoundingBox::iou(self : BoundingBox, other : BoundingBox) -> Double {
  if !self.valid() || !other.valid() {
    return 0.0
  }
  let left = if self.x1 > other.x1 { self.x1 } else { other.x1 }
  let top = if self.y1 > other.y1 { self.y1 } else { other.y1 }
  let right = if self.x2 < other.x2 { self.x2 } else { other.x2 }
  let bottom = if self.y2 < other.y2 { self.y2 } else { other.y2 }
  let width = right - left
  let height = bottom - top
  if width <= 0.0 || height <= 0.0 {
    return 0.0
  }
  let intersection = width * height
  intersection / (self.area() + other.area() - intersection)
}