///|
/// Errors raised by the deterministic message scheduler.
pub suberror SchedulerError {
  InvalidPeriod
  InvalidDeadline
  DuplicateTask
  UnknownTask
} derive(Debug)

///|
/// A periodic or one-shot transmission task.
pub struct CanTask {
  name : String
  frame : Frame
  mut next_due_us : UInt64
  period_us : UInt64
  deadline_us : UInt64
  mut remaining : Int
  priority : Int
}

///|
/// A scheduler execution result.
pub struct SchedulerTick {
  timestamp_us : UInt64
  task_name : String
  frame : Frame
  late : Bool
}

///|
/// A deterministic task scheduler independent from wall-clock time.
pub struct CanScheduler {
  tasks : Array[CanTask]
  mut now_us : UInt64
  mut executed : Int
  mut missed : Int
}

///|
pub fn new_scheduler() -> CanScheduler {
  { tasks: [], now_us: 0, executed: 0, missed: 0 }
}

///|
/// Add a task. `period_us == 0` creates a one-shot task.
pub fn CanScheduler::add(
  self : CanScheduler,
  name : String,
  frame : Frame,
  first_due_us : UInt64,
  period_us : UInt64,
  deadline_us : UInt64,
  count : Int,
) -> Unit raise SchedulerError {
  if count < 0 || (period_us == 0 && count != 1) {
    raise InvalidPeriod
  }
  if deadline_us > 0 && period_us > 0 && deadline_us > period_us {
    raise InvalidDeadline
  }
  if self.find(name) is Some(_) {
    raise DuplicateTask
  }
  self.tasks.push({
    name,
    frame,
    next_due_us: first_due_us,
    period_us,
    deadline_us,
    remaining: count,
    priority: frame.id().reinterpret_as_int(),
  })
  self.tasks.sort_by((left, right) => compare_tasks(left, right))
}

///|
pub fn CanScheduler::remove(
  self : CanScheduler,
  name : String,
) -> Unit raise SchedulerError {
  match self.find_index(name) {
    Some(index) => ignore(self.tasks.remove(index))
    None => raise UnknownTask
  }
}

///|
pub fn CanScheduler::find(self : CanScheduler, name : String) -> CanTask? {
  for task in self.tasks {
    if task.name == name {
      return Some(task)
    }
  }
  None
}

///|
pub fn CanScheduler::task_count(self : CanScheduler) -> Int {
  self.tasks.length()
}

///|
pub fn CanScheduler::now(self : CanScheduler) -> UInt64 {
  self.now_us
}

///|
/// Return the next due time among pending tasks.
pub fn CanScheduler::next_due(self : CanScheduler) -> UInt64? {
  let mut result : UInt64? = None
  for task in self.tasks {
    if task.remaining > 0 {
      match result {
        Some(value) =>
          if task.next_due_us < value {
            result = Some(task.next_due_us)
          }
        None => result = Some(task.next_due_us)
      }
    }
  }
  result
}

///|
/// Execute all tasks due by `timestamp_us`, ordered by CAN arbitration.
pub fn CanScheduler::tick(
  self : CanScheduler,
  timestamp_us : UInt64,
) -> Array[SchedulerTick] raise SchedulerError {
  if timestamp_us < self.now_us {
    raise InvalidDeadline
  }
  self.now_us = timestamp_us
  let result : Array[SchedulerTick] = []
  while true {
    match self.next_task() {
      Some(index) => {
        let task = self.tasks[index]
        if task.remaining <= 0 || task.next_due_us > timestamp_us {
          break
        }
        let late = task.deadline_us > 0 &&
          timestamp_us > task.next_due_us + task.deadline_us
        if late {
          self.missed += 1
        }
        self.executed += 1
        result.push({
          timestamp_us,
          task_name: task.name,
          frame: task.frame,
          late,
        })
        if self.tasks[index].remaining > 0 {
          self.tasks[index].remaining -= 1
        }
        if self.tasks[index].period_us > 0 && self.tasks[index].remaining > 0 {
          self.tasks[index].next_due_us += self.tasks[index].period_us
        } else {
          self.tasks[index].remaining = 0
        }
      }
      None => break
    }
  }
  result.sort_by((left, right) => compare_frames(left.frame, right.frame))
  result
}

///|
pub fn CanScheduler::executed(self : CanScheduler) -> Int {
  self.executed
}

///|
pub fn CanScheduler::missed(self : CanScheduler) -> Int {
  self.missed
}

///|
pub fn SchedulerTick::timestamp(self : SchedulerTick) -> UInt64 {
  self.timestamp_us
}

///|
pub fn SchedulerTick::task_name(self : SchedulerTick) -> String {
  self.task_name
}

///|
pub fn SchedulerTick::frame(self : SchedulerTick) -> Frame {
  self.frame
}

///|
pub fn SchedulerTick::is_late(self : SchedulerTick) -> Bool {
  self.late
}

///|
fn CanScheduler::find_index(self : CanScheduler, name : String) -> Int? {
  for index in 0.. Int? {
  let mut best : Int? = None
  for index in 0..
        if compare_tasks(candidate, self.tasks[previous]) < 0 {
          best = Some(index)
        }
      None => best = Some(index)
    }
  }
  best
}

///|
fn compare_tasks(left : CanTask, right : CanTask) -> Int {
  if left.next_due_us < right.next_due_us {
    -1
  } else if left.next_due_us > right.next_due_us {
    1
  } else if left.priority < right.priority {
    -1
  } else if left.priority > right.priority {
    1
  } else {
    left.name.compare(right.name)
  }
}

///|
/// Estimate the total wire bits for one scheduler window.
pub fn schedule_wire_bits(ticks : Array[SchedulerTick]) -> Int {
  ticks.fold(init=0, (total, tick) => total + frame_wire_bits(tick.frame))
}

///|
/// Return the fraction of ticks that missed their deadline.
pub fn schedule_miss_rate(ticks : Array[SchedulerTick]) -> Double {
  if ticks.is_empty() {
    0.0
  } else {
    ticks.count_if(tick => tick.late).to_double() / ticks.length().to_double()
  }
}