import Std.Internal.UV
open Std.Internal.UV

def assertElapsed (t1 t2 : Nat) (should : Nat) (eps : Nat) : IO Unit := do
  let dur := t2 - t1
  if (Int.ofNat dur - Int.ofNat should).natAbs > eps then
    throw <| .userError s!"elapsed time was too different, measured {dur}, should: {should}, tolerance: {eps}"

def assertDuration (should : Nat) (eps : Nat) (x : IO α) : IO α := do
  let t1 ← IO.monoMsNow
  let res ← x
  let t2 ← IO.monoMsNow
  assertElapsed t1 t2 should eps
  return res


def BASE_DURATION : Nat := 1000

-- generous tolerance for slow CI systems
def EPS : Nat := 150

def await (x : Task α) : IO α := pure x.get

namespace SleepTest

def oneShotSleep : IO Unit := do
  assertDuration BASE_DURATION EPS do
    let timer ← Timer.mk BASE_DURATION.toUInt64 false
    let p ← timer.next
    await p.result!

def promiseBehavior1 : IO Unit := do
    let timer ← Timer.mk BASE_DURATION.toUInt64 false
    let p ← timer.next
    let r := p.result!
    assert! (← IO.getTaskState r) != .finished
    IO.sleep (BASE_DURATION + EPS).toUInt32
    assert! (← IO.getTaskState r) == .finished

def promiseBehavior2 : IO Unit := do
  let timer ← Timer.mk BASE_DURATION.toUInt64 false
  let p1 ← timer.next
  let p2 ← timer.next
  assert! (← IO.getTaskState p1.result!) != .finished
  assert! (← IO.getTaskState p2.result!) != .finished
  IO.sleep (BASE_DURATION + EPS).toUInt32
  assert! (← IO.getTaskState p1.result!) == .finished
  assert! (← IO.getTaskState p2.result!) == .finished

def promiseBehavior3 : IO Unit := do
  let timer ← Timer.mk BASE_DURATION.toUInt64 false
  let p1 ← timer.next
  assert! (← IO.getTaskState p1.result!) != .finished
  IO.sleep (BASE_DURATION + EPS).toUInt32
  assert! (← IO.getTaskState p1.result!) == .finished
  let p3 ← timer.next
  assert! (← IO.getTaskState p3.result!) == .finished

def resetBehavior : IO Unit := do
  let timer ← Timer.mk BASE_DURATION.toUInt64 false
  let p ← timer.next
  assert! (← IO.getTaskState p.result!) != .finished

  IO.sleep (BASE_DURATION / 2).toUInt32
  assert! (← IO.getTaskState p.result!) != .finished
  timer.reset

  IO.sleep (BASE_DURATION / 2).toUInt32
  assert! (← IO.getTaskState p.result!) != .finished

  IO.sleep ((BASE_DURATION / 2) + EPS).toUInt32
  assert! (← IO.getTaskState p.result!) == .finished

def cancelBehaviorNoRepeat : IO Unit := do
  let timer ← Timer.mk BASE_DURATION.toUInt64 false
  let prom ← timer.next
  assert! (← IO.getTaskState prom.result?) != IO.TaskState.finished
  timer.cancel
  assert! (← IO.getTaskState prom.result?) == IO.TaskState.finished

def stopBehaviorNoRepeat : IO Unit := do
  let timer ← Timer.mk BASE_DURATION.toUInt64 false
  let prom ← timer.next
  assert! (← IO.getTaskState prom.result?) != IO.TaskState.finished
  timer.stop
  assert! (← IO.getTaskState prom.result?) == IO.TaskState.finished
  assert! prom.result?.get == none
  let prom ← timer.next
  assert! (← IO.getTaskState prom.result?) == IO.TaskState.finished
  assert! prom.result?.get == none
  let prom ← timer.next
  assert! (← IO.getTaskState prom.result?) == IO.TaskState.finished
  assert! prom.result?.get == none
  let prom ← timer.next
  assert! (← IO.getTaskState prom.result?) == IO.TaskState.finished
  assert! prom.result?.get == none
  let prom ← timer.next
  assert! (← IO.getTaskState prom.result?) == IO.TaskState.finished
  assert! prom.result?.get == none

def stopCancelNoRepeat : IO Unit := do
  let timer ← Timer.mk 1000 false
  let prom ← timer.next
  timer.stop
  timer.cancel
  timer.stop
  timer.cancel
  timer.stop
  timer.cancel
  assert! (← IO.getTaskState prom.result?) == IO.TaskState.finished
  assert! prom.result?.get == none

def cancelStopNoRepeat : IO Unit := do
  let timer ← Timer.mk 1000 false
  let prom ← timer.next
  timer.cancel
  timer.stop
  timer.cancel
  timer.stop
  timer.cancel
  timer.stop
  timer.cancel
  timer.stop
  IO.sleep EPS.toUInt32
  assert! (← IO.getTaskState prom.result?) == IO.TaskState.finished
  assert! prom.result?.get == none

def stopNoRepeat : IO Unit := do
  let timer ← Timer.mk 1000 false
  let prom ← timer.next

  timer.stop

  assert! prom.result?.get == none

  let prom ← timer.next

  assert! (← IO.getTaskState prom.result?) == IO.TaskState.finished
  assert! prom.result?.get == none

def cancelNoRepeat : IO Unit := do
  let timer ← Timer.mk 1000 false
  timer.cancel

  let prom ← timer.next
  assert! prom.result?.get == some ()

#eval cancelBehaviorNoRepeat
#eval stopBehaviorNoRepeat

#eval stopCancelNoRepeat
#eval cancelStopNoRepeat

#eval stopNoRepeat
#eval cancelNoRepeat

#eval oneShotSleep
#eval promiseBehavior1
#eval promiseBehavior2
#eval promiseBehavior3
#eval resetBehavior
#eval oneShotSleep

end SleepTest

namespace IntervalTest

def sleepFirst : IO Unit := do
  assertDuration 0 EPS go
where
  go : IO Unit := do
    let timer ← Timer.mk BASE_DURATION.toUInt64 true
    let prom ← timer.next
    await prom.result!
    timer.stop

def sleepSecond : IO Unit := do
  discard <| assertDuration BASE_DURATION EPS go
where
  go : IO Unit := do
    let timer ← Timer.mk BASE_DURATION.toUInt64 true

    let task ←
      IO.bindTask (← timer.next).result! fun _ => do
      IO.bindTask (← timer.next).result! fun _ => pure (Task.pure (.ok 2))

    discard <| await task
    timer.stop

def promiseBehavior1 : IO Unit := do
  let timer ← Timer.mk BASE_DURATION.toUInt64 true
  let p1 ← timer.next
  IO.sleep EPS.toUInt32
  assert! (← IO.getTaskState p1.result!) == .finished
  let p2 ← timer.next
  assert! (← IO.getTaskState p2.result!) != .finished
  IO.sleep (BASE_DURATION + EPS).toUInt32
  assert! (← IO.getTaskState p2.result!) == .finished
  timer.stop

def promiseBehavior2 : IO Unit := do
  let timer ← Timer.mk BASE_DURATION.toUInt64 true
  let p1 ← timer.next
  IO.sleep EPS.toUInt32
  assert! (← IO.getTaskState p1.result!) == .finished

  let prom1 ← timer.next
  let prom2 ← timer.next
  assert! (← IO.getTaskState prom1.result!) != .finished
  assert! (← IO.getTaskState prom2.result!) != .finished
  IO.sleep (BASE_DURATION + EPS).toUInt32
  assert! (← IO.getTaskState prom1.result!) == .finished
  assert! (← IO.getTaskState prom2.result!) == .finished
  timer.stop

def promiseBehavior3 : IO Unit := do
  let timer ← Timer.mk BASE_DURATION.toUInt64 true
  let p1 ← timer.next
  IO.sleep EPS.toUInt32
  assert! (← IO.getTaskState p1.result!) == .finished

  let prom1 ← timer.next
  assert! (← IO.getTaskState prom1.result!) != .finished
  IO.sleep (BASE_DURATION + EPS).toUInt32
  assert! (← IO.getTaskState prom1.result!) == .finished
  let prom2 ← timer.next
  assert! (← IO.getTaskState prom2.result!) != .finished
  IO.sleep (BASE_DURATION + EPS).toUInt32
  assert! (← IO.getTaskState prom2.result!) == .finished
  timer.stop

def delayedTickBehavior : IO Unit := do
  let timer ← Timer.mk BASE_DURATION.toUInt64 true
  let p1 ← timer.next
  IO.sleep EPS.toUInt32
  assert! (← IO.getTaskState p1.result!) == .finished

  IO.sleep (BASE_DURATION / 2).toUInt32
  let p2 ← timer.next
  assert! (← IO.getTaskState p2.result!) != .finished
  IO.sleep ((BASE_DURATION / 2) + EPS).toUInt32
  assert! (← IO.getTaskState p2.result!) == .finished
  timer.stop

def skippedTickBehavior : IO Unit := do
  let timer ← Timer.mk BASE_DURATION.toUInt64 true
  let p1 ← timer.next
  IO.sleep EPS.toUInt32
  assert! (← IO.getTaskState p1.result!) == .finished

  IO.sleep (2 * BASE_DURATION + BASE_DURATION / 2).toUInt32
  let p2 ← timer.next
  assert! (← IO.getTaskState p2.result!) != .finished
  IO.sleep ((BASE_DURATION / 2) + EPS).toUInt32
  assert! (← IO.getTaskState p2.result!) == .finished
  timer.stop

def resetBehavior : IO Unit := do
  let timer ← Timer.mk BASE_DURATION.toUInt64 true
  let p1 ← timer.next
  IO.sleep EPS.toUInt32
  assert! (← IO.getTaskState p1.result!) == .finished

  let prom ← timer.next
  assert! (← IO.getTaskState prom.result!) != .finished

  IO.sleep (BASE_DURATION / 2).toUInt32
  assert! (← IO.getTaskState prom.result!) != .finished
  timer.reset

  IO.sleep (BASE_DURATION / 2).toUInt32
  assert! (← IO.getTaskState prom.result!) != .finished

  IO.sleep ((BASE_DURATION / 2) + EPS).toUInt32
  assert! (← IO.getTaskState prom.result!) == .finished
  timer.stop

def sequentialSleep : IO Unit := do
  discard <| assertDuration BASE_DURATION EPS go
where
  go : IO Unit := do
    let timer ← Timer.mk (BASE_DURATION / 2).toUInt64 true
    -- 0th interval ticks instantly
    let task ←
      IO.bindTask (← timer.next).result! fun _ => do
      IO.bindTask (← timer.next).result! fun _ => do
      IO.bindTask (← timer.next).result! fun _ => pure (Task.pure (.ok 2))

    discard <| await task
    timer.stop

def cancelBehaviorRepeat : IO Unit := do
  let timer ← Timer.mk BASE_DURATION.toUInt64 true
  let prom ← timer.next
  IO.sleep EPS.toUInt32
  assert! (← IO.getTaskState prom.result?) == IO.TaskState.finished
  let prom ← timer.next
  IO.sleep EPS.toUInt32
  assert! (← IO.getTaskState prom.result?) != IO.TaskState.finished
  timer.cancel
  IO.sleep EPS.toUInt32
  assert! (← IO.getTaskState prom.result?) == IO.TaskState.finished
  let prom ← timer.next
  IO.sleep EPS.toUInt32
  assert! (← IO.getTaskState prom.result?) != IO.TaskState.finished
  timer.cancel
  IO.sleep EPS.toUInt32
  assert! (← IO.getTaskState prom.result?) == IO.TaskState.finished
  assert! prom.result?.get == none

def stopBehaviorRepeat : IO Unit := do
  let timer ← Timer.mk BASE_DURATION.toUInt64 true
  let prom ← timer.next
  IO.sleep EPS.toUInt32
  assert! (← IO.getTaskState prom.result?) == IO.TaskState.finished
  let prom ← timer.next
  IO.sleep EPS.toUInt32
  assert! (← IO.getTaskState prom.result?) != IO.TaskState.finished
  timer.stop
  IO.sleep EPS.toUInt32
  assert! (← IO.getTaskState prom.result?) == IO.TaskState.finished
  assert! prom.result?.get == none

def stopCancelRepeat : IO Unit := do
  let timer ← Timer.mk BASE_DURATION.toUInt64 true
  let prom ← timer.next
  assert! prom.result?.get == some ()
  let prom ← timer.next
  timer.stop
  timer.cancel
  timer.stop
  timer.cancel
  timer.stop
  timer.cancel
  assert! (← IO.getTaskState prom.result?) == IO.TaskState.finished
  assert! prom.result?.get == none

def cancelStopRepeat : IO Unit := do
  let timer ← Timer.mk BASE_DURATION.toUInt64 true
  let prom ← timer.next
  assert! prom.result?.get == some ()
  let prom ← timer.next
  timer.cancel
  timer.stop
  timer.cancel
  timer.stop
  timer.cancel
  timer.stop
  timer.cancel
  timer.stop
  IO.sleep EPS.toUInt32
  assert! (← IO.getTaskState prom.result?) == IO.TaskState.finished
  assert! prom.result?.get == none

def stopRepeat : IO Unit := do
  let timer ← Timer.mk BASE_DURATION.toUInt64 true
  let prom ← timer.next

  assert! prom.result?.get == some ()

  timer.stop

  let prom ← timer.next

  assert! (← IO.getTaskState prom.result?) == IO.TaskState.finished
  assert! prom.result?.get == none

def cancelRepeat : IO Unit := do
  let timer ← Timer.mk BASE_DURATION.toUInt64 true
  timer.cancel

  let prom ← timer.next
  assert! prom.result?.get == some ()

#eval cancelBehaviorRepeat
#eval stopBehaviorRepeat

#eval stopCancelRepeat
#eval cancelStopRepeat

#eval stopRepeat
#eval cancelRepeat

#eval sleepFirst
#eval sleepSecond
#eval promiseBehavior1
#eval promiseBehavior2
#eval promiseBehavior3
#eval delayedTickBehavior
#eval skippedTickBehavior
#eval resetBehavior
#eval sequentialSleep

end IntervalTest
