///|
/// Priority and preemptive resource model for L4B.

///|
pub(all) enum PreemptiveResourceEventKind {
  Grant
  Queue
  Preempt
  Interrupt
  Cancel
  DeadlineOverrun
  ResourceExhaustion
  Release
} derive(Eq, Debug)

///|
pub fn PreemptiveResourceEventKind::label(
  self : PreemptiveResourceEventKind,
) -> String {
  match self {
    Grant => "grant"
    Queue => "queue"
    Preempt => "preempt"
    Interrupt => "interrupt"
    Cancel => "cancel"
    DeadlineOverrun => "deadline-overrun"
    ResourceExhaustion => "resource-exhaustion"
    Release => "release"
  }
}

///|
pub fn PreemptiveResourceEventKind::code(
  self : PreemptiveResourceEventKind,
) -> Int {
  match self {
    Grant => 1
    Queue => 2
    Preempt => 3
    Interrupt => 4
    Cancel => 5
    DeadlineOverrun => 6
    ResourceExhaustion => 7
    Release => 8
  }
}

///|
pub(all) struct PriorityResourceRequest {
  owner_id : String
  priority : Int
  amount : Int
  deadline : @core.VTime?
  preemptible : Bool
} derive(Eq, Debug)

///|
pub fn PriorityResourceRequest::make(
  owner_id~ : String,
  priority~ : Int,
  amount? : Int = 1,
  deadline? : @core.VTime,
  preemptible? : Bool = true,
) -> PriorityResourceRequest {
  {
    owner_id,
    priority,
    amount: clamp_non_negative(amount),
    deadline,
    preemptible,
  }
}

///|
pub(all) struct PriorityResourceLease {
  resource_name : String
  owner_id : String
  amount : Int
  priority : Int
  granted_at : @core.VTime
  deadline : @core.VTime?
  preemptible : Bool
} derive(Eq, Debug)

///|
priv struct PreemptiveResourceWaiter {
  request : PriorityResourceRequest
  queued_at : @core.VTime
}

///|
pub(all) struct PreemptiveResourceEvent {
  id : Int
  kind : PreemptiveResourceEventKind
  resource_name : String
  owner_id : String
  priority : Int
  amount : Int
  vtime : @core.VTime
  note : String
} derive(Eq, Debug)

///|
pub fn PreemptiveResourceEvent::to_trace_event(
  self : PreemptiveResourceEvent,
  seed~ : Int,
  backend? : @core.BackendProfile = @core.SimNative,
  clock_domain? : String = "sim",
  node_id? : String = "resource",
  medium_id? : String = "lockwire-resource",
) -> @trace.TraceEvent {
  @trace.TraceEvent::make(
    event_id=self.id,
    parent_id=None,
    vtime=self.vtime,
    clock_domain~,
    raw_ns=self.vtime.ns(),
    node_id~,
    medium_id~,
    channel_id=None,
    direction=@trace.Resource,
    payload_digest=Some(self.kind.code()),
    rng_step=self.id,
    seed~,
    backend~,
    label="resource." +
      self.resource_name +
      "." +
      self.kind.label() +
      "." +
      self.owner_id,
  )
}

///|
pub(all) struct PreemptiveResourceReport {
  scenario : String
  seed : Int
  resource_name : String
  capacity : Int
  grant_count : Int
  queue_count : Int
  preemption_count : Int
  interrupt_count : Int
  cancel_count : Int
  deadline_overrun_count : Int
  resource_exhaustion_count : Int
  max_queue_length : Int
  active_count : Int
  trace : @trace.TraceLog
  digest : @core.SimDigest
  real_os_scheduler : Bool
} derive(Debug)

///|
pub fn PreemptiveResourceReport::passes(
  self : PreemptiveResourceReport,
) -> Bool {
  self.capacity > 0 &&
  self.grant_count >= 2 &&
  self.preemption_count >= 1 &&
  self.interrupt_count >= 1 &&
  self.cancel_count >= 1 &&
  self.deadline_overrun_count >= 1 &&
  self.resource_exhaustion_count >= 1 &&
  self.max_queue_length >= 1 &&
  self.trace.len() == self.digest.event_count &&
  !self.real_os_scheduler
}

///|
pub fn PreemptiveResourceReport::to_text(
  self : PreemptiveResourceReport,
) -> String {
  let buf = StringBuilder::new()
  buf.write_string("schema=lockwire.preemptive-resource-report.v1\n")
  buf.write_string("scenario=" + self.scenario + "\n")
  buf.write_string("seed=" + self.seed.to_string() + "\n")
  buf.write_string("resource_name=" + self.resource_name + "\n")
  buf.write_string("capacity=" + self.capacity.to_string() + "\n")
  buf.write_string("grant_count=" + self.grant_count.to_string() + "\n")
  buf.write_string("queue_count=" + self.queue_count.to_string() + "\n")
  buf.write_string(
    "preemption_count=" + self.preemption_count.to_string() + "\n",
  )
  buf.write_string("interrupt_count=" + self.interrupt_count.to_string() + "\n")
  buf.write_string("cancel_count=" + self.cancel_count.to_string() + "\n")
  buf.write_string(
    "deadline_overrun_count=" + self.deadline_overrun_count.to_string() + "\n",
  )
  buf.write_string(
    "resource_exhaustion_count=" +
    self.resource_exhaustion_count.to_string() +
    "\n",
  )
  buf.write_string(
    "max_queue_length=" + self.max_queue_length.to_string() + "\n",
  )
  buf.write_string("active_count=" + self.active_count.to_string() + "\n")
  buf.write_string(
    "digest_state=" + self.digest.state_digest.to_string() + "\n",
  )
  buf.write_string(
    "digest_events=" + self.digest.event_count.to_string() + "\n",
  )
  buf.write_string(
    "real_os_scheduler=" + bool_text(self.real_os_scheduler) + "\n",
  )
  for event in self.trace.events {
    buf.write_string("trace=" + event.to_golden_line() + "\n")
  }
  buf.to_string()
}

///|
pub struct PriorityResource {
  priv inner : PreemptiveResource
}

///|
pub fn PriorityResource::new(
  name~ : String,
  capacity~ : Int,
) -> PriorityResource {
  { inner: PreemptiveResource::new(name~, capacity~) }
}

///|
pub fn PriorityResource::request(
  self : PriorityResource,
  request : PriorityResourceRequest,
  at~ : @core.VTime,
) -> PriorityResourceLease? {
  self.inner.request_internal(request, at~, allow_preemption=false)
}

///|
pub fn PriorityResource::release(
  self : PriorityResource,
  owner_id~ : String,
  at~ : @core.VTime,
) -> Bool {
  self.inner.release(owner_id~, at~)
}

///|
pub fn PriorityResource::report(
  self : PriorityResource,
  scenario~ : String,
  seed~ : Int,
) -> PreemptiveResourceReport {
  self.inner.report(scenario~, seed~)
}

///|
pub struct PreemptiveResource {
  priv name : String
  priv capacity : Int
  priv active : Array[PriorityResourceLease]
  priv waiters : Array[PreemptiveResourceWaiter]
  priv events : Array[PreemptiveResourceEvent]
  priv mut next_event_id : Int
  priv mut max_queue_length : Int
}

///|
pub fn PreemptiveResource::new(
  name~ : String,
  capacity~ : Int,
) -> PreemptiveResource {
  {
    name,
    capacity: clamp_non_negative(capacity),
    active: [],
    waiters: [],
    events: [],
    next_event_id: 1,
    max_queue_length: 0,
  }
}

///|
pub fn PreemptiveResource::name(self : PreemptiveResource) -> String {
  self.name
}

///|
pub fn PreemptiveResource::capacity(self : PreemptiveResource) -> Int {
  self.capacity
}

///|
pub fn PreemptiveResource::in_use(self : PreemptiveResource) -> Int {
  let mut total = 0
  for lease in self.active {
    total += lease.amount
  }
  total
}

///|
pub fn PreemptiveResource::available(self : PreemptiveResource) -> Int {
  self.capacity - self.in_use()
}

///|
pub fn PreemptiveResource::queue_length(self : PreemptiveResource) -> Int {
  self.waiters.length()
}

///|
pub fn PreemptiveResource::max_queue_length(self : PreemptiveResource) -> Int {
  self.max_queue_length
}

///|
pub fn PreemptiveResource::events(
  self : PreemptiveResource,
) -> Array[PreemptiveResourceEvent] {
  self.events.copy()
}

///|
pub fn PreemptiveResource::request(
  self : PreemptiveResource,
  request : PriorityResourceRequest,
  at~ : @core.VTime,
) -> PriorityResourceLease? {
  self.request_internal(request, at~, allow_preemption=true)
}

///|
fn PreemptiveResource::request_internal(
  self : PreemptiveResource,
  request : PriorityResourceRequest,
  at~ : @core.VTime,
  allow_preemption~ : Bool,
) -> PriorityResourceLease? {
  let request = sanitize_request(request)
  if request.amount > self.capacity {
    self.record_event(
      ResourceExhaustion,
      request,
      at~,
      note="amount-exceeds-capacity",
    )
    return None
  }
  if request.amount <= self.available() {
    return Some(self.grant(request, at~))
  }
  if allow_preemption && self.try_preempt_for(request, at~) {
    return Some(self.grant(request, at~))
  }
  self.waiters.push({ request, queued_at: at })
  if self.waiters.length() > self.max_queue_length {
    self.max_queue_length = self.waiters.length()
  }
  self.record_event(Queue, request, at~, note="waiting-for-capacity")
  None
}

///|
pub fn PreemptiveResource::release(
  self : PreemptiveResource,
  owner_id~ : String,
  at~ : @core.VTime,
) -> Bool {
  match self.active_index(owner_id) {
    None => false
    Some(index) => {
      let lease = self.active.remove(index)
      self.record_lease_event(Release, lease, at~, note="released")
      self.grant_waiters(at~)
      true
    }
  }
}

///|
pub fn PreemptiveResource::check_deadlines(
  self : PreemptiveResource,
  at~ : @core.VTime,
) -> Unit {
  let mut index = 0
  while index < self.waiters.length() {
    let waiter = self.waiters[index]
    match waiter.request.deadline {
      Some(deadline) if deadline < at => {
        ignore(self.waiters.remove(index))
        self.record_event(
          DeadlineOverrun,
          waiter.request,
          at~,
          note="queued-deadline-overrun",
        )
        self.record_event(
          Cancel,
          waiter.request,
          at~,
          note="cancelled-after-deadline-overrun",
        )
      }
      _ => index += 1
    }
  }
}

///|
pub fn PreemptiveResource::trace_log(
  self : PreemptiveResource,
  seed~ : Int,
) -> @trace.TraceLog {
  let log = @trace.TraceLog::new()
  for event in self.events {
    log.append(event.to_trace_event(seed~))
  }
  log
}

///|
pub fn PreemptiveResource::report(
  self : PreemptiveResource,
  scenario~ : String,
  seed~ : Int,
) -> PreemptiveResourceReport {
  let trace = self.trace_log(seed~)
  {
    scenario,
    seed,
    resource_name: self.name,
    capacity: self.capacity,
    grant_count: self.count_events(Grant),
    queue_count: self.count_events(Queue),
    preemption_count: self.count_events(Preempt),
    interrupt_count: self.count_events(Interrupt),
    cancel_count: self.count_events(Cancel),
    deadline_overrun_count: self.count_events(DeadlineOverrun),
    resource_exhaustion_count: self.count_events(ResourceExhaustion),
    max_queue_length: self.max_queue_length,
    active_count: self.active.length(),
    trace,
    digest: trace.portable_digest(seed~),
    real_os_scheduler: false,
  }
}

///|
pub fn preemptive_resource_contention_fixture(
  seed? : Int = 744,
) -> PreemptiveResourceReport {
  let resource = PreemptiveResource::new(name="rt-cpu", capacity=1)
  ignore(
    resource.request(
      PriorityResourceRequest::make(
        owner_id="low-cycle",
        priority=5,
        deadline=@core.VTime::from_ns(100L),
      ),
      at=@core.VTime::from_ns(0L),
    ),
  )
  ignore(
    resource.request(
      PriorityResourceRequest::make(
        owner_id="high-cycle",
        priority=-10,
        deadline=@core.VTime::from_ns(40L),
      ),
      at=@core.VTime::from_ns(10L),
    ),
  )
  ignore(
    resource.request(
      PriorityResourceRequest::make(
        owner_id="background",
        priority=20,
        deadline=@core.VTime::from_ns(15L),
      ),
      at=@core.VTime::from_ns(12L),
    ),
  )
  resource.check_deadlines(at=@core.VTime::from_ns(20L))
  ignore(
    resource.request(
      PriorityResourceRequest::make(
        owner_id="oversize-dma",
        priority=0,
        amount=2,
        deadline=@core.VTime::from_ns(50L),
      ),
      at=@core.VTime::from_ns(21L),
    ),
  )
  resource.report(scenario="l4b.preemptive-resource-contention", seed~)
}

///|
fn PreemptiveResource::grant(
  self : PreemptiveResource,
  request : PriorityResourceRequest,
  at~ : @core.VTime,
) -> PriorityResourceLease {
  let lease : PriorityResourceLease = {
    resource_name: self.name,
    owner_id: request.owner_id,
    amount: request.amount,
    priority: request.priority,
    granted_at: at,
    deadline: request.deadline,
    preemptible: request.preemptible,
  }
  self.active.push(lease)
  self.record_lease_event(Grant, lease, at~, note="granted")
  lease
}

///|
fn PreemptiveResource::grant_waiters(
  self : PreemptiveResource,
  at~ : @core.VTime,
) -> Unit {
  let mut keep_granting = true
  while keep_granting {
    match self.best_waiter_index() {
      None => keep_granting = false
      Some(index) => {
        let waiter = self.waiters[index]
        if waiter.request.amount > self.available() {
          keep_granting = false
        } else {
          ignore(self.waiters.remove(index))
          ignore(self.grant(waiter.request, at~))
        }
      }
    }
  }
}

///|
fn PreemptiveResource::try_preempt_for(
  self : PreemptiveResource,
  request : PriorityResourceRequest,
  at~ : @core.VTime,
) -> Bool {
  match self.preemptable_victim_index(request) {
    None => false
    Some(index) => {
      let victim = self.active.remove(index)
      self.record_lease_event(
        Interrupt,
        victim,
        at~,
        note="interrupted-by-" + request.owner_id,
      )
      self.record_lease_event(
        Cancel,
        victim,
        at~,
        note="cancelled-by-" + request.owner_id,
      )
      self.record_event(
        Preempt,
        request,
        at~,
        note="preempted-" + victim.owner_id,
      )
      request.amount <= self.available()
    }
  }
}

///|
fn PreemptiveResource::preemptable_victim_index(
  self : PreemptiveResource,
  request : PriorityResourceRequest,
) -> Int? {
  let mut best : Int? = None
  for index, lease in self.active {
    if lease.preemptible &&
      lease.priority > request.priority &&
      request.amount <= self.available() + lease.amount {
      match best {
        None => best = Some(index)
        Some(current) =>
          if lease.priority > self.active[current].priority {
            best = Some(index)
          }
      }
    }
  }
  best
}

///|
fn PreemptiveResource::active_index(
  self : PreemptiveResource,
  owner_id : String,
) -> Int? {
  for index, lease in self.active {
    if lease.owner_id == owner_id {
      return Some(index)
    }
  }
  None
}

///|
fn PreemptiveResource::best_waiter_index(self : PreemptiveResource) -> Int? {
  let mut best : Int? = None
  for index, waiter in self.waiters {
    match best {
      None => best = Some(index)
      Some(current) => {
        let current_waiter = self.waiters[current]
        if waiter.request.priority < current_waiter.request.priority ||
          (
            waiter.request.priority == current_waiter.request.priority &&
            waiter.queued_at < current_waiter.queued_at
          ) {
          best = Some(index)
        }
      }
    }
  }
  best
}

///|
fn PreemptiveResource::record_event(
  self : PreemptiveResource,
  kind : PreemptiveResourceEventKind,
  request : PriorityResourceRequest,
  at~ : @core.VTime,
  note~ : String,
) -> Unit {
  self.events.push({
    id: self.next_event_id,
    kind,
    resource_name: self.name,
    owner_id: request.owner_id,
    priority: request.priority,
    amount: request.amount,
    vtime: at,
    note,
  })
  self.next_event_id += 1
}

///|
fn PreemptiveResource::record_lease_event(
  self : PreemptiveResource,
  kind : PreemptiveResourceEventKind,
  lease : PriorityResourceLease,
  at~ : @core.VTime,
  note~ : String,
) -> Unit {
  self.events.push({
    id: self.next_event_id,
    kind,
    resource_name: self.name,
    owner_id: lease.owner_id,
    priority: lease.priority,
    amount: lease.amount,
    vtime: at,
    note,
  })
  self.next_event_id += 1
}

///|
fn PreemptiveResource::count_events(
  self : PreemptiveResource,
  kind : PreemptiveResourceEventKind,
) -> Int {
  let mut count = 0
  for event in self.events {
    if event.kind == kind {
      count += 1
    }
  }
  count
}

///|
fn sanitize_request(
  request : PriorityResourceRequest,
) -> PriorityResourceRequest {
  { ..request, amount: clamp_non_negative(request.amount) }
}

///|
fn bool_text(value : Bool) -> String {
  if value {
    "true"
  } else {
    "false"
  }
}