// The seven channelz RPCs over the registry: top channels, servers, server sockets,
// and one channel, subchannel or socket by id. The descriptor is the canonical
// channelz one, so a real channelz client can describe and call this service.

///|
pub let top_channels_path : String = "/grpc.channelz.v1.Channelz/GetTopChannels"

///|
pub let servers_path : String = "/grpc.channelz.v1.Channelz/GetServers"

///|
pub let server_path : String = "/grpc.channelz.v1.Channelz/GetServer"

///|
pub let server_sockets_path : String = "/grpc.channelz.v1.Channelz/GetServerSockets"

///|
pub let channel_path : String = "/grpc.channelz.v1.Channelz/GetChannel"

///|
pub let subchannel_path : String = "/grpc.channelz.v1.Channelz/GetSubchannel"

///|
pub let socket_path : String = "/grpc.channelz.v1.Channelz/GetSocket"

///|
/// The channelz service over one registry.
pub(all) struct ChannelzService {
  registry : @service.Registry
}

///|
/// A channelz service reading `registry`.
pub fn ChannelzService::new(registry : @service.Registry) -> ChannelzService {
  { registry, }
}

///|
/// Register the seven RPCs on a server, in the clear or over TLS.
pub fn[R : @stream.Registrar] ChannelzService::install(
  self : ChannelzService,
  server : R,
) -> Unit {
  server.register_handler(
    top_channels_path,
    @service.unary(fn(
      ctx : @stream.RpcContext,
      req : GetTopChannelsRequest,
    ) -> GetTopChannelsResponse {
      self.top_channels(ctx, req)
    }),
  )
  server.register_handler(
    servers_path,
    @service.unary(fn(
      ctx : @stream.RpcContext,
      req : GetServersRequest,
    ) -> GetServersResponse {
      self.get_servers(ctx, req)
    }),
  )
  server.register_handler(
    server_path,
    @service.unary(fn(
      ctx : @stream.RpcContext,
      req : GetServerRequest,
    ) -> GetServerResponse {
      self.get_server(ctx, req)
    }),
  )
  server.register_handler(
    server_sockets_path,
    @service.unary(fn(
      ctx : @stream.RpcContext,
      req : GetServerSocketsRequest,
    ) -> GetServerSocketsResponse {
      self.get_server_sockets(ctx, req)
    }),
  )
  server.register_handler(
    channel_path,
    @service.unary(fn(
      ctx : @stream.RpcContext,
      req : GetChannelRequest,
    ) -> GetChannelResponse {
      self.get_channel(ctx, req)
    }),
  )
  server.register_handler(
    subchannel_path,
    @service.unary(fn(
      ctx : @stream.RpcContext,
      req : GetSubchannelRequest,
    ) -> GetSubchannelResponse {
      self.get_subchannel(ctx, req)
    }),
  )
  server.register_handler(
    socket_path,
    @service.unary(fn(
      ctx : @stream.RpcContext,
      req : GetSocketRequest,
    ) -> GetSocketResponse {
      self.get_socket(ctx, req)
    }),
  )
}

///|
/// Add channelz.proto and the well-known files it imports to a reflection service.
pub fn ChannelzService::add_to_reflection(
  self : ChannelzService,
  reflection : @service.ReflectionService,
) -> Unit {
  ignore(self)
  for file in well_known_files() {
    reflection.add_file(file)
  }
  reflection.add_file(descriptor())
}

///|
fn ChannelzService::top_channels(
  self : ChannelzService,
  ctx : @stream.RpcContext,
  req : GetTopChannelsRequest,
) -> GetTopChannelsResponse {
  ignore(ctx)
  let limit = if req.max_results > 0L { req.max_results } else { 100L }
  let out : Array[Channel] = []
  let mut n = 0L
  for node in self.registry.channels() {
    if node.id.to_int64() >= req.start_channel_id && n < limit {
      out.push(self.channel_message(node))
      n = n + 1L
    }
  }
  { channel: out, is_end: n < limit, }
}

///|
fn ChannelzService::get_servers(
  self : ChannelzService,
  ctx : @stream.RpcContext,
  req : GetServersRequest,
) -> GetServersResponse {
  ignore(ctx)
  let limit = if req.max_results > 0L { req.max_results } else { 100L }
  let out : Array[Server] = []
  let mut n = 0L
  for node in self.registry.servers() {
    if node.id.to_int64() >= req.start_server_id && n < limit {
      out.push(self.server_message(node))
      n = n + 1L
    }
  }
  { server: out, is_end: n < limit, }
}

///|
fn ChannelzService::get_server(
  self : ChannelzService,
  ctx : @stream.RpcContext,
  req : GetServerRequest,
) -> GetServerResponse {
  match self.registry.server(req.server_id.to_int()) {
    Some(node) => { server: self.server_message(node), }
    None => {
      ctx.fail(
        @wire.Status::NotFound.code(),
        "server \{req.server_id} not found",
      )
      { server: Server::empty(), }
    }
  }
}

///|
fn ChannelzService::get_server_sockets(
  self : ChannelzService,
  ctx : @stream.RpcContext,
  req : GetServerSocketsRequest,
) -> GetServerSocketsResponse {
  let limit = if req.max_results > 0L { req.max_results } else { 100L }
  match self.registry.server(req.server_id.to_int()) {
    Some(node) => {
      let out : Array[SocketRef] = []
      let mut n = 0L
      for id in node.listen_sockets {
        if id.to_int64() >= req.start_socket_id && n < limit {
          out.push(self.socket_ref(id))
          n = n + 1L
        }
      }
      { socket_ref: out, is_end: n < limit, }
    }
    None => {
      ctx.fail(
        @wire.Status::NotFound.code(),
        "server \{req.server_id} not found",
      )
      { socket_ref: [], is_end: true, }
    }
  }
}

///|
fn ChannelzService::get_channel(
  self : ChannelzService,
  ctx : @stream.RpcContext,
  req : GetChannelRequest,
) -> GetChannelResponse {
  match self.registry.channel(req.channel_id.to_int()) {
    Some(node) => { channel: self.channel_message(node), }
    None => {
      ctx.fail(
        @wire.Status::NotFound.code(),
        "channel \{req.channel_id} not found",
      )
      { channel: Channel::empty(), }
    }
  }
}

///|
fn ChannelzService::get_subchannel(
  self : ChannelzService,
  ctx : @stream.RpcContext,
  req : GetSubchannelRequest,
) -> GetSubchannelResponse {
  match self.registry.subchannel(req.subchannel_id.to_int()) {
    Some(node) =>
      {
        subchannel: {
          reference: { id: node.id.to_int64(), name: node.address, },
          state: state_code(node.state),
          target: node.address,
          calls_started: node.calls.started,
          calls_succeeded: node.calls.succeeded,
          calls_failed: node.calls.failed,
          channel_refs: [],
          subchannel_refs: [],
          socket_refs: node.sockets.map(id => self.socket_ref(id)),
        },
      }
    None => {
      ctx.fail(
        @wire.Status::NotFound.code(),
        "subchannel \{req.subchannel_id} not found",
      )
      { subchannel: Subchannel::empty(), }
    }
  }
}

///|
fn ChannelzService::get_socket(
  self : ChannelzService,
  ctx : @stream.RpcContext,
  req : GetSocketRequest,
) -> GetSocketResponse {
  match self.registry.socket(req.socket_id.to_int()) {
    Some(node) =>
      {
        socket: {
          reference: { id: node.id.to_int64(), name: node.address, },
          address: node.address,
          streams_started: node.streams.started,
          streams_succeeded: node.streams.succeeded,
          streams_failed: node.streams.failed,
          messages_sent: node.messages_sent,
          messages_received: node.messages_received,
        },
      }
    None => {
      ctx.fail(
        @wire.Status::NotFound.code(),
        "socket \{req.socket_id} not found",
      )
      { socket: Socket::empty(), }
    }
  }
}

///|
fn ChannelzService::channel_message(
  self : ChannelzService,
  node : @service.ChannelNode,
) -> Channel {
  {
    reference: { id: node.id.to_int64(), name: node.target, },
    state: state_code(node.state),
    target: node.target,
    calls_started: node.calls.started,
    calls_succeeded: node.calls.succeeded,
    calls_failed: node.calls.failed,
    last_call_ms: None,
    channel_refs: [],
    subchannel_refs: [],
    socket_refs: node.sockets.map(id => self.socket_ref(id)),
  }
}

///|
fn ChannelzService::server_message(
  self : ChannelzService,
  node : @service.ServerNode,
) -> Server {
  {
    reference: { id: node.id.to_int64(), name: "moonrpc", },
    calls_started: node.calls.started,
    calls_succeeded: node.calls.succeeded,
    calls_failed: node.calls.failed,
    last_call_ms: None,
    listen_sockets: node.listen_sockets.map(id => self.socket_ref(id)),
  }
}

///|
fn ChannelzService::socket_ref(self : ChannelzService, id : Int32) -> SocketRef {
  match self.registry.socket(id) {
    Some(node) => { id: node.id.to_int64(), name: node.address, }
    None => { id: id.to_int64(), name: "", }
  }
}

///|
fn state_code(state : String) -> Int32 {
  match state {
    "IDLE" => 1
    "CONNECTING" => 2
    "READY" => 3
    "TRANSIENT_FAILURE" => 4
    "SHUTDOWN" => 5
    _ => 0
  }
}