///|
/// Supported biquad response shapes for the Phase 1 filter primitive.
pub(all) enum BiquadMode {
  LowPass
  HighPass
  BandPass
} derive(Eq)

///|
/// Stateful biquad filter using Direct Form II Transposed processing.
pub struct Biquad {
  priv mut b0 : Double
  priv mut b1 : Double
  priv mut b2 : Double
  priv mut a1 : Double
  priv mut a2 : Double
  priv mut z1 : Double
  priv mut z2 : Double
  priv mut configured : Bool
}

///|
/// Create a new biquad filter with zeroed state and no active coefficients.
#alias(new)
pub fn Biquad::Biquad() -> Biquad {
  {
    b0: 0.0,
    b1: 0.0,
    b2: 0.0,
    a1: 0.0,
    a2: 0.0,
    z1: 0.0,
    z2: 0.0,
    configured: false,
  }
}

///|
/// Clear the filter delay state without changing the current coefficients.
pub fn Biquad::reset(self : Biquad) -> Unit {
  self.z1 = 0.0
  self.z2 = 0.0
}

///|
/// Recalculate filter coefficients for the given mode, cutoff, and resonance.
///
/// Returns `false` and disables processing when the parameters are invalid.
pub fn Biquad::update(
  self : Biquad,
  context : DspContext,
  mode : BiquadMode,
  cutoff : Double,
  q : Double,
) -> Bool {
  let sample_rate = context.sample_rate()
  if !valid_biquad_params(sample_rate, cutoff, q) {
    self.invalidate()
    return false
  }

  let w0 = 2.0 * @math.PI * cutoff / sample_rate
  let cos_w0 = @math.cos(w0)
  let alpha = @math.sin(w0) / (2.0 * q)
  let a0 = 1.0 + alpha
  if !is_finite(a0) || a0 <= 0.0 {
    self.invalidate()
    return false
  }

  let b0 = match mode {
    LowPass => (1.0 - cos_w0) * 0.5
    HighPass => (1.0 + cos_w0) * 0.5
    BandPass => alpha
  }
  let b1 = match mode {
    LowPass => 1.0 - cos_w0
    HighPass => -(1.0 + cos_w0)
    BandPass => 0.0
  }
  let b2 = match mode {
    LowPass => (1.0 - cos_w0) * 0.5
    HighPass => (1.0 + cos_w0) * 0.5
    BandPass => -alpha
  }
  let a1 = -2.0 * cos_w0
  let a2 = 1.0 - alpha
  let inv_a0 = 1.0 / a0

  self.b0 = b0 * inv_a0
  self.b1 = b1 * inv_a0
  self.b2 = b2 * inv_a0
  self.a1 = a1 * inv_a0
  self.a2 = a2 * inv_a0
  self.configured = self.finite_coefficients()
  if !self.configured {
    self.invalidate()
  }
  self.configured
}

///|
/// Filter one input sample and advance the internal delay state.
pub fn Biquad::tick(self : Biquad, input : Double) -> Double {
  if !self.configured {
    return 0.0
  }

  let output = self.b0 * input + self.z1
  let next_z1 = self.b1 * input - self.a1 * output + self.z2
  let next_z2 = self.b2 * input - self.a2 * output
  if !is_finite(output) || !is_finite(next_z1) || !is_finite(next_z2) {
    self.invalidate()
    return 0.0
  }

  self.z1 = next_z1
  self.z2 = next_z2
  output
}

///|
/// Filter one block of samples in place.
pub fn Biquad::process(
  self : Biquad,
  context : DspContext,
  buffer : AudioBuffer,
) -> Unit {
  let sample_rate = context.sample_rate()
  let sample_count = effective_sample_count(context, buffer)
  if sample_rate <= 0.0 || sample_count <= 0 || !self.configured {
    buffer.fill(0.0)
    return
  }

  for index = 0; index < sample_count; index = index + 1 {
    buffer.set(index, self.tick(buffer.get(index)))
  }

  for index = sample_count; index < buffer.length(); index = index + 1 {
    buffer.set(index, 0.0)
  }
}

///|
fn valid_biquad_params(
  sample_rate : Double,
  cutoff : Double,
  q : Double,
) -> Bool {
  let nyquist = sample_rate * 0.5
  sample_rate > 0.0 &&
  is_finite(sample_rate) &&
  cutoff > 0.0 &&
  cutoff < nyquist &&
  is_finite(cutoff) &&
  q > 0.0 &&
  is_finite(q)
}

///|
fn Biquad::invalidate(self : Biquad) -> Unit {
  self.b0 = 0.0
  self.b1 = 0.0
  self.b2 = 0.0
  self.a1 = 0.0
  self.a2 = 0.0
  self.z1 = 0.0
  self.z2 = 0.0
  self.configured = false
}

///|
fn Biquad::finite_coefficients(self : Biquad) -> Bool {
  is_finite(self.b0) &&
  is_finite(self.b1) &&
  is_finite(self.b2) &&
  is_finite(self.a1) &&
  is_finite(self.a2)
}