///|
/// Clock package for P0-CLOCK-KERNEL.

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

///|
pub(all) enum DomainId {
  DomainId(String)
} derive(Eq, Debug)

///|
pub fn DomainId::new(name~ : String) -> DomainId {
  DomainId(name)
}

///|
pub fn DomainId::name(self : DomainId) -> String {
  match self {
    DomainId(name) => name
  }
}

///|
pub(all) enum SimTruth {
  SimTruth
} derive(Eq, Debug)

///|
pub(all) enum DcDomain {
  DcDomain
} derive(Eq, Debug)

///|
pub(all) enum WallDomain {
  WallDomain
} derive(Eq, Debug)

///|
pub struct Instant[D] {
  priv domain : DomainId
  priv ns : Int64
  priv phantom : D?
}

///|
pub fn[D] Instant::new(domain~ : DomainId, ns~ : Int64) -> Instant[D] {
  { domain, ns, phantom: None }
}

///|
pub fn[D] Instant::domain(self : Instant[D]) -> DomainId {
  ignore(self.phantom)
  self.domain
}

///|
pub fn[D] Instant::ns(self : Instant[D]) -> Int64 {
  self.ns
}

///|
pub fn[D] Instant::add(self : Instant[D], delta : @core.Duration) -> Instant[D] {
  Instant::new(domain=self.domain, ns=self.ns + delta.ns())
}

///|
pub fn[D] Instant::duration_since(
  self : Instant[D],
  earlier : Instant[D],
) -> @core.Duration {
  @core.Duration::from_ns(self.ns - earlier.ns)
}

///|
pub fn[D] Instant::equals(self : Instant[D], other : Instant[D]) -> Bool {
  self.domain == other.domain && self.ns == other.ns
}

///|
pub type SimInstant = Instant[SimTruth]

///|
pub(all) struct DomainInstant {
  domain : DomainId
  ns : Int64
} derive(Eq, Debug)

///|
pub fn DomainInstant::new(domain~ : DomainId, ns~ : Int64) -> DomainInstant {
  { domain, ns }
}

///|
pub fn[D] DomainInstant::from_typed(instant : Instant[D]) -> DomainInstant {
  { domain: instant.domain(), ns: instant.ns() }
}

///|
pub fn DomainInstant::domain(self : DomainInstant) -> DomainId {
  self.domain
}

///|
pub fn DomainInstant::ns(self : DomainInstant) -> Int64 {
  self.ns
}

///|
pub(all) struct Uncertain[T] {
  value : T
  bound : @core.Duration
}

///|
pub struct ClockRelation[A, B] {
  priv source_domain : DomainId
  priv target_domain : DomainId
  priv offset : @core.Duration
  priv rate_ppb : Int64
  priv bound : @core.Duration
  priv asof : Instant[A]
  priv target_phantom : B?
}

///|
pub fn[A, B] ClockRelation::new(
  source_domain~ : DomainId,
  target_domain~ : DomainId,
  offset~ : @core.Duration,
  rate_ppb~ : Int64,
  bound~ : @core.Duration,
  asof~ : Instant[A],
) -> ClockRelation[A, B] {
  {
    source_domain,
    target_domain,
    offset,
    rate_ppb,
    bound,
    asof,
    target_phantom: None,
  }
}

///|
pub fn[A, B] ClockRelation::source_domain(
  self : ClockRelation[A, B],
) -> DomainId {
  self.source_domain
}

///|
pub fn[A, B] ClockRelation::target_domain(
  self : ClockRelation[A, B],
) -> DomainId {
  ignore(self.target_phantom)
  self.target_domain
}

///|
pub fn[A, B] ClockRelation::bound(self : ClockRelation[A, B]) -> @core.Duration {
  self.bound
}

///|
pub fn[A, B] ClockRelation::convert(
  self : ClockRelation[A, B],
  instant : Instant[A],
) -> Uncertain[Instant[B]] {
  ignore(self.target_phantom)
  let elapsed = instant.duration_since(self.asof).ns()
  let drift = elapsed * self.rate_ppb / 1_000_000_000L
  let converted : Instant[B] = Instant::new(
    domain=self.target_domain,
    ns=instant.ns() + self.offset.ns() + drift,
  )
  { value: converted, bound: self.bound }
}

///|
pub(all) struct CrossStamp[A, B] {
  source : Instant[A]
  target : Instant[B]
  bound : @core.Duration
}

///|
pub(all) struct DomainCrossStamp {
  source : DomainInstant
  target : DomainInstant
  bound : @core.Duration
} derive(Eq, Debug)

///|
pub(open) trait MonotonicClock {
  fn now(Self) -> DomainInstant
  fn domain(Self) -> DomainId
  fn resolution(Self) -> @core.Duration
}

///|
pub(open) trait TimerDriver {
  fn sleep_until(Self, DomainInstant) -> Unit
}

///|
pub(open) trait CrossStamper {
  fn cross_stamp(Self) -> DomainCrossStamp
}

///|
pub struct VirtualClock[D] {
  priv domain : DomainId
  priv mut current : Instant[D]
  priv resolution : @core.Duration
  priv seed : Int
}

///|
pub fn[D] VirtualClock::new(domain~ : DomainId, seed~ : Int) -> VirtualClock[D] {
  {
    domain,
    current: Instant::new(domain~, ns=0L),
    resolution: @core.Duration::from_ns(1L),
    seed,
  }
}

///|
pub fn[D] VirtualClock::seed(self : VirtualClock[D]) -> Int {
  self.seed
}

///|
pub fn[D] VirtualClock::now(self : VirtualClock[D]) -> Instant[D] {
  self.current
}

///|
pub fn[D] VirtualClock::domain(self : VirtualClock[D]) -> DomainId {
  self.domain
}

///|
pub fn[D] VirtualClock::resolution(self : VirtualClock[D]) -> @core.Duration {
  self.resolution
}

///|
pub fn[D] VirtualClock::set(
  self : VirtualClock[D],
  instant : Instant[D],
) -> Unit {
  if instant.domain() == self.domain {
    self.current = instant
  } else {
    self.current = Instant::new(domain=self.domain, ns=instant.ns())
  }
}

///|
pub fn[D] VirtualClock::sleep_until(
  self : VirtualClock[D],
  deadline : Instant[D],
) -> Unit {
  if self.now().ns() < deadline.ns() {
    self.set(deadline)
  }
}

///|
pub fn[D] VirtualClock::sleep_until_domain(
  self : VirtualClock[D],
  deadline : DomainInstant,
) -> Unit {
  if self.domain == deadline.domain() && self.now().ns() < deadline.ns() {
    self.set(Instant::new(domain=self.domain, ns=deadline.ns()))
  }
}

///|
pub fn[D] VirtualClock::advance(
  self : VirtualClock[D],
  delta : @core.Duration,
) -> Instant[D] {
  self.current = self.current.add(delta)
  self.current
}

///|
pub impl[D] MonotonicClock for VirtualClock[D] with fn now(self) {
  DomainInstant::from_typed(VirtualClock::now(self))
}

///|
pub impl[D] MonotonicClock for VirtualClock[D] with fn domain(self) {
  VirtualClock::domain(self)
}

///|
pub impl[D] MonotonicClock for VirtualClock[D] with fn resolution(self) {
  VirtualClock::resolution(self)
}

///|
pub impl[D] TimerDriver for VirtualClock[D] with fn sleep_until(self, deadline) {
  self.sleep_until_domain(deadline)
}

///|
pub struct VirtualCrossStamper[A, B] {
  priv source : VirtualClock[A]
  priv relation : ClockRelation[A, B]
}

///|
pub fn[A, B] VirtualCrossStamper::new(
  source~ : VirtualClock[A],
  relation~ : ClockRelation[A, B],
) -> VirtualCrossStamper[A, B] {
  { source, relation }
}

///|
pub fn[A, B] VirtualCrossStamper::typed_cross_stamp(
  self : VirtualCrossStamper[A, B],
) -> CrossStamp[A, B] {
  let source = self.source.now()
  let target = self.relation.convert(source)
  { source, target: target.value, bound: target.bound }
}

///|
pub impl[A, B] CrossStamper for VirtualCrossStamper[A, B] with fn cross_stamp(
  self,
) {
  let typed = self.typed_cross_stamp()
  {
    source: DomainInstant::from_typed(typed.source),
    target: DomainInstant::from_typed(typed.target),
    bound: typed.bound,
  }
}

///|
pub fn[D] deterministic_sequence(
  domain~ : DomainId,
  seed~ : Int,
  count~ : Int,
  step~ : @core.Duration,
) -> Array[Instant[D]] {
  let clock : VirtualClock[D] = VirtualClock::new(domain~, seed~)
  let out : Array[Instant[D]] = []
  for _ in 0.. String {
  "instant-ghost-domain-supported; trait-domainid-fallback"
}