///|
/// Deterministic procedural scenes used by examples, tests, and demos.
pub(all) enum PatternKind {
  Checker
  Stripes
  Rings
  Waves
  Noise
  Gradient
} derive(Eq, Debug)

///|
pub(all) struct PatternOptions {
  kind : PatternKind
  scale : Int
  seed : Int
  first : Rgba
  second : Rgba
} derive(Debug)

///|
pub fn PatternOptions::default() -> PatternOptions {
  {
    kind: Checker,
    scale: 8,
    seed: 1,
    first: rgb(24, 28, 42),
    second: rgb(230, 180, 60),
  }
}

///|
pub fn PatternOptions::kind(
  self : PatternOptions,
  value : PatternKind,
) -> PatternOptions {
  { ..self, kind: value }
}

///|
pub fn PatternOptions::scale(
  self : PatternOptions,
  value : Int,
) -> PatternOptions {
  { ..self, scale: if value < 1 { 1 } else { value } }
}

///|
pub fn PatternOptions::seed(
  self : PatternOptions,
  value : Int,
) -> PatternOptions {
  { ..self, seed: value }
}

///|
pub fn PatternOptions::colors(
  self : PatternOptions,
  first : Rgba,
  second : Rgba,
) -> PatternOptions {
  { ..self, first, second }
}

///|
fn pattern_noise(x : Int, y : Int, seed : Int) -> Int {
  let mut value = x * 374761393 + y * 668265263 + seed * 1442695041
  value = (value ^ (value >> 13)) * 1274126177
  let result = value ^ (value >> 16)
  let positive = if result < 0 { -result } else { result }
  positive % 256
}

///|
fn mix_pattern_color(
  first : Rgba,
  second : Rgba,
  numerator : Int,
  denominator : Int,
) -> Rgba {
  let safe = if numerator < 0 {
    0
  } else if numerator > denominator {
    denominator
  } else {
    numerator
  }
  let inverse = denominator - safe
  rgba(
    ((first.red().to_int() * inverse + second.red().to_int() * safe) /
    denominator).to_byte(),
    ((first.green().to_int() * inverse + second.green().to_int() * safe) /
    denominator).to_byte(),
    ((first.blue().to_int() * inverse + second.blue().to_int() * safe) /
    denominator).to_byte(),
    ((first.alpha().to_int() * inverse + second.alpha().to_int() * safe) /
    denominator).to_byte(),
  )
}

///|
/// Render a deterministic procedural pattern at a requested size.
pub fn procedural_pattern(
  width : Int,
  height : Int,
  options? : PatternOptions = PatternOptions::default(),
) -> Canvas {
  let safe_width = if width < 0 { 0 } else { width }
  let safe_height = if height < 0 { 0 } else { height }
  let safe_scale = if options.scale < 1 { 1 } else { options.scale }
  let result = Canvas::new(
    width=safe_width,
    height=safe_height,
    fill=options.first,
  )
  for y in 0..
          if (x / safe_scale + y / safe_scale) % 2 == 0 {
            0
          } else {
            1000
          }
        Stripes => if x / safe_scale % 2 == 0 { 0 } else { 1000 }
        Rings => {
          let dx = x - safe_width / 2
          let dy = y - safe_height / 2
          if (dx * dx + dy * dy) / safe_scale % 2 == 0 {
            0
          } else {
            1000
          }
        }
        Waves => (x * 31 + y * 17) / safe_scale % 20 * 1000 / 19
        Noise => pattern_noise(x, y, options.seed) * 1000 / 255
        Gradient =>
          if safe_width <= 1 {
            0
          } else {
            x * 1000 / (safe_width - 1)
          }
      }
      result.pixels[x + y * safe_width] = mix_pattern_color(
        options.first,
        options.second,
        value,
        1000,
      )
    }
  }
  result
}

///|
/// Build a palette-friendly animation by shifting a pattern phase.
pub fn procedural_animation(
  width : Int,
  height : Int,
  frames : Int,
  options : PatternOptions,
  delay_cs? : Int = 6,
) -> Animation {
  let result = Animation::new(width~, height~)
  let count = if frames < 1 { 1 } else { frames }
  for index in 0.. Canvas {
  let safe_width = if width < 4 { 4 } else { width }
  let safe_height = if height < 4 { 4 } else { height }
  let result = Canvas::new(
    width=safe_width,
    height=safe_height,
    fill=rgb(16, 16, 16),
  )
  let cell_width = safe_width / 2
  let cell_height = safe_height / 3
  let kinds = [Checker, Stripes, Rings, Waves, Noise, Gradient]
  for index, kind in kinds {
    let row = index / 2
    let column = index % 2
    let cell = procedural_pattern(
      cell_width,
      cell_height,
      options=PatternOptions::default().kind(kind).scale(4 + index),
    )
    ignore(
      result.blit(
        cell,
        destination_x=column * cell_width,
        destination_y=row * cell_height,
      ),
    )
  }
  result
}

///|
/// Apply a phase-shifted pattern behind an existing animation.
pub fn Animation::pattern_background(
  self : Animation,
  options : PatternOptions,
) -> Animation {
  let result = Animation::new(width=self.width, height=self.height)
    .with_loop_count(loop_count=self.loop_count)
    .with_background(background=self.background)
  for index, frame in self.frames {
    let background = procedural_pattern(
      self.width,
      self.height,
      options=options.seed(options.seed + index),
    )
    let composed = background.overlay(frame.canvas(), left=0, top=0)
    ignore(result.add_frame(frame.with_canvas(composed)))
  }
  result
}