///|
/// Coordinates in metres. The room origin is its low-x/low-y/low-z corner.
pub(all) struct Vec3 {
  x : Double
  y : Double
  z : Double
}

///|
pub struct Scene {
  priv room : Vec3
  priv source : Vec3
  priv receivers : Array[Vec3]
  priv absorption : Array[Double]
  priv sample_rate : Int32
  priv max_order : Int32
  priv frames : Int32
  priv speed : Double
  priv half_width : Int32
}

///|
fn inside(p : Vec3, room : Vec3) -> Bool {
  p.x > 0.0 &&
  p.x < room.x &&
  p.y > 0.0 &&
  p.y < room.y &&
  p.z > 0.0 &&
  p.z < room.z
}

///|
fn distance(a : Vec3, b : Vec3) -> Double {
  let x = a.x - b.x
  let y = a.y - b.y
  let z = a.z - b.z
  (x * x + y * y + z * z).sqrt()
}

///|
/// Validates before allocating. Arrays are copied to isolate caller mutation.
pub fn scene(
  room : Vec3,
  source : Vec3,
  receivers : Array[Vec3],
  absorption : Array[Double],
  sample_rate : Int32,
  max_order : Int32,
  duration_ms : Int32,
  speed~ : Double = 343.0,
  half_width~ : Int32 = 16,
) -> Result[Scene, String] {
  for v in [room.x, room.y, room.z] {
    if !(v >= 0.1 && v <= 1000.0) {
      return Err("room dimensions must be finite in 0.1..1000 metres")
    }
  }
  if !inside(source, room) {
    return Err("source must be strictly inside the room")
  }
  if receivers.is_empty() || receivers.length() > 8 {
    return Err("receiver count must be 1..8")
  }
  for r in receivers {
    if !inside(r, room) {
      return Err("receiver must be strictly inside the room")
    }
    if distance(source, r) < 0.01 {
      return Err("source-receiver distance must be at least 0.01 metres")
    }
  }
  if absorption.length() != 6 {
    return Err("six wall absorption coefficients are required")
  }
  for a in absorption {
    if !(a >= 0.0 && a <= 1.0) {
      return Err("energy absorption must be finite in 0..1")
    }
  }
  if sample_rate < 8000 ||
    sample_rate > 96000 ||
    max_order < 0 ||
    max_order > 12 ||
    duration_ms < 1 ||
    duration_ms > 2000 ||
    half_width < 4 ||
    half_width > 32 ||
    !(speed >= 100.0 && speed <= 1000.0) {
    return Err("invalid rate, order, duration, kernel width or sound speed")
  }
  let frames = (sample_rate * duration_ms + 999) / 1000
  if (frames + 2 * half_width) * receivers.length() > 1000000 {
    return Err("RIR sample budget exceeded: 1000000")
  }
  Ok({
    room,
    source,
    receivers: receivers.copy(),
    absorption: absorption.copy(),
    sample_rate,
    max_order,
    frames,
    speed,
    half_width,
  })
}

///|
pub fn Scene::physical_frames(self : Scene) -> Int32 {
  self.frames
}

///|
pub fn Scene::output_frames(self : Scene) -> Int32 {
  self.frames + 2 * self.half_width
}

///|
pub fn Scene::latency_samples(self : Scene) -> Int32 {
  self.half_width
}

///|
pub fn Scene::rate(self : Scene) -> Int32 {
  self.sample_rate
}

///|
pub fn Scene::order(self : Scene) -> Int32 {
  self.max_order
}

///|
pub fn Scene::sound_speed(self : Scene) -> Double {
  self.speed
}

///|
pub fn Scene::dimensions(self : Scene) -> Vec3 {
  self.room
}

///|
pub fn Scene::source_position(self : Scene) -> Vec3 {
  self.source
}

///|
pub fn Scene::receiver_positions(self : Scene) -> Array[Vec3] {
  self.receivers.copy()
}

///|
pub fn Scene::wall_absorption(self : Scene) -> Array[Double] {
  self.absorption.copy()
}