lib/grizzly/command_handlers/aggregate_report.ex

defmodule Grizzly.CommandHandlers.AggregateReport do
  @moduledoc """
  Handler for working with reports that could take many report frames to
  complete

  This handler will handle aggregating the responses into one report command
  for ease of consumption by callers.
  """
  @behaviour Grizzly.CommandHandler

  alias Grizzly.ZWave.Command

  @type state :: %{complete_report: atom(), aggregate_param: atom(), aggregates: [any()]}

  @type opt :: {:complete_report, atom(), aggregate_param: atom()}

  @spec init([opt]) :: {:ok, state()}
  def init(opts) do
    report_name = Keyword.fetch!(opts, :complete_report)
    aggregate_param = Keyword.fetch!(opts, :aggregate_param)

    {:ok, %{complete_report: report_name, aggregate_param: aggregate_param, aggregates: []}}
  end

  @spec handle_ack(state()) :: {:continue, state()}
  def handle_ack(state), do: {:continue, state}

  @spec handle_command(Command.t(), state()) ::
          {:continue, state} | {:complete, Command.t()}
  def handle_command(command, state) do
    if command.name == state.complete_report do
      do_handle_command(command, state)
    else
      {:continue, state}
    end
  end

  defp aggregate(command, state) do
    %{aggregate_param: aggregate_param, aggregates: aggregates} = state

    new_aggregate_values = Command.param!(command, aggregate_param)

    %{state | aggregates: do_aggregate(aggregates, new_aggregate_values)}
  end

  defp prepare_aggregate_data(command, state) do
    %{aggregate_param: aggregate_param, aggregates: aggregates} = state
    final_values = Command.param!(command, aggregate_param)

    Command.put_param(command, aggregate_param, do_aggregate(aggregates, final_values))
  end

  defp do_aggregate(aggregates, new_aggregate_values) when is_list(aggregates),
    do: aggregates ++ new_aggregate_values

  defp do_aggregate(aggregates, new_aggregate_values) when is_binary(aggregates),
    do: aggregates <> new_aggregate_values

  defp do_handle_command(command, state) do
    rtf = Command.param!(command, :reports_to_follow)

    if rtf == 0 do
      {:complete, prepare_aggregate_data(command, state)}
    else
      {:continue, aggregate(command, state)}
    end
  end
end