lib/Statux/models/status.ex

defmodule Statux.Models.Status do
  @moduledoc """
  Represents the current and historic Status
  """
  use StructAccess
  use TypedStruct

  typedstruct do
    # The current status, e.g. :low, :critical
    field :current, atom()
    # if a transition occurs, it is added to the history like {%DateTime{}, :status_name}
    field :history, list, default: []
    # counts the number of times this status transitioned
    field :transition_count, Integer.t(), default: 0

    field :transitioned_at, DateTime.t()
  end

  def transition(data, option) do
    transition(data, option, DateTime.utc_now())
  end

  def transition(nil, option, datetime) do
    %__MODULE__{
      current: option,
      transitioned_at: datetime,
      transition_count: 1
    }
  end

  def transition(%__MODULE__{current: current_option} = status, option, datetime) do
    case current_option == option do
      true -> status
      false -> update_status(status, option, datetime)
    end
  end

  # no history, has never transitioned before.
  defp update_status(%__MODULE__{current: nil, history: []} = status, option, datetime) do
    %{
      status |
        current: option,
        transitioned_at: datetime,
        transition_count: 1,
    }
  end

  # no history, but has transitioned before
  defp update_status(
    %__MODULE__{
      current: transition_from,
      history: [],
      transitioned_at: transitioned_at
    } = status,
    option,
    datetime
  ) do
    %{
      status |
        current: option,
        transitioned_at: datetime,
        transition_count: status.transition_count + 1,
        history: [{transition_from, transitioned_at, nil}]
    }
  end

  # Has previously transitioned and a history -> update previous entry to add an end DateTime
  defp update_status(
    %__MODULE__{
      current: transition_from,
      history: [{previous_option, previous_started_at, _} | older_history],
      transitioned_at: transitioned_at
    } = status,
    option,
    datetime
  ) do
    %{
      status |
        current: option,
        transitioned_at: datetime,
        transition_count: status.transition_count + 1,
        history: [{transition_from, transitioned_at, nil}, {previous_option, previous_started_at, transitioned_at} | older_history]
    }
  end
end