// Primitive vector shape definitions and path conversion routines.

///|
pub(all) struct RectData {
  width : Double
  height : Double
  rx : Double
  ry : Double
} derive(Eq, Debug)

///|
pub fn RectData::new(
  width : Double,
  height : Double,
  rx : Double,
  ry : Double,
) -> RectData {
  { width, height, rx, ry }
}

///|
pub(all) struct EllipseData {
  radius_x : Double
  radius_y : Double
} derive(Eq, Debug)

///|
pub fn EllipseData::new(radius_x : Double, radius_y : Double) -> EllipseData {
  { radius_x, radius_y }
}

///|
pub(all) struct PolygonData {
  sides : Int
  radius : Double
} derive(Eq, Debug)

///|
pub fn PolygonData::new(sides : Int, radius : Double) -> PolygonData {
  { sides, radius }
}

///|
pub(all) struct StarData {
  points : Int
  inner_radius : Double
  outer_radius : Double
} derive(Eq, Debug)

///|
pub fn StarData::new(
  points : Int,
  inner_radius : Double,
  outer_radius : Double,
) -> StarData {
  { points, inner_radius, outer_radius }
}

///|
pub(all) struct LineData {
  start : @math.Vec2
  end : @math.Vec2
} derive(Eq, Debug)

///|
pub fn LineData::new(start : @math.Vec2, end : @math.Vec2) -> LineData {
  { start, end }
}

///|
pub(all) struct PolylineData {
  points : Array[@math.Vec2]
  closed : Bool
} derive(Eq, Debug)

///|
pub fn PolylineData::new(
  points : Array[@math.Vec2],
  closed : Bool,
) -> PolylineData {
  { points, closed }
}

///|
pub(all) enum ShapeGeometry {
  Rectangle(RectData)
  Ellipse(EllipseData)
  Polygon(PolygonData)
  Star(StarData)
  Line(LineData)
  Polyline(PolylineData)
  Path(Path)
} derive(Eq, Debug)

///|
pub fn ShapeGeometry::rectangle(
  w : Double,
  h : Double,
  rx : Double,
  ry : Double,
) -> ShapeGeometry {
  Rectangle(RectData::new(w, h, rx, ry))
}

///|
pub fn ShapeGeometry::ellipse(rx : Double, ry : Double) -> ShapeGeometry {
  Ellipse(EllipseData::new(rx, ry))
}

///|
pub fn ShapeGeometry::polygon(sides : Int, radius : Double) -> ShapeGeometry {
  Polygon(PolygonData::new(sides, radius))
}

///|
pub fn ShapeGeometry::star(
  pts : Int,
  inner : Double,
  outer : Double,
) -> ShapeGeometry {
  Star(StarData::new(pts, inner, outer))
}

///|
pub fn ShapeGeometry::line(
  start : @math.Vec2,
  end : @math.Vec2,
) -> ShapeGeometry {
  Line(LineData::new(start, end))
}

///|
pub fn ShapeGeometry::polyline(
  pts : Array[@math.Vec2],
  closed : Bool,
) -> ShapeGeometry {
  Polyline(PolylineData::new(pts, closed))
}

///|
pub fn ShapeGeometry::path(p : Path) -> ShapeGeometry {
  Path(p)
}

///|
pub fn ShapeGeometry::to_path(self : ShapeGeometry) -> Path {
  match self {
    Rectangle(rect) => {
      let w = rect.width
      let h = rect.height
      let rx = if rect.rx * 2.0 > w { w * 0.5 } else { rect.rx }
      let ry = if rect.ry * 2.0 > h { h * 0.5 } else { rect.ry }

      if rx <= 0.0 || ry <= 0.0 {
        PathBuilder::new()
        .move_to(@math.Vec2::new(0.0, 0.0))
        .line_to(@math.Vec2::new(w, 0.0))
        .line_to(@math.Vec2::new(w, h))
        .line_to(@math.Vec2::new(0.0, h))
        .close_path()
        .build()
      } else {
        let k_x = rx * 0.5522847498
        let k_y = ry * 0.5522847498

        PathBuilder::new()
        .move_to(@math.Vec2::new(rx, 0.0))
        .line_to(@math.Vec2::new(w - rx, 0.0))
        .cubic_to(
          @math.Vec2::new(w - rx + k_x, 0.0),
          @math.Vec2::new(w, ry - k_y),
          @math.Vec2::new(w, ry),
        )
        .line_to(@math.Vec2::new(w, h - ry))
        .cubic_to(
          @math.Vec2::new(w, h - ry + k_y),
          @math.Vec2::new(w - rx + k_x, h),
          @math.Vec2::new(w - rx, h),
        )
        .line_to(@math.Vec2::new(rx, h))
        .cubic_to(
          @math.Vec2::new(rx - k_x, h),
          @math.Vec2::new(0.0, h - ry + k_y),
          @math.Vec2::new(0.0, h - ry),
        )
        .line_to(@math.Vec2::new(0.0, ry))
        .cubic_to(
          @math.Vec2::new(0.0, ry - k_y),
          @math.Vec2::new(rx - k_x, 0.0),
          @math.Vec2::new(rx, 0.0),
        )
        .close_path()
        .build()
      }
    }
    Ellipse(ell) => {
      let rx = ell.radius_x
      let ry = ell.radius_y
      let k_x = rx * 0.5522847498
      let k_y = ry * 0.5522847498

      PathBuilder::new()
      .move_to(@math.Vec2::new(rx, 0.0))
      .cubic_to(
        @math.Vec2::new(rx, k_y),
        @math.Vec2::new(k_x, ry),
        @math.Vec2::new(0.0, ry),
      )
      .cubic_to(
        @math.Vec2::new(-k_x, ry),
        @math.Vec2::new(-rx, k_y),
        @math.Vec2::new(-rx, 0.0),
      )
      .cubic_to(
        @math.Vec2::new(-rx, -k_y),
        @math.Vec2::new(-k_x, -ry),
        @math.Vec2::new(0.0, -ry),
      )
      .cubic_to(
        @math.Vec2::new(k_x, -ry),
        @math.Vec2::new(rx, -k_y),
        @math.Vec2::new(rx, 0.0),
      )
      .close_path()
      .build()
    }
    Polygon(poly) => {
      let n = if poly.sides < 3 { 3 } else { poly.sides }
      let step = 6.283185307179586 / n.to_double()
      let builder = PathBuilder::new()
      let mut first = true
      for i = 0; i < n; i = i + 1 {
        let angle = i.to_double() * step - 1.5707963267948966
        let x = poly.radius * @cmath.cos(angle)
        let y = poly.radius * @cmath.sin(angle)
        let p = @math.Vec2::new(x, y)
        if first {
          ignore(builder.move_to(p))
          first = false
        } else {
          ignore(builder.line_to(p))
        }
      }
      ignore(builder.close_path())
      builder.build()
    }
    Star(star) => {
      let pts = if star.points < 3 { 3 } else { star.points }
      let total_pts = pts * 2
      let step = 6.283185307179586 / total_pts.to_double()
      let builder = PathBuilder::new()
      let mut first = true
      for i = 0; i < total_pts; i = i + 1 {
        let r = if i % 2 == 0 { star.outer_radius } else { star.inner_radius }
        let angle = i.to_double() * step - 1.5707963267948966
        let x = r * @cmath.cos(angle)
        let y = r * @cmath.sin(angle)
        let p = @math.Vec2::new(x, y)
        if first {
          ignore(builder.move_to(p))
          first = false
        } else {
          ignore(builder.line_to(p))
        }
      }
      ignore(builder.close_path())
      builder.build()
    }
    Line(line) =>
      PathBuilder::new().move_to(line.start).line_to(line.end).build()
    Polyline(poly) => {
      let builder = PathBuilder::new()
      if poly.points.is_empty() {
        Path::empty()
      } else {
        ignore(builder.move_to(poly.points[0]))
        for i = 1; i < poly.points.length(); i = i + 1 {
          ignore(builder.line_to(poly.points[i]))
        }
        if poly.closed {
          ignore(builder.close_path())
        }
        builder.build()
      }
    }
    Path(path) => path
  }
}