// A descriptor model for gRPC services and their messages, and the codec that
// turns it into the `FileDescriptorProto` / `FileDescriptorSet` wire bytes of
// `google/protobuf/descriptor.proto`. This is what Server Reflection hands a
// client so `grpcurl` can `list` and `describe` a service: reflection returns the
// serialised `FileDescriptorProto` of the file that defines a symbol. The model is
// built programmatically (a service registers its descriptor in code) and both
// encodes to and decodes from the wire. It carries the fields Server Reflection uses
// — package, message names and fields, services and methods — enough to describe a
// self-contained proto; enum, nested-type, import, and option fields are skipped on
// decode (and so not re-emitted), so a `protoc` set that uses them re-encodes to that
// modelled subset. Field numbers below are the ones fixed by descriptor.proto.

///|
/// The `FieldDescriptorProto.Type` enum (proto3's scalar and composite field
/// types). `TypeGroup` is intentionally absent — groups are removed from proto3.
pub(all) enum FieldType {
  TypeDouble
  TypeFloat
  TypeInt64
  TypeUint64
  TypeInt32
  TypeFixed64
  TypeFixed32
  TypeBool
  TypeString
  TypeMessage
  TypeBytes
  TypeUint32
  TypeEnum
  TypeSfixed32
  TypeSfixed64
  TypeSint32
  TypeSint64
} derive(Eq)

///|
/// The descriptor.proto enum number of a field type.
pub fn FieldType::code(self : FieldType) -> Int {
  match self {
    TypeDouble => 1
    TypeFloat => 2
    TypeInt64 => 3
    TypeUint64 => 4
    TypeInt32 => 5
    TypeFixed64 => 6
    TypeFixed32 => 7
    TypeBool => 8
    TypeString => 9
    TypeMessage => 11
    TypeBytes => 12
    TypeUint32 => 13
    TypeEnum => 14
    TypeSfixed32 => 15
    TypeSfixed64 => 16
    TypeSint32 => 17
    TypeSint64 => 18
  }
}

///|
/// The field type for a descriptor.proto enum number; an unrecognised number
/// (including the removed group type `10`) reads as `TypeMessage`.
pub fn FieldType::from_code(n : Int) -> FieldType {
  match n {
    1 => TypeDouble
    2 => TypeFloat
    3 => TypeInt64
    4 => TypeUint64
    5 => TypeInt32
    6 => TypeFixed64
    7 => TypeFixed32
    8 => TypeBool
    9 => TypeString
    12 => TypeBytes
    13 => TypeUint32
    14 => TypeEnum
    15 => TypeSfixed32
    16 => TypeSfixed64
    17 => TypeSint32
    18 => TypeSint64
    _ => TypeMessage
  }
}

///|
/// The `FieldDescriptorProto.Label`: proto3 fields are `LabelOptional` unless
/// `repeated`.
pub(all) enum FieldLabel {
  LabelOptional
  LabelRequired
  LabelRepeated
} derive(Eq)

///|
/// The descriptor.proto enum number of a label.
pub fn FieldLabel::code(self : FieldLabel) -> Int {
  match self {
    LabelOptional => 1
    LabelRequired => 2
    LabelRepeated => 3
  }
}

///|
/// The label for a descriptor.proto enum number; anything else reads as
/// `LabelOptional`.
pub fn FieldLabel::from_code(n : Int) -> FieldLabel {
  match n {
    2 => LabelRequired
    3 => LabelRepeated
    _ => LabelOptional
  }
}

///|
/// One field of a message (`FieldDescriptorProto`). `type_name` is the
/// fully-qualified name of the referenced message or enum for `TypeMessage` /
/// `TypeEnum`, and empty for scalars.
pub(all) struct FieldDescriptor {
  name : String
  number : Int
  label : FieldLabel
  type_ : FieldType
  type_name : String
} derive(Eq)

///|
/// A field with a scalar type and no composite `type_name`.
pub fn FieldDescriptor::scalar(
  name : String,
  number : Int,
  type_ : FieldType,
  label? : FieldLabel = LabelOptional,
) -> FieldDescriptor {
  { name, number, label, type_, type_name: "" }
}

///|
/// Encode a `FieldDescriptorProto`.
pub fn FieldDescriptor::encode(self : FieldDescriptor) -> Bytes {
  let w = PbWriter::new()
  w.string_(1, self.name)
  w.int32(3, self.number)
  w.enum_(4, self.label.code())
  w.enum_(5, self.type_.code())
  if self.type_name.length() > 0 {
    w.string_(6, self.type_name)
  }
  w.to_bytes()
}

///|
/// Decode a `FieldDescriptorProto`.
pub fn FieldDescriptor::decode(body : Bytes) -> FieldDescriptor raise PbError {
  let r = PbReader::new(body)
  let mut name = ""
  let mut number = 0
  let mut label = LabelOptional
  let mut type_ = TypeMessage
  let mut type_name = ""
  while !r.eof() {
    match r.read_tag() {
      (1, LengthDelim) => name = r.read_string()
      (3, Varint) => number = r.read_int32()
      (4, Varint) => label = FieldLabel::from_code(r.read_int32())
      (5, Varint) => type_ = FieldType::from_code(r.read_int32())
      (6, LengthDelim) => type_name = r.read_string()
      (_, w) => r.skip(w)
    }
  }
  { name, number, label, type_, type_name }
}

///|
/// A message type (`DescriptorProto`): its (simple) name and its fields.
pub(all) struct MessageDescriptor {
  name : String
  fields : Array[FieldDescriptor]
} derive(Eq)

///|
/// Encode a `DescriptorProto`.
pub fn MessageDescriptor::encode(self : MessageDescriptor) -> Bytes {
  let w = PbWriter::new()
  w.string_(1, self.name)
  for f in self.fields {
    w.message_(2, f.encode())
  }
  w.to_bytes()
}

///|
/// Decode a `DescriptorProto`.
pub fn MessageDescriptor::decode(
  body : Bytes,
) -> MessageDescriptor raise PbError {
  let r = PbReader::new(body)
  let mut name = ""
  let fields : Array[FieldDescriptor] = []
  while !r.eof() {
    match r.read_tag() {
      (1, LengthDelim) => name = r.read_string()
      (2, LengthDelim) => fields.push(FieldDescriptor::decode(r.read_bytes()))
      (_, w) => r.skip(w)
    }
  }
  { name, fields }
}

///|
/// One RPC method (`MethodDescriptorProto`): its name, the fully-qualified request
/// and response message names, and the two streaming flags that together pick the
/// call cardinality.
pub(all) struct MethodDescriptor {
  name : String
  input_type : String
  output_type : String
  client_streaming : Bool
  server_streaming : Bool
} derive(Eq)

///|
/// Encode a `MethodDescriptorProto`.
pub fn MethodDescriptor::encode(self : MethodDescriptor) -> Bytes {
  let w = PbWriter::new()
  w.string_(1, self.name)
  w.string_(2, self.input_type)
  w.string_(3, self.output_type)
  if self.client_streaming {
    w.bool_(5, true)
  }
  if self.server_streaming {
    w.bool_(6, true)
  }
  w.to_bytes()
}

///|
/// Decode a `MethodDescriptorProto`.
pub fn MethodDescriptor::decode(body : Bytes) -> MethodDescriptor raise PbError {
  let r = PbReader::new(body)
  let mut name = ""
  let mut input_type = ""
  let mut output_type = ""
  let mut client_streaming = false
  let mut server_streaming = false
  while !r.eof() {
    match r.read_tag() {
      (1, LengthDelim) => name = r.read_string()
      (2, LengthDelim) => input_type = r.read_string()
      (3, LengthDelim) => output_type = r.read_string()
      (5, Varint) => client_streaming = r.read_bool()
      (6, Varint) => server_streaming = r.read_bool()
      (_, w) => r.skip(w)
    }
  }
  { name, input_type, output_type, client_streaming, server_streaming }
}

///|
/// A service (`ServiceDescriptorProto`): its (simple) name and its methods.
pub(all) struct ServiceDescriptor {
  name : String
  methods : Array[MethodDescriptor]
} derive(Eq)

///|
/// Encode a `ServiceDescriptorProto`.
pub fn ServiceDescriptor::encode(self : ServiceDescriptor) -> Bytes {
  let w = PbWriter::new()
  w.string_(1, self.name)
  for m in self.methods {
    w.message_(2, m.encode())
  }
  w.to_bytes()
}

///|
/// Decode a `ServiceDescriptorProto`.
pub fn ServiceDescriptor::decode(
  body : Bytes,
) -> ServiceDescriptor raise PbError {
  let r = PbReader::new(body)
  let mut name = ""
  let methods : Array[MethodDescriptor] = []
  while !r.eof() {
    match r.read_tag() {
      (1, LengthDelim) => name = r.read_string()
      (2, LengthDelim) => methods.push(MethodDescriptor::decode(r.read_bytes()))
      (_, w) => r.skip(w)
    }
  }
  { name, methods }
}

///|
/// A single `.proto` file (`FileDescriptorProto`): its filename, package, the
/// message and service types it defines, and the syntax level. This is the unit
/// Server Reflection returns.
pub(all) struct FileDescriptor {
  name : String
  package_ : String
  messages : Array[MessageDescriptor]
  services : Array[ServiceDescriptor]
  syntax : String
} derive(Eq)

///|
/// A proto3 file with the given filename and package.
pub fn FileDescriptor::new(
  name : String,
  package_ : String,
  messages? : Array[MessageDescriptor] = [],
  services? : Array[ServiceDescriptor] = [],
) -> FileDescriptor {
  { name, package_, messages, services, syntax: "proto3" }
}

///|
/// Encode a `FileDescriptorProto`.
pub fn FileDescriptor::encode(self : FileDescriptor) -> Bytes {
  let w = PbWriter::new()
  w.string_(1, self.name)
  if self.package_.length() > 0 {
    w.string_(2, self.package_)
  }
  for m in self.messages {
    w.message_(4, m.encode())
  }
  for s in self.services {
    w.message_(6, s.encode())
  }
  if self.syntax.length() > 0 {
    w.string_(12, self.syntax)
  }
  w.to_bytes()
}

///|
/// Decode a `FileDescriptorProto`.
pub fn FileDescriptor::decode(body : Bytes) -> FileDescriptor raise PbError {
  let r = PbReader::new(body)
  let mut name = ""
  let mut package_ = ""
  let mut syntax = ""
  let messages : Array[MessageDescriptor] = []
  let services : Array[ServiceDescriptor] = []
  while !r.eof() {
    match r.read_tag() {
      (1, LengthDelim) => name = r.read_string()
      (2, LengthDelim) => package_ = r.read_string()
      (4, LengthDelim) =>
        messages.push(MessageDescriptor::decode(r.read_bytes()))
      (6, LengthDelim) =>
        services.push(ServiceDescriptor::decode(r.read_bytes()))
      (12, LengthDelim) => syntax = r.read_string()
      (_, w) => r.skip(w)
    }
  }
  { name, package_, messages, services, syntax }
}

///|
/// The fully-qualified names this file defines: `package.Service` for each service
/// and `package.Message` for each message. These are the symbols a
/// `FileContainingSymbol` reflection request can resolve to this file.
pub fn FileDescriptor::symbols(self : FileDescriptor) -> Array[String] {
  let out : Array[String] = []
  let prefix = if self.package_.length() > 0 { self.package_ + "." } else { "" }
  for s in self.services {
    out.push(prefix + s.name)
  }
  for m in self.messages {
    out.push(prefix + m.name)
  }
  out
}

///|
/// The fully-qualified names of the services this file defines (`package.Service`).
pub fn FileDescriptor::service_names(self : FileDescriptor) -> Array[String] {
  let out : Array[String] = []
  let prefix = if self.package_.length() > 0 { self.package_ + "." } else { "" }
  for s in self.services {
    out.push(prefix + s.name)
  }
  out
}

///|
/// Encode a `FileDescriptorSet` (`protoc --descriptor_set_out`): the concatenation
/// of `FileDescriptorProto`s under repeated field 1.
pub fn encode_file_descriptor_set(files : Array[FileDescriptor]) -> Bytes {
  let w = PbWriter::new()
  for f in files {
    w.message_(1, f.encode())
  }
  w.to_bytes()
}

///|
/// Decode a `FileDescriptorSet` into its files.
pub fn decode_file_descriptor_set(
  body : Bytes,
) -> Array[FileDescriptor] raise PbError {
  let r = PbReader::new(body)
  let files : Array[FileDescriptor] = []
  while !r.eof() {
    match r.read_tag() {
      (1, LengthDelim) => files.push(FileDescriptor::decode(r.read_bytes()))
      (_, w) => r.skip(w)
    }
  }
  files
}