lib/teiserver/communication/libs/match_message_lib.ex

defmodule Teiserver.Communication.MatchMessageLib do
  @moduledoc """
  Library of match_message related functions.
  """
  use TeiserverMacros, :library
  alias Teiserver.Communication.{MatchMessage, MatchMessageQueries}
  alias Teiserver.Game
  alias Teiserver.Game.Match

  @doc false
  @spec match_messaging_topic(Teiserver.match_id() | Match.t()) :: String.t()
  def match_messaging_topic(%Match{id: match_id}), do: "Teiserver.Communication.Match.#{match_id}"
  def match_messaging_topic(match_id), do: "Teiserver.Communication.Match.#{match_id}"

  @doc """
  Joins your process to match messages
  """
  @spec subscribe_to_match_messages(Match.id() | Match.t()) :: :ok
  def subscribe_to_match_messages(match_or_match_id) do
    match_or_match_id
    |> match_messaging_topic()
    |> Teiserver.subscribe()
  end

  @doc """
  Removes your process from a match's messages
  """
  @spec unsubscribe_from_match_messages(Match.id() | Match.t()) :: :ok
  def unsubscribe_from_match_messages(match_or_match_id) do
    match_or_match_id
    |> match_messaging_topic()
    |> Teiserver.unsubscribe()
  end

  @doc """
  Returns a list of messages from a match ordered as the newest first.

  In the event of there being ne messages for a match of that ID the function will return an empty list.

  ## Examples

      iex> list_recent_match_messages("uuid")
      [%MatchMessage{}, ...]

      iex> list_recent_match_messages("uuid")
      []
  """
  @spec list_recent_match_messages(Match.id(), non_neg_integer()) :: [MatchMessage.t()]
  def list_recent_match_messages(match_id, limit \\ 50) when is_binary(match_id) do
    list_match_messages(where: [match_id: match_id], limit: limit, order_by: ["Newest first"])
  end


  @doc """
  Wraps `send_match_message/3` to send a message when in the lobby instead of the match

    - Creates a match message
    - If successful generate a pubsub message for both the Match and the Lobby

    ## Examples

        iex> send_lobby_message("uuid", "uuid", "Message content")
        {:ok, %MatchMessage{}}

        iex> send_lobby_message("uuid", "uuid", "Message content")
        {:error, %Ecto.Changeset{}}
  """
  @spec send_lobby_message(Teiserver.user_id(), Lobby.id(), String.t()) ::
          {:ok, MatchMessage.t()} | {:error, Ecto.Changeset.t()}
  def send_lobby_message(sender_id, lobby_id, content) do
    match_id = Game.get_lobby_attribute(lobby_id, :match_id)

    case send_match_message(sender_id, match_id, content) do
      {:ok, match_message} ->
        topic = Game.lobby_topic(lobby_id)

        Teiserver.broadcast(
          topic,
          %{
            event: :message_received,
            lobby_id: lobby_id,
            match_message: match_message
          }
        )
        {:ok, match_message}

      err ->
        err

    end
  end

  @doc """
    - Creates a match message
    - If successful generate a pubsub message

    ## Examples

        iex> send_match_message("uuid", "uuid", "Message content")
        {:ok, %MatchMessage{}}

        iex> send_match_message("uuid", "uuid", "Message content")
        {:error, %Ecto.Changeset{}}
  """
  @spec send_match_message(Teiserver.user_id(), Match.id(), String.t()) ::
          {:ok, MatchMessage.t()} | {:error, Ecto.Changeset.t()}
  @spec send_match_message(Teiserver.user_id(), Match.id(), String.t(), map()) ::
          {:ok, MatchMessage.t()} | {:error, Ecto.Changeset.t()}
  def send_match_message(sender_id, match_id, content, attrs \\ %{}) do
    attrs =
      Map.merge(
        %{
          sender_id: sender_id,
          match_id: match_id,
          content: content,
          inserted_at: Timex.now()
        },
        attrs
      )

    case create_match_message(attrs) do
      {:ok, match_message} ->
        topic = match_messaging_topic(match_message.match_id)

        Teiserver.broadcast(
          topic,
          %{
            event: :message_received,
            match_id: match_id,
            match_message: match_message
          }
        )

        {:ok, match_message}

      err ->
        err
    end
  end

  @doc """
  Returns the list of match_messages.

  ## Examples

      iex> list_match_messages()
      [%MatchMessage{}, ...]

  """
  @spec list_match_messages(list) :: list
  def list_match_messages(query_args \\ []) do
    query_args
    |> MatchMessageQueries.match_message_query()
    |> Repo.all()
  end

  @doc """
  Gets a single match_message.

  Raises `Ecto.NoResultsError` if the MatchMessage does not exist.

  ## Examples

      iex> get_match_message!("uuid")
      %MatchMessage{}

      iex> get_match_message!("uuid")
      ** (Ecto.NoResultsError)

  """
  @spec get_match_message!(MatchMessage.id()) :: MatchMessage.t()
  @spec get_match_message!(MatchMessage.id(), Teiserver.query_args()) :: MatchMessage.t()
  def get_match_message!(match_message_id, query_args \\ []) do
    (query_args ++ [id: match_message_id])
    |> MatchMessageQueries.match_message_query()
    |> Repo.one!()
  end

  @doc """
  Gets a single match_message.

  Returns nil if the MatchMessage does not exist.

  ## Examples

      iex> get_match_message("uuid")
      %MatchMessage{}

      iex> get_match_message("uuid")
      nil

  """
  @spec get_match_message(MatchMessage.id()) :: MatchMessage.t() | nil
  @spec get_match_message(MatchMessage.id(), Teiserver.query_args()) :: MatchMessage.t() | nil
  def get_match_message(match_message_id, query_args \\ []) do
    (query_args ++ [id: match_message_id])
    |> MatchMessageQueries.match_message_query()
    |> Repo.one()
  end

  @doc """
  Creates a match_message.

  ## Examples

      iex> create_match_message(%{field: value})
      {:ok, %MatchMessage{}}

      iex> create_match_message(%{field: bad_value})
      {:error, %Ecto.Changeset{}}

  """
  @spec create_match_message(map) :: {:ok, MatchMessage.t()} | {:error, Ecto.Changeset.t()}
  def create_match_message(attrs \\ %{}) do
    %MatchMessage{}
    |> MatchMessage.changeset(attrs)
    |> Repo.insert()
  end

  @doc """
  Updates a match_message.

  ## Examples

      iex> update_match_message(match_message, %{field: new_value})
      {:ok, %MatchMessage{}}

      iex> update_match_message(match_message, %{field: bad_value})
      {:error, %Ecto.Changeset{}}

  """
  @spec update_match_message(MatchMessage.t(), map) ::
          {:ok, MatchMessage.t()} | {:error, Ecto.Changeset.t()}
  def update_match_message(%MatchMessage{} = match_message, attrs) do
    match_message
    |> MatchMessage.changeset(attrs)
    |> Repo.update()
  end

  @doc """
  Deletes a match_message.

  ## Examples

      iex> delete_match_message(match_message)
      {:ok, %MatchMessage{}}

      iex> delete_match_message(match_message)
      {:error, %Ecto.Changeset{}}

  """
  @spec delete_match_message(MatchMessage.t()) ::
          {:ok, MatchMessage.t()} | {:error, Ecto.Changeset.t()}
  def delete_match_message(%MatchMessage{} = match_message) do
    Repo.delete(match_message)
  end

  @doc """
  Returns an `%Ecto.Changeset{}` for tracking match_message changes.

  ## Examples

      iex> change_match_message(match_message)
      %Ecto.Changeset{data: %MatchMessage{}}

  """
  @spec change_match_message(MatchMessage.t(), map) :: Ecto.Changeset.t()
  def change_match_message(%MatchMessage{} = match_message, attrs \\ %{}) do
    MatchMessage.changeset(match_message, attrs)
  end
end