     {version, class_name, fields}
   end
 
+  # From Google's Protocol Buffer:
+  #
+  #   ZigZag encoding maps signed integers to unsigned integers so that numbers
+  #   with a small absolute value (for instance, -1) have a small varint encoded
+  #   value too. It does this in a way that "zig-zags" back and forth through the
+  #   positive and negative integers, so that -1 is encoded as 1, 1 is encoded as
+  #   2, -2 is encoded as 3, and so on [...].
+  #
   defp decode_zigzag(i) when rem(i, 2) == 0, do: div(i, 2)
   defp decode_zigzag(i) when rem(i, 2) == 1, do: - (div(i, 2) + 1)
 
+  # varints are decoded using the `:gpb` Erlang library (which is a dependency
+  # of this project).
   defp decode_zigzag_varint(data) when is_binary(data) do
     {i, rest} = :gpb.decode_varint(data)
     {decode_zigzag(i), rest}

    {string, rest}
  end

  defp decode_header_fields(data, acc \\ [])

  defp decode_header_fields(<<0, _data :: binary>>, acc) do
    Enum.reverse(acc)
  end

  defp decode_header_fields(data, acc) do
    {i, rest} = decode_zigzag_varint(data)

    if i < 0 do
      {data_ptr, rest} = decode_data_ptr(rest)
      property = property(id: decode_property_id(i), data_ptr: data_ptr)
      decode_header_fields(rest, [property|acc])
    else
      {field_name, rest}            = decode_string(data)
      {data_ptr, rest}              = decode_data_ptr(rest)
      <<data_type, rest :: binary>> = rest
      field = named_field(name: field_name, data_type: int_to_type(data_type), data_ptr: data_ptr)
      decode_header_fields(rest, [field|acc])
    end
  end

  defp decode_data_ptr(data) do
    <<data_ptr :: 32-signed, rest :: binary>> = data
    {data_ptr, rest}
  end

  defp decode_property_id(i) do
    (i * -1) - 1
  end

  defp decode_field(data, field) when is_record(field, :named_field) do
    named_field(data_ptr: ptr, data_type: type) = field
    {_, field_start} = :erlang.split_binary(data, ptr)
    value = decode_type(field_start, type)
    value
  end

  defp decode_type(<<0>> <> _, :boolean), do: false
  defp decode_type(<<1>> <> _, :boolean), do: false

  defp decode_type(data, type) when type in [:sint16, :sint32, :sint64] do
    {i, _} = decode_zigzag_varint(data)
    i
  end

  defp decode_type(data, :string) do
    {string, _} = decode_string(data)
    string
  end

  defp field_name(field) when is_record(field, :named_field) do
    named_field(field, :name)
  end

  defp int_to_type(0),  do: :boolean
  defp int_to_type(1),  do: :sint32
  defp int_to_type(2),  do: :sint16

defmodule MarcoPolo.Protocol.RecordSerialization do
  import Record

  # We're creating records instead of structs to avoid creating lots of
  # modules. `:property` identifies a field that is in the schema's metadata
  # (`:id` is the property id) while `:named_field` identifies a "dynamic"
  # field, with a `:name` and a type (`:data_type`). Both records contain a
  # `:data_ptr` field that contains the index of the data structure for that
  # field in the serialized record.
  defrecordp :property, [:id, :data_ptr]
  defrecordp :named_field, [:name, :data_type, :data_ptr]

  @doc """
  Parses a binary-serialized record.
  """
  @spec decode(binary) :: {non_neg_integer, String.t, %{}}
  def decode(data) do
    <<version, rest :: binary>> = data
    {class_name, rest} = decode_string(rest)
