lib/archeometer/reports/section.ex

defmodule Archeometer.Reports.Section do
  @moduledoc """
  Represents a Section, which is part of a Page.
  """

  require Logger
  use Archeometer.Repo
  alias Archeometer.Reports.Fragment

  defstruct [:id, :desc, fragments: []]

  defmodule Definition do
    @moduledoc """
    Represents the definition of a Section.
    """

    defstruct [:id, :desc, fragments: []]
  end

  def process(%__MODULE__.Definition{} = sdef, bindings, db_name \\ default_db_name()) do
    Logger.debug("Processing section #{sdef.id}")

    {fragments, _} =
      Enum.reduce(sdef.fragments, {[], bindings, []}, fn fdef, acc ->
        validate_fragment_input(fdef, acc, db_name)
      end)
      |> Tuple.delete_at(2)

    res = struct(__MODULE__, Map.from_struct(sdef))
    %{res | fragments: fragments}
  end

  defp validate_fragment_input(fdef, {_, _, empty_bindings} = acc, db_name) do
    if Enum.member?(empty_bindings, fdef.receives) do
      process_empty_fragment(fdef, acc, db_name)
    else
      process_fragment(fdef, acc, db_name)
    end
  end

  defp process_empty_fragment(fdef, acc, db_name) do
    Map.put(fdef, :query_type, :empty_input)
    |> process_fragment(acc, db_name)
  end

  defp process_fragment(
         %Fragment.Definition{result_type: :table} = fdef,
         {results, bindings, empty_bindings},
         db_name
       ) do
    fragment = Fragment.process(fdef, bindings, db_name)
    assign_bindings(fragment, fdef, bindings, empty_bindings, results)
  end

  defp process_fragment(
         %Fragment.Definition{} = fdef,
         {results, bindings, empty_bindings},
         db_name
       ) do
    fragment = Fragment.process(fdef, bindings, db_name)
    {results ++ [fragment], bindings, empty_bindings}
  end

  defp assign_bindings(
         %{result: %{values: []}} = fragment,
         fdef,
         bindings,
         empty_bindings,
         results
       ) do
    new_bindings = new_bindings(bindings, fdef.result_name, fragment.result)
    {results ++ [fragment], new_bindings, [empty_bindings | [fdef.result_name]]}
  end

  defp assign_bindings(fragment, fdef, bindings, empty_bindings, results) do
    new_bindings = new_bindings(bindings, fdef.result_name, fragment.result)
    {results ++ [fragment], new_bindings, empty_bindings}
  end

  defp new_bindings(bindings, :none, _value) do
    bindings
  end

  defp new_bindings(bindings, name, value) do
    [{name, to_binding_val(value)} | bindings]
  end

  defp to_binding_val(table) when is_map(table) do
    table.values |> Enum.join(" ")
  end
end