///|
let object_magic : Bytes = b"MOONVCS\x00"

///|
priv struct Encoder {
  data : Array[Byte]
}

///|
fn Encoder::new() -> Encoder {
  { data: [], }
}

///|
fn Encoder::byte(self : Encoder, value : Int32) -> Unit {
  self.data.push(value.to_byte())
}

///|
fn Encoder::u32(self : Encoder, value : Int32) -> Unit raise VcsError {
  if value < 0 {
    raise VcsError::InvalidObject("negative length")
  }
  self.byte((value >> 24) & 0xff)
  self.byte((value >> 16) & 0xff)
  self.byte((value >> 8) & 0xff)
  self.byte(value & 0xff)
}

///|
fn Encoder::u64(self : Encoder, value : UInt64) -> Unit {
  for shift in [56, 48, 40, 32, 24, 16, 8, 0] {
    self.byte(((value >> shift) & 0xff).to_int())
  }
}

///|
fn Encoder::bytes(self : Encoder, value : BytesView) -> Unit {
  for byte in value {
    self.data.push(byte)
  }
}

///|
fn Encoder::string(self : Encoder, value : String) -> Unit raise VcsError {
  let encoded = @utf8.encode(value)
  self.u32(encoded.length())
  self.bytes(encoded)
}

///|
fn Encoder::object_id(self : Encoder, value : ObjectId) -> Unit raise VcsError {
  ignore(ObjectId::parse(value.hex))
  self.bytes(@utf8.encode(value.hex))
}

///|
fn Encoder::finish(self : Encoder) -> Bytes {
  Bytes::from_array(self.data)
}

///|
priv struct Decoder {
  data : Bytes
  mut offset : Int32
}

///|
fn Decoder::new(data : Bytes) -> Decoder {
  { data, offset: 0, }
}

///|
fn Decoder::remaining(self : Decoder) -> Int32 {
  self.data.length() - self.offset
}

///|
fn Decoder::byte(self : Decoder) -> Int32 raise VcsError {
  if self.remaining() < 1 {
    raise VcsError::InvalidObject("unexpected end of object")
  }
  let result = self.data[self.offset].to_int()
  self.offset = self.offset + 1
  result
}

///|
fn Decoder::u32(self : Decoder) -> Int32 raise VcsError {
  let a = self.byte()
  let b = self.byte()
  let c = self.byte()
  let d = self.byte()
  if a >= 128 {
    raise VcsError::InvalidObject("field count or length exceeds 2147483647")
  }
  (a << 24) | (b << 16) | (c << 8) | d
}

///|
fn Decoder::u64(self : Decoder) -> UInt64 raise VcsError {
  let mut value = 0UL
  for _ in 0..<8 {
    value = (value << 8) | self.byte().to_uint64()
  }
  value
}

///|
fn Decoder::bytes(self : Decoder, length : Int32) -> Bytes raise VcsError {
  if length < 0 || self.remaining() < length {
    raise VcsError::InvalidObject("invalid field length")
  }
  let value = self.data[self.offset:self.offset + length].to_owned()
  self.offset = self.offset + length
  value
}

///|
fn Decoder::string(self : Decoder) -> String raise {
  @utf8.decode(self.bytes(self.u32()))
}

///|
fn Decoder::object_id(self : Decoder) -> ObjectId raise {
  ObjectId::parse(@utf8.decode(self.bytes(64)))
}

///|
fn encode_payload(object : VcsObject) -> Bytes raise VcsError {
  let encoder = Encoder::new()
  match object {
    BlobObject(blob) => encoder.bytes(blob.content)
    TreeObject(tree) => {
      let canonical = Tree::new(tree.entries)
      encoder.u32(canonical.entries.length())
      for entry in canonical.entries {
        encoder.byte(
          match entry.kind {
            BlobEntry => 1
            TreeEntry => 2
          },
        )
        encoder.string(entry.name)
        encoder.object_id(entry.object_id)
      }
    }
    CommitObject(commit) => {
      encoder.object_id(commit.tree_id)
      encoder.u32(commit.parents.length())
      for parent in commit.parents {
        encoder.object_id(parent)
      }
      encoder.string(commit.author)
      encoder.u64(commit.timestamp)
      encoder.string(commit.message)
    }
  }
  encoder.finish()
}

///|
pub fn encode_object(object : VcsObject) -> Bytes raise VcsError {
  let payload = encode_payload(object)
  let encoder = Encoder::new()
  encoder.bytes(object_magic)
  encoder.byte(1)
  encoder.byte(
    match object {
      BlobObject(_) => 1
      TreeObject(_) => 2
      CommitObject(_) => 3
    },
  )
  encoder.u64(payload.length().to_uint64())
  encoder.bytes(payload)
  encoder.finish()
}

///|
pub fn object_id_for_bytes(encoded : Bytes) -> ObjectId {
  let digest = @sha256.Digest::new()
  for byte in encoded {
    digest.write(byte)
  }
  { hex: digest.check_sum(), }
}

///|
pub fn object_id_for(object : VcsObject) -> ObjectId raise VcsError {
  object_id_for_bytes(encode_object(object))
}

///|
pub fn decode_object(encoded : Bytes) -> VcsObject raise {
  let decoder = Decoder::new(encoded)
  if decoder.bytes(object_magic.length()) != object_magic {
    raise VcsError::InvalidObject("invalid object magic")
  }
  if decoder.byte() != 1 {
    raise VcsError::InvalidObject("unsupported object format version")
  }
  let kind = decoder.byte()
  let payload_length = decoder.u64()
  if payload_length > 2147483647UL ||
    payload_length.to_int() != decoder.remaining() {
    raise VcsError::InvalidObject("object payload length mismatch")
  }
  let payload = Decoder::new(decoder.bytes(payload_length.to_int()))
  let object = match kind {
    1 => BlobObject({ content: payload.bytes(payload.remaining()), })
    2 => {
      let count = payload.u32()
      let entries : Array[TreeEntry] = []
      for _ in 0.. BlobEntry
          2 => TreeEntry
          _ => raise VcsError::InvalidObject("invalid tree entry kind")
        }
        entries.push({
          name: payload.string(),
          kind: entry_kind,
          object_id: payload.object_id(),
        })
      }
      TreeObject(Tree::new(entries))
    }
    3 => {
      let tree_id = payload.object_id()
      let parent_count = payload.u32()
      let parents : Array[ObjectId] = []
      for _ in 0.. raise VcsError::InvalidObject("unknown object kind")
  }
  if payload.remaining() != 0 {
    raise VcsError::InvalidObject("trailing bytes in object payload")
  }
  if encode_object(object) != encoded {
    raise VcsError::InvalidObject("noncanonical object encoding")
  }
  object
}

///|
fn verify_encoded_object(id : ObjectId, encoded : Bytes) -> Bool {
  if object_id_for_bytes(encoded) != id {
    return false
  }
  try decode_object(encoded) |> ignore catch {
    _ => return false
  }
  true
}