///- --package .
:# readiness static =
    :< testing

:! attack_group = ()
:! healing_group = ()
:! action_slot = ()

:| private ready g =
    | AttackReady :: units::amount quantity::milliseconds -> ready attack_group
    | HealingReady :: units::amount quantity::milliseconds -> ready healing_group
    | ActionReady :: units::amount quantity::milliseconds -> ready action_slot

:| gate group =
    | Open : ready group
    | ClosedUntil : units::amount quantity::milliseconds

:| command =
    | Strike : ready attack_group
    | Heal : ready healing_group
    | Hold

:= since \* group. (proof : ready group) : units::amount quantity::milliseconds =
    ?= proof ?|
    | AttackReady at => at
    | HealingReady at => at
    | ActionReady at => at

:== instant = units::amount quantity::milliseconds

:= closed_by \* group. (spell : cooldowns::spell) (timers : cooldowns::timers) : gate group =
    ClosedUntil (quantity::ms (cooldowns::ready_at spell timers))

:= attack_gate (now : instant) (timers : cooldowns::timers) : gate attack_group =
    := spell = cooldowns::strike_spell ()
    ? cooldowns::can_cast (units::value_of now) spell timers ?> Open (AttackReady now) |: closed_by spell timers

:= healing_gate (now : instant) (timers : cooldowns::timers) : gate healing_group =
    := spell = vocations::as_cooldown (vocations::knight_heal ())
    ? cooldowns::can_cast (units::value_of now) spell timers ?> Open (HealingReady now) |: closed_by spell timers

:= action_gate
    (now : units::amount quantity::milliseconds) (next_action_at : units::amount quantity::milliseconds)
    : gate action_slot
    =
    ? units::at_least now next_action_at ?> Open (ActionReady now) |: ClosedUntil next_action_at

:= command_code (c : command) : i32 =
    ?= c ?|
    | Strike _ => 1
    | Heal _ => 2
    | Hold => 0

:= gate_code \* group. (g : gate group) : i32 =
    ?= g ?|
    | Open proof => units::value_of (since proof)
    | ClosedUntil until => 0 - units::value_of until

:= busy_after_strike (now : i32) : cooldowns::timers =
    cooldowns::after_cast now (cooldowns::strike_spell ()) (cooldowns::idle_timers ())

///> c
:# the_attack_gate_opens_when_ready =
    := ~now = 5000i32
    gate_code (attack_gate (quantity::ms now) (cooldowns::idle_timers ())) |> _assert_eq 5000i32
    gate_code (attack_gate (quantity::ms (now + 100)) (busy_after_strike now)) |> _assert_eq -7000i32

///> c
:# healing_stays_open_after_a_strike =
    := ~now = 5000i32
    gate_code (healing_gate (quantity::ms (now + 100)) (busy_after_strike now)) |> _assert_eq 5100i32

///> c
:# a_strike_needs_an_attack_proof =
    := ~now = 0i32
    ?= attack_gate (quantity::ms now) (cooldowns::idle_timers ()) ?|
    | Open proof => command_code (Strike proof) |> _assert_eq 1i32
    | ClosedUntil _ => command_code Hold |> _assert_eq 1i32

///> c
:# a_healing_proof_cannot_authorize_a_strike fails "Unification failure" =
    Strike (HealingReady (quantity::ms 0))

///> c
:# a_strike_cannot_be_issued_without_a_proof fails "Unification failure" =
    Strike (quantity::ms 0)

///> wgsl
:# gates_on_gpu =
    := ~now = 5000i32
    := idle = cooldowns::idle_timers ()
    ?= attack_gate (quantity::ms now) idle ?|
    | Open proof => ? units::value_of (since proof) = 5000i32 ?> 0i32 |: 1i32
    | ClosedUntil _ => 2i32

///> c
:# the_action_gate_opens_once_per_gap =
    := ~now = 1000i32
    gate_code (action_gate (quantity::ms now) (quantity::ms 1250)) |> _assert_eq -1250i32
    gate_code (action_gate (quantity::ms (now + 250)) (quantity::ms 1250)) |> _assert_eq 1250i32

///> c
:# an_attack_proof_cannot_pay_for_an_action_slot fails "Unification failure" =
    := drink (slot : ready action_slot) = since slot
    drink (AttackReady (quantity::ms 0))
