///|
/// Builds a transform from a design-friendly set of parameters.
pub(all) struct TransformSpec {
  position : Vec2
  scale : Vec2
  rotation : Double
  pivot : Vec2
} derive(Eq, Debug, ToJson)

///|
pub fn TransformSpec::new(position : Vec2) -> TransformSpec {
  { position, scale: Vec2::new(1.0, 1.0), rotation: 0.0, pivot: Vec2::zero() }
}

///|
pub fn TransformSpec::matrix(self : TransformSpec) -> Matrix2D {
  Matrix2D::translate(self.position.x, self.position.y)
  .multiply(Matrix2D::translate(self.pivot.x, self.pivot.y))
  .multiply(Matrix2D::rotate(self.rotation))
  .multiply(Matrix2D::scale(self.scale.x, self.scale.y))
  .multiply(Matrix2D::translate(-self.pivot.x, -self.pivot.y))
}

///|
pub fn TransformSpec::at(
  self : TransformSpec,
  position : Vec2,
) -> TransformSpec {
  { ..self, position, }
}

///|
pub fn TransformSpec::scaled(
  self : TransformSpec,
  scale : Vec2,
) -> TransformSpec {
  { ..self, scale, }
}

///|
pub fn TransformSpec::rotated(
  self : TransformSpec,
  rotation : Double,
) -> TransformSpec {
  { ..self, rotation, }
}

///|
pub fn TransformSpec::around(
  self : TransformSpec,
  pivot : Vec2,
) -> TransformSpec {
  { ..self, pivot, }
}

///|
pub fn map_motifs(
  motifs : Array[Motif],
  transform : TransformSpec,
) -> Array[Motif] {
  let result = []
  let matrix = transform.matrix()
  for motif in motifs {
    let (x, y) = matrix.transform_point(motif.anchor.x, motif.anchor.y)
    result.push(
      motif.placed(x, y, motif.scale, motif.rotation + transform.rotation),
    )
  }
  result
}

///|
pub fn transform_path_about(
  path : Path,
  center : Vec2,
  scale : Double,
  rotation : Double,
) -> Path {
  let matrix = Matrix2D::translate(center.x, center.y)
    .multiply(Matrix2D::rotate(rotation))
    .multiply(Matrix2D::scale(scale, scale))
    .multiply(Matrix2D::translate(-center.x, -center.y))
  path.transform(matrix)
}

///|
pub fn fit_rect(source : Rect, target : Rect, contain : Bool) -> Matrix2D {
  let sx = target.width / source.width
  let sy = target.height / source.height
  let scale = if contain { sx.min(sy) } else { sx.max(sy) }
  let source_center = source.center()
  let target_center = target.center()
  Matrix2D::translate(target_center.x, target_center.y)
  .multiply(Matrix2D::scale(scale, scale))
  .multiply(Matrix2D::translate(-source_center.x, -source_center.y))
}

///|
pub fn fit_points(
  points : Array[Vec2],
  target : Rect,
  contain : Bool,
) -> Array[Vec2] {
  let source = bounds_of_points(points)
  transform_points(points, fit_rect(source, target, contain))
}

///|
pub fn cascade_transforms(
  base : Matrix2D,
  transforms : Array[Matrix2D],
) -> Array[Matrix2D] {
  let result = []
  let mut current = base
  for transform in transforms {
    current = current.multiply(transform)
    result.push(current)
  }
  result
}