///|
/// Deterministic procedural motion primitives. They are seeded explicitly so
/// a preview and a final export produce identical camera shake and particles.
pub struct NoiseSource2 {
  seed : Int
  frequency : Double
  octaves : Int
  persistence : Double
  lacunarity : Double
} derive(Debug)

///|
pub fn noise_source2(seed : Int) -> NoiseSource2 {
  { seed, frequency: 1.0, octaves: 4, persistence: 0.5, lacunarity: 2.0 }
}

///|
pub fn NoiseSource2::with_frequency(
  self : NoiseSource2,
  frequency : Double,
) -> NoiseSource2 {
  {
    seed: self.seed,
    frequency: frequency.max(0.000001),
    octaves: self.octaves,
    persistence: self.persistence,
    lacunarity: self.lacunarity,
  }
}

///|
pub fn NoiseSource2::with_octaves(
  self : NoiseSource2,
  octaves : Int,
) -> NoiseSource2 {
  {
    seed: self.seed,
    frequency: self.frequency,
    octaves: octaves.max(1).min(16),
    persistence: self.persistence,
    lacunarity: self.lacunarity,
  }
}

///|
pub fn NoiseSource2::with_persistence(
  self : NoiseSource2,
  persistence : Double,
) -> NoiseSource2 {
  {
    seed: self.seed,
    frequency: self.frequency,
    octaves: self.octaves,
    persistence: clamp01(persistence),
    lacunarity: self.lacunarity,
  }
}

///|
pub fn NoiseSource2::with_lacunarity(
  self : NoiseSource2,
  lacunarity : Double,
) -> NoiseSource2 {
  {
    seed: self.seed,
    frequency: self.frequency,
    octaves: self.octaves,
    persistence: self.persistence,
    lacunarity: lacunarity.max(1.0),
  }
}

///|
pub fn NoiseSource2::seed(self : NoiseSource2) -> Int {
  self.seed
}

///|
pub fn NoiseSource2::frequency(self : NoiseSource2) -> Double {
  self.frequency
}

///|
pub fn NoiseSource2::octaves(self : NoiseSource2) -> Int {
  self.octaves
}

///|
fn noise_hash(seed : Int, x : Int, y : Int) -> Double {
  let raw = seed + x * 374761393 + y * 668265263
  let folded = (raw * (raw * 1274126177 + 60493) + 1442695041) % 2147483647
  let positive = if folded < 0 { folded + 2147483647 } else { folded }
  positive.to_double() / 2147483647.0 * 2.0 - 1.0
}

///|
fn smooth_noise_curve(value : Double) -> Double {
  value * value * (3.0 - 2.0 * value)
}

///|
fn lattice_noise(seed : Int, x : Double, y : Double) -> Double {
  let floor_x = @math.floor(x)
  let floor_y = @math.floor(y)
  let fraction_x = smooth_noise_curve(x - floor_x)
  let fraction_y = smooth_noise_curve(y - floor_y)
  let ix = floor_x.to_int()
  let iy = floor_y.to_int()
  let bottom_left = noise_hash(seed, ix, iy)
  let bottom_right = noise_hash(seed, ix + 1, iy)
  let top_left = noise_hash(seed, ix, iy + 1)
  let top_right = noise_hash(seed, ix + 1, iy + 1)
  let bottom = bottom_left + (bottom_right - bottom_left) * fraction_x
  let top = top_left + (top_right - top_left) * fraction_x
  bottom + (top - bottom) * fraction_y
}

///|
pub fn NoiseSource2::value(
  self : NoiseSource2,
  x : Double,
  y : Double,
) -> Double {
  lattice_noise(self.seed, x * self.frequency, y * self.frequency)
}

///|
pub fn NoiseSource2::fractal(
  self : NoiseSource2,
  x : Double,
  y : Double,
) -> Double {
  let mut amplitude = 1.0
  let mut frequency = self.frequency
  let mut total = 0.0
  let mut weight = 0.0
  for octave in 0.. Array[Double] {
  let values : Array[Double] = []
  for index in 0.. Point2 {
  let step = epsilon.max(0.000000000001)
  let dx = (self.fractal(x + step, y) - self.fractal(x - step, y)) /
    (2.0 * step)
  let dy = (self.fractal(x, y + step) - self.fractal(x, y - step)) /
    (2.0 * step)
  point2(x=dx, y=dy)
}

///|
pub struct JitterSequence2 {
  source : NoiseSource2
  amplitude : Point2
  frequency : Double
  phase : Point2
} derive(Debug)

///|
pub fn jitter_sequence2(seed : Int, amplitude : Point2) -> JitterSequence2 {
  {
    source: noise_source2(seed),
    amplitude,
    frequency: 1.0,
    phase: point2(x=0.0, y=0.0),
  }
}

///|
pub fn JitterSequence2::with_frequency(
  self : JitterSequence2,
  frequency : Double,
) -> JitterSequence2 {
  {
    source: self.source,
    amplitude: self.amplitude,
    frequency: frequency.max(0.000001),
    phase: self.phase,
  }
}

///|
pub fn JitterSequence2::with_phase(
  self : JitterSequence2,
  phase : Point2,
) -> JitterSequence2 {
  {
    source: self.source,
    amplitude: self.amplitude,
    frequency: self.frequency,
    phase,
  }
}

///|
pub fn JitterSequence2::sample(self : JitterSequence2, time : Double) -> Point2 {
  let x_noise = self.source.fractal(time * self.frequency + self.phase.x(), 0.0)
  let y_noise = self.source.fractal(
    time * self.frequency + self.phase.y(),
    17.0,
  )
  point2(x=x_noise * self.amplitude.x(), y=y_noise * self.amplitude.y())
}

///|
pub fn JitterSequence2::sample_many(
  self : JitterSequence2,
  start : Double,
  step : Double,
  count : Int,
) -> Array[Point2] {
  let values : Array[Point2] = []
  for index in 0.. ProceduralMotion2 {
  { base, velocity, jitter, envelope }
}

///|
pub fn ProceduralMotion2::sample(
  self : ProceduralMotion2,
  time : Double,
  normalized_time : Double,
) -> Point2 {
  let noise = self.jitter.sample(time)
  let envelope = self.envelope.apply(clamp01(normalized_time))
  point2(
    x=self.base.x() + self.velocity.x() * time + noise.x() * envelope,
    y=self.base.y() + self.velocity.y() * time + noise.y() * envelope,
  )
}

///|
pub fn ProceduralMotion2::sample_many(
  self : ProceduralMotion2,
  start : Double,
  end : Double,
  count : Int,
) -> Array[Point2] {
  let values : Array[Point2] = []
  let actual = count.max(1)
  for index in 0.. Array[ShakeFalloff2] {
  [ConstantShake, LinearShake, SmoothShake, ExponentialShake(2.0)]
}

///|
pub fn shake_falloff_value(mode : ShakeFalloff2, ratio : Double) -> Double {
  let value = clamp01(ratio)
  match mode {
    ConstantShake => 1.0
    LinearShake => 1.0 - value
    SmoothShake => 1.0 - value * value * (3.0 - 2.0 * value)
    ExponentialShake(power) => @math.pow(1.0 - value, power.max(0.000001))
  }
}

///|
pub fn apply_shake_falloff(
  value : Point2,
  mode : ShakeFalloff2,
  ratio : Double,
) -> Point2 {
  let scale = shake_falloff_value(mode, ratio)
  point2(x=value.x() * scale, y=value.y() * scale)
}

///|
pub fn deterministic_jitter(seed : Int, index : Int) -> Double {
  noise_hash(seed, index, 0)
}