lib/Statux/constraints.ex

defmodule Statux.Constraints do

  alias Statux.Models.EntityStatus
  alias Statux.Models.TrackingData

  def filter_valid_transition_options(%EntityStatus{} = entity_state, status_name, status_options, options) when is_list(options) do
    options
    |> Enum.map(fn option ->
      entity_state
      |> check_transition_constraints(status_name, status_options, option)
    end)
    |> Enum.filter(fn {transition?, _from, _to} -> transition? end)
  end

  def check_transition_constraints(%EntityStatus{} = entity_state, status_name, status_options, option) when is_atom(option) do
    current_status = entity_state.current_status[status_name][:current]

    case status_options[option][:constraints] do
      nil ->
        {true, current_status, option}
      constraints ->
        transition? =
          constraints_fulfilled?(
            constraints,
            option,
            entity_state.tracking[status_name][option]
          )

        {transition?, current_status, option}
    end
  end

  # Termination conditions
  def constraints_fulfilled?(nil = _constraints, _option, _tracking), do: true
  def constraints_fulfilled?(constraints, _option, _tracking) when constraints == %{}, do: true

  # Previous status OK?
  def constraints_fulfilled?(%{previous_status: status_constraints} = constraints, option, %TrackingData{} = tracking) do
    case Statux.ValueRules.valid?(tracking.consecutive_message_count, status_constraints) do
      true -> constraints_fulfilled?(constraints |> Map.delete(:previous_status), option, tracking)
      false -> false
    end
  end

  # count okay?
  def constraints_fulfilled?(%{count: %{n_of_m: [n, _m]}} = constraints, option, %TrackingData{} = tracking) do
    case tracking.valid_history_true_count >= n do
      true -> constraints_fulfilled?(constraints |> Map.delete(:count), option, tracking)
      false -> false
    end
  end
  def constraints_fulfilled?(%{count: count_constraints} = constraints, option, %TrackingData{n_of_m_constraint: nil} = tracking) do
    case Statux.ValueRules.valid?(tracking.consecutive_message_count, count_constraints) do
      true -> constraints_fulfilled?(constraints |> Map.delete(:count), option, tracking)
      false -> false
    end
  end

  # duration okay?
  def constraints_fulfilled?(%{duration: duration_constraints} = constraints, option, %TrackingData{} = tracking) do
    case Statux.ValueRules.valid?(tracking.occurred_at, duration_constraints) do
      true -> constraints_fulfilled?(constraints |> Map.delete(:duration), option, tracking)
      false -> false
    end
  end
end