///|
pub(all) struct ScheduledOsc10Batch {
  ticket : UInt64
  due : TimeTag
  messages : Array[Message]
} derive(Eq, Debug)

///|
pub extend ScheduledOsc10Batch with Eq::{equal, not_equal}

///|
pub extend ScheduledOsc10Batch with @debug.Debug::{to_repr}

///|
pub struct Osc10Scheduler {
  priv pending : Array[ScheduledOsc10Batch]
  priv capacity : Int32
  priv mut next_ticket : UInt64
}

///|
pub fn Osc10Scheduler::new(
  capacity~ : Int32 = 4096,
) -> Osc10Scheduler raise OscError {
  if capacity < 1 || capacity > 65536 {
    raise Limit("batch capacity must be 1..65536")
  }
  { pending: [], capacity, next_ticket: 1UL, }
}

///|
/// Transactional admission; capacity counts indivisible batches, not messages.
pub fn Osc10Scheduler::enqueue(
  self : Osc10Scheduler,
  packet : Packet,
  now : TimeTag,
  limits~ : Limits = Limits::default(),
) -> Array[UInt64] raise OscError {
  let batches = plan_osc10(packet, now, limits~)
  if batches.length() > self.capacity - self.pending.length() {
    raise Limit("batch capacity")
  }
  if batches.length().to_uint64() > 0xffffffffffffffffUL - self.next_ticket {
    raise Limit("batch ticket counter exhausted")
  }
  let tickets : Array[UInt64] = []
  for batch in batches {
    let ticket = self.next_ticket
    self.next_ticket += 1UL
    self.pending.push({ ticket, due: batch.due, messages: batch.messages, })
    tickets.push(ticket)
  }
  tickets
}

///|
fn Osc10Scheduler::earliest_index(self : Osc10Scheduler) -> Int32 {
  let mut best = 0
  for i in 1.. Array[ScheduledOsc10Batch] raise OscError {
  if now.is_immediate() {
    raise Invalid("batch poll requires absolute now", 0)
  }
  if max_batches < 0 || max_batches > 65536 {
    raise Limit("poll batch count must be 0..65536")
  }
  let out : Array[ScheduledOsc10Batch] = []
  while !self.pending.is_empty() && out.length() < max_batches {
    let best = self.earliest_index()
    if self.pending[best].due.bits() > now.bits() {
      break
    }
    out.push(self.pending.remove(best))
  }
  out
}

///|
pub fn Osc10Scheduler::len(self : Osc10Scheduler) -> Int32 {
  self.pending.length()
}

///|
pub fn Osc10Scheduler::next_due(self : Osc10Scheduler) -> TimeTag? {
  if self.pending.is_empty() {
    None
  } else {
    Some(self.pending[self.earliest_index()].due)
  }
}

///|
pub fn Osc10Scheduler::cancel(self : Osc10Scheduler, ticket : UInt64) -> Bool {
  for i in 0.. Int32 {
  let count = self.pending.length()
  self.pending.clear()
  count
}