/-
Copyright (c) 2022 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Gabriel Ebner
-/
prelude
import Init.Data.Queue
import Init.System.Promise
import Init.System.Mutex

namespace IO

/--
Internal state of an `Channel`.

We maintain the invariant that at all times either `consumers` or `values` is empty.
-/
structure Channel.State (α : Type) where
  values : Std.Queue α := ∅
  consumers : Std.Queue (Promise (Option α)) := ∅
  closed := false
  deriving Inhabited

/--
FIFO channel with unbounded buffer, where `recv?` returns a `Task`.

A channel can be closed.  Once it is closed, all `send`s are ignored, and
`recv?` returns `none` once the queue is empty.
-/
def Channel (α : Type) : Type := Mutex (Channel.State α)

instance : Nonempty (Channel α) :=
  inferInstanceAs (Nonempty (Mutex _))

/-- Creates a new `Channel`. -/
def Channel.new : BaseIO (Channel α) :=
  Mutex.new {}

/--
Sends a message on an `Channel`.

This function does not block.
-/
def Channel.send (v : α) (ch : Channel α) : BaseIO Unit :=
  ch.atomically do
    let st ← get
    if st.closed then return
    if let some (consumer, consumers) := st.consumers.dequeue? then
      consumer.resolve (some v)
      set { st with consumers }
    else
      set { st with values := st.values.enqueue v }

/--
Closes an `Channel`.
-/
def Channel.close (ch : Channel α) : BaseIO Unit :=
  ch.atomically do
    let st ← get
    for consumer in st.consumers.toArray do consumer.resolve none
    set { st with closed := true, consumers := ∅ }

/--
Receives a message, without blocking.
The returned task waits for the message.
Every message is only received once.

Returns `none` if the channel is closed and the queue is empty.
-/
def Channel.recv? (ch : Channel α) : BaseIO (Task (Option α)) :=
  ch.atomically do
    let st ← get
    if let some (a, values) := st.values.dequeue? then
      set { st with values }
      return .pure a
    else if !st.closed then
      let promise ← Promise.new
      set { st with consumers := st.consumers.enqueue promise }
      return promise.result
    else
      return .pure none

/--
`ch.forAsync f` calls `f` for every messages received on `ch`.

Note that if this function is called twice, each `forAsync` only gets half the messages.
-/
partial def Channel.forAsync (f : α → BaseIO Unit) (ch : Channel α)
    (prio : Task.Priority := .default) : BaseIO (Task Unit) := do
  BaseIO.bindTask (prio := prio) (← ch.recv?) fun
    | none => return .pure ()
    | some v => do f v; ch.forAsync f prio

/--
Receives all currently queued messages from the channel.

Those messages are dequeued and will not be returned by `recv?`.
-/
def Channel.recvAllCurrent (ch : Channel α) : BaseIO (Array α) :=
  ch.atomically do
    modifyGet fun st => (st.values.toArray, { st with values := ∅ })

/-- Type tag for synchronous (blocking) operations on a `Channel`. -/
def Channel.Sync := Channel

/--
Accesses synchronous (blocking) version of channel operations.

For example, `ch.sync.recv?` blocks until the next message,
and `for msg in ch.sync do ...` iterates synchronously over the channel.
These functions should only be used in dedicated threads.
-/
def Channel.sync (ch : Channel α) : Channel.Sync α := ch

/--
Synchronously receives a message from the channel.

Every message is only received once.
Returns `none` if the channel is closed and the queue is empty.
-/
def Channel.Sync.recv? (ch : Channel.Sync α) : BaseIO (Option α) := do
  IO.wait (← Channel.recv? ch)

private partial def Channel.Sync.forIn [Monad m] [MonadLiftT BaseIO m]
    (ch : Channel.Sync α) (f : α → β → m (ForInStep β)) : β → m β := fun b => do
  match ← ch.recv? with
    | some a =>
      match ← f a b with
        | .done b => pure b
        | .yield b => ch.forIn f b
    | none => pure b

/-- `for msg in ch.sync do ...` receives all messages in the channel until it is closed. -/
instance [MonadLiftT BaseIO m] : ForIn m (Channel.Sync α) α where
  forIn ch b f := ch.forIn f b
