///|
pub fn Path::transform(self : Path, matrix : Matrix) -> Path {
  fn map_verb(verb : PathVerb) -> PathVerb {
    match verb {
      MoveTo(point) => MoveTo(matrix.map_point(point))
      LineTo(point) => LineTo(matrix.map_point(point))
      QuadTo(control, end) =>
        QuadTo(matrix.map_point(control), matrix.map_point(end))
      ConicTo(control, end, weight) =>
        ConicTo(matrix.map_point(control), matrix.map_point(end), weight)
      CubicTo(control0, control1, end) =>
        CubicTo(
          matrix.map_point(control0),
          matrix.map_point(control1),
          matrix.map_point(end),
        )
      Close => Close
    }
  }

  {
    fill_type: self.fill_type,
    verbs: self.verbs.map(map_verb),
    current_point: match self.current_point {
      None => None
      Some(point) => Some(matrix.map_point(point))
    },
  }
}

///|
pub fn Path::offset(self : Path, dx : Scalar, dy : Scalar) -> Path {
  self.transform(Matrix::translate(dx, dy))
}

///|
pub fn Path::reset(self : Path) -> Path {
  ignore(self)
  Path::new()
}

///|
pub fn Path::rewind(self : Path) -> Path {
  self.reset()
}

///|
fn Path::append_verb(
  self : Path,
  verb : PathVerb,
  current_point : Point?,
) -> Path {
  let verbs = self.verbs.copy()
  verbs.push(verb)
  { ..self, verbs, current_point }
}

///|
pub fn Path::move_to(self : Path, point : Point) -> Path {
  self.append_verb(MoveTo(point), Some(point))
}

///|
pub fn Path::line_to(self : Path, point : Point) -> Path {
  self.append_verb(LineTo(point), Some(point))
}

///|
pub fn Path::quad_to(self : Path, control : Point, end : Point) -> Path {
  self.append_verb(QuadTo(control, end), Some(end))
}

///|
pub fn Path::conic_to(
  self : Path,
  control : Point,
  end : Point,
  weight : Scalar,
) -> Path {
  self.append_verb(ConicTo(control, end, weight), Some(end))
}

///|
pub fn Path::cubic_to(
  self : Path,
  control0 : Point,
  control1 : Point,
  end : Point,
) -> Path {
  self.append_verb(CubicTo(control0, control1, end), Some(end))
}

///|
pub fn Path::close(self : Path) -> Path {
  self.append_verb(Close, self.current_point)
}

///|
pub fn Path::add_poly(
  self : Path,
  points : Array[Point],
  close? : Bool = false,
) -> Path {
  if points.is_empty() {
    self
  } else {
    let path = for path = self.move_to(points[0]), i = 1
                   i < points.length()
                   i = i + 1 {
      continue path.line_to(points[i]), i + 1
    } nobreak {
      path
    }
    if close {
      path.close()
    } else {
      path
    }
  }
}

///|
fn Path::append_path_verbs(
  self : Path,
  path : Path,
  mode : AddPathMode,
) -> Path {
  if path.is_empty() {
    self
  } else {
    let (result, start_index) = if mode is Extend {
      match (self.current_point, path.verbs[0]) {
        (Some(_), MoveTo(point)) => (self.line_to(point), 1)
        _ => (self, 0)
      }
    } else {
      (self, 0)
    }
    for result = result, i = start_index; i < path.verbs.length(); i = i + 1 {
      let result = match path.verbs[i] {
        MoveTo(point) => result.move_to(point)
        LineTo(point) => result.line_to(point)
        QuadTo(control, end) => result.quad_to(control, end)
        ConicTo(control, end, weight) => result.conic_to(control, end, weight)
        CubicTo(control0, control1, end) =>
          result.cubic_to(control0, control1, end)
        Close => result.close()
      }
      continue result, i + 1
    } nobreak {
      result
    }
  }
}

///|
pub fn Path::add_path(
  self : Path,
  path : Path,
  matrix? : Matrix = Matrix::identity(),
  mode? : AddPathMode = Append,
) -> Path {
  self.append_path_verbs(path.transform(matrix), mode)
}

///|
pub fn Path::add_path_matrix(
  self : Path,
  path : Path,
  matrix : Matrix,
  mode? : AddPathMode = Append,
) -> Path {
  self.add_path(path, matrix~, mode~)
}

///|
pub fn Path::add_path_offset(
  self : Path,
  path : Path,
  offset : Point,
  mode? : AddPathMode = Append,
) -> Path {
  self.add_path(path, matrix=Matrix::translate(offset.x, offset.y), mode~)
}

///|
pub fn Path::with_offset(self : Path, offset : Point) -> Path {
  self.offset(offset.x, offset.y)
}

///|
pub fn Path::add_rect(
  self : Path,
  rect : Rect,
  direction? : PathDirection = CW,
) -> Path {
  if direction is CW {
    self
    .move_to(Point::new(rect.left, rect.top))
    .line_to(Point::new(rect.right, rect.top))
    .line_to(Point::new(rect.right, rect.bottom))
    .line_to(Point::new(rect.left, rect.bottom))
    .close()
  } else {
    self
    .move_to(Point::new(rect.left, rect.top))
    .line_to(Point::new(rect.left, rect.bottom))
    .line_to(Point::new(rect.right, rect.bottom))
    .line_to(Point::new(rect.right, rect.top))
    .close()
  }
}

///|
pub fn Path::add_oval(
  self : Path,
  oval : Rect,
  direction? : PathDirection = CW,
) -> Path {
  let oval = oval.sorted()
  if oval.is_empty() {
    self
  } else {
    let cx = (oval.left + oval.right) / 2.0
    let cy = (oval.top + oval.bottom) / 2.0
    let right = Point::new(oval.right, cy)
    let bottom = Point::new(cx, oval.bottom)
    let left = Point::new(oval.left, cy)
    let top = Point::new(cx, oval.top)
    let weight : Scalar = 0.70710677
    if direction is CW {
      self
      .move_to(right)
      .conic_to(Point::new(oval.right, oval.bottom), bottom, weight)
      .conic_to(Point::new(oval.left, oval.bottom), left, weight)
      .conic_to(Point::new(oval.left, oval.top), top, weight)
      .conic_to(Point::new(oval.right, oval.top), right, weight)
      .close()
    } else {
      self
      .move_to(right)
      .conic_to(Point::new(oval.right, oval.top), top, weight)
      .conic_to(Point::new(oval.left, oval.top), left, weight)
      .conic_to(Point::new(oval.left, oval.bottom), bottom, weight)
      .conic_to(Point::new(oval.right, oval.bottom), right, weight)
      .close()
    }
  }
}

///|
pub fn Path::add_circle(
  self : Path,
  center : Point,
  radius : Scalar,
  direction? : PathDirection = CW,
) -> Path {
  if radius <= 0.0 {
    self
  } else {
    self.add_oval(
      Rect::new(
        center.x - radius,
        center.y - radius,
        center.x + radius,
        center.y + radius,
      ),
      direction~,
    )
  }
}

///|
pub fn Path::add_round_rect(
  self : Path,
  rect : Rect,
  rx : Scalar,
  ry : Scalar,
  direction? : PathDirection = CW,
) -> Path {
  let rect = rect.sorted()
  if rect.is_empty() {
    self
  } else if rx <= 0.0 || ry <= 0.0 {
    self.add_rect(rect, direction~)
  } else {
    let rx = Float::min(rx, rect.width() / 2.0)
    let ry = Float::min(ry, rect.height() / 2.0)
    let weight : Scalar = 0.70710677
    let top_left_start = Point::new(rect.left + rx, rect.top)
    let top_right_start = Point::new(rect.right - rx, rect.top)
    let right_top_end = Point::new(rect.right, rect.top + ry)
    let right_bottom_start = Point::new(rect.right, rect.bottom - ry)
    let bottom_right_end = Point::new(rect.right - rx, rect.bottom)
    let bottom_left_start = Point::new(rect.left + rx, rect.bottom)
    let left_bottom_end = Point::new(rect.left, rect.bottom - ry)
    let left_top_start = Point::new(rect.left, rect.top + ry)
    if direction is CW {
      self
      .move_to(top_left_start)
      .line_to(top_right_start)
      .conic_to(Point::new(rect.right, rect.top), right_top_end, weight)
      .line_to(right_bottom_start)
      .conic_to(Point::new(rect.right, rect.bottom), bottom_right_end, weight)
      .line_to(bottom_left_start)
      .conic_to(Point::new(rect.left, rect.bottom), left_bottom_end, weight)
      .line_to(left_top_start)
      .conic_to(Point::new(rect.left, rect.top), top_left_start, weight)
      .close()
    } else {
      self
      .move_to(top_left_start)
      .conic_to(Point::new(rect.left, rect.top), left_top_start, weight)
      .line_to(left_bottom_end)
      .conic_to(Point::new(rect.left, rect.bottom), bottom_left_start, weight)
      .line_to(bottom_right_end)
      .conic_to(Point::new(rect.right, rect.bottom), right_bottom_start, weight)
      .line_to(right_top_end)
      .conic_to(Point::new(rect.right, rect.top), top_right_start, weight)
      .line_to(top_left_start)
      .close()
    }
  }
}

///|
fn Size::is_rrect_square_corner(self : Size) -> Bool {
  self.width <= 0.0 || self.height <= 0.0
}

///|
fn Path::append_rrect_corner(
  self : Path,
  corner : Point,
  end : Point,
  radius : Size,
) -> Path {
  if radius.is_rrect_square_corner() {
    self.line_to(corner)
  } else {
    let weight : Scalar = 0.70710677
    self.conic_to(corner, end, weight)
  }
}

///|
pub fn Path::add_rrect(
  self : Path,
  rrect : RRect,
  direction? : PathDirection = CW,
) -> Path {
  if rrect.is_empty() {
    self
  } else if rrect.is_rect() {
    self.add_rect(rrect.rect, direction~)
  } else {
    let rect = rrect.rect
    let ul = rrect.upper_left
    let ur = rrect.upper_right
    let lr = rrect.lower_right
    let ll = rrect.lower_left
    let top_left_start = Point::new(rect.left + ul.width, rect.top)
    let top_right_start = Point::new(rect.right - ur.width, rect.top)
    let right_top_end = Point::new(rect.right, rect.top + ur.height)
    let right_bottom_start = Point::new(rect.right, rect.bottom - lr.height)
    let bottom_right_end = Point::new(rect.right - lr.width, rect.bottom)
    let bottom_left_start = Point::new(rect.left + ll.width, rect.bottom)
    let left_bottom_end = Point::new(rect.left, rect.bottom - ll.height)
    let left_top_start = Point::new(rect.left, rect.top + ul.height)
    if direction is CW {
      self
      .move_to(top_left_start)
      .line_to(top_right_start)
      .append_rrect_corner(Point::new(rect.right, rect.top), right_top_end, ur)
      .line_to(right_bottom_start)
      .append_rrect_corner(
        Point::new(rect.right, rect.bottom),
        bottom_right_end,
        lr,
      )
      .line_to(bottom_left_start)
      .append_rrect_corner(
        Point::new(rect.left, rect.bottom),
        left_bottom_end,
        ll,
      )
      .line_to(left_top_start)
      .append_rrect_corner(Point::new(rect.left, rect.top), top_left_start, ul)
      .close()
    } else {
      self
      .move_to(top_left_start)
      .append_rrect_corner(Point::new(rect.left, rect.top), left_top_start, ul)
      .line_to(left_bottom_end)
      .append_rrect_corner(
        Point::new(rect.left, rect.bottom),
        bottom_left_start,
        ll,
      )
      .line_to(bottom_right_end)
      .append_rrect_corner(
        Point::new(rect.right, rect.bottom),
        right_bottom_start,
        lr,
      )
      .line_to(right_top_end)
      .append_rrect_corner(
        Point::new(rect.right, rect.top),
        top_right_start,
        ur,
      )
      .line_to(top_left_start)
      .close()
    }
  }
}