///|
/// Kind of event in an auditable measurement run.
pub(all) enum TraceEventKind {
  PlanStarted
  PlanStep
  ObservationRecorded
  DecisionIssued
  WarningIssued
  PlanFinished
} derive(Eq, Debug)

///|
/// One immutable trace event.
pub(all) struct TraceEvent {
  kind : TraceEventKind
  part_id : String
  feature_id : String
  step_index : Int
  message : String
}

///|
/// Ordered trace log for a plan run.
pub(all) struct TraceLog {
  part_id : String
  events : Array[TraceEvent]
}

///|
/// Create a trace log containing lifecycle and step events.
pub fn trace_log_from_plan(plan : MeasurementPlan) -> TraceLog {
  let events = [
    {
      kind: TraceEventKind::PlanStarted,
      part_id: plan.part_id,
      feature_id: "",
      step_index: -1,
      message: "plan started",
    },
  ]
  for i, step in plan.steps {
    events.push({
      kind: TraceEventKind::PlanStep,
      part_id: plan.part_id,
      feature_id: step.feature_id,
      step_index: i,
      message: "scheduled on " + step.device_id,
    })
  }
  events.push({
    kind: TraceEventKind::PlanFinished,
    part_id: plan.part_id,
    feature_id: "",
    step_index: -1,
    message: "plan finished",
  })
  { part_id: plan.part_id, events }
}

///|
/// Append an event to a trace log.
pub fn trace_append(log : TraceLog, event : TraceEvent) -> TraceLog {
  let events = []
  for item in log.events {
    events.push(item)
  }
  events.push(event)
  { part_id: log.part_id, events }
}

///|
/// Count events of one kind.
pub fn trace_event_count(log : TraceLog, kind : TraceEventKind) -> Int {
  let mut result = 0
  for event in log.events {
    if event.kind == kind {
      result += 1
    }
  }
  result
}

///|
/// Check lifecycle markers and step index ordering.
pub fn trace_is_complete(log : TraceLog) -> Bool {
  if log.events.length() < 2 {
    false
  } else {
    let first = log.events[0].kind == TraceEventKind::PlanStarted
    let last = log.events[log.events.length() - 1].kind ==
      TraceEventKind::PlanFinished
    first && last && trace_steps_are_ordered(log)
  }
}

///|
fn trace_steps_are_ordered(log : TraceLog) -> Bool {
  let mut expected = 0
  let mut result = true
  for event in log.events {
    if event.kind == TraceEventKind::PlanStep {
      if event.step_index != expected {
        result = false
      }
      expected += 1
    }
  }
  result
}

///|
/// Check whether every event belongs to a part identifier.
pub fn trace_has_part(log : TraceLog, part_id : String) -> Bool {
  if log.part_id != part_id {
    false
  } else {
    let mut result = true
    for event in log.events {
      if event.part_id != part_id {
        result = false
      }
    }
    result
  }
}

///|
/// Return feature IDs represented by trace step events.
pub fn trace_feature_ids(log : TraceLog) -> Array[String] {
  let result = []
  for event in log.events {
    if event.kind == TraceEventKind::PlanStep {
      result.push(event.feature_id)
    }
  }
  result
}

///|
/// Return the number of events associated with a feature.
pub fn trace_feature_event_count(log : TraceLog, feature_id : String) -> Int {
  let mut result = 0
  for event in log.events {
    if event.feature_id == feature_id {
      result += 1
    }
  }
  result
}

///|
/// Return whether a warning event is present.
pub fn trace_has_warning(log : TraceLog) -> Bool {
  trace_event_count(log, TraceEventKind::WarningIssued) > 0
}

///|
/// Return all step indices in trace order.
pub fn trace_step_indices(log : TraceLog) -> Array[Int] {
  let result = []
  for event in log.events {
    if event.kind == TraceEventKind::PlanStep {
      result.push(event.step_index)
    }
  }
  result
}

///|
/// Return a stable one-line trace summary.
pub fn trace_summary(log : TraceLog) -> String {
  log.part_id +
  " events=" +
  log.events.length().to_string() +
  " steps=" +
  trace_event_count(log, TraceEventKind::PlanStep).to_string() +
  " warnings=" +
  trace_event_count(log, TraceEventKind::WarningIssued).to_string()
}

///|
/// Return whether a trace contains a feature step.
pub fn trace_has_feature(log : TraceLog, feature_id : String) -> Bool {
  trace_feature_event_count(log, feature_id) > 0
}