///|
/// Lockwire core package.

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

///|
pub(all) enum Duration {
  Duration(Int64)
} derive(Eq, Debug, Compare)

///|
pub fn Duration::from_ns(ns : Int64) -> Duration {
  Duration(ns)
}

///|
pub fn Duration::ns(self : Duration) -> Int64 {
  match self {
    Duration(ns) => ns
  }
}

///|
pub(all) enum VTime {
  VTime(Int64)
} derive(Eq, Debug, Compare)

///|
pub fn VTime::from_ns(ns : Int64) -> VTime {
  VTime(ns)
}

///|
pub fn VTime::ns(self : VTime) -> Int64 {
  match self {
    VTime(ns) => ns
  }
}

///|
pub fn VTime::add(self : VTime, delta : Duration) -> VTime {
  VTime(self.ns() + delta.ns())
}

///|
pub(all) enum EndpointId {
  EndpointId(Int)
} derive(Eq, Debug, Compare)

///|
pub fn EndpointId::value(self : EndpointId) -> Int {
  match self {
    EndpointId(value) => value
  }
}

///|
pub(all) enum ChannelId {
  ChannelId(Int)
} derive(Eq, Debug, Compare)

///|
pub fn ChannelId::value(self : ChannelId) -> Int {
  match self {
    ChannelId(value) => value
  }
}

///|
pub(all) struct Channel {
  id : ChannelId
  next_seq : Int
} derive(Eq, Debug)

///|
pub fn Channel::new(id~ : ChannelId) -> Channel {
  { id, next_seq: 1 }
}

///|
pub fn Channel::reserve(self : Channel) -> (Channel, Int) {
  ({ id: self.id, next_seq: self.next_seq + 1 }, self.next_seq)
}

///|
pub(all) enum PortId {
  PortId(Int)
} derive(Eq, Debug, Compare)

///|
pub(all) suberror TransportError {
  LinkDown
  InvalidFrame(String)
  UnboundEndpoint(EndpointId)
} derive(Debug)

///|
pub(all) enum FrameState {
  Free
  Acquired
  Filled
  Sending
  Sent
  Receiving
  Received
  Released
} derive(Eq, Debug)

///|
pub(all) struct Frame {
  id : Int
  payload : Bytes
  state : FrameState
} derive(Eq, Debug)

///|
pub fn Frame::acquire(id~ : Int) -> Frame {
  { id, payload: b"", state: Acquired }
}

///|
pub fn Frame::filled(self : Frame, payload~ : Bytes) -> Frame {
  { id: self.id, payload, state: Filled }
}

///|
pub fn Frame::sending(self : Frame) -> Frame {
  { id: self.id, payload: self.payload, state: Sending }
}

///|
pub fn Frame::sent(self : Frame) -> Frame {
  { id: self.id, payload: self.payload, state: Sent }
}

///|
pub fn Frame::receiving(self : Frame) -> Frame {
  { id: self.id, payload: self.payload, state: Receiving }
}

///|
pub fn Frame::received(self : Frame) -> Frame {
  { id: self.id, payload: self.payload, state: Received }
}

///|
pub fn Frame::released(self : Frame) -> Frame {
  { id: self.id, payload: self.payload, state: Released }
}

///|
pub(all) struct TxEvent {
  id : Int
  parent_id : Int?
  vtime : VTime
  channel_id : ChannelId
  seq : Int
  source : EndpointId
  target : EndpointId
  frame : Frame
} derive(Eq, Debug)

///|
pub fn TxEvent::make(
  id~ : Int,
  parent_id? : Int,
  vtime~ : VTime,
  channel_id~ : ChannelId,
  seq~ : Int,
  source~ : EndpointId,
  target~ : EndpointId,
  frame~ : Frame,
) -> TxEvent {
  { id, parent_id, vtime, channel_id, seq, source, target, frame }
}

///|
pub(all) struct RxEvent {
  id : Int
  parent_id : Int
  vtime : VTime
  channel_id : ChannelId
  seq : Int
  source : EndpointId
  target : EndpointId
  frame : Frame
} derive(Eq, Debug)

///|
pub(all) struct LinearizationKey {
  vtime : VTime
  channel_id : ChannelId
  seq : Int
  parent_sort : Int
  event_id : Int
} derive(Eq, Debug, Compare)

///|
pub fn TxEvent::linearization_key(self : TxEvent) -> LinearizationKey {
  let parent_sort = match self.parent_id {
    Some(id) => id
    None => -1
  }
  {
    vtime: self.vtime,
    channel_id: self.channel_id,
    seq: self.seq,
    parent_sort,
    event_id: self.id,
  }
}

///|
pub fn tx_event_precedes(left : TxEvent, right : TxEvent) -> Bool {
  left.linearization_key() <= right.linearization_key()
}

///|
pub fn rx_event_precedes(left : RxEvent, right : RxEvent) -> Bool {
  (left.vtime, left.channel_id, left.seq, left.parent_id, left.id) <=
  (right.vtime, right.channel_id, right.seq, right.parent_id, right.id)
}

///|
pub fn check_rx_invariants(events : ArrayView[RxEvent]) -> Bool {
  for i in 0.. current.id {
      return false
    }
    for j in 0.. current.seq {
        return false
      }
    }
  }
  true
}

///|
pub(all) struct SimDigest {
  state_digest : Int64
  rng_steps : Int
  event_count : Int
} derive(Eq, Debug)

///|
pub fn SimDigest::empty(seed~ : Int) -> SimDigest {
  { state_digest: Int64::from_int(seed), rng_steps: 0, event_count: 0 }
}

///|
pub fn SimDigest::mix(self : SimDigest, value : Int) -> SimDigest {
  {
    state_digest: self.state_digest * 131L + Int64::from_int(value),
    rng_steps: self.rng_steps,
    event_count: self.event_count + 1,
  }
}

///|
pub fn SimDigest::step_rng(self : SimDigest) -> SimDigest {
  {
    state_digest: self.state_digest,
    rng_steps: self.rng_steps + 1,
    event_count: self.event_count,
  }
}

///|
pub(open) trait Endpoint {
  fn on_rx(Self, RxEvent) -> Unit
  fn on_tick(Self, VTime) -> Unit
}

///|
pub(open) trait Medium {
  fn submit(Self, TxEvent) -> Array[RxEvent] raise TransportError
  fn lookahead(Self) -> Duration
}

///|
pub(open) trait PointToPointMedium: Medium {
  fn bind(Self, EndpointId, EndpointId) -> Unit
}

///|
pub(open) trait Reactor {
  fn now(Self) -> VTime
  fn schedule(Self, TxEvent) -> Unit raise TransportError
  fn run(Self) -> SimDigest raise TransportError
}

///|
pub(all) enum MvpStage {
  M0Skeleton
  M1CoreP2p
  M2ScenarioEvent
  M3TraceReplay
  M4FaultResource
  M5NativeWasmDiff
  M6SegmentToy
  M7Packaging
} derive(Debug, Eq)

///|
pub fn mvp_stages() -> Array[MvpStage] {
  [
    M0Skeleton,
    M1CoreP2p,
    M2ScenarioEvent,
    M3TraceReplay,
    M4FaultResource,
    M5NativeWasmDiff,
    M6SegmentToy,
    M7Packaging,
  ]
}

///|
pub fn mvp_stage_label(stage : MvpStage) -> String {
  match stage {
    M0Skeleton => "M0 Skeleton"
    M1CoreP2p => "M1 Core+P2P"
    M2ScenarioEvent => "M2 Scenario/Event"
    M3TraceReplay => "M3 Trace+Replay"
    M4FaultResource => "M4 Fault+Resource"
    M5NativeWasmDiff => "M5 Native/Wasm Diff"
    M6SegmentToy => "M6 Segment Toy"
    M7Packaging => "M7 Packaging"
  }
}

///|
pub(all) enum BackendProfile {
  SimNative
  SimWasm
  Replay
  RealLinux
  RealEmbedded
} derive(Debug, Eq)

///|
pub fn backend_profiles() -> Array[BackendProfile] {
  [SimNative, SimWasm, Replay, RealLinux, RealEmbedded]
}

///|
pub fn backend_profile_label(profile : BackendProfile) -> String {
  match profile {
    SimNative => "sim-native"
    SimWasm => "sim-wasm"
    Replay => "replay"
    RealLinux => "real-linux"
    RealEmbedded => "real-embedded"
  }
}

///|
pub fn BackendProfile::is_deterministic(profile : BackendProfile) -> Bool {
  match profile {
    SimNative => true
    SimWasm => true
    Replay => true
    RealLinux => false
    RealEmbedded => false
  }
}