// trace/trace.mbt
// Deterministic simulation event tracer.
//
// The Tracer records every EventRecord emitted by the simulation in
// chronological order.  It supports:
//   - Filtered export (by tag or time window)
//   - CSV/JSON-style textual formatting
//   - Replay verification: check whether a recorded trace is valid

///|
/// Categories of simulation events that can be traced.
pub(all) enum EventCategory {
  /// A simulation entity arrived (job, packet, customer, etc.)
  Arrival
  /// A simulation entity departed or was served.
  Departure
  /// A resource was acquired.
  ResourceAcquire
  /// A resource was released.
  ResourceRelease
  /// A state machine transitioned to a new state.
  StateChange
  /// A user-defined annotation or debugging marker.
  Annotation
  /// A deadline was missed.
  DeadlineMiss
  /// A fault or failure event was injected.
  FaultInjection
} derive(Debug, Eq)

///|
pub impl Show for EventCategory with fn output(self, logger) {
  let s = match self {
    Arrival => "Arrival"
    Departure => "Departure"
    ResourceAcquire => "ResourceAcquire"
    ResourceRelease => "ResourceRelease"
    StateChange => "StateChange"
    Annotation => "Annotation"
    DeadlineMiss => "DeadlineMiss"
    FaultInjection => "FaultInjection"
  }
  logger.write_string(s)
}

///|
/// A single recorded simulation event.
pub(all) struct EventRecord {
  /// Wall-clock simulation time (seconds) at which the event occurred.
  time : Double
  /// Source entity identifier (e.g. process name, node id converted to string).
  source : String
  /// Human-readable description of what happened.
  message : String
  /// Category for filtering.
  category : EventCategory
  /// Optional numeric payload (e.g. queue length, resource usage).
  value : Double?
}

///|
pub fn EventRecord::to_csv_row(self : EventRecord) -> String {
  let val_str = match self.value {
    None => ""
    Some(v) => "\{v}"
  }
  "\{self.time},\{self.source},\{self.category},\{self.message},\{val_str}"
}

///|
/// The central event tracer.  Attach it to a simulation and call `record`
/// from event callbacks to accumulate a full audit trail.
pub(all) struct Tracer {
  name : String
  priv records : Array[EventRecord]
  mut enabled : Bool
  mut record_count : Int
}

///|
pub fn Tracer::new(name : String) -> Tracer {
  { name, records: [], enabled: true, record_count: 0 }
}

///|
pub fn Tracer::enable(self : Tracer) -> Unit {
  self.enabled = true
}

///|
pub fn Tracer::disable(self : Tracer) -> Unit {
  self.enabled = false
}

///|
pub fn Tracer::is_enabled(self : Tracer) -> Bool {
  self.enabled
}

///|
pub fn Tracer::record(
  self : Tracer,
  time : Double,
  source : String,
  category : EventCategory,
  message : String,
  value : Double?,
) -> Unit {
  if !self.enabled {
    return
  }
  self.records.push({ time, source, message, category, value })
  self.record_count = self.record_count + 1
}

///|
pub fn Tracer::record_arrival(
  self : Tracer,
  time : Double,
  source : String,
  message : String,
) -> Unit {
  self.record(time, source, Arrival, message, None)
}

///|
pub fn Tracer::record_departure(
  self : Tracer,
  time : Double,
  source : String,
  message : String,
  sojourn_time : Double?,
) -> Unit {
  self.record(time, source, Departure, message, sojourn_time)
}

///|
pub fn Tracer::record_state_change(
  self : Tracer,
  time : Double,
  source : String,
  from_state : String,
  to_state : String,
) -> Unit {
  self.record(
    time,
    source,
    StateChange,
    "transition: \{from_state} -> \{to_state}",
    None,
  )
}

///|
pub fn Tracer::record_annotation(
  self : Tracer,
  time : Double,
  source : String,
  message : String,
) -> Unit {
  self.record(time, source, Annotation, message, None)
}

///|
/// Return the total number of records written (including while disabled).
pub fn Tracer::total_records(self : Tracer) -> Int {
  self.record_count
}

///|
/// Return the number of currently stored records (not cleared).
pub fn Tracer::stored_count(self : Tracer) -> Int {
  self.records.length()
}

///|
/// Return records within the given time window [from, to] (inclusive).
pub fn Tracer::records_in_window(
  self : Tracer,
  from : Double,
  to : Double,
) -> Array[EventRecord] {
  let result : Array[EventRecord] = []
  for r in self.records {
    if r.time >= from && r.time <= to {
      result.push(r)
    }
  }
  result
}

///|
/// Return all records matching a given category.
pub fn Tracer::records_by_category(
  self : Tracer,
  category : EventCategory,
) -> Array[EventRecord] {
  let result : Array[EventRecord] = []
  for r in self.records {
    if r.category == category {
      result.push(r)
    }
  }
  result
}

///|
/// Return all records from a specific source entity.
pub fn Tracer::records_by_source(
  self : Tracer,
  source : String,
) -> Array[EventRecord] {
  let result : Array[EventRecord] = []
  for r in self.records {
    if r.source == source {
      result.push(r)
    }
  }
  result
}

///|
/// Export the full trace as a CSV string.
/// The header row is: `time,source,category,message,value`
pub fn Tracer::to_csv(self : Tracer) -> String {
  let mut buf = "time,source,category,message,value\n"
  for r in self.records {
    buf = buf + r.to_csv_row() + "\n"
  }
  buf
}

///|
/// Verify that the trace is monotonically non-decreasing in time.
/// Returns `true` if valid, `false` if any record has a timestamp earlier
/// than the previous record.
pub fn Tracer::verify_monotonic(self : Tracer) -> Bool {
  if self.records.length() <= 1 {
    return true
  }
  let mut prev_time = self.records[0].time
  for i = 1; i < self.records.length(); i = i + 1 {
    if self.records[i].time < prev_time {
      return false
    }
    prev_time = self.records[i].time
  }
  true
}

///|
/// Clear all stored records (does not reset the total_count counter).
pub fn Tracer::clear(self : Tracer) -> Unit {
  self.records.clear()
}

///|
/// Compute a simple histogram of event counts per category.
/// Returns an array of (category_name, count) pairs in insertion order.
pub fn Tracer::category_histogram(self : Tracer) -> Array[(String, Int)] {
  let categories = [
    "Arrival", "Departure", "ResourceAcquire", "ResourceRelease", "StateChange",
    "Annotation", "DeadlineMiss", "FaultInjection",
  ]
  let result : Array[(String, Int)] = []
  for cat_name in categories {
    let mut count = 0
    for r in self.records {
      let r_name = match r.category {
        Arrival => "Arrival"
        Departure => "Departure"
        ResourceAcquire => "ResourceAcquire"
        ResourceRelease => "ResourceRelease"
        StateChange => "StateChange"
        Annotation => "Annotation"
        DeadlineMiss => "DeadlineMiss"
        FaultInjection => "FaultInjection"
      }
      if r_name == cat_name {
        count = count + 1
      }
    }
    if count > 0 {
      result.push((cat_name, count))
    }
  }
  result
}