// Proto3 JSON: the mapping between a message's wire bytes and the JSON form the ecosystem
// expects, driven by the descriptor model rather than by generated code. A message this
// library has a descriptor for travels as JSON without a second codec being written for it,
// which is what the `+json` codec of a JSON-speaking transport needs.
//
// The mapping is proto3's: field names in lowerCamelCase, `int64` and its unsigned and fixed
// relatives as strings, `bytes` as base64, enums by name, a map as a JSON object, and a
// message's zero values left out. The well-known types keep the shapes the specification
// gives them — a `Timestamp` is an RFC 3339 string, a `Duration` ends in `s` — because those
// are what every other implementation writes and reads.

///|
/// The types one JSON mapping resolves a field's `type_name` against: every message and enum
/// of every file that can appear in the value, keyed by its fully-qualified name with the
/// leading dot.
pub(all) struct Schema {
  messages : Map[String, MessageDescriptor]
  enums : Map[String, EnumDescriptor]
}

///|
pub fn Schema::empty() -> Schema {
  { messages: Map([]), enums: Map([]), }
}

///|
/// A schema over `files`, nested types included.
pub fn Schema::of(files : Array[FileDescriptor]) -> Schema {
  let schema = Schema::empty()
  for file in files {
    schema.add_file(file)
  }
  schema
}

///|
fn Schema::add_file(self : Schema, file : FileDescriptor) -> Unit {
  let prefix = if file.package_.length() > 0 { "." + file.package_ } else { "" }
  for message in file.messages {
    self.add_message(prefix, message)
  }
  for e in file.enums {
    self.enums[prefix + "." + e.name] = e
  }
}

///|
fn Schema::add_message(
  self : Schema,
  prefix : String,
  message : MessageDescriptor,
) -> Unit {
  let full = prefix + "." + message.name
  self.messages[full] = message
  for nested in message.nested {
    self.add_message(full, nested)
  }
  for e in message.enums {
    self.enums[full + "." + e.name] = e
  }
}

///|
/// The field numbered `number`, or `None` for one this descriptor does not declare.
fn field_of(desc : MessageDescriptor, number : Int32) -> FieldDescriptor? {
  for f in desc.fields {
    if f.number == number {
      return Some(f)
    }
  }
  None
}

///|
/// The ASCII uppercase of a letter, which is all the camelCase conversion needs: a field name
/// is an identifier, so the only characters in it are letters, digits and `_`.
fn ascii_upper(ch : Char) -> Char {
  let code = ch.to_int()
  if code >= 0x61 && code <= 0x7A {
    Int32::unsafe_to_char(code - 32)
  } else {
    ch
  }
}

///|
/// The JSON name of a field: the lowerCamelCase of its wire name, which is what protoc
/// generates into `json_name` when nothing overrides it.
pub fn json_name(field : FieldDescriptor) -> String {
  let mut out = ""
  let mut upcase = false
  for ch in field.name {
    if ch == '_' {
      upcase = true
    } else if upcase {
      out = out + ascii_upper(ch).to_string()
      upcase = false
    } else {
      out = out + ch.to_string()
    }
  }
  out
}

///|
/// A value read off the wire, before it becomes JSON: enough of the wire form that the JSON
/// writer can shape it, and no more.
priv enum Raw {
  RBool(Bool)
  RInt(Int64)
  RUInt(UInt64)
  RDouble(Double)
  RBits(UInt64)
  RBlob(Bytes)
  RRun(Array[Raw])
}

///|
/// Whether a repeated field of this type travels packed when the length-delimited wire type is
/// what arrived. Strings, bytes and messages are length-delimited by nature and never packed.
fn is_packable(type_ : FieldType) -> Bool {
  match type_ {
    TypeString | TypeBytes | TypeMessage => false
    _ => true
  }
}

///|
/// The wire type a field of this type is written with.
fn wire_of(type_ : FieldType) -> WireType {
  match type_ {
    TypeDouble | TypeFixed64 | TypeSfixed64 => Fixed64
    TypeFloat | TypeFixed32 | TypeSfixed32 => Fixed32
    TypeString | TypeBytes | TypeMessage => LengthDelim
    _ => Varint
  }
}

///|
/// Read one value of `type_` at the reader's cursor.
fn read_raw(reader : PbReader, type_ : FieldType) -> Raw raise PbError {
  match type_ {
    TypeBool => RBool(reader.read_bool())
    TypeInt32 => RInt(reader.read_int32().to_int64())
    TypeInt64 => RInt(reader.read_int64())
    TypeSint32 => RInt(reader.read_sint32().to_int64())
    TypeSint64 => RInt(reader.read_sint64())
    TypeSfixed32 => RInt(reader.read_fixed32().reinterpret_as_int().to_int64())
    TypeSfixed64 => RInt(reader.read_fixed64().reinterpret_as_int64())
    TypeUint32 => RUInt(reader.read_uint32().to_uint64())
    TypeUint64 => RUInt(reader.read_uint64())
    TypeFixed32 => RBits(reader.read_fixed32().to_uint64())
    TypeFixed64 => RBits(reader.read_fixed64())
    TypeEnum => RInt(reader.read_varint().reinterpret_as_int64())
    TypeDouble => RDouble(reader.read_double())
    TypeFloat => RDouble(reader.read_float().to_double())
    TypeString | TypeBytes | TypeMessage => RBlob(reader.read_len_delim())
  }
}

///|
/// Every field of `desc` that the wire bytes carry, keyed by field number. A field the number
/// belongs to is typed by its descriptor; a number with no field is skipped, which is what
/// proto3 asks a parser to do with an unknown field.
fn read_fields(
  desc : MessageDescriptor,
  bytes : Bytes,
) -> Map[Int32, Array[Raw]] raise PbError {
  let out : Map[Int32, Array[Raw]] = Map([])
  let reader = PbReader::new(bytes)
  while !reader.eof() {
    let (number, wire) = reader.read_tag()
    match field_of(desc, number) {
      None => reader.skip(wire)
      Some(f) => {
        let values = match out.get(number) {
          Some(v) => v
          None => []
        }
        if f.type_ != TypeMessage &&
          f.label == LabelRepeated &&
          wire == LengthDelim &&
          is_packable(f.type_) {
          // A packed run: every element of one numeric type inside a single length-delimited
          // field. protoc writes these by default for proto3, and a reader has to take both.
          let run = PbReader::new(reader.read_len_delim())
          let items = []
          while !run.eof() {
            items.push(read_raw(run, f.type_))
          }
          values.push(RRun(items))
        } else if wire == wire_of(f.type_) {
          values.push(read_raw(reader, f.type_))
        } else {
          reader.skip(wire)
        }
        out[number] = values
      }
    }
  }
  out
}

///|
/// Whether this field is a `map`: a repeated field whose entry message protoc marked
/// `map_entry`.
fn is_map(schema : Schema, field : FieldDescriptor) -> Bool {
  if field.label != LabelRepeated || field.type_ != TypeMessage {
    return false
  }
  match schema.messages.get(field.type_name) {
    Some(entry) => entry.map_entry
    None => false
  }
}

///|
/// The JSON a message's wire bytes stand for. `name` is its fully-qualified descriptor name,
/// which is how a well-known type is recognised.
pub fn Schema::to_json(
  self : Schema,
  name : String,
  bytes : Bytes,
) -> Json raise PbError {
  match self.messages.get(name) {
    None => raise BadWireType(0)
    Some(desc) => self.message_json(name, desc, bytes)
  }
}

///|
fn Schema::message_json(
  self : Schema,
  name : String,
  desc : MessageDescriptor,
  bytes : Bytes,
) -> Json raise PbError {
  match self.well_known_json(name, desc, bytes) {
    Some(value) => value
    None => {
      let fields = read_fields(desc, bytes)
      let members : Map[String, Json] = Map([])
      for f in desc.fields {
        match fields.get(f.number) {
          None => ()
          Some(raws) =>
            if is_map(self, f) {
              members[json_name(f)] = self.map_json(f, raws)
            } else if f.label == LabelRepeated {
              let items = []
              for raw in raws {
                for one in flatten(raw) {
                  items.push(self.value_json(f, one))
                }
              }
              members[json_name(f)] = Json::array(items)
            } else if raws.length() > 0 {
              // A singular field repeated on the wire keeps its last value, as proto3 says.
              for one in flatten(raws[raws.length() - 1]) {
                members[json_name(f)] = self.value_json(f, one)
              }
            }
        }
      }
      Json::object(members)
    }
  }
}

///|
/// A packed run stands for its elements; anything else stands for itself.
fn flatten(raw : Raw) -> Array[Raw] {
  match raw {
    RRun(items) => items
    other => [other]
  }
}

///|
fn Schema::map_json(
  self : Schema,
  field : FieldDescriptor,
  raws : Array[Raw],
) -> Json raise PbError {
  let members : Map[String, Json] = Map([])
  let entry = match self.messages.get(field.type_name) {
    Some(e) => e
    None => return Json::object(members)
  }
  let key_field = match field_of(entry, 1) {
    Some(f) => f
    None => return Json::object(members)
  }
  let value_field = match field_of(entry, 2) {
    Some(f) => f
    None => return Json::object(members)
  }
  for raw in raws {
    for one in flatten(raw) {
      match one {
        RBlob(bytes) => {
          let parts = read_fields(entry, bytes)
          let key = match parts.get(1) {
            Some(v) if v.length() > 0 => key_text(key_field, v[0])
            _ => default_key(key_field)
          }
          let value = match parts.get(2) {
            Some(v) if v.length() > 0 => self.value_json(value_field, v[0])
            _ => self.value_json(value_field, zero_raw(value_field.type_))
          }
          members[key] = value
        }
        _ => ()
      }
    }
  }
  Json::object(members)
}

///|
/// A map key as JSON spells it: the number as text, the boolean as `true` or `false`.
fn key_text(field : FieldDescriptor, raw : Raw) -> String {
  match field.type_ {
    TypeBool =>
      match raw {
        RBool(b) => if b { "true" } else { "false" }
        _ => "false"
      }
    TypeUint32 | TypeUint64 | TypeFixed32 | TypeFixed64 =>
      match raw {
        RUInt(v) => v.to_string()
        RBits(v) => v.to_string()
        RInt(v) => v.to_string()
        _ => "0"
      }
    TypeString =>
      match raw {
        RBlob(b) => @utf8.decode_lossy(b)
        _ => ""
      }
    _ =>
      match raw {
        RInt(v) => v.to_string()
        _ => "0"
      }
  }
}

///|
fn default_key(field : FieldDescriptor) -> String {
  match field.type_ {
    TypeBool => "false"
    _ => "0"
  }
}

///|
/// The raw value a field's zero stands for, which is what a map's value falls back to.
fn zero_raw(type_ : FieldType) -> Raw {
  match type_ {
    TypeBool => RBool(false)
    TypeString | TypeBytes | TypeMessage => RBlob(b"")
    TypeDouble | TypeFloat => RDouble(0.0)
    TypeUint32 | TypeUint64 => RUInt(0UL)
    TypeFixed32 | TypeFixed64 => RBits(0UL)
    _ => RInt(0L)
  }
}

///|
/// One value as JSON, in the shape its field's type asks for.
fn Schema::value_json(
  self : Schema,
  field : FieldDescriptor,
  raw : Raw,
) -> Json raise PbError {
  match field.type_ {
    TypeBool =>
      match raw {
        RBool(b) => Json::boolean(b)
        _ => Json::null()
      }
    TypeInt32 | TypeSint32 | TypeSfixed32 =>
      match raw {
        RInt(v) => Json::number(v.to_double(), repr=v.to_string())
        _ => Json::null()
      }
    // 64-bit integers travel as strings: JSON numbers are doubles, and a double cannot hold
    // every value these types do.
    TypeInt64 | TypeSint64 | TypeSfixed64 =>
      match raw {
        RInt(v) => Json::string(v.to_string())
        _ => Json::null()
      }
    TypeUint32 =>
      match raw {
        RUInt(v) => Json::number(v.to_double(), repr=v.to_string())
        _ => Json::null()
      }
    TypeUint64 | TypeFixed64 =>
      match raw {
        RUInt(v) => Json::string(v.to_string())
        RBits(v) => Json::string(v.to_string())
        _ => Json::null()
      }
    TypeFixed32 =>
      match raw {
        RBits(v) => Json::number(v.to_double(), repr=v.to_string())
        _ => Json::null()
      }
    TypeDouble | TypeFloat =>
      match raw {
        RDouble(v) => double_json(v)
        _ => Json::null()
      }
    TypeString =>
      match raw {
        RBlob(bytes) => Json::string(@utf8.decode_lossy(bytes))
        _ => Json::null()
      }
    TypeBytes =>
      match raw {
        RBlob(bytes) => Json::string(@base64.encode(bytes[:]))
        _ => Json::null()
      }
    TypeEnum =>
      match raw {
        RInt(v) =>
          match self.enum_name(field.type_name, v.to_int()) {
            Some(name) => Json::string(name)
            None => Json::number(v.to_double(), repr=v.to_string())
          }
        _ => Json::null()
      }
    TypeMessage =>
      match raw {
        RBlob(bytes) =>
          match self.messages.get(field.type_name) {
            Some(desc) => self.message_json(field.type_name, desc, bytes)
            None => Json::object(Map([]))
          }
        _ => Json::null()
      }
  }
}

///|
/// A double as JSON: the three values JSON has no number for become strings, and anything
/// else is a number written so it reads back as the same double.
fn double_json(value : Double) -> Json {
  if value.is_nan() {
    Json::string("NaN")
  } else if value == @double.infinity {
    Json::string("Infinity")
  } else if value == -@double.infinity {
    Json::string("-Infinity")
  } else if value == value.to_int().to_double() && value.abs() < 1.0e16 {
    // An integral double is written without a fractional part, as every implementation does.
    Json::number(value, repr=value.to_int().to_string())
  } else {
    Json::number(value)
  }
}

///|
/// The name of `number` in the enum `name`, or `None` for a value the enum does not declare.
fn Schema::enum_name(self : Schema, name : String, number : Int32) -> String? {
  match self.enums.get(name) {
    None => None
    Some(e) => {
      for v in e.values {
        if v.number == number {
          return Some(v.name)
        }
      }
      None
    }
  }
}

// -- the well-known types ------------------------------------------------

///|
/// The JSON a well-known type stands for, or `None` when `name` is not one this mapping
/// gives a shape of its own.
fn Schema::well_known_json(
  self : Schema,
  name : String,
  desc : MessageDescriptor,
  bytes : Bytes,
) -> Json? raise PbError {
  if name == ".google.protobuf.Timestamp" {
    return Some(timestamp_json(read_fields(desc, bytes)))
  }
  if name == ".google.protobuf.Duration" {
    return Some(duration_json(read_fields(desc, bytes)))
  }
  if name == ".google.protobuf.Empty" {
    return Some(Json::object(Map([])))
  }
  if name == ".google.protobuf.FieldMask" {
    return Some(field_mask_json(read_fields(desc, bytes)))
  }
  if name == ".google.protobuf.Struct" {
    let fields = read_fields(desc, bytes)
    return Some(
      match fields.get(1) {
        Some(v) if v.length() > 0 => self.struct_json(v[0])
        _ => Json::object(Map([]))
      },
    )
  }
  if name == ".google.protobuf.Value" {
    return Some(self.wrapped_value_json(desc, bytes))
  }
  if name == ".google.protobuf.ListValue" {
    let fields = read_fields(desc, bytes)
    return Some(
      match fields.get(1) {
        Some(v) => self.list_json(v)
        _ => Json::array([])
      },
    )
  }
  if name == ".google.protobuf.Any" {
    return Some(self.any_json(desc, bytes))
  }
  match wrapper_type(name) {
    Some(type_) => Some(wrapper_json(type_, read_fields(desc, bytes)))
    None => None
  }
}

///|
/// The scalar a wrapper message wraps, or `None` for a message that is not a wrapper.
fn wrapper_type(name : String) -> FieldType? {
  if name == ".google.protobuf.DoubleValue" {
    Some(TypeDouble)
  } else if name == ".google.protobuf.FloatValue" {
    Some(TypeFloat)
  } else if name == ".google.protobuf.Int64Value" {
    Some(TypeInt64)
  } else if name == ".google.protobuf.UInt64Value" {
    Some(TypeUint64)
  } else if name == ".google.protobuf.Int32Value" {
    Some(TypeInt32)
  } else if name == ".google.protobuf.UInt32Value" {
    Some(TypeUint32)
  } else if name == ".google.protobuf.BoolValue" {
    Some(TypeBool)
  } else if name == ".google.protobuf.StringValue" {
    Some(TypeString)
  } else if name == ".google.protobuf.BytesValue" {
    Some(TypeBytes)
  } else {
    None
  }
}

///|
/// A wrapper's JSON is the bare value it wraps, and `null` when it wraps nothing.
fn wrapper_json(type_ : FieldType, fields : Map[Int32, Array[Raw]]) -> Json {
  match fields.get(1) {
    Some(v) if v.length() > 0 =>
      match (type_, v[0]) {
        (_, RBool(b)) => Json::boolean(b)
        (_, RInt(n)) =>
          match type_ {
            TypeInt64 => Json::string(n.to_string())
            _ => Json::number(n.to_double(), repr=n.to_string())
          }
        (_, RUInt(n)) =>
          match type_ {
            TypeUint64 => Json::string(n.to_string())
            _ => Json::number(n.to_double(), repr=n.to_string())
          }
        (_, RDouble(d)) => double_json(d)
        (_, RBlob(bytes)) =>
          match type_ {
            TypeBytes => Json::string(@base64.encode(bytes[:]))
            _ => Json::string(@utf8.decode_lossy(bytes))
          }
        _ => Json::null()
      }
    _ => Json::null()
  }
}

///|
fn Schema::struct_json(self : Schema, raw : Raw) -> Json raise PbError {
  match raw {
    RBlob(bytes) => {
      // `Struct` is a message whose field 1 is a `map`; the map's entry
      // message is what carries each member.
      let members : Map[String, Json] = Map([])
      let reader = PbReader::new(bytes)
      while !reader.eof() {
        let (number, wire) = reader.read_tag()
        if number == 1 && wire == LengthDelim {
          let entry = reader.read_len_delim()
          let er = PbReader::new(entry)
          let mut key = ""
          let mut value = Json::null()
          while !er.eof() {
            let (n, w) = er.read_tag()
            if n == 1 && w == LengthDelim {
              key = @utf8.decode_lossy(er.read_len_delim())
            } else if n == 2 && w == LengthDelim {
              let inner = er.read_len_delim()
              match self.messages.get(".google.protobuf.Value") {
                Some(desc) => value = self.wrapped_value_json(desc, inner)
                None => ()
              }
            } else {
              er.skip(w)
            }
          }
          members[key] = value
        } else {
          reader.skip(wire)
        }
      }
      Json::object(members)
    }
    _ => Json::object(Map([]))
  }
}

///|
/// A `Value`: exactly one of its six kinds is set.
fn Schema::wrapped_value_json(
  self : Schema,
  desc : MessageDescriptor,
  bytes : Bytes,
) -> Json raise PbError {
  let fields = read_fields(desc, bytes)
  for one in [6, 5, 4, 3, 2, 1] {
    match fields.get(one) {
      Some(v) if v.length() > 0 =>
        match one {
          1 => return Json::null()
          2 =>
            for raw in flatten(v[0]) {
              match raw {
                RDouble(d) => return double_json(d)
                _ => ()
              }
            }
          3 =>
            for raw in flatten(v[0]) {
              match raw {
                RBlob(b) => return Json::string(@utf8.decode_lossy(b))
                _ => ()
              }
            }
          4 =>
            for raw in flatten(v[0]) {
              match raw {
                RBool(b) => return Json::boolean(b)
                _ => ()
              }
            }
          5 =>
            for raw in flatten(v[0]) {
              match raw {
                RBlob(b) =>
                  match self.messages.get(".google.protobuf.Struct") {
                    Some(struct_desc) => {
                      let inner = read_fields(struct_desc, b)
                      match inner.get(1) {
                        Some(s) if s.length() > 0 =>
                          return self.struct_json(s[0])
                        _ => return Json::object(Map([]))
                      }
                    }
                    None => return Json::object(Map([]))
                  }
                _ => ()
              }
            }
          _ =>
            for raw in flatten(v[0]) {
              match raw {
                RBlob(b) =>
                  match self.messages.get(".google.protobuf.ListValue") {
                    Some(list_desc) => {
                      let inner = read_fields(list_desc, b)
                      match inner.get(1) {
                        Some(s) => return self.list_json(s)
                        _ => return Json::array([])
                      }
                    }
                    None => return Json::array([])
                  }
                _ => ()
              }
            }
        }
      _ => ()
    }
  }
  Json::null()
}

///|
/// A `ListValue`'s members, each a `Value`.
fn Schema::list_json(self : Schema, raws : Array[Raw]) -> Json raise PbError {
  let items = []
  for raw in raws {
    for one in flatten(raw) {
      match one {
        RBlob(b) =>
          match self.messages.get(".google.protobuf.Value") {
            Some(desc) => items.push(self.wrapped_value_json(desc, b))
            None => ()
          }
        _ => ()
      }
    }
  }
  Json::array(items)
}

///|
fn Schema::any_json(
  self : Schema,
  desc : MessageDescriptor,
  bytes : Bytes,
) -> Json raise PbError {
  let fields = read_fields(desc, bytes)
  let mut url = ""
  let mut value = b""
  match fields.get(1) {
    Some(v) if v.length() > 0 =>
      match v[0] {
        RBlob(b) => url = @utf8.decode_lossy(b)
        _ => ()
      }
    _ => ()
  }
  match fields.get(2) {
    Some(v) if v.length() > 0 =>
      match v[0] {
        RBlob(b) => value = b
        _ => ()
      }
    _ => ()
  }
  let members : Map[String, Json] = Map([])
  members["@type"] = Json::string(url)
  // The type is named by the last segment of the URL: `type.googleapis.com/pkg.Msg`.
  let name = "." + last_segment_after_slash(url)
  match self.messages.get(name) {
    Some(inner) =>
      match self.message_json(name, inner, value) {
        Json::Object(fields_json) =>
          for k, v in fields_json {
            members[k] = v
          }
        _ => ()
      }
    None => ()
  }
  Json::object(members)
}

///|
fn last_segment_after_slash(text : String) -> String {
  let mut cut = 0
  let mut i = 0
  for ch in text {
    i = i + 1
    if ch == '/' {
      cut = i
    }
  }
  text.exact_view(start=cut, end=text.length()).to_owned()
}

///|
fn timestamp_json(fields : Map[Int32, Array[Raw]]) -> Json {
  let mut seconds : Int64 = 0L
  let mut nanos : Int64 = 0L
  match fields.get(1) {
    Some(v) if v.length() > 0 =>
      match v[0] {
        RInt(s) => seconds = s
        _ => ()
      }
    _ => ()
  }
  match fields.get(2) {
    Some(v) if v.length() > 0 =>
      match v[0] {
        RInt(n) => nanos = n
        _ => ()
      }
    _ => ()
  }
  Json::string(rfc3339(seconds, nanos))
}

///|
fn duration_json(fields : Map[Int32, Array[Raw]]) -> Json {
  let mut seconds : Int64 = 0L
  let mut nanos : Int64 = 0L
  match fields.get(1) {
    Some(v) if v.length() > 0 =>
      match v[0] {
        RInt(s) => seconds = s
        _ => ()
      }
    _ => ()
  }
  match fields.get(2) {
    Some(v) if v.length() > 0 =>
      match v[0] {
        RInt(n) => nanos = n
        _ => ()
      }
    _ => ()
  }
  let negative = seconds < 0L || nanos < 0L
  let s = if seconds < 0L { -seconds } else { seconds }
  let n = if nanos < 0L { -nanos } else { nanos }
  let mut text = if negative { "-" } else { "" }
  text = text + s.to_string()
  if n > 0L {
    text = text + "." + fraction(n, 9).trim_end(chars="0").to_owned()
  }
  Json::string(text + "s")
}

///|
fn field_mask_json(fields : Map[Int32, Array[Raw]]) -> Json {
  let paths = []
  match fields.get(1) {
    Some(v) =>
      for raw in v {
        for one in flatten(raw) {
          match one {
            RBlob(b) => paths.push(camel_path(@utf8.decode_lossy(b)))
            _ => ()
          }
        }
      }
    None => ()
  }
  Json::string(paths.join(","))
}

///|
/// A field mask path in the JSON spelling: each snake_case segment becomes lowerCamelCase.
fn camel_path(path : String) -> String {
  let out = []
  for segment in path.split(".") {
    out.push(json_name(FieldDescriptor::new(segment.to_owned(), 0, TypeString)))
  }
  out.join(".")
}

///|
/// `seconds` and `nanos` as RFC 3339 in UTC: the fraction is written with three, six or nine
/// digits, whichever holds the nanoseconds exactly, and left out when there are none.
fn rfc3339(seconds : Int64, nanos : Int64) -> String {
  let days = floor_div(seconds, 86400L)
  let of_day = seconds - days * 86400L
  let (year, month, day) = civil_from_days(days)
  let hour = of_day / 3600L
  let minute = (of_day - hour * 3600L) / 60L
  let second = of_day - hour * 3600L - minute * 60L
  let mut text = pad(year, 4) +
    "-" +
    pad(month, 2) +
    "-" +
    pad(day, 2) +
    "T" +
    pad(hour, 2) +
    ":" +
    pad(minute, 2) +
    ":" +
    pad(second, 2)
  if nanos > 0L {
    let n = nanos
    let digits = if n % 1000000L == 0L {
      3
    } else if n % 1000L == 0L {
      6
    } else {
      9
    }
    text = text + "." + fraction(n, digits)
  }
  text + "Z"
}

///|
fn fraction(nanos : Int64, digits : Int32) -> String {
  let text = pad(nanos, 9)
  text.exact_view(start=0, end=digits).to_owned()
}

///|
fn pad(value : Int64, width : Int32) -> String {
  let text = value.to_string()
  let mut out = ""
  for _ in 0..<(width - text.length()) {
    out = out + "0"
  }
  out + text
}

///|
fn floor_div(a : Int64, b : Int64) -> Int64 {
  let q = a / b
  if a % b != 0L && (a < 0L) != (b < 0L) {
    q - 1L
  } else {
    q
  }
}

///|
/// Days since 1970-01-01 for a civil date, by the algorithm the standard library of every
/// language uses: it is exact for the whole range a `Timestamp` covers.
fn days_from_civil(year_in : Int64, month : Int64, day : Int64) -> Int64 {
  let y = if month <= 2L { year_in - 1L } else { year_in }
  let era = floor_div(y, 400L)
  let yoe = y - era * 400L
  let mp = if month > 2L { month - 3L } else { month + 9L }
  let doy = (153L * mp + 2L) / 5L + day - 1L
  let doe = yoe * 365L + yoe / 4L - yoe / 100L + doy
  era * 146097L + doe - 719468L
}

///|
fn civil_from_days(days : Int64) -> (Int64, Int64, Int64) {
  let z = days + 719468L
  let era = floor_div(z, 146097L)
  let doe = z - era * 146097L
  let yoe = (doe - doe / 1460L + doe / 36524L - doe / 146096L) / 365L
  let y = yoe + era * 400L
  let doy = doe - (365L * yoe + yoe / 4L - yoe / 100L)
  let mp = (5L * doy + 2L) / 153L
  let d = doy - (153L * mp + 2L) / 5L + 1L
  let m = if mp < 10L { mp + 3L } else { mp - 9L }
  (if m <= 2L { y + 1L } else { y }, m, d)
}

// -- JSON to the wire ----------------------------------------------------

///|
/// The wire bytes a JSON value stands for, under the message `name`.
pub fn Schema::to_bytes(
  self : Schema,
  name : String,
  value : Json,
) -> Bytes raise PbError {
  match self.messages.get(name) {
    None => raise BadWireType(0)
    Some(desc) => self.message_bytes(name, desc, value)
  }
}

///|
fn Schema::message_bytes(
  self : Schema,
  name : String,
  desc : MessageDescriptor,
  value : Json,
) -> Bytes raise PbError {
  match self.well_known_bytes(name, desc, value) {
    Some(bytes) => bytes
    None => {
      let writer = PbWriter::new()
      match value {
        Null => ()
        Object(members) =>
          for f in desc.fields {
            let held = member_of(members, f)
            match held {
              None => ()
              Some(v) =>
                match v {
                  Null => ()
                  _ => self.write_field(writer, f, v)
                }
            }
          }
        _ => raise BadWireType(0)
      }
      writer.to_bytes()
    }
  }
}

///|
/// The member a field is written from: its JSON name, or the proto spelling for a writer that
/// used that instead.
fn member_of(members : Map[String, Json], field : FieldDescriptor) -> Json? {
  match members.get(json_name(field)) {
    Some(v) => Some(v)
    None => members.get(field.name)
  }
}

///|
fn Schema::write_field(
  self : Schema,
  writer : PbWriter,
  field : FieldDescriptor,
  value : Json,
) -> Unit raise PbError {
  if is_map(self, field) {
    match value {
      Object(members) =>
        for key, item in members {
          let entry = PbWriter::new()
          let entry_desc = match self.messages.get(field.type_name) {
            Some(e) => e
            None => return ()
          }
          let key_field = match field_of(entry_desc, 1) {
            Some(f) => f
            None => return ()
          }
          let value_field = match field_of(entry_desc, 2) {
            Some(f) => f
            None => return ()
          }
          self.write_singular(entry, key_field, Json::string(key))
          match item {
            Null => ()
            _ => self.write_one(entry, value_field, item)
          }
          writer.message_(field.number, entry.to_bytes())
        }
      _ => ()
    }
    return
  }
  if field.label == LabelRepeated {
    match value {
      Array(items) =>
        for item in items {
          match item {
            Null => ()
            _ => self.write_one(writer, field, item)
          }
        }
      _ => ()
    }
    return
  }
  self.write_one(writer, field, value)
}

///|
/// One element of a repeated field, or the whole of a singular one.
fn Schema::write_one(
  self : Schema,
  writer : PbWriter,
  field : FieldDescriptor,
  value : Json,
) -> Unit raise PbError {
  self.write_singular(writer, field, value)
}

///|
fn Schema::write_singular(
  self : Schema,
  writer : PbWriter,
  field : FieldDescriptor,
  value : Json,
) -> Unit raise PbError {
  let number = field.number
  match field.type_ {
    TypeBool =>
      match value {
        True => writer.bool_(number, true)
        False => writer.bool_(number, false)
        _ => ()
      }
    TypeInt32 | TypeSint32 | TypeSfixed32 =>
      match number_text(value) {
        Some(text) =>
          match parse_i32(text) {
            Some(n) =>
              match field.type_ {
                TypeSint32 => writer.sint32(number, n)
                _ => writer.int32(number, n)
              }
            None => ()
          }
        None => ()
      }
    TypeInt64 | TypeSint64 | TypeSfixed64 =>
      match number_text(value) {
        Some(text) =>
          match parse_i64(text) {
            Some(n) =>
              match field.type_ {
                TypeSint64 => writer.sint64(number, n)
                _ => writer.int64(number, n)
              }
            None => ()
          }
        None => ()
      }
    TypeUint32 =>
      match number_text(value) {
        Some(text) =>
          match parse_u32(text) {
            Some(n) => writer.uint32(number, n)
            None => ()
          }
        None => ()
      }
    TypeUint64 | TypeFixed64 =>
      match number_text(value) {
        Some(text) =>
          match parse_u64(text) {
            Some(n) =>
              match field.type_ {
                TypeFixed64 => writer.fixed64(number, n)
                _ => writer.uint64(number, n)
              }
            None => ()
          }
        None => ()
      }
    TypeFixed32 =>
      match number_text(value) {
        Some(text) =>
          match parse_u32(text) {
            Some(n) => writer.fixed32(number, n)
            None => ()
          }
        None => ()
      }
    TypeDouble | TypeFloat =>
      match value {
        Number(d, ..) =>
          match field.type_ {
            TypeFloat => writer.float_(number, Float::from_double(d))
            _ => writer.double_(number, d)
          }
        String(s) =>
          match special_double(s) {
            Some(d) =>
              match field.type_ {
                TypeFloat => writer.float_(number, Float::from_double(d))
                _ => writer.double_(number, d)
              }
            None => ()
          }
        _ => ()
      }
    TypeString =>
      match value {
        String(s) => writer.string_(number, s)
        _ => ()
      }
    TypeBytes =>
      match value {
        String(s) => {
          let bytes = @base64.decode_any(s) catch { _ => raise BadWireType(0) }
          writer.bytes_(number, bytes)
        }
        _ => ()
      }
    TypeEnum =>
      match value {
        String(name) =>
          match self.enum_number(field.type_name, name) {
            Some(n) => writer.enum_(number, n)
            None => raise BadWireType(0)
          }
        Number(d, ..) => writer.enum_(number, d.to_int())
        _ => ()
      }
    TypeMessage =>
      match self.messages.get(field.type_name) {
        Some(desc) => {
          let body = self.message_bytes(field.type_name, desc, value)
          writer.message_(number, body)
        }
        None => ()
      }
  }
}

///|
/// The number a JSON number, or a number written as a string, stands for.
fn number_text(value : Json) -> String? {
  match value {
    Number(d, repr~) =>
      match repr {
        Some(text) => Some(text)
        None =>
          if d == d.to_int64().to_double() {
            Some(d.to_int64().to_string())
          } else {
            None
          }
      }
    String(s) => Some(s)
    _ => None
  }
}

///|
/// The integers a JSON number may spell, as options: a value out of range is not a number
/// this field can hold, and the field is left unset rather than written wrong.
fn parse_i64(text : String) -> Int64? {
  let result : Result[Int64, Error] = Ok(@string.parse_int64(text[:])) catch {
    e => Err(e)
  }
  match result {
    Ok(v) => Some(v)
    Err(_) => None
  }
}

///|
fn parse_u64(text : String) -> UInt64? {
  let result : Result[UInt64, Error] = Ok(@string.parse_uint64(text[:])) catch {
    e => Err(e)
  }
  match result {
    Ok(v) => Some(v)
    Err(_) => None
  }
}

///|
fn parse_i32(text : String) -> Int32? {
  let result : Result[Int32, Error] = Ok(@string.parse_int(text[:])) catch {
    e => Err(e)
  }
  match result {
    Ok(v) => Some(v)
    Err(_) => None
  }
}

///|
fn parse_u32(text : String) -> UInt32? {
  let result : Result[UInt32, Error] = Ok(@string.parse_uint(text[:])) catch {
    e => Err(e)
  }
  match result {
    Ok(v) => Some(v)
    Err(_) => None
  }
}

///|
/// The three doubles JSON writes as strings.
fn special_double(text : String) -> Double? {
  if text == "NaN" {
    Some(0.0 / 0.0)
  } else if text == "Infinity" {
    Some(@double.infinity)
  } else if text == "-Infinity" {
    Some(-@double.infinity)
  } else {
    None
  }
}

///|
/// The number an enum value name stands for.
fn Schema::enum_number(self : Schema, name : String, value : String) -> Int32? {
  match self.enums.get(name) {
    None => None
    Some(e) => {
      for v in e.values {
        if v.name == value {
          return Some(v.number)
        }
      }
      None
    }
  }
}

// -- the well-known types, read back ------------------------------------

///|
/// The wire bytes a well-known type's JSON stands for, or `None` when the name is not one.
fn Schema::well_known_bytes(
  self : Schema,
  name : String,
  desc : MessageDescriptor,
  value : Json,
) -> Bytes? raise PbError {
  if name == ".google.protobuf.Timestamp" {
    return Some(timestamp_bytes(desc, value))
  }
  if name == ".google.protobuf.Duration" {
    return Some(duration_bytes(desc, value))
  }
  if name == ".google.protobuf.Empty" {
    return Some(b"")
  }
  if name == ".google.protobuf.FieldMask" {
    return Some(field_mask_bytes(desc, value))
  }
  if name == ".google.protobuf.Struct" {
    return Some(self.struct_bytes(desc, value))
  }
  if name == ".google.protobuf.Value" {
    return Some(self.value_bytes(desc, value))
  }
  if name == ".google.protobuf.ListValue" {
    return Some(self.list_bytes(desc, value))
  }
  if name == ".google.protobuf.Any" {
    return Some(self.any_bytes(desc, value))
  }
  match wrapper_type(name) {
    Some(type_) => Some(wrapper_bytes(desc, type_, value))
    None => None
  }
}

///|
fn wrapper_bytes(
  desc : MessageDescriptor,
  type_ : FieldType,
  value : Json,
) -> Bytes raise PbError {
  let writer = PbWriter::new()
  match field_of(desc, 1) {
    Some(f) =>
      // The wrapped value is the field's own type; the schema is not consulted for a scalar.
      match (type_, value) {
        (TypeBool, True) => writer.bool_(1, true)
        (TypeBool, False) => writer.bool_(1, false)
        (TypeString, String(s)) => writer.string_(1, s)
        (TypeBytes, String(s)) => {
          let bytes = @base64.decode_any(s) catch { _ => raise BadWireType(0) }
          writer.bytes_(1, bytes)
        }
        (TypeDouble, Number(d, ..)) => writer.double_(1, d)
        (TypeFloat, Number(d, ..)) => writer.float_(1, Float::from_double(d))
        (_, Number(d, ..)) =>
          match f.type_ {
            TypeInt64 => writer.int64(1, d.to_int64())
            TypeInt32 => writer.int32(1, d.to_int())
            TypeUint64 => writer.uint64(1, d.to_int64().reinterpret_as_uint64())
            TypeUint32 => writer.uint32(1, d.to_int().reinterpret_as_uint())
            _ => ()
          }
        (_, String(s)) =>
          match f.type_ {
            TypeInt64 =>
              match parse_i64(s) {
                Some(n) => writer.int64(1, n)
                None => ()
              }
            TypeInt32 =>
              match parse_i32(s) {
                Some(n) => writer.int32(1, n)
                None => ()
              }
            TypeUint64 =>
              match parse_u64(s) {
                Some(n) => writer.uint64(1, n)
                None => ()
              }
            TypeUint32 =>
              match parse_u32(s) {
                Some(n) => writer.uint32(1, n)
                None => ()
              }
            _ => ()
          }
        _ => ()
      }
    None => ()
  }
  writer.to_bytes()
}

///|
fn timestamp_bytes(
  desc : MessageDescriptor,
  value : Json,
) -> Bytes raise PbError {
  let writer = PbWriter::new()
  match value {
    String(text) =>
      match parse_rfc3339(text) {
        Some((seconds, nanos)) => {
          if seconds != 0L {
            writer.int64(1, seconds)
          }
          if nanos != 0L {
            writer.int32(2, nanos.to_int())
          }
        }
        None => raise BadWireType(0)
      }
    _ => ()
  }
  ignore(desc)
  writer.to_bytes()
}

///|
fn duration_bytes(
  desc : MessageDescriptor,
  value : Json,
) -> Bytes raise PbError {
  let writer = PbWriter::new()
  match value {
    String(text) => {
      guard text.length() > 1 && text[text.length() - 1] == 's' else {
        raise BadWireType(0)
      }
      let body = text.exact_view(start=0, end=text.length() - 1).to_owned()
      let (negative, digits) = if body.length() > 0 && body[0] == '-' {
        (true, body.exact_view(start=1, end=body.length()).to_owned())
      } else {
        (false, body)
      }
      let mut whole = digits
      let mut fraction_text = ""
      let dot = digits.find(".")
      if dot is Some(i) {
        ignore(i)
        let at = i
        whole = digits.exact_view(start=0, end=at).to_owned()
        fraction_text = digits
          .exact_view(start=at + 1, end=digits.length())
          .to_owned()
      }
      let seconds = match parse_i64(whole) {
        Some(n) => n
        None => 0L
      }
      let nanos = fraction_nanos(fraction_text)
      let sign = if negative { -1L } else { 1L }
      if seconds != 0L {
        writer.int64(1, sign * seconds)
      }
      if nanos != 0L {
        writer.int32(2, (sign * nanos).to_int())
      }
    }
    _ => ()
  }
  ignore(desc)
  writer.to_bytes()
}

///|
/// The nanoseconds a fraction's digits stand for, at however many digits were written.
fn fraction_nanos(text : String) -> Int64 {
  let mut value : Int64 = 0L
  let mut scale : Int64 = 1L
  for ch in text {
    if value < 100000000L {
      value = value * 10L + (ch.to_int() - 0x30).to_int64()
      scale = scale * 10L
    }
  }
  let mut nanos = value
  while scale < 1000000000L {
    nanos = nanos * 10L
    scale = scale * 10L
  }
  nanos
}

///|
fn field_mask_bytes(desc : MessageDescriptor, value : Json) -> Bytes {
  let writer = PbWriter::new()
  match value {
    String(text) =>
      if text.length() > 0 {
        for path in text.split(",") {
          writer.string_(1, snake_path(path.to_owned()))
        }
      }
    _ => ()
  }
  ignore(desc)
  writer.to_bytes()
}

///|
/// A field mask path in its wire spelling: each lowerCamelCase segment becomes snake_case.
fn snake_path(path : String) -> String {
  let out = []
  for segment in path.split(".") {
    let mut text = ""
    for ch in segment {
      let code = ch.to_int()
      if code >= 0x41 && code <= 0x5A {
        text = text + "_" + Int32::unsafe_to_char(code + 32).to_string()
      } else {
        text = text + ch.to_string()
      }
    }
    out.push(text)
  }
  out.join(".")
}

///|
fn Schema::struct_bytes(
  self : Schema,
  desc : MessageDescriptor,
  value : Json,
) -> Bytes raise PbError {
  let writer = PbWriter::new()
  match value {
    Object(members) =>
      for key, item in members {
        let entry = PbWriter::new()
        entry.string_(1, key)
        match self.messages.get(".google.protobuf.Value") {
          Some(value_desc) => {
            let inner = self.value_bytes(value_desc, item)
            entry.message_(2, inner)
          }
          None => ()
        }
        writer.message_(1, entry.to_bytes())
      }
    _ => ()
  }
  ignore(desc)
  writer.to_bytes()
}

///|
fn Schema::value_bytes(
  self : Schema,
  desc : MessageDescriptor,
  value : Json,
) -> Bytes raise PbError {
  let writer = PbWriter::new()
  match value {
    Null => writer.enum_(1, 0)
    True => writer.bool_(4, true)
    False => writer.bool_(4, false)
    Number(d, ..) => writer.double_(2, d)
    String(s) => writer.string_(3, s)
    Array(items) =>
      match self.messages.get(".google.protobuf.ListValue") {
        Some(list_desc) => {
          let inner = self.list_bytes(list_desc, Json::array(items))
          writer.message_(6, inner)
        }
        None => ()
      }
    Object(members) =>
      match self.messages.get(".google.protobuf.Struct") {
        Some(struct_desc) => {
          let inner = self.struct_bytes(struct_desc, Json::object(members))
          writer.message_(5, inner)
        }
        None => ()
      }
  }
  ignore(desc)
  writer.to_bytes()
}

///|
fn Schema::list_bytes(
  self : Schema,
  desc : MessageDescriptor,
  value : Json,
) -> Bytes raise PbError {
  let writer = PbWriter::new()
  match value {
    Array(items) =>
      match self.messages.get(".google.protobuf.Value") {
        Some(value_desc) =>
          for item in items {
            let inner = self.value_bytes(value_desc, item)
            writer.message_(1, inner)
          }
        None => ()
      }
    _ => ()
  }
  ignore(desc)
  writer.to_bytes()
}

///|
fn Schema::any_bytes(
  self : Schema,
  desc : MessageDescriptor,
  value : Json,
) -> Bytes raise PbError {
  let writer = PbWriter::new()
  match value {
    Object(members) => {
      let url = match members.get("@type") {
        Some(String(s)) => s
        _ => ""
      }
      writer.string_(1, url)
      let name = "." + last_segment_after_slash(url)
      match self.messages.get(name) {
        Some(inner) => {
          // The remaining members are the wrapped message's own fields.
          let rest : Map[String, Json] = Map([])
          for key, item in members {
            if key != "@type" {
              rest[key] = item
            }
          }
          let body = self.message_bytes(name, inner, Json::object(rest))
          if body.length() > 0 {
            writer.bytes_(2, body)
          }
        }
        None => ()
      }
    }
    _ => ()
  }
  ignore(desc)
  writer.to_bytes()
}

///|
/// An RFC 3339 timestamp as `(seconds since the epoch, nanoseconds)`.
fn parse_rfc3339(text : String) -> (Int64, Int64)? {
  // YYYY-MM-DDTHH:MM:SS
  if text.length() < 20 {
    return None
  }
  if text[4] != '-' || text[7] != '-' || (text[10] != 'T' && text[10] != 't') {
    return None
  }
  let year = digits_at(text, 0, 4)
  let month = digits_at(text, 5, 2)
  let day = digits_at(text, 8, 2)
  let hour = digits_at(text, 11, 2)
  let minute = digits_at(text, 14, 2)
  let second = digits_at(text, 17, 2)
  if year is None ||
    month is None ||
    day is None ||
    hour is None ||
    minute is None ||
    second is None {
    return None
  }
  let mut at = 19
  let mut nanos : Int64 = 0L
  if at < text.length() && text[at] == '.' {
    let start = at + 1
    let mut stop = start
    while stop < text.length() && is_digit(text[stop].to_int()) {
      stop = stop + 1
    }
    if stop == start {
      return None
    }
    nanos = fraction_nanos(text.exact_view(start~, end=stop).to_owned())
    at = stop
  }
  let mut offset : Int64 = 0L
  if at < text.length() {
    match text[at] {
      'Z' | 'z' => at = at + 1
      '+' | '-' => {
        let sign : Int64 = if text[at] == '+' { 1L } else { -1L }
        let oh = digits_at(text, at + 1, 2)
        let om = digits_at(text, at + 4, 2)
        if oh is None || om is None {
          return None
        }
        offset = sign * (oh.unwrap() * 3600L + om.unwrap() * 60L)
        at = at + 6
      }
      _ => return None
    }
  }
  if at != text.length() {
    return None
  }
  let seconds = days_from_civil(year.unwrap(), month.unwrap(), day.unwrap()) *
    86400L +
    hour.unwrap() * 3600L +
    minute.unwrap() * 60L +
    second.unwrap() -
    offset
  Some((seconds, nanos))
}

///|
fn is_digit(code : Int32) -> Bool {
  code >= 0x30 && code <= 0x39
}

///|
fn digits_at(text : String, start : Int32, width : Int32) -> Int64? {
  if start + width > text.length() {
    return None
  }
  let mut value : Int64 = 0L
  for i in start..<(start + width) {
    let ch = text[i]
    if !is_digit(ch.to_int()) {
      return None
    }
    value = value * 10L + (ch.to_int() - 0x30).to_int64()
  }
  Some(value)
}