///|
/// Small vector primitives used to draw overlays without a foreign runtime.
pub(all) struct Point2i {
  x : Int
  y : Int
} derive(Eq, Debug)

///|
pub fn Point2i::new(x : Int, y : Int) -> Point2i {
  { x, y }
}

///|
pub fn Point2i::x(self : Point2i) -> Int {
  self.x
}

///|
pub fn Point2i::y(self : Point2i) -> Int {
  self.y
}

///|
pub fn Point2i::offset(self : Point2i, dx : Int, dy : Int) -> Point2i {
  { x: self.x + dx, y: self.y + dy }
}

///|
pub(all) struct Polyline {
  points : Array[Point2i]
  closed : Bool
} derive(Debug)

///|
pub fn Polyline::new(points : Array[Point2i], closed : Bool) -> Polyline {
  { points, closed }
}

///|
pub fn Polyline::points(self : Polyline) -> ArrayView[Point2i] {
  self.points[:]
}

///|
pub fn Polyline::closed(self : Polyline) -> Bool {
  self.closed
}

///|
pub fn Polyline::translated(self : Polyline, dx : Int, dy : Int) -> Polyline {
  let result : Array[Point2i] = []
  for point in self.points {
    result.push(point.offset(dx, dy))
  }
  { ..self, points: result }
}

///|
pub fn Polyline::bounds(self : Polyline) -> GifRect? {
  if self.points.length() == 0 {
    None
  } else {
    let mut left = self.points[0].x
    let mut right = left
    let mut top = self.points[0].y
    let mut bottom = top
    for point in self.points {
      if point.x < left {
        left = point.x
      }
      if point.x > right {
        right = point.x
      }
      if point.y < top {
        top = point.y
      }
      if point.y > bottom {
        bottom = point.y
      }
    }
    Some(
      GifRect::new(left~, top~, width=right - left + 1, height=bottom - top + 1),
    )
  }
}

///|
/// Draw an anti-aliased-looking polyline using integer supersampling.
pub fn Canvas::stroke_polyline(
  self : Canvas,
  polyline : Polyline,
  color : Rgba,
  thickness : Int,
) -> Int {
  if polyline.points.length() < 2 {
    0
  } else {
    let mut drawn = 0
    let safe_thickness = if thickness < 1 { 1 } else { thickness }
    for i in 0..<(polyline.points.length() - 1) {
      let start = polyline.points[i]
      let end = polyline.points[i + 1]
      drawn += self.draw_line(
        x0=start.x,
        y0=start.y,
        x1=end.x,
        y1=end.y,
        color~,
      )
      if safe_thickness > 1 {
        drawn += self.draw_circle(
          center_x=start.x,
          center_y=start.y,
          radius=safe_thickness / 2,
          color~,
        )
        drawn += self.draw_circle(
          center_x=end.x,
          center_y=end.y,
          radius=safe_thickness / 2,
          color~,
        )
      }
    }
    if polyline.closed {
      let start = polyline.points[polyline.points.length() - 1]
      let end = polyline.points[0]
      drawn += self.draw_line(
        x0=start.x,
        y0=start.y,
        x1=end.x,
        y1=end.y,
        color~,
      )
    }
    drawn
  }
}

///|
/// Sample a quadratic Bezier curve into a polyline.
pub fn quadratic_bezier(
  start : Point2i,
  control : Point2i,
  end : Point2i,
  samples : Int,
) -> Polyline {
  let count = if samples < 2 { 2 } else { samples }
  let points : Array[Point2i] = []
  for i in 0..<=count {
    let t = i * 1000 / count
    let inverse = 1000 - t
    let x = (
        start.x * inverse * inverse +
        control.x * 2 * inverse * t +
        end.x * t * t
      ) /
      1000000
    let y = (
        start.y * inverse * inverse +
        control.y * 2 * inverse * t +
        end.y * t * t
      ) /
      1000000
    points.push(Point2i::new(x, y))
  }
  Polyline::new(points, false)
}

///|
/// Draw a quadratic Bezier curve in one operation.
pub fn Canvas::draw_quadratic(
  self : Canvas,
  start : Point2i,
  control : Point2i,
  end : Point2i,
  color : Rgba,
  samples : Int,
) -> Int {
  self.stroke_polyline(quadratic_bezier(start, control, end, samples), color, 1)
}

///|
/// Fill a regular polygon centered at a coordinate.
pub fn Canvas::fill_regular_polygon(
  self : Canvas,
  center_x : Int,
  center_y : Int,
  radius : Int,
  sides : Int,
  color : Rgba,
) -> Int {
  let safe_sides = if sides < 3 { 3 } else if sides > 64 { 64 } else { sides }
  let safe_radius = if radius < 0 { 0 } else { radius }
  let vertices : Array[(Int, Int)] = []
  for i in 0.. Int {
  let safe_dash = if dash < 1 { 1 } else { dash }
  let safe_gap = if gap < 0 { 0 } else { gap }
  let dx = end_x - start_x
  let dy = end_y - start_y
  let length = if dx.abs() > dy.abs() { dx.abs() } else { dy.abs() }
  let mut drawn = 0
  let safe_length = if length == 0 { 1 } else { length }
  for i in 0..<(length + 1) {
    let period = safe_dash + safe_gap
    if period == 0 || i % period < safe_dash {
      let x = start_x + dx * i / safe_length
      let y = start_y + dy * i / safe_length
      if self.set(x~, y~, color~) {
        drawn += 1
      }
    }
  }
  drawn
}

///|
/// Create an outline-only version of a polygon.
pub fn Canvas::stroke_polygon(
  self : Canvas,
  vertices : ArrayView[(Int, Int)],
  color : Rgba,
) -> Int {
  if vertices.length() < 2 {
    0
  } else {
    let mut drawn = 0
    for i in 0..