import Std.Async
import Std.Internal.UV
import Std.Net.Addr

open Std.Async
open Std.Net

def assertBEq [BEq α] [ToString α] (actual expected : α) : IO Unit := do
  unless actual == expected do
    throw <| IO.userError <|
      s!"expected '{expected}', got '{actual}'"

/-- Mike is another client. -/
def runMike (client: TCP.Socket.Client) : Async Unit := do
  let message ← client.recv? 1024
  assertBEq (String.fromUTF8? =<< message) none

/-- Joe is another client. -/
def runJoe (client: TCP.Socket.Client) : Async Unit := do
  let message ← client.recv? 1024
  assertBEq (String.fromUTF8? =<< message) none

/-- Robert is the server. -/
def runRobert (server: TCP.Socket.Server) : Async Unit := do
  discard <| server.accept
  discard <| server.accept

def clientServer : IO Unit := do
  let addr := SocketAddressV4.mk (.ofParts 127 0 0 1) 8083

  let server ← TCP.Socket.Server.mk
  server.bind addr
  server.listen 128

  assertBEq (← server.getSockName).port 8083

  let joe ← TCP.Socket.Client.mk
  let task ← joe.connect addr |>.toBaseIO
  task.block

  assertBEq (← joe.getPeerName).port 8083

  joe.noDelay

  let mike ← TCP.Socket.Client.mk
  let task ← mike.connect addr |>.toBaseIO
  task.block

  assertBEq (← mike.getPeerName).port 8083

  mike.noDelay

  let serverTask ← (runRobert server).toIO

  let joeTask ← (runJoe joe).toIO
  let mikeTask ← (runMike mike).toIO

  serverTask.block

  joeTask.block
  mikeTask.block

#eval clientServer
