///|
/// Resource package for M4-FAULT-RESOURCE.

///|
pub fn package_id() -> String {
  "lockwire/resource"
}

///|
pub(all) struct ResourceLease {
  resource_name : String
  amount : Int
  granted_at : @core.VTime
} derive(Eq, Debug)

///|
priv struct ResourceWaiter {
  amount : Int
  event : @scenario.Event[ResourceLease]
}

///|
pub(all) struct ResourceSnapshot {
  name : String
  capacity : Int
  in_use : Int
  queue_length : Int
  max_queue_length : Int
  preemption_count : Int
} derive(Eq, Debug)

///|
pub fn ResourceSnapshot::to_trace_event(
  self : ResourceSnapshot,
  event_id~ : Int,
  vtime~ : @core.VTime,
  clock_domain~ : String,
  node_id~ : String,
  medium_id~ : String,
  seed~ : Int,
  rng_step~ : Int,
  backend~ : @core.BackendProfile,
) -> @trace.TraceEvent {
  @trace.TraceEvent::make(
    event_id~,
    parent_id=None,
    vtime~,
    clock_domain~,
    raw_ns=vtime.ns(),
    node_id~,
    medium_id~,
    channel_id=None,
    direction=@trace.Resource,
    payload_digest=None,
    rng_step~,
    seed~,
    backend~,
    label=self.name,
  )
}

///|
pub struct Resource {
  priv name : String
  priv capacity : Int
  priv mut in_use : Int
  priv waiters : Array[ResourceWaiter]
  priv mut max_queue_length : Int
  priv mut preemption_count : Int
}

///|
pub fn Resource::new(name~ : String, capacity~ : Int) -> Resource {
  {
    name,
    capacity: clamp_non_negative(capacity),
    in_use: 0,
    waiters: [],
    max_queue_length: 0,
    preemption_count: 0,
  }
}

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

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

///|
pub fn Resource::in_use(self : Resource) -> Int {
  self.in_use
}

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

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

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

///|
pub fn Resource::preemption_count(self : Resource) -> Int {
  self.preemption_count
}

///|
pub fn Resource::request(
  self : Resource,
  env : @scenario.SimEnv,
  amount? : Int = 1,
) -> @scenario.Event[ResourceLease] {
  let amount = clamp_non_negative(amount)
  let ev : @scenario.Event[ResourceLease] = @scenario.event(env)
  if amount <= self.available() {
    self.in_use += amount
    @scenario.succeed(ev, {
      resource_name: self.name,
      amount,
      granted_at: env.now(),
    })
  } else {
    self.waiters.push({ amount, event: ev })
    if self.waiters.length() > self.max_queue_length {
      self.max_queue_length = self.waiters.length()
    }
  }
  ev
}

///|
pub fn Resource::release(
  self : Resource,
  env : @scenario.SimEnv,
  lease : ResourceLease,
) -> Unit {
  self.in_use = clamp_non_negative(self.in_use - lease.amount)
  self.grant_waiters(env)
}

///|
pub fn Resource::preempt(
  self : Resource,
  env : @scenario.SimEnv,
  amount? : Int = 1,
) -> Unit {
  self.preemption_count += 1
  self.in_use = clamp_non_negative(self.in_use - clamp_non_negative(amount))
  self.grant_waiters(env)
}

///|
pub fn Resource::snapshot(self : Resource) -> ResourceSnapshot {
  {
    name: self.name,
    capacity: self.capacity,
    in_use: self.in_use,
    queue_length: self.queue_length(),
    max_queue_length: self.max_queue_length,
    preemption_count: self.preemption_count,
  }
}

///|
fn Resource::grant_waiters(self : Resource, env : @scenario.SimEnv) -> Unit {
  while !self.waiters.is_empty() {
    let waiter = self.waiters[0]
    if waiter.amount > self.available() {
      break
    }
    ignore(self.waiters.remove(0))
    self.in_use += waiter.amount
    @scenario.succeed(waiter.event, {
      resource_name: self.name,
      amount: waiter.amount,
      granted_at: env.now(),
    })
  }
}

///|
pub struct Container {
  priv name : String
  priv capacity : Int
  priv mut level : Int
}

///|
pub fn Container::new(
  name~ : String,
  capacity~ : Int,
  init~ : Int,
) -> Container {
  let capacity = clamp_non_negative(capacity)
  { name, capacity, level: clamp_between(init, 0, capacity) }
}

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

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

///|
pub fn Container::level(self : Container) -> Int {
  self.level
}

///|
pub fn Container::get(self : Container, amount? : Int = 1) -> Bool {
  let amount = clamp_non_negative(amount)
  if amount <= self.level {
    self.level -= amount
    true
  } else {
    false
  }
}

///|
pub fn Container::put(self : Container, amount? : Int = 1) -> Unit {
  self.level = clamp_between(
    self.level + clamp_non_negative(amount),
    0,
    self.capacity,
  )
}

///|
pub struct Store[T] {
  priv name : String
  priv capacity : Int
  priv items : Array[T]
}

///|
pub fn[T] Store::new(name~ : String, capacity~ : Int) -> Store[T] {
  { name, capacity: clamp_non_negative(capacity), items: [] }
}

///|
pub fn[T] Store::name(self : Store[T]) -> String {
  self.name
}

///|
pub fn[T] Store::capacity(self : Store[T]) -> Int {
  self.capacity
}

///|
pub fn[T] Store::len(self : Store[T]) -> Int {
  self.items.length()
}

///|
pub fn[T] Store::put(self : Store[T], item : T) -> Bool {
  if self.items.length() >= self.capacity {
    false
  } else {
    self.items.push(item)
    true
  }
}

///|
pub fn[T] Store::get(self : Store[T]) -> T? {
  if self.items.is_empty() {
    None
  } else {
    Some(self.items.remove(0))
  }
}

///|
fn clamp_non_negative(value : Int) -> Int {
  if value < 0 {
    0
  } else {
    value
  }
}

///|
fn clamp_between(value : Int, min : Int, max : Int) -> Int {
  if value < min {
    min
  } else if value > max {
    max
  } else {
    value
  }
}