lib/glific/state/simulator.ex

defmodule Glific.State.Simulator do
  @moduledoc """
  Manage simulator state and allocation to ensure we can have multiple simulators
  run at the same time
  """
  import Ecto.Query, warn: false

  alias Glific.{
    Contacts,
    Contacts.Contact,
    Repo,
    State,
    Users.User
  }

  @doc """
  Check if there is an available simulator for this user
  - If available, return the free Simulator Contact
    (there are multiple simulator contacts per organization)
  - If none available, return nil
  """
  @spec get_simulator(User.t(), map()) :: {Contact.t(), map()}
  def get_simulator(user, state) do
    organization_id = user.organization_id

    {org_state, contact} =
      State.get_state(state, organization_id)
      |> State.free_entity(:simulators, %{user: user})
      |> get_org_simulator(user)

    {contact, Map.put(state, organization_id, org_state)}
  end

  @spec get_org_simulator(map(), map()) :: {map(), Contact.t()} | {map(), nil}
  defp get_org_simulator(
         %{
           simulator: %{free: free, busy: busy}
         } = state,
         user
       ) do
    key = {user.id, user.fingerprint}

    if Enum.empty?(free) do
      # if no simulator is present
      {state, nil}
    else
      # if simulator is present
      [contact | free] = free

      {
        State.update_state(
          state,
          :simulator,
          free,
          Map.put(busy, key, {contact, DateTime.utc_now()})
        ),
        contact
      }
    end
  end

  @doc false
  @spec init_state(non_neg_integer) :: map()
  def init_state(organization_id) do
    phone = Contacts.simulator_phone_prefix() <> "%"

    # fetch all the simulator contacts for this organization
    contacts =
      Glific.Contacts.Contact
      |> where([c], like(c.phone, ^phone))
      |> where([c], c.organization_id == ^organization_id)
      |> Repo.all(skip_organization_id: true, skip_permission: true)

    %{simulator: %{free: contacts, busy: %{}}}
  end
end