 
   defrecordp :map_key, [:key, :data_type, :data_ptr]
 
+  defmodule Field do
+    @type t :: %__MODULE__{
+      name: binary,
+      type: atom,
+      pointer_to_data: non_neg_integer,
+      encoded_value: iodata,
+    }
+
+    defstruct ~w(name type pointer_to_data value encoded_value)a
+  end
+
   @doc """
   Parses a binary-serialized record.
   """
   @spec decode(binary) :: {non_neg_integer, String.t, %{}}
   def decode(data) do
     <<_version, rest :: binary>> = data
-    {record, <<>>} = decode_document(rest)
-    record
+    rest |> decode_document |> elem(0)
   end
 
   @doc """
   Serializes a record using the binary serialization protocol.
 
   `class_name` is a string containing the class name of the record being
-  encoded. `fields` is a list of fields.
+  encoded. `fields` is a list of `Field` structs.
   """
   def encode({class_name, fields}) do
     version_and_class = [0, encode_type(class_name || "", :string)]

  end

  defp encode_fields(fields, offset) do
    fields =
      for {name, value} <- fields do
        type  = infer_type_from_term(value)
        field = named_field(name: name, data_type: type)
        value = encode_type(value, type)
        {field, value}
      end

    fields_offset = Enum.map(fields, fn({field, _}) ->
      encode_header_field(field) |> IO.iodata_length
    end) |> Enum.sum

    # The last +1 is for the `0` that signals the end of the header.
    offset = offset + fields_offset + 1

    {fields, values, _} = Enum.reduce fields, {[], [], 0}, fn({field, value}, {fs, vs, index}) ->
      ptr = offset + index
      encoded_field = named_field(field, data_ptr: ptr) |> encode_header_field

      index = index + IO.iodata_length(value)

      {[encoded_field|fs], [value|vs], index}
    end

    fields = Enum.reverse(fields)
    values = Enum.reverse(values)

    [fields, 0, values]
  end

  defp encode_header_field(field) when is_record(field, :named_field) do
    named_field(name: name, data_ptr: ptr, data_type: type) = field

    if is_nil(ptr) do
      ptr = 0
    end

    [encode_type(name, type), <<ptr :: 32-signed>>, type_to_int(type)]
  end

  # Encodes an instance of `type`. Returns an iodata instead of a binary.

    {{class_name, fields}, rest}
  end

  # Decodes an header returning a list of field definitions (which is a list of
  # `%Field{}` structs).
  defp decode_header(data, acc \\ []) do
    {i, rest} = :small_ints.decode_zigzag_varint(data)

    # If `i` is positive, that means the next field definition is a "named
    # field" and `i` is the length of the field's name. If it's negative, it
    # represents the property id of a property. If it's 0, it signals the end of
    # the header segment.
    cond do
      i == 0 ->
        # Remember to return `rest` and not `data` since `rest` doesn't contain
        # the 0 byte that signals the end of the header, while `data` does; we
        # want to ditch that byte.
        {Enum.reverse(acc), rest}
      i < 0 ->
         raise "properties aren't supported yet, only fields with a name and an explicit type"
      i > 0 ->
        {field, rest} = decode_field_definition(:named_field, data)
        decode_header(rest, [field|acc])
    end
  end

  # Decodes the definition of a named field in the header (`data`). Returns a
  # `%Field{}` struct with an empty value.
  defp decode_field_definition(:named_field, data) do
    {field_name, rest}            = decode_type(data, :string)
    {data_ptr, rest}              = decode_data_ptr(rest)
    <<data_type, rest :: binary>> = rest

    field = %Field{name: field_name, type: int_to_type(data_type), pointer_to_data: data_ptr}
    {field, rest}
  end

  # Decodes fields from the body of a serialized document (`data`) and a list of
  # `%Field{}` structs (with no `:value` field, they're definitions). Returns a
  # list of `%Field{}`s and the rest of the given data.
  defp decode_fields(data, field_definitions) do
    {fields, rest} = Enum.map_reduce field_definitions, data, fn(%Field{} = field, acc) ->
      if field.pointer_to_data == 0 do
        {%{field | value: nil}, acc}
      else
        {value, rest} = decode_type(acc, field.type)
        {%{field | value: value}, rest}
      end
    end
  end

