defmodule Statux.Models.TrackingData do
@moduledoc """
This Structure holds data that is required to track :count or :duration constraints.
It is updated whenever new data comes in and is used to decide wether or not a
state can be transitioned into.
"""
use StructAccess
use TypedStruct
typedstruct do
# To check :count constraints like :min, :max, :is, :not, :gt, :lt.
# These constraints expect a number of consecutive message fulfilling
# the value requirements. If a message comes in that does not fulfill
# the value requirements, the count is reset
field :consecutive_message_count, Integer.t(), default: 0
# To check :duration constraints like :min, :max, :is, :not, :gt, :lt.
# Is set whenever the first consecutive message is received.
# TODO: How to use with n_of_m constraints?
field :occurred_at, DateTime.t()
# indicates wether an n_of_m constraint is used
field :n_of_m_constraint, list(), default: nil
# If n_of_m constraint is used, this holds the result of the last
# m values. Otherwise, the list remains empty.
field :valid_history, list(boolean()), default: []
# If n_of_m is used, this holds the cound of `true` elements in
# the :valid_history (so we d oont have to count every time)
field :valid_history_true_count, Integer.t(), default: 0
end
def from_option(option) do
n_of_m_constraint =
option[:constraints][:count][:n_of_m] # nil or [n, m]
%__MODULE__{n_of_m_constraint: n_of_m_constraint}
end
def put_valid(%__MODULE__{
n_of_m_constraint: nil,
consecutive_message_count: 0,
} = tracking_data
) do
%{ tracking_data | consecutive_message_count: 1, occurred_at: DateTime.utc_now()}
end
def put_valid(%__MODULE__{
n_of_m_constraint: nil,
consecutive_message_count: n,
} = tracking_data
) do
%{ tracking_data | consecutive_message_count: n + 1}
end
def put_valid(%__MODULE__{
n_of_m_constraint: [n, m],
valid_history: history,
valid_history_true_count: history_count,
occurred_at: occurred_at,
consecutive_message_count: count,
} = tracking_data)
do
updated_history_true_count =
case history |> Enum.at(m - 1) do
true -> history_count
_ -> history_count + 1 # false or nil
end
updated_occurred_at =
cond do
updated_history_true_count < n -> nil
updated_history_true_count == n and history_count < n -> DateTime.utc_now()
true -> occurred_at
end
updated_history =
[true | Enum.take(history, m - 1)]
%{ tracking_data |
consecutive_message_count: count + 1,
occurred_at: updated_occurred_at,
valid_history_true_count: updated_history_true_count,
valid_history: updated_history
}
end
def put_invalid(%__MODULE__{n_of_m_constraint: nil} = tracking_data) do
%{ tracking_data |
consecutive_message_count: 0,
occurred_at: nil
}
end
def put_invalid(%__MODULE__{n_of_m_constraint: [n, m], valid_history: history, valid_history_true_count: history_count, occurred_at: occurred_at} = tracking_data) do
updated_history_true_count =
case history |> Enum.at(m - 1) do
true -> history_count - 1
_ -> history_count # false or nil
end
updated_history =
[false | Enum.take(history, m - 1)]
updated_occurred_at =
cond do
updated_history_true_count < n -> nil
true -> occurred_at
end
%{ tracking_data |
consecutive_message_count: 0,
occurred_at: updated_occurred_at,
valid_history_true_count: updated_history_true_count,
valid_history: updated_history
}
end
def reset(%__MODULE__{} = tracking_data) do
%{ tracking_data |
consecutive_message_count: 0,
occurred_at: nil,
valid_history_true_count: 0,
valid_history: []
}
end
end