///|
/// Stateful integer-sample delay line backed by a circular buffer.
pub struct DelayLine {
  priv buffer : FixedArray[Double]
  priv mut write_pos : Int
  priv mut delay_samples : Int
  priv mut feedback : Double
}

///|
/// Create a delay line with preallocated storage and a normalized delay length.
#alias(new)
pub fn DelayLine::DelayLine(
  max_delay_samples : Int,
  delay_samples? : Int = default_delay_samples(max_delay_samples),
  feedback? : Double = 0.0,
) -> DelayLine {
  let capacity = max_delay_capacity(max_delay_samples)
  {
    buffer: FixedArray::make(capacity, 0.0),
    write_pos: 0,
    delay_samples: normalize_delay_samples(delay_samples, capacity),
    feedback: normalize_feedback(feedback),
  }
}

///|
/// Return the maximum representable delay length in samples.
pub fn DelayLine::max_delay_samples(self : DelayLine) -> Int {
  self.buffer.length()
}

///|
/// Return the active delay length in samples.
pub fn DelayLine::delay_samples(self : DelayLine) -> Int {
  self.delay_samples
}

///|
/// Return the active feedback amount.
pub fn DelayLine::feedback(self : DelayLine) -> Double {
  self.feedback
}

///|
/// Update the active delay length, clamping to the allocated storage range.
pub fn DelayLine::set_delay_samples(
  self : DelayLine,
  delay_samples : Int,
) -> Unit {
  self.delay_samples = normalize_delay_samples(
    delay_samples,
    self.buffer.length(),
  )
}

///|
/// Update the feedback amount, clamping to the stable supported range.
pub fn DelayLine::set_feedback(self : DelayLine, feedback : Double) -> Unit {
  self.feedback = normalize_feedback(feedback)
}

///|
/// Clear the circular buffer state without changing the configured delay.
pub fn DelayLine::reset(self : DelayLine) -> Unit {
  self.buffer.fill(0.0)
  self.write_pos = 0
}

///|
/// Process one input sample and return the delayed output sample.
pub fn DelayLine::tick(self : DelayLine, input : Double) -> Double {
  if self.delay_samples == 0 {
    self.buffer[self.write_pos] = input
    self.advance_write_pos()
    input
  } else {
    let read_pos = delayed_read_pos(
      self.write_pos,
      self.delay_samples,
      self.buffer.length(),
    )
    let output = self.buffer[read_pos]
    self.buffer[self.write_pos] = input + output * self.feedback
    self.advance_write_pos()
    output
  }
}

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

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

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

///|
fn DelayLine::advance_write_pos(self : DelayLine) -> Unit {
  self.write_pos = self.write_pos + 1
  if self.write_pos >= self.buffer.length() {
    self.write_pos = 0
  }
}

///|
fn delayed_read_pos(
  write_pos : Int,
  delay_samples : Int,
  capacity : Int,
) -> Int {
  let read_pos = write_pos - delay_samples
  if read_pos < 0 {
    read_pos + capacity
  } else {
    read_pos
  }
}

///|
fn max_delay_capacity(value : Int) -> Int {
  if value > 0 {
    value
  } else {
    1
  }
}

///|
fn default_delay_samples(value : Int) -> Int {
  if value > 0 {
    value
  } else {
    0
  }
}

///|
fn normalize_delay_samples(value : Int, capacity : Int) -> Int {
  if value < 0 {
    0
  } else if value > capacity {
    capacity
  } else {
    value
  }
}

///|
fn normalize_feedback(value : Double) -> Double {
  if !is_finite(value) {
    0.0
  } else if value < -max_feedback_amount() {
    -max_feedback_amount()
  } else if value > max_feedback_amount() {
    max_feedback_amount()
  } else {
    value
  }
}

///|
pub fn max_feedback_amount() -> Double {
  0.99
}