Message

use "immutable-json"
use "collections"

trait Message is Stringable
  fun json(): JsonObject

  fun string(): String iso^ =>
    let content = this.json().string()
    let size_str: String val = content.size().string()
      recover iso
        String.create(content.size() + size_str.size() + 20)
          .>append("Content-Length: ")
          .>append(size_str)
          .>append("\r\n\r\n")
          .>append(content)
      end


primitive Err
  fun tag apply(): I64 => 1
  fun string(): String => "ERROR"
primitive Warning
  fun tag apply(): I64 => 2
  fun string(): String => "WARNING"
primitive Info
  fun tag apply(): I64 => 3
  fun string(): String => "INFO"
primitive Log
  fun tag apply(): I64 => 4
  fun string(): String => "LOG"
primitive Debug
  fun tag apply(): I64 => 5
  fun string(): String => "DEBUG"

type MessageType is (Err | Warning | Info | Log | Debug)


type RequestId is (I64 | String)

class val RequestMessage is Message

  let id: RequestId
  let method: String
  let params: (JsonObject | JsonArray | None)

  new val create(
    id': RequestId,
    method': String,
    params': (JsonObject | JsonArray | None) = None)
  =>
    id = id'
    method = method'
    params = params'


  fun json(): JsonObject =>
    JsonObject(
      recover val
        let m = Map[String, JsonType](4)
          .>update("jsonrpc", "2.0")
          .>update("id", id)
          .>update("method", method)
        match this.params
        | let obj: JsonObject =>
          m.update("params", obj)
        | let arr: JsonArray =>
          m.update("params", arr)
        end
        consume m
      end
    )

class val Notification is Message

  let method: String
  let params: (JsonObject | JsonArray | None)

  new val create(
    method': String,
    params': (JsonObject | JsonArray | None) = None)
  =>
    method = method'
    params = params'

  fun json(): JsonObject =>
    Obj("jsonrpc", "2.0")(
        "method", method)(
        "params", this.params).build()

class val ResponseMessage is Message
  let id: (RequestId | None)
  let result: JsonType
  let _error: (ResponseError val | None)

  new val create(
    id': (RequestId | None),
    result': JsonType,
    error': (ResponseError val | None) = None)
  =>
    """
    Constructor for a Response to a Request bearing a request id
    """
    id = id'
    result = result'
    _error = error'

  fun json(): JsonObject =>
    JsonObject(
      recover val
        let m = Map[String, JsonType](3)
          .>update("jsonrpc", "2.0")
          .>update("id", id)
        match this._error
        | let r: ResponseError val =>
          m.update("error", r.json())
        | None =>
          m.update("result", result)
        end
        m
      end
    )


class val ResponseError
  let code: I64
  let message: String
  let data: JsonType

  new val create(
    code': I64,
    message': String,
    data': JsonType = None)
  =>
    code = code'
    message = message'
    data = data'

  fun json(): JsonObject =>
    JsonObject(
      recover val
        Map[String, JsonType](3)
          .>update("code", code)
          .>update("message", message)
          .>update("data", data)
      end
    )


primitive ErrorCodes
  fun parse_error(): I64 => -32700
    fun invalid_request(): I64 => -32600
    fun method_not_found(): I64 => -32601
    fun invalid_params(): I64 => -32602
    fun internal_error(): I64 => -32603
  fun server_not_initialized(): I64 =>
    """
    Error code indicating that a server received a notification or
      request before the server has received the `initialize` request.
    """
    -32002
    fun unknown_error_code(): I64 =>
    -32001
    fun request_failed(): I64 =>
    """
    A request failed but it was syntactically correct, e.g the
      method name was known and the parameters were valid. The error
      message should contain human readable information about why
      the request failed.
    """
    -32803

    fun server_cancelled(): I64 =>
    """
    The server cancelled the request. This error code should
      only be used for requests that explicitly support being
      server cancellable.
    """
    -32802

    fun content_modified(): I64 =>
    """
    The server detected that the content of a document got
      modified outside normal conditions. A server should
      NOT send this error code if it detects a content change
      in it unprocessed messages. The result even computed
      on an older state might still be useful for the client.

      If a client decides that a result is not of any use anymore
      the client should cancel the request.
    """
    -32801

    fun request_cancelled(): I64 =>
    """
    The client has canceled a request and a server has detected
      the cancel.
    """
    -32800