<commit_before>defmodule MarcoPolo.Protocol do
  require Integer
  import MarcoPolo.Protocol.BinaryHelpers
  alias MarcoPolo.Error

  @type sid :: non_neg_integer
  @type op_code :: non_neg_integer
  @type op_name :: atom

  @ok    <<0>>
  @error <<1>>
  @null  <<-1 :: int>>

  @doc """
  """
  @spec encode_op(op_name, [term]) :: iodata
  def encode_op(op_name, args \\ []) do
    [req_code(op_name), Enum.map(args, &serialize/1)]
  end

  @doc """
  Serializes an Elixir term to an OrientDB term according to the binary
  protocol.

  Supported Elixir terms are:

    * booleans (`true` and `false`)
    * `nil`
    * binaries (hence strings)
    * integers (by default encoded as "int", but the size of the integer can be
      specified by "tagging" the integer, e.g., `{:short, 28}` or
      `{:long, 1000}`).

  """
  @spec serialize(term) :: iodata
  def serialize(term)

  # Booleans.
  def serialize(true),  do: <<1>>
  def serialize(false), do: <<0>>

  # nil.
  def serialize(nil), do: serialize({:int, -1})

  # Strings and bytes.
  def serialize(str) when is_binary(str), do: serialize({:int, byte_size(str)}) <> str

  # Encoding an Elixir integer defaults to encoding an OrientDB int (4 bytes).
  def serialize(i) when is_integer(i), do: serialize({:int, i})

  # Typed integers (short, int and long) have to be tagged.
  def serialize({:short, i}), do: <<i :: short>>
  def serialize({:int, i}),   do: <<i :: int>>
  def serialize({:long, i}),  do: <<i :: long>>

  @doc """
  """
  @spec parse_connection_header(binary) :: {:ok, sid, binary} | Error.t
  def parse_connection_header(@ok <> @null <> <<sid :: int, rest :: binary>>),
    do: {:ok, sid, rest}
  def parse_connection_header(@error <> @null <> rest),
    do: %Error{message: "error (binary dump: #{inspect rest})"}

  @doc """
  """
  @spec parse_header(binary, boolean) ::
    {:ok, sid, binary, binary}
    | {:ok, sid, binary}
    | Error.t
  def parse_header(data, token?)

  def parse_header(@ok <> <<sid :: int, rest :: binary>>, true = _token?) do
    {token, rest} = parse(rest, :bytes)
    {:ok, sid, token, rest}
  end

  def parse_header(@ok <> <<sid :: int, rest :: binary>>, false = _token?) do
    {:ok, sid, rest}
  end

  def parse_header(@error <> <<_sid :: int, rest :: binary>>, token?) do
    if token? do
      {_token, rest} = parse(rest, :bytes)
    end

    %Error{message: "error (binary dump: #{inspect rest})"}
  end

  @doc """
  """
  @spec parse_resp(op_name, binary, boolean) ::
    {:ok, sid, binary, binary}
    | {:ok, sid, binary}
    | Error.t
  def parse_resp(op_name, data, token?) do
    case parse_header(data, token?) do
      {:ok, sid, rest}        -> {:ok, sid, parse_resp_contents(op_name, rest)}
      {:ok, sid, token, rest} -> {:ok, sid, token, parse_resp_contents(op_name, rest)}
      %Error{} = error        -> error
    end
  end

  @doc """
  """
  @spec parse(binary, atom) :: {binary, binary}
  def parse(<<length :: int, data :: binary>>, :bytes) do
    length = bytes(length)
    <<parsed :: bits-size(length), rest :: binary>> = data
    {parsed, rest}
  end

  def parse(data, :string) do
    parse(data, :bytes)
  end

  defp parse_resp_contents(:db_create, <<>>) do
    []
  end

  defp parse_resp_contents(:db_exist, <<exists>>) do
    [exists == 1]
  end

  defp parse_resp_contents(:db_drop, <<>>) do
    []
  end

  defp parse_resp_contents(:db_size, <<size :: long>>) do
    [size]
  end

  defp parse_resp_contents(:db_countrecords, <<count :: long>>) do
    [count]
  end

  defp parse_resp_contents(:db_reload, <<num_of_clusters :: short, rest :: binary>>) do
    Enum.map_reduce 1..num_of_clusters, rest, fn _, acc ->
      {cluster_name, acc} = parse(acc, :string)
      <<cluster_id :: short, acc :: binary>> = acc
      {{cluster_name, cluster_id}, acc}
    end
  end

  defp parse_resp_contents(:record_load, data) do
    parse_resp_contents(:record_load, data, []) |> Enum.reverse
  end

  defp parse_resp_contents(:record_load, <<1, type, version :: int, rest :: binary>>, acc) do
    {record_content, rest} = parse(rest, :bytes)
    parse_resp_contents(:record_load, rest, [{type, version, record_content}|acc])
  end

  defp parse_resp_contents(:record_load, <<0>>, acc) do<commit_msg>Actually parse the record in REQUEST_RECORD_LOAD<commit_after>defmodule MarcoPolo.Protocol do
  require Integer
  import MarcoPolo.Protocol.BinaryHelpers
  alias MarcoPolo.Error
  alias MarcoPolo.Protocol.RecordSerialization

  @type sid :: non_neg_integer
  @type op_code :: non_neg_integer
  @type op_name :: atom

  @ok    <<0>>
  @error <<1>>
  @null  <<-1 :: int>>

  @doc """
  """
  @spec encode_op(op_name, [term]) :: iodata
  def encode_op(op_name, args \\ []) do
    [req_code(op_name), Enum.map(args, &serialize/1)]
  end

  @doc """
  Serializes an Elixir term to an OrientDB term according to the binary
  protocol.

  Supported Elixir terms are:

    * booleans (`true` and `false`)
    * `nil`
    * binaries (hence strings)
    * integers (by default encoded as "int", but the size of the integer can be
      specified by "tagging" the integer, e.g., `{:short, 28}` or
      `{:long, 1000}`).

  """
  @spec serialize(term) :: iodata
  def serialize(term)

  # Booleans.
  def serialize(true),  do: <<1>>
  def serialize(false), do: <<0>>

  # nil.
  def serialize(nil), do: serialize({:int, -1})

  # Strings and bytes.
  def serialize(str) when is_binary(str), do: serialize({:int, byte_size(str)}) <> str

  # Encoding an Elixir integer defaults to encoding an OrientDB int (4 bytes).
  def serialize(i) when is_integer(i), do: serialize({:int, i})

  # Typed integers (short, int and long) have to be tagged.
  def serialize({:short, i}), do: <<i :: short>>
  def serialize({:int, i}),   do: <<i :: int>>
  def serialize({:long, i}),  do: <<i :: long>>

  @doc """
  """
  @spec parse_connection_header(binary) :: {:ok, sid, binary} | Error.t
  def parse_connection_header(@ok <> @null <> <<sid :: int, rest :: binary>>),
    do: {:ok, sid, rest}
  def parse_connection_header(@error <> @null <> rest),
    do: %Error{message: "error (binary dump: #{inspect rest})"}

  @doc """
  """
  @spec parse_header(binary, boolean) ::
    {:ok, sid, binary, binary}
    | {:ok, sid, binary}
    | Error.t
  def parse_header(data, token?)

  def parse_header(@ok <> <<sid :: int, rest :: binary>>, true = _token?) do
    {token, rest} = parse(rest, :bytes)
    {:ok, sid, token, rest}
  end

  def parse_header(@ok <> <<sid :: int, rest :: binary>>, false = _token?) do
    {:ok, sid, rest}
  end

  def parse_header(@error <> <<_sid :: int, rest :: binary>>, token?) do
    if token? do
      {_token, rest} = parse(rest, :bytes)
    end

    %Error{message: "error (binary dump: #{inspect rest})"}
  end

  @doc """
  """
  @spec parse_resp(op_name, binary, boolean) ::
    {:ok, sid, binary, binary}
    | {:ok, sid, binary}
    | Error.t
  def parse_resp(op_name, data, token?) do
    case parse_header(data, token?) do
      {:ok, sid, rest}        -> {:ok, sid, parse_resp_contents(op_name, rest)}
      {:ok, sid, token, rest} -> {:ok, sid, token, parse_resp_contents(op_name, rest)}
      %Error{} = error        -> error
    end
  end

  @doc """
  """
  @spec parse(binary, atom) :: {binary, binary}
  def parse(<<length :: int, data :: binary>>, :bytes) do
    length = bytes(length)
    <<parsed :: bits-size(length), rest :: binary>> = data
    {parsed, rest}
  end

  def parse(data, :string) do
    parse(data, :bytes)
  end

  defp parse_resp_contents(:db_create, <<>>) do
    []
  end

  defp parse_resp_contents(:db_exist, <<exists>>) do
    [exists == 1]
  end

  defp parse_resp_contents(:db_drop, <<>>) do
    []
  end

  defp parse_resp_contents(:db_size, <<size :: long>>) do
    [size]
  end

  defp parse_resp_contents(:db_countrecords, <<count :: long>>) do
    [count]
  end

  defp parse_resp_contents(:db_reload, <<num_of_clusters :: short, rest :: binary>>) do
    Enum.map_reduce 1..num_of_clusters, rest, fn _, acc ->
      {cluster_name, acc} = parse(acc, :string)
      <<cluster_id :: short, acc :: binary>> = acc
      {{cluster_name, cluster_id}, acc}
    end
  end

  defp parse_resp_contents(:record_load, data) do
    parse_resp_contents(:record_load, data, []) |> Enum.reverse
  end

  defp parse_resp_contents(:record_load, <<1, type, version :: int, rest :: binary>>, acc) do
    {record_content, rest} = parse(rest, :bytes)
    record = RecordSerialization.decode(record_content)
    parse_resp_contents(:record_load, rest, [{type, version, record}|acc])
  end

  defp parse_resp_contents(:record_load, <<0>>, acc) do
{'fraction_non_alphanumeric': 0.16554446250290225, 'fraction_numerical': 0.004179243092639889, 'mean_word_length': 3.491657977059437, 'pattern_counts': {'":': 0, '<': 111, '<?xml version=': 0, '>': 121, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1750671', 'n_tokens_mistral': 3132, 'n_tokens_neox': 3013, 'n_words': 860}