///|
/// Layer policy for combining animation sources at a common timestamp.
pub enum LayerBlendMode {
  OverrideLayer
  AdditiveLayer
  MultiplyLayer
  DifferenceLayer
} derive(Debug)

///|
pub fn layer_blend_modes() -> Array[LayerBlendMode] {
  [OverrideLayer, AdditiveLayer, MultiplyLayer, DifferenceLayer]
}

///|
pub struct ScalarBlendLayer {
  value : Double
  weight : Double
  mode : LayerBlendMode
  priority : Int
} derive(Debug)

///|
pub fn scalar_blend_layer(
  value : Double,
  weight : Double,
  mode? : LayerBlendMode = OverrideLayer,
  priority? : Int = 0,
) -> ScalarBlendLayer {
  { value, weight: weight.max(0.0), mode, priority }
}

///|
pub fn ScalarBlendLayer::value(self : ScalarBlendLayer) -> Double {
  self.value
}

///|
pub fn ScalarBlendLayer::weight(self : ScalarBlendLayer) -> Double {
  self.weight
}

///|
pub fn ScalarBlendLayer::mode(self : ScalarBlendLayer) -> LayerBlendMode {
  self.mode
}

///|
pub fn ScalarBlendLayer::priority(self : ScalarBlendLayer) -> Int {
  self.priority
}

///|
pub fn normalize_weights(weights : Array[Double]) -> Array[Double] {
  let total = weights.fold(init=0.0, fn(sum, value) { sum + value.max(0.0) })
  let values : Array[Double] = []
  if total <= 0.0 {
    for _ in weights {
      values.push(0.0)
    }
  } else {
    for weight in weights {
      values.push(weight.max(0.0) / total)
    }
  }
  values
}

///|
fn blend_scalar_value(
  mode : LayerBlendMode,
  base : Double,
  layer : Double,
) -> Double {
  match mode {
    OverrideLayer => layer
    AdditiveLayer => base + layer
    MultiplyLayer => base * layer
    DifferenceLayer => base - layer
  }
}

///|
pub fn blend_scalar_layers(
  layers : Array[ScalarBlendLayer],
  fallback : Double,
) -> Double {
  if layers.length() == 0 {
    return fallback
  }
  let sorted = layers.copy()
  sorted.sort_by(fn(left, right) { right.priority.compare(left.priority) })
  let weights : Array[Double] = []
  for layer in sorted {
    weights.push(layer.weight)
  }
  let normalized = normalize_weights(weights)
  let mut result = fallback
  for index in 0.. Point2BlendLayer {
  { value, weight: weight.max(0.0), mode, priority }
}

///|
pub fn Point2BlendLayer::value(self : Point2BlendLayer) -> Point2 {
  self.value
}

///|
pub fn Point2BlendLayer::weight(self : Point2BlendLayer) -> Double {
  self.weight
}

///|
pub fn Point2BlendLayer::mode(self : Point2BlendLayer) -> LayerBlendMode {
  self.mode
}

///|
pub fn Point2BlendLayer::priority(self : Point2BlendLayer) -> Int {
  self.priority
}

///|
pub fn blend_point2_layers(
  layers : Array[Point2BlendLayer],
  fallback : Point2,
) -> Point2 {
  if layers.length() == 0 {
    return fallback
  }
  let sorted = layers.copy()
  sorted.sort_by(fn(left, right) { right.priority.compare(left.priority) })
  let weights : Array[Double] = []
  for layer in sorted {
    weights.push(layer.weight)
  }
  let normalized = normalize_weights(weights)
  let mut result = fallback
  for index in 0.. layer.value
      AdditiveLayer =>
        point2(x=result.x() + layer.value.x(), y=result.y() + layer.value.y())
      MultiplyLayer =>
        point2(x=result.x() * layer.value.x(), y=result.y() * layer.value.y())
      DifferenceLayer =>
        point2(x=result.x() - layer.value.x(), y=result.y() - layer.value.y())
    }
    result = result.lerp(value, normalized[index])
  }
  result
}

///|
pub struct Point3BlendLayer {
  value : Point3
  weight : Double
  mode : LayerBlendMode
  priority : Int
} derive(Debug)

///|
pub fn point3_blend_layer(
  value : Point3,
  weight : Double,
  mode? : LayerBlendMode = OverrideLayer,
  priority? : Int = 0,
) -> Point3BlendLayer {
  { value, weight: weight.max(0.0), mode, priority }
}

///|
pub fn Point3BlendLayer::value(self : Point3BlendLayer) -> Point3 {
  self.value
}

///|
pub fn Point3BlendLayer::weight(self : Point3BlendLayer) -> Double {
  self.weight
}

///|
pub fn Point3BlendLayer::mode(self : Point3BlendLayer) -> LayerBlendMode {
  self.mode
}

///|
pub fn Point3BlendLayer::priority(self : Point3BlendLayer) -> Int {
  self.priority
}

///|
pub fn blend_point3_layers(
  layers : Array[Point3BlendLayer],
  fallback : Point3,
) -> Point3 {
  if layers.length() == 0 {
    return fallback
  }
  let sorted = layers.copy()
  sorted.sort_by(fn(left, right) { right.priority.compare(left.priority) })
  let weights : Array[Double] = []
  for layer in sorted {
    weights.push(layer.weight)
  }
  let normalized = normalize_weights(weights)
  let mut result = fallback
  for index in 0.. layer.value
      AdditiveLayer =>
        point3(
          x=result.x() + layer.value.x(),
          y=result.y() + layer.value.y(),
          z=result.z() + layer.value.z(),
        )
      MultiplyLayer =>
        point3(
          x=result.x() * layer.value.x(),
          y=result.y() * layer.value.y(),
          z=result.z() * layer.value.z(),
        )
      DifferenceLayer =>
        point3(
          x=result.x() - layer.value.x(),
          y=result.y() - layer.value.y(),
          z=result.z() - layer.value.z(),
        )
    }
    result = result.lerp(value, normalized[index])
  }
  result
}

///|
pub struct ColorBlendLayer {
  value : Rgba
  weight : Double
  mode : BlendMode
  priority : Int
} derive(Debug)

///|
pub fn color_blend_layer(
  value : Rgba,
  weight : Double,
  mode? : BlendMode = Normal,
  priority? : Int = 0,
) -> ColorBlendLayer {
  { value, weight: weight.max(0.0), mode, priority }
}

///|
pub fn ColorBlendLayer::value(self : ColorBlendLayer) -> Rgba {
  self.value
}

///|
pub fn ColorBlendLayer::weight(self : ColorBlendLayer) -> Double {
  self.weight
}

///|
pub fn ColorBlendLayer::mode(self : ColorBlendLayer) -> BlendMode {
  self.mode
}

///|
pub fn blend_color_layers(
  layers : Array[ColorBlendLayer],
  fallback : Rgba,
) -> Rgba {
  if layers.length() == 0 {
    return fallback
  }
  let sorted = layers.copy()
  sorted.sort_by(fn(left, right) { right.priority.compare(left.priority) })
  let weights : Array[Double] = []
  for layer in sorted {
    weights.push(layer.weight)
  }
  let normalized = normalize_weights(weights)
  let mut result = fallback
  for index in 0.. Transform3BlendLayer {
  { value, weight: weight.max(0.0), priority }
}

///|
pub fn Transform3BlendLayer::value(self : Transform3BlendLayer) -> Transform3D {
  self.value
}

///|
pub fn Transform3BlendLayer::weight(self : Transform3BlendLayer) -> Double {
  self.weight
}

///|
pub fn Transform3BlendLayer::priority(self : Transform3BlendLayer) -> Int {
  self.priority
}

///|
pub fn blend_transform3_layers(
  layers : Array[Transform3BlendLayer],
  fallback : Transform3D,
) -> Transform3D {
  if layers.length() == 0 {
    return fallback
  }
  let sorted = layers.copy()
  sorted.sort_by(fn(left, right) { right.priority.compare(left.priority) })
  let weights : Array[Double] = []
  for layer in sorted {
    weights.push(layer.weight)
  }
  let normalized = normalize_weights(weights)
  let mut result = fallback
  for index in 0.. BlendMask2D {
  { values: values.copy(), default_weight: clamp01(default_weight) }
}

///|
pub fn BlendMask2D::length(self : BlendMask2D) -> Int {
  self.values.length()
}

///|
pub fn BlendMask2D::sample(self : BlendMask2D, index : Int) -> Double {
  if index < 0 || index >= self.values.length() {
    self.default_weight
  } else {
    clamp01(self.values[index])
  }
}

///|
pub fn BlendMask2D::multiply(
  self : BlendMask2D,
  other : BlendMask2D,
) -> BlendMask2D {
  let count = self.values.length().max(other.values.length())
  let values : Array[Double] = []
  for index in 0.. BlendMask2D {
  if radius <= 0 || self.values.length() == 0 {
    return self
  }
  let values : Array[Double] = []
  for index in 0..= 0 && cursor < self.values.length() {
        sum = sum + self.values[cursor]
        count = count + 1
      }
    }
    values.push(sum / count.to_double())
  }
  blend_mask2d(values, default_weight=self.default_weight)
}