lib/grizzly/trace/record.ex

defmodule Grizzly.Trace.Record do
  @moduledoc """
  Data structure for a single item in the trace log
  """

  alias Grizzly.{Trace, ZWave}
  alias Grizzly.ZWave.Command

  @type t() :: %__MODULE__{
          timestamp: Time.t(),
          binary: binary(),
          src: Trace.src() | nil,
          dest: Trace.src() | nil
        }

  @type opt() :: {:src, Trace.src()} | {:dest, Trace.dest()} | {:timestamp, Time.t()}

  defstruct src: nil, dest: nil, binary: nil, timestamp: nil

  @doc """
  Make a new `Grizzly.Record.t()` from a binary string

  Options:
    * `:src` - the src as a string
    * `:dest` - the dest as a string
  """
  @spec new(binary(), [opt()]) :: t()
  def new(binary, opts \\ []) do
    timestamp = Keyword.get(opts, :timestamp, Time.utc_now())
    src = Keyword.get(opts, :src)
    dest = Keyword.get(opts, :dest)

    %__MODULE__{
      src: src,
      dest: dest,
      binary: binary,
      timestamp: timestamp
    }
  end

  @doc """
  Turn a record into the string format
  """
  @spec to_string(t()) :: String.t()
  def to_string(record) do
    %__MODULE__{timestamp: ts, src: src, dest: dest, binary: binary} = record
    {:ok, zip_packet} = ZWave.from_binary(binary)

    "#{Time.to_string(ts)} #{src_dest_to_string(src)} #{src_dest_to_string(dest)} #{command_info_str(zip_packet)}"
  end

  defp src_dest_to_string(nil) do
    Enum.reduce(1..18, "", fn _, str -> str <> " " end)
  end

  defp src_dest_to_string(src_or_dest), do: src_or_dest

  defp command_info_str(%Command{name: :keep_alive}) do
    "    keep_alive"
  end

  defp command_info_str(zip_packet) do
    seq_number = Command.param!(zip_packet, :seq_number)
    flag = Command.param!(zip_packet, :flag)

    case flag do
      f when f in [:ack_response, :nack_response, :nack_waiting] ->
        command_info_empty_response(seq_number, flag)

      _ ->
        command_info_with_encapsulated_command(seq_number, zip_packet)
    end
  end

  defp command_info_empty_response(seq_number, flag) do
    "#{seq_number_to_str(seq_number)} #{flag}"
  end

  defp command_info_with_encapsulated_command(seq_number, zip_packet) do
    command = Command.param!(zip_packet, :command)

    "#{seq_number_to_str(seq_number)} #{command.name} #{inspect(Command.encode_params(command))}"
  end

  defp seq_number_to_str(seq_number) do
    case seq_number do
      seq_number when seq_number < 10 ->
        "#{seq_number}  "

      seq_number when seq_number < 100 ->
        "#{seq_number} "

      seq_number ->
        "#{seq_number}"
    end
  end
end