import Std.Async
import Std.Sync

open Std.Async

-- Test tryRecv with empty channel
def tryRecvEmpty : Async Unit := do
  let channel ← Std.Broadcast.new (capacity := 4) (α := Nat)
  let subs ← channel.subscribe

  let result ← subs.tryRecv
  assert! result.isNone

#eval tryRecvEmpty.block

-- Test tryRecv with messages available
def tryRecvWithMessages : Async Unit := do
  let channel ← Std.Broadcast.new (capacity := 4)
  let subs ← channel.subscribe

  discard <| await (← channel.send 42)
  discard <| await (← channel.send 100)

  let msg1 ← subs.tryRecv
  let msg2 ← subs.tryRecv
  let msg3 ← subs.tryRecv

  assert! msg1 == some 42
  assert! msg2 == some 100
  assert! msg3.isNone

#eval tryRecvWithMessages.block

-- Test unsubscribe functionality
def testUnsubscribe : Async Unit := do
  let channel ← Std.Broadcast.new (capacity := 4)
  let subs1 ← channel.subscribe
  let subs2 ← channel.subscribe

  -- Send before unsubscribe
  discard <| await (← channel.send 1)

  -- Unsubscribe subs1
  subs1.unsubscribe

  -- Send after unsubscribe
  discard <| await (← channel.send 2)

  -- subs1 should not receive the second message
  let msg1 ← await (← subs1.recv)
  let result ← subs1.tryRecv

  -- subs2 should receive both messages
  let msg2 ← await (← subs2.recv)
  let msg3 ← await (← subs2.recv)

  assert! msg1 == none
  assert! result.isNone  -- No more messages for unsubscribed
  assert! msg2 == some 1
  assert! msg3 == some 2

#eval testUnsubscribe.block

def testUnsubscribeUnblock : Async Unit := do
  let channel ← Std.Broadcast.new (capacity := 4)

  let subs1 ← channel.subscribe
  let subs2 ← channel.subscribe

  -- Add 4 messages, so it reaches the limit.
  for i in [0:4] do
    assert! (← channel.trySend i).isSome

  -- Mark subs1 messages as read
  for i in [0:10] do
    if i < 4 then
      assert! (← subs1.tryRecv) = some i
    else
      assert! (← subs1.tryRecv) = none

  -- Mark 2 messages as read so it cleans 2 messages
  assert! (← subs2.tryRecv).isSome
  assert! (← subs2.tryRecv).isSome

  assert! (← channel.trySend 5).isSome
  assert! (← channel.trySend 5).isSome
  assert! not (← channel.trySend 6).isSome

  -- It unsubscribe and mark all subs2 messages as read.
  subs2.unsubscribe

  -- Create a new subscriber to verify channel still works
  let subs3 ← channel.subscribe

  -- Send one more message that the new subscriber should receive
  assert! (← channel.trySend 8).isSome

  -- subs1 should be able to receive the messages sent after it last read:
  -- the two 5's and the 8
  let subs1Msg1 ← subs1.tryRecv
  let subs1Msg2 ← subs1.tryRecv
  let subs1Msg3 ← subs1.tryRecv
  let subs1Msg4 ← subs1.tryRecv -- should be none

  assert! subs1Msg1 == some 5
  assert! subs1Msg2 == some 5
  assert! subs1Msg3 == some 8
  assert! subs1Msg4.isNone

  -- The new subscriber should only get the most recent message
  let msg ← subs3.tryRecv
  assert! msg == some 8

  -- No more messages should be available for the new subscriber
  let noMsg ← subs3.tryRecv
  assert! noMsg.isNone

  -- Verify unsubscribed subs2 can't receive anything
  let subs2NoMsg ← subs2.tryRecv
  assert! subs2NoMsg.isNone

#eval testUnsubscribeUnblock.block

def unsubscribedCannotReceive : Async Unit := do
  let channel ← Std.Broadcast.new

  let subs1 ← channel.subscribe
  let subs2 ← channel.subscribe

  discard <| await (← channel.send 1)
  discard <| await (← channel.send 2)

  let msg1 ← await (← subs1.recv)
  let msg2 ← await (← subs1.recv)
  let msg3 ← await (← subs2.recv)
  let msg4 ← await (← subs2.recv)

  assert! msg1 == some 1
  assert! msg2 == some 2

  assert! msg3 == some 1
  assert! msg4 == some 2

#eval unsubscribedCannotReceive.block

def fullBuffer : Async Unit := do
  let channel ← Std.Broadcast.new (capacity := 4)

  let subs1 ← channel.subscribe

  for i in [0:5] do
    if not (← channel.trySend i).isSome then
      assert! i == 4

#eval fullBuffer.block

def noSubscribers : Async Unit := do
  let channel ← Std.Broadcast.new (capacity := 4)

  assert! (← channel.trySend 0) == some 0

#eval noSubscribers.block

-- Test unsubscribe during message consumption
def testUnsubscribeDuringConsumption : Async Unit := do
  let channel ← Std.Broadcast.new (capacity := 4)
  let subs1 ← channel.subscribe
  let subs2 ← channel.subscribe

  -- Send several messages
  for i in [0:4] do
    discard <| await (← channel.send i)

  -- subs1 reads first message then unsubscribes
  let msg1 ← await (← subs1.recv)
  subs1.unsubscribe

  -- subs2 should still be able to read all messages
  let msgs2 ← [0, 1, 2, 3].mapM (fun _ => subs2.recv >>= fun r => await r)

  assert! msg1 == some 0
  assert! msgs2 == [some 0, some 1, some 2, some 3]

  -- subs1 should have no more messages available
  let result ← subs1.tryRecv
  assert! result.isNone

-- Test mixed send and trySend operations
def testMixedSendOperations : Async Unit := do
  let channel ← Std.Broadcast.new (capacity := 3)
  let subs ← channel.subscribe

  -- Use trySend
  assert! (← channel.trySend 1).isSome

  -- Use regular send
  discard <| await (← channel.send 2)

  -- Use trySend again
  assert! (← channel.trySend 3).isSome

  -- Buffer should be full now
  assert! not (← channel.trySend 4).isSome

  -- Verify all messages received correctly
  let msgs ← [1, 2, 3].mapM (fun _ => subs.recv >>= fun r => await r)
  assert! msgs == [some 1, some 2, some 3]

#eval testMixedSendOperations.block

-- Test recv on closed channel with no pending messages
def testRecvOnClosedEmpty : Async Unit := do
  let channel ← Std.Broadcast.new (capacity := 4) (α := Nat)
  let subs ← channel.subscribe

  channel.close

  -- tryRecv should return none immediately
  let result ← subs.tryRecv
  assert! result.isNone

#eval testRecvOnClosedEmpty.block

-- Test recv block
def testRecvOnEmpty : Async Unit := do
  let channel ← Std.Broadcast.new (capacity := 4) (α := Nat)
  let subs ← channel.subscribe

  let recv ← subs.recv

  assert! (← IO.getTaskState recv) == IO.TaskState.waiting

  let result ← await (← channel.send 3)
  let result ← await recv

  assert! (← IO.getTaskState recv) == IO.TaskState.finished
  assert! result == some 3

#eval testRecvOnEmpty.block

-- Test recv
def recvConditions : Async Unit := do
  let channel ← Std.Broadcast.new (capacity := 16) (α := Nat)
  let subs1 ← channel.subscribe
  let subs2 ← channel.subscribe
  let subs3 ← channel.subscribe

  discard <| EAsync.ofETask (← channel.send 1)
  discard <| EAsync.ofETask (← channel.send 2)
  discard <| EAsync.ofETask (← channel.send 3)

  channel.close

  let recv ← subs1.recv
  assert! (← IO.getTaskState recv) == IO.TaskState.finished
  assert! recv.get == some 1

  let recv ← subs1.recv
  assert! (← IO.getTaskState recv) == IO.TaskState.finished
  assert! recv.get == some 2

  let recv ← subs1.recv
  assert! (← IO.getTaskState recv) == IO.TaskState.finished
  assert! recv.get == some 3

  let recv ← subs1.recv
  assert! (← IO.getTaskState recv) == IO.TaskState.finished
  assert! recv.get == none

  let recv ← subs1.recv
  assert! (← IO.getTaskState recv) == IO.TaskState.finished
  assert! recv.get == none

  let recv ← subs2.recv
  assert! (← IO.getTaskState recv) == IO.TaskState.finished
  assert! recv.get == some 1

  let recv ← subs2.recv
  assert! (← IO.getTaskState recv) == IO.TaskState.finished
  assert! recv.get == some 2

  let recv ← subs2.recv
  assert! (← IO.getTaskState recv) == IO.TaskState.finished
  assert! recv.get == some 3

  let recv ← subs2.recv
  assert! (← IO.getTaskState recv) == IO.TaskState.finished
  assert! recv.get == none

  let recv ← subs2.recv
  assert! (← IO.getTaskState recv) == IO.TaskState.finished
  assert! recv.get == none

  subs3.unsubscribe

  let recv ← subs3.recv
  assert! (← IO.getTaskState recv) == IO.TaskState.finished
  assert! recv.get == none

#eval recvConditions.block

-- Test selectables
def selectableConditions : Async Unit := do
  let channel1 ← Std.Channel.new

  let channel ← Std.Broadcast.new (capacity := 16) (α := Nat)
  let subs1 ← channel.subscribe
  let subs2 ← channel.subscribe
  let subs3 ← channel.subscribe

  discard <| EAsync.ofETask (← channel.send 1)
  discard <| EAsync.ofETask (← channel.send 2)
  discard <| EAsync.ofETask (← channel.send 3)

  channel.close

  let recv ← Async.toIO <| Selectable.one #[
    .case subs1.recvSelector pure,
    .case channel1.recvSelector pure
  ]

  assert! (← IO.getTaskState recv) == IO.TaskState.finished
  assert! (← IO.ofExcept recv.get) == some 1

  let recv ← Async.toIO <| Selectable.one #[
    .case subs1.recvSelector pure,
    .case channel1.recvSelector pure
  ]

  assert! (← IO.getTaskState recv) == IO.TaskState.finished
  assert! (← IO.ofExcept recv.get) == some 2

  let recv ← Async.toIO <| Selectable.one #[
    .case subs1.recvSelector pure,
    .case channel1.recvSelector pure
  ]

  assert! (← IO.getTaskState recv) == IO.TaskState.finished
  assert! (← IO.ofExcept recv.get) == some 3

  let recv ← Async.toIO <| Selectable.one #[
    .case subs1.recvSelector pure,
    .case channel1.recvSelector pure
  ]

  assert! (← IO.getTaskState recv) == IO.TaskState.finished
  assert! (← IO.ofExcept recv.get) == none

  let recv ← Async.toIO <| Selectable.one #[
    .case subs2.recvSelector pure,
    .case channel1.recvSelector pure
  ]

  assert! (← IO.getTaskState recv) == IO.TaskState.finished
  assert! (← IO.ofExcept recv.get) == some 1

  let recv ← Async.toIO <| Selectable.one #[
    .case subs2.recvSelector pure,
    .case channel1.recvSelector pure
  ]

  assert! (← IO.getTaskState recv) == IO.TaskState.finished
  assert! (← IO.ofExcept recv.get) == some 2

  let recv ← Async.toIO <| Selectable.one #[
    .case subs2.recvSelector pure,
    .case channel1.recvSelector pure
  ]

  assert! (← IO.getTaskState recv) == IO.TaskState.finished
  assert! (← IO.ofExcept recv.get) == some 3

  let recv ← Async.toIO <| Selectable.one #[
    .case subs2.recvSelector pure,
    .case channel1.recvSelector pure
  ]

  assert! (← IO.getTaskState recv) == IO.TaskState.finished
  assert! (← IO.ofExcept recv.get) == none

  subs3.unsubscribe

  let recv ← Async.toIO <| Selectable.one #[
    .case subs3.recvSelector pure,
    .case channel1.recvSelector pure
  ]

  assert! (← IO.getTaskState recv) == IO.TaskState.finished
  assert! (← IO.ofExcept recv.get) == none

#eval selectableConditions.block
