///|
/// A compact analysis of identifier and payload behavior.
pub struct FrameAnalysis {
  unique_ids : Int
  changed_payloads : Int
  repeated_payloads : Int
  average_payload : Double
  minimum_gap_us : UInt64?
  maximum_gap_us : UInt64?
}

///|
/// Analyze adjacent frames in capture order.
pub fn analyze_trace(trace : Trace) -> FrameAnalysis {
  let entries = trace.entries()
  let ids : Array[UInt] = []
  let mut changed = 0
  let mut repeated = 0
  let mut payload_total = 0
  let mut previous_frame : Frame? = None
  let mut previous_time : UInt64? = None
  let mut minimum_gap : UInt64? = None
  let mut maximum_gap : UInt64? = None
  for entry in entries {
    ids.push(entry.frame.id())
    payload_total += entry.frame.data().length()
    match previous_frame {
      Some(frame) =>
        if frame.data() == entry.frame.data() {
          repeated += 1
        } else {
          changed += 1
        }
      None => ()
    }
    match previous_time {
      Some(timestamp) => {
        let gap = entry.timestamp_us - timestamp
        match minimum_gap {
          Some(value) => if gap < value { minimum_gap = Some(gap) }
          None => minimum_gap = Some(gap)
        }
        match maximum_gap {
          Some(value) => if gap > value { maximum_gap = Some(gap) }
          None => maximum_gap = Some(gap)
        }
      }
      None => ()
    }
    previous_frame = Some(entry.frame)
    previous_time = Some(entry.timestamp_us)
  }
  ids.sort()
  ids.dedup()
  {
    unique_ids: ids.length(),
    changed_payloads: changed,
    repeated_payloads: repeated,
    average_payload: if entries.is_empty() {
      0.0
    } else {
      payload_total.to_double() / entries.length().to_double()
    },
    minimum_gap_us: minimum_gap,
    maximum_gap_us: maximum_gap,
  }
}

///|
pub fn FrameAnalysis::unique_ids(self : FrameAnalysis) -> Int {
  self.unique_ids
}

///|
pub fn FrameAnalysis::changed_payloads(self : FrameAnalysis) -> Int {
  self.changed_payloads
}

///|
pub fn FrameAnalysis::repeated_payloads(self : FrameAnalysis) -> Int {
  self.repeated_payloads
}

///|
pub fn FrameAnalysis::average_payload(self : FrameAnalysis) -> Double {
  self.average_payload
}

///|
pub fn FrameAnalysis::minimum_gap(self : FrameAnalysis) -> UInt64? {
  self.minimum_gap_us
}

///|
pub fn FrameAnalysis::maximum_gap(self : FrameAnalysis) -> UInt64? {
  self.maximum_gap_us
}

///|
pub fn FrameAnalysis::to_text(self : FrameAnalysis) -> String {
  "unique_ids=\{self.unique_ids} changed=\{self.changed_payloads} repeated=\{self.repeated_payloads} average_payload=\{self.average_payload}"
}