///|
/// A self-describing waveform frame for streaming transport.
pub struct WaveformFrame {
  sequence : Int
  station : String
  channel : String
  start_time : Double
  sample_rate : Double
  data : Array[Double]
  checksum : String
}

///|
/// Index metadata for a framed waveform bundle.
pub struct FrameIndexEntry {
  sequence : Int
  start_time : Double
  end_time : Double
  sample_count : Int
  checksum : String
}

///|
/// A set of frames and its reconstruction diagnostics.
pub struct WaveformFrameBundle {
  station : String
  channel : String
  sample_rate : Double
  frames : Array[WaveformFrame]
  total_samples : Int
  complete : Bool
  missing_sequences : Array[Int]
}

///|
/// Compute a deterministic checksum over quantized sample values.
pub fn waveform_checksum(data : Array[Double]) -> String {
  let modulus = 2147483647
  let mut state = 17
  for value in data {
    let quantized = (value * 1000000.0).round().to_int()
    state = (state * 65599 + quantized) % modulus
    if state < 0 {
      state += modulus
    }
  }
  state.to_string()
}

///|
/// Construct one frame and calculate its checksum.
pub fn make_waveform_frame(
  sequence : Int,
  station : String,
  channel : String,
  start_time : Double,
  sample_rate : Double,
  data : Array[Double],
) -> WaveformFrame {
  {
    sequence,
    station,
    channel,
    start_time,
    sample_rate,
    data: data.copy(),
    checksum: waveform_checksum(data),
  }
}

///|
/// Split a trace into non-overlapping transport frames.
pub fn frame_trace(trace : Trace, frame_size : Int) -> Array[WaveformFrame] {
  let result = Array::new()
  if frame_size <= 0 || trace.data.is_empty() {
    return result
  }
  let mut sequence = 0
  let mut start = 0
  while start < trace.data.length() {
    let end = (start + frame_size).min(trace.data.length())
    result.push(
      make_waveform_frame(
        sequence,
        trace.station,
        trace.channel,
        trace.start_time + start.to_double() * trace.delta(),
        trace.sampling_rate,
        slice_samples(trace.data, start, end),
      ),
    )
    sequence += 1
    start = end
  }
  result
}

///|
/// Split a trace with a fixed overlap for analysis or retransmission.
pub fn frame_trace_overlapping(
  trace : Trace,
  frame_size : Int,
  overlap : Int,
) -> Array[WaveformFrame] {
  let result = Array::new()
  if frame_size <= 0 || trace.data.is_empty() {
    return result
  }
  let step = (frame_size - overlap).max(1)
  let starts = frame_start_indexes(trace.data.length(), frame_size, step, true)
  for sequence = 0; sequence < starts.length(); sequence = sequence + 1 {
    let start = starts[sequence]
    let end = (start + frame_size).min(trace.data.length())
    result.push(
      make_waveform_frame(
        sequence,
        trace.station,
        trace.channel,
        trace.start_time + start.to_double() * trace.delta(),
        trace.sampling_rate,
        slice_samples(trace.data, start, end),
      ),
    )
  }
  result
}

///|
/// Verify the checksum and metadata of a frame.
pub fn validate_waveform_frame(frame : WaveformFrame) -> Result[Unit, String] {
  if frame.sequence < 0 {
    return Err("frame sequence cannot be negative")
  }
  if frame.sample_rate <= 0.0 {
    return Err("frame sample rate must be positive")
  }
  if waveform_checksum(frame.data) != frame.checksum {
    return Err("frame checksum mismatch")
  }
  Ok(())
}

///|
/// Sort frames by sequence without changing frame values.
pub fn sort_waveform_frames(
  frames : Array[WaveformFrame],
) -> Array[WaveformFrame] {
  let result = frames.copy()
  result.sort_by((left, right) => left.sequence - right.sequence)
  result
}

///|
/// Return missing sequence numbers in a frame set.
pub fn missing_frame_sequences(frames : Array[WaveformFrame]) -> Array[Int] {
  let sorted = sort_waveform_frames(frames)
  let result = Array::new()
  if sorted.is_empty() {
    return result
  }
  let mut expected = 0
  for frame in sorted {
    while expected < frame.sequence {
      result.push(expected)
      expected += 1
    }
    if frame.sequence == expected {
      expected += 1
    }
  }
  result
}

///|
/// Build bundle metadata and identify missing frames.
pub fn make_frame_bundle(frames : Array[WaveformFrame]) -> WaveformFrameBundle {
  let sorted = sort_waveform_frames(frames)
  let missing = missing_frame_sequences(sorted)
  let first = if sorted.is_empty() { None } else { Some(sorted[0]) }
  let station = match first {
    Some(frame) => frame.station
    None => ""
  }
  let channel = match first {
    Some(frame) => frame.channel
    None => ""
  }
  let rate = match first {
    Some(frame) => frame.sample_rate
    None => 0.0
  }
  {
    station,
    channel,
    sample_rate: rate,
    frames: sorted,
    total_samples: frames
    .map(frame => frame.data.length())
    .fold(init=0, (total, count) => total + count),
    complete: missing.is_empty(),
    missing_sequences: missing,
  }
}

///|
/// Reassemble contiguous frames into a trace, rejecting gaps or mismatched metadata.
pub fn reassemble_frame_bundle(
  bundle : WaveformFrameBundle,
) -> Result[Trace, String] {
  if bundle.frames.is_empty() {
    return Err("cannot reassemble an empty frame bundle")
  }
  if !bundle.missing_sequences.is_empty() {
    return Err("frame bundle contains missing sequences")
  }
  let first = bundle.frames[0]
  let data = Array::new(capacity=bundle.total_samples)
  let mut expected_start = first.start_time
  for frame in bundle.frames {
    match validate_waveform_frame(frame) {
      Ok(_) => ()
      Err(message) => return Err(message)
    }
    if frame.station != bundle.station || frame.channel != bundle.channel {
      return Err("frame metadata does not agree with bundle")
    }
    if (frame.start_time - expected_start).abs() >
      1.0 / bundle.sample_rate * 0.01 {
      return Err("frame timing contains a discontinuity")
    }
    for value in frame.data {
      data.push(value)
    }
    expected_start += frame.data.length().to_double() / bundle.sample_rate
  }
  Ok(
    Trace::new(
      bundle.station,
      bundle.channel,
      bundle.sample_rate,
      first.start_time,
      data,
    ),
  )
}

///|
/// Build index entries for fast frame browsing.
pub fn frame_index(frames : Array[WaveformFrame]) -> Array[FrameIndexEntry] {
  let result = Array::new(capacity=frames.length())
  for frame in sort_waveform_frames(frames) {
    let end_time = if frame.data.is_empty() {
      frame.start_time
    } else {
      frame.start_time +
      (frame.data.length() - 1).to_double() / frame.sample_rate
    }
    result.push({
      sequence: frame.sequence,
      start_time: frame.start_time,
      end_time,
      sample_count: frame.data.length(),
      checksum: frame.checksum,
    })
  }
  result
}

///|
/// Serialize a frame in a readable transport format.
pub fn waveform_frame_to_text(frame : WaveformFrame) -> String {
  let mut output = "WAVEFORM_FRAME\n" +
    frame.sequence.to_string() +
    "\n" +
    frame.station +
    "\n" +
    frame.channel +
    "\n" +
    frame.start_time.to_string() +
    "\n" +
    frame.sample_rate.to_string() +
    "\n" +
    frame.checksum +
    "\n"
  for value in frame.data {
    output = output + value.to_string() + "\n"
  }
  output
}

///|
/// Parse the readable frame format emitted by `waveform_frame_to_text`.
pub fn waveform_frame_from_text(text : String) -> Result[WaveformFrame, String] {
  let lines = Array::new()
  for line_view in text.split("\n") {
    let line = line_view.trim().to_owned()
    if line != "" {
      lines.push(line)
    }
  }
  if lines.length() < 7 || lines[0] != "WAVEFORM_FRAME" {
    return Err("invalid waveform frame header")
  }
  let sequence = match parse_text_number(lines[1], 2) {
    Ok(value) => value.to_int()
    Err(_) => return Err("invalid frame sequence")
  }
  let start_time = match parse_text_number(lines[4], 5) {
    Ok(value) => value
    Err(_) => return Err("invalid frame start time")
  }
  let sample_rate = match parse_text_number(lines[5], 6) {
    Ok(value) => value
    Err(_) => return Err("invalid frame sample rate")
  }
  let data = Array::new()
  for index = 7; index < lines.length(); index = index + 1 {
    match parse_text_number(lines[index], index + 1) {
      Ok(value) => data.push(value)
      Err(_) => return Err("invalid frame sample")
    }
  }
  let frame = {
    sequence,
    station: lines[2],
    channel: lines[3],
    start_time,
    sample_rate,
    data,
    checksum: lines[6],
  }
  match validate_waveform_frame(frame) {
    Ok(_) => Ok(frame)
    Err(message) => Err(message)
  }
}

///|
/// Serialize a frame index as a CSV table.
pub fn frame_index_to_csv(index : Array[FrameIndexEntry]) -> String {
  let mut output = "sequence,start_time,end_time,sample_count,checksum\n"
  for entry in index {
    output = output +
      entry.sequence.to_string() +
      "," +
      entry.start_time.to_string() +
      "," +
      entry.end_time.to_string() +
      "," +
      entry.sample_count.to_string() +
      "," +
      entry.checksum +
      "\n"
  }
  output
}

///|
/// Return the physical duration represented by a frame.
pub fn frame_duration(frame : WaveformFrame) -> Double {
  if frame.sample_rate <= 0.0 || frame.data.length() < 2 {
    0.0
  } else {
    (frame.data.length() - 1).to_double() / frame.sample_rate
  }
}

///|
/// Format bundle status for the streaming monitor.
pub fn frame_bundle_status(bundle : WaveformFrameBundle) -> String {
  "station=" +
  bundle.station +
  " channel=" +
  bundle.channel +
  " frames=" +
  bundle.frames.length().to_string() +
  " samples=" +
  bundle.total_samples.to_string() +
  " complete=" +
  bundle.complete.to_string() +
  " missing=" +
  bundle.missing_sequences.length().to_string()
}