///|
pub(all) suberror AudioError {
  InvalidAudio
  InvalidDirection
  TransitionBusy
}

///|
pub struct Hrir {
  left : Array[Double]
  right : Array[Double]
}

///|
pub(all) struct Stereo {
  left : Array[Double]
  right : Array[Double]
}

///|
pub struct Renderer {
  mut current : Hrir
  mut target : Hrir?
  history : Array[Double]
  mut cursor : Int32
  mut progress : Int32
  mut fade_length : Int32
}

///|
fn finite(x : Double) -> Bool {
  x == x && x - x == 0.0
}

///|
pub fn Hrir::new(
  left : Array[Double],
  right : Array[Double],
) -> Hrir raise AudioError {
  if left.is_empty() || left.length() != right.length() || left.length() > 4096 {
    raise InvalidAudio
  }
  for x in left {
    if !finite(x) || x.abs() > 1.0e6 {
      raise InvalidAudio
    }
  }
  for x in right {
    if !finite(x) || x.abs() > 1.0e6 {
      raise InvalidAudio
    }
  }
  { left: left.copy(), right: right.copy(), }
}

///|
pub fn Renderer::new(hrir : Hrir) -> Renderer {
  {
    current: hrir,
    target: None,
    history: Array::make(hrir.left.length(), 0.0),
    cursor: 0,
    progress: 0,
    fade_length: 0,
  }
}

// The old and new filters both read the complete existing input history.
// A transition cannot lose a block's tail or restart a newly selected filter.

///|
pub fn Renderer::transition(
  self : Renderer,
  target : Hrir,
  samples : Int32,
) -> Unit raise AudioError {
  if self.target is Some(_) {
    raise TransitionBusy
  }
  if target.left.length() != self.history.length() ||
    samples < 1 ||
    samples > 480000 {
    raise InvalidAudio
  }
  self.target = Some(target)
  self.progress = 0
  self.fade_length = samples
}

///|
fn convolve(renderer : Renderer, h : Hrir) -> (Double, Double) {
  let mut l = 0.0
  let mut r = 0.0
  let size = renderer.history.length()
  for tap in 0.. Stereo raise AudioError {
  if input.length() > 1000000 ||
    input.length().to_int64() * self.history.length().to_int64() > 16000000L {
    raise InvalidAudio
  }
  // Validate the whole block before mutating stream state.
  for x in input {
    if !finite(x) || x.abs() > 1.0e6 {
      raise InvalidAudio
    }
  }
  let left : Array[Double] = []
  let right : Array[Double] = []
  for x in input {
    self.history[self.cursor] = x
    let (old_l, old_r) = convolve(self, self.current)
    let (l, r) = match self.target {
      None => (old_l, old_r)
      Some(next) => {
        let (new_l, new_r) = convolve(self, next)
        self.progress += 1
        let alpha = self.progress.to_double() / self.fade_length.to_double()
        if self.progress == self.fade_length {
          self.current = next
          self.target = None
        }
        (
          (1.0 - alpha) * old_l + alpha * new_l,
          (1.0 - alpha) * old_r + alpha * new_r,
        )
      }
    }
    left.push(l)
    right.push(r)
    self.cursor = (self.cursor + 1) % self.history.length()
  }
  { left, right, }
}

// Drains M-1 zeros; caller controls end-of-stream. Repeated drains advance time.

///|
pub fn Renderer::drain(self : Renderer) -> Stereo raise AudioError {
  let out : Stereo = { left: [], right: [], }
  let mut remaining = self.history.length() - 1
  while remaining > 0 {
    let count = if remaining > 1024 { 1024 } else { remaining }
    let block = self.process(Array::make(count, 0.0))
    for x in block.left {
      out.left.push(x)
    }
    for x in block.right {
      out.right.push(x)
    }
    remaining -= count
  }
  out
}

///|
pub(all) struct Direction {
  x : Double
  y : Double
  z : Double
}

///|
pub(all) struct Measurement {
  direction : Direction
  hrir : Hrir
}

///|
fn normalized(d : Direction) -> Direction raise AudioError {
  if !finite(d.x) ||
    !finite(d.y) ||
    !finite(d.z) ||
    d.x.abs() > 1.0e6 ||
    d.y.abs() > 1.0e6 ||
    d.z.abs() > 1.0e6 {
    raise InvalidDirection
  }
  let length = (d.x * d.x + d.y * d.y + d.z * d.z).sqrt()
  if length < 1.0e-12 {
    raise InvalidDirection
  }
  { x: d.x / length, y: d.y / length, z: d.z / length, }
}

// Nearest measured direction on the sphere; deterministic first-entry ties.
// No undocumented interpolation or individual-listener accuracy claim.

///|
pub fn select(
  measurements : Array[Measurement],
  direction : Direction,
) -> Hrir raise AudioError {
  if measurements.is_empty() || measurements.length() > 4096 {
    raise InvalidDirection
  }
  let d = normalized(direction)
  let mut best = 0
  let mut score = -2.0
  for i in 0.. score {
      best = i
      score = dot
    }
  }
  measurements[best].hrir
}