///|
/// SVG Path Segment representation
pub(all) enum PathSegment {
  MoveTo(Double, Double)
  LineTo(Double, Double)
  QuadTo(x1~ : Double, y1~ : Double, x~ : Double, y~ : Double)
  CubicTo(
    x1~ : Double,
    y1~ : Double,
    x2~ : Double,
    y2~ : Double,
    x~ : Double,
    y~ : Double
  )
  ClosePath
} derive(Eq, Debug, ToJson)

///|
/// Programmatic SVG Path Builder
pub(all) struct Path {
  segments : Array[PathSegment]
} derive(Eq, Debug, ToJson)

///|
/// Create a new empty Path
pub fn Path::new() -> Path {
  { segments: [] }
}

///|
/// Move to a point
pub fn Path::move_to(self : Path, x : Double, y : Double) -> Path {
  self.segments.push(MoveTo(x, y))
  self
}

///|
/// Draw a line to a point
pub fn Path::line_to(self : Path, x : Double, y : Double) -> Path {
  self.segments.push(LineTo(x, y))
  self
}

///|
/// Draw a quadratic bezier curve
pub fn Path::quad_to(
  self : Path,
  x1 : Double,
  y1 : Double,
  x : Double,
  y : Double,
) -> Path {
  self.segments.push(QuadTo(x1~, y1~, x~, y~))
  self
}

///|
/// Draw a cubic bezier curve
pub fn Path::cubic_to(
  self : Path,
  x1 : Double,
  y1 : Double,
  x2 : Double,
  y2 : Double,
  x : Double,
  y : Double,
) -> Path {
  self.segments.push(CubicTo(x1~, y1~, x2~, y2~, x~, y~))
  self
}

///|
/// Close the current path
pub fn Path::close(self : Path) -> Path {
  self.segments.push(ClosePath)
  self
}

///|
/// Convert path to SVG path string (d attribute value)
pub fn Path::to_svg_d(self : Path) -> String {
  let sb = StringBuilder()
  for i, seg in self.segments {
    if i > 0 {
      sb.write_string(" ")
    }
    match seg {
      MoveTo(x, y) => sb.write_string("M \{x} \{y}")
      LineTo(x, y) => sb.write_string("L \{x} \{y}")
      QuadTo(x1~, y1~, x~, y~) => sb.write_string("Q \{x1} \{y1} \{x} \{y}")
      CubicTo(x1~, y1~, x2~, y2~, x~, y~) =>
        sb.write_string("C \{x1} \{y1} \{x2} \{y2} \{x} \{y}")
      ClosePath => sb.write_string("Z")
    }
  }
  sb.to_string()
}

///|
/// Transform all path coordinates using a 2D affine matrix
pub fn Path::transform(self : Path, matrix : Matrix2D) -> Path {
  let new_segments = Array::new(capacity=self.segments.length())
  for seg in self.segments {
    let new_seg = match seg {
      MoveTo(x, y) => {
        let (nx, ny) = matrix.transform_point(x, y)
        PathSegment::MoveTo(nx, ny)
      }
      LineTo(x, y) => {
        let (nx, ny) = matrix.transform_point(x, y)
        PathSegment::LineTo(nx, ny)
      }
      QuadTo(x1~, y1~, x~, y~) => {
        let (nx1, ny1) = matrix.transform_point(x1, y1)
        let (nx, ny) = matrix.transform_point(x, y)
        PathSegment::QuadTo(x1=nx1, y1=ny1, x=nx, y=ny)
      }
      CubicTo(x1~, y1~, x2~, y2~, x~, y~) => {
        let (nx1, ny1) = matrix.transform_point(x1, y1)
        let (nx2, ny2) = matrix.transform_point(x2, y2)
        let (nx, ny) = matrix.transform_point(x, y)
        PathSegment::CubicTo(x1=nx1, y1=ny1, x2=nx2, y2=ny2, x=nx, y=ny)
      }
      ClosePath => PathSegment::ClosePath
    }
    new_segments.push(new_seg)
  }
  { segments: new_segments }
}

///|
/// Creates a rectangular path
pub fn Path::rect(w : Double, h : Double, x? : Double, y? : Double) -> Path {
  let px = x.unwrap_or(0.0)
  let py = y.unwrap_or(0.0)
  Path::new()
  .move_to(px, py)
  .line_to(px + w, py)
  .line_to(px + w, py + h)
  .line_to(px, py + h)
  .close()
}

///|
/// Creates a circular path (approximated with 4 cubic Bezier curves)
pub fn Path::circle(r : Double, cx? : Double, cy? : Double) -> Path {
  let pcx = cx.unwrap_or(0.0)
  let pcy = cy.unwrap_or(0.0)
  // kappa = 4 * (sqrt(2) - 1) / 3 approx 0.5522847498
  let kappa = 0.5522847498
  let ox = r * kappa
  let oy = r * kappa

  Path::new()
  .move_to(pcx - r, pcy)
  .cubic_to(pcx - r, pcy - oy, pcx - ox, pcy - r, pcx, pcy - r)
  .cubic_to(pcx + ox, pcy - r, pcx + r, pcy - oy, pcx + r, pcy)
  .cubic_to(pcx + r, pcy + oy, pcx + ox, pcy + r, pcx, pcy + r)
  .cubic_to(pcx - ox, pcy + r, pcx - r, pcy + oy, pcx - r, pcy)
  .close()
}

///|
/// Creates a star shape path
pub fn Path::star(
  cx : Double,
  cy : Double,
  spikes : Int,
  inner_radius : Double,
  outer_radius : Double,
) -> Path {
  let path = Path::new()
  let rot = @math.PI / 2.0 * 3.0
  let step = @math.PI / spikes.to_double()

  let x0 = cx + @math.cos(rot) * outer_radius
  let y0 = cy + @math.sin(rot) * outer_radius
  let _ = path.move_to(x0, y0)

  for i in 0.. Path {
  let path = Path::new()
  let rot = 0.0
  let step = 2.0 * @math.PI / petals.to_double()

  let x0 = cx + @math.cos(rot) * inner_radius
  let y0 = cy + @math.sin(rot) * inner_radius
  let _ = path.move_to(x0, y0)

  for i in 0..