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

open Std.Internal.IO Async
open Std.Net

open Std.Net

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

def baseSelector (asyncWaiter : AsyncTask α) : Selector α :=
 {
    tryFn := do
      if ← IO.hasFinished asyncWaiter then
        let result ← IO.ofExcept asyncWaiter.get
        return some result
      else
        return none
    registerFn waiter := do
      discard <| AsyncTask.mapIO (x := asyncWaiter) fun data => do
        let lose := return ()
        let win promise := promise.resolve (.ok data)
        waiter.race lose win
    unregisterFn := pure ()
  }

def race (a : AsyncTask α) (b : AsyncTask β) (map : Except α β → AsyncTask γ) : IO (AsyncTask γ) := do
  Selectable.one #[
    .case (baseSelector a) fun a => return map (.error a),
    .case (baseSelector b) fun b => return map (.ok b),
  ]

def timeout (a : AsyncTask α) (time : Std.Time.Millisecond.Offset) : IO (AsyncTask α) := do
  race (← sleep time) a fun
    | .ok res => Task.pure (.ok res)
    | .error _ => Task.pure (.error (IO.userError "Timeout."))

def runDNS : Async Unit := do
  let infos ← await <| (← timeout (← DNS.getAddrInfo "google.com" "http") 10000)

  unless infos.size > 0 do
    (throw <| IO.userError <| "No DNS results for google.com" : IO _)

def runDNSNoAscii : Async Unit := do
  let infos ← await <| (← timeout (← DNS.getAddrInfo "google.com▸" "http") 10000)

  unless infos.size > 0 do
    (throw <| IO.userError <| "No DNS results for google.com" : IO _)

def runReverseDNS : Async Unit := do
  let result ← await (← DNS.getNameInfo (.v4 ⟨.ofParts 8 8 8 8, 53⟩))
  assertBEq result.service "domain"
  assertBEq result.host "dns.google"

#eval runDNS.toIO >>= AsyncTask.block

/--
error: invalid argument (error code: 22, invalid argument)
  file: name is not ASCII.
-/
#guard_msgs in #eval runDNSNoAscii.toIO >>= AsyncTask.block

#eval runReverseDNS.toIO >>= AsyncTask.block
