/- Inspired by `Lean/Data/Lsp/Communication.lean` -/
import Lean.Data.JsonRpc

/-! Reading/writing Game Server Protocol messages from/to IO handles. -/

namespace IO.FS.Stream

open Lean
open Lean.JsonRpc

section

def readJsonLine (h : FS.Stream) : IO Json := do
  let s ← h.getLine
  ofExcept (Json.parse s)

def readGspMessage (h : FS.Stream) : IO Message := do
  let j ← h.readJsonLine
  match fromJson? j with
  | Except.ok m => pure m
  | Except.error inner =>
    throw $
      userError s!"JSON '{j.compress}' did not have the format of a JSON-RPC message.\n{inner}"

def readGspRequestAs (h : FS.Stream) (expectedMethod : String) (α) [FromJson α]
    : IO (Request α) := do
  let m ← h.readGspMessage
  match m with
  | Message.request id method params? =>
    if method = expectedMethod then
      let j := toJson params?
      match fromJson? j with
      | Except.ok v => pure ⟨id, expectedMethod, v⟩
      | Except.error inner =>
        throw $
          userError s!"Unexpected param '{j.compress}' for method '{expectedMethod}'\n{inner}"
    else
      throw $ userError s!"Expected method '{expectedMethod}', got method '{method}'"
  | _ => throw $ userError s!"Expected JSON-RPC request, got: '{(toJson m).compress}'"

def readGspNotificationAs
    (h : FS.Stream) (nBytes : Nat) (expectedMethod : String) (α) [FromJson α]
    : IO (Notification α) := do
  let m ← h.readMessage nBytes
  match m with
  | Message.notification method params? =>
    if method = expectedMethod then
      let j := toJson params?
      match fromJson? j with
      | Except.ok v => pure ⟨expectedMethod, v⟩
      | Except.error inner =>
        throw $
          userError s!"Unexpected param '{j.compress}' for method '{expectedMethod}'\n{inner}"
    else
      throw $ userError s!"Expected method '{expectedMethod}', got method '{method}'"
  | _ => throw $ userError s!"Expected JSON-RPC notification, got: '{(toJson m).compress}'"

def readGspResponseAs
    (h : FS.Stream) (nBytes : Nat) (expectedID : RequestID) (α) [FromJson α]
    : IO (Response α) := do
  let m ← h.readMessage nBytes
  match m with
  | Message.response id result =>
    if id == expectedID then
      match fromJson? result with
      | Except.ok v => pure ⟨expectedID, v⟩
      | Except.error inner => throw $ userError s!"Unexpected result '{result.compress}'\n{inner}"
    else
      throw $ userError s!"Expected id {expectedID}, got id {id}"
  | Message.notification .. => readResponseAs h nBytes expectedID α
  | _ => throw $ userError s!"Expected JSON-RPC response, got: '{(toJson m).compress}'"

end

section
  variable [ToJson α]

  def writeGspMessage (h : FS.Stream) (m : Message) : IO Unit := do
    h.writeMessage m
    h.putStr "\n"

  def writeGspRequest (h : FS.Stream) (r : Request α) : IO Unit :=
    h.writeGspMessage r

  def writeGspNotification (h : FS.Stream) (n : Notification α) : IO Unit :=
    h.writeGspMessage n

  def writeGspResponse (h : FS.Stream) (r : Response α) : IO Unit :=
    h.writeGspMessage r

  def writeGspResponseError (h : FS.Stream) (e : ResponseError Unit) : IO Unit :=
    h.writeGspMessage (Message.responseError e.id e.code e.message none)

  def writeGspResponseErrorWithData (h : FS.Stream) (e : ResponseError α) : IO Unit :=
    h.writeGspMessage e
end

end IO.FS.Stream
