///|
/// Multi-rate TaskSchedule validation and analysis.
///
/// C-12: Validates and analyzes multi-rate task schedules with mixed
/// cycle_divisor values (2/4/8 etc.) to ensure deterministic behavior
/// and detect scheduling conflicts before real-time execution.
///
/// Key validations:
/// - Hyperperiod calculation (LCM of all cycle_divisors)
/// - Worst-case cycle utilization analysis
/// - Phase alignment check (no two high-priority tasks collide)

///|
/// MultiRateAnalysis — result of analyzing a multi-rate schedule.
pub(all) struct MultiRateAnalysis {
  /// The hyperperiod in base cycles (LCM of all divisors).
  hyperperiod : Int
  /// Maximum number of tasks executing in any single cycle.
  worst_case_tasks_per_cycle : Int
  /// Cycle numbers within the hyperperiod that have peak load.
  peak_cycles : Array[Int]
  /// Whether all divisors are powers of 2 (harmonic schedule).
  is_harmonic : Bool
  /// Total output bytes that could be written in the worst-case cycle.
  worst_case_output_bytes : Int
  /// Total input bytes that could be read in the worst-case cycle.
  worst_case_input_bytes : Int
} derive(Debug)

///|
fn gcd(a : Int, b : Int) -> Int {
  let mut x = if a < 0 { -a } else { a }
  let mut y = if b < 0 { -b } else { b }
  while y != 0 {
    let t = y
    y = x % y
    x = t
  }
  x
}

///|
fn lcm(a : Int, b : Int) -> Int {
  if a == 0 || b == 0 {
    return 0
  }
  let g = gcd(a, b)
  a / g * b
}

///|
/// Analyze a multi-rate TaskSchedule for worst-case behavior.
pub fn analyze_multi_rate(schedule : TaskSchedule) -> MultiRateAnalysis {
  if schedule.tasks.length() == 0 {
    return {
      hyperperiod: 1,
      worst_case_tasks_per_cycle: 0,
      peak_cycles: [],
      is_harmonic: true,
      worst_case_output_bytes: 0,
      worst_case_input_bytes: 0,
    }
  }
  // Compute hyperperiod (LCM of all divisors)
  let mut hp = 1
  for t in schedule.tasks {
    hp = lcm(hp, t.cycle_divisor)
  }
  // Check harmonic (all divisors are powers of 2)
  let is_harmonic = schedule.tasks
    .iter()
    .all(fn(t) {
      let d = t.cycle_divisor
      d > 0 && d.land(d - 1) == 0
    })
  // Simulate one hyperperiod to find worst-case
  let mut worst = 0
  let mut worst_out = 0
  let mut worst_in = 0
  let peaks : Array[Int] = []
  for cycle = 0; cycle < hp; cycle = cycle + 1 {
    let mut count = 0
    let mut out_bytes = 0
    let mut in_bytes = 0
    for t in schedule.tasks {
      if cycle % t.cycle_divisor == 0 {
        count = count + 1
        out_bytes = out_bytes + t.slice.output_length
        in_bytes = in_bytes + t.slice.input_length
      }
    }
    if count > worst {
      worst = count
      worst_out = out_bytes
      worst_in = in_bytes
      peaks.clear()
      peaks.push(cycle)
    } else if count == worst {
      peaks.push(cycle)
      if out_bytes > worst_out {
        worst_out = out_bytes
      }
      if in_bytes > worst_in {
        worst_in = in_bytes
      }
    }
  }
  {
    hyperperiod: hp,
    worst_case_tasks_per_cycle: worst,
    peak_cycles: peaks,
    is_harmonic,
    worst_case_output_bytes: worst_out,
    worst_case_input_bytes: worst_in,
  }
}

///|
/// Validate that a multi-rate schedule is safe for real-time execution.
///
/// Checks:
///   1. All divisors > 0 (already enforced by build_task_schedule)
///   2. Hyperperiod is bounded (≤ max_hyperperiod)
///   3. No cycle exceeds max_tasks_per_cycle
///   4. Total worst-case I/O fits within frame budget
///
/// Returns a list of validation messages (empty = all OK).
pub fn validate_multi_rate(
  schedule : TaskSchedule,
  max_hyperperiod? : Int = 1024,
  max_tasks_per_cycle? : Int = 8,
  max_io_bytes_per_cycle? : Int = 1486,
) -> Array[String] {
  let errors : Array[String] = []
  let analysis = analyze_multi_rate(schedule)
  if analysis.hyperperiod > max_hyperperiod {
    errors.push(
      "Hyperperiod " +
      analysis.hyperperiod.to_string() +
      " exceeds limit " +
      max_hyperperiod.to_string(),
    )
  }
  if analysis.worst_case_tasks_per_cycle > max_tasks_per_cycle {
    errors.push(
      "Peak tasks per cycle " +
      analysis.worst_case_tasks_per_cycle.to_string() +
      " exceeds limit " +
      max_tasks_per_cycle.to_string(),
    )
  }
  let total_io = analysis.worst_case_output_bytes +
    analysis.worst_case_input_bytes
  if total_io > max_io_bytes_per_cycle {
    errors.push(
      "Worst-case I/O " +
      total_io.to_string() +
      " bytes exceeds frame budget " +
      max_io_bytes_per_cycle.to_string(),
    )
  }
  if !analysis.is_harmonic {
    // Warning, not error
    errors.push(
      "Schedule is non-harmonic; consider using power-of-2 divisors for predictability",
    )
  }
  errors
}