lib/notifier_server.ex

defmodule Perudex.NotifierServer do
  @moduledoc """
  This module is a GenServer to handle communication going to the players by defining an interface a module need to implements.
  """

  use GenServer, restart: :transient

  @callback start_game(GameServer.callback_arg(), Game.player_id(), [Game.player_id()]) :: any
  @callback new_hand(GameServer.callback_arg(), Game.player_id(), Hand.t()) :: any
  @callback move(GameServer.callback_arg(), Game.player_id()) :: any
  @callback reveal_players_hands(GameServer.callback_arg(), Game.player_id(), [
              {Game.player_id(), Perudex.Hand.t()}
            ]) :: any
  @callback unauthorized_move(GameServer.callback_arg(), Game.player_id()) :: any
  @callback new_bid(GameServer.callback_arg(), Game.player_id(), Game.bid()) :: any
  @callback invalid_bid(GameServer.callback_arg(), Game.player_id()) :: any
  @callback illegal_move(GameServer.callback_arg(), Game.player_id()) :: any
  @callback successful_calza(GameServer.callback_arg(), Game.player_id()) :: any
  @callback unsuccessful_calza(GameServer.callback_arg(), Game.player_id()) :: any
  @callback successful_dudo(GameServer.callback_arg(), Game.player_id()) :: any
  @callback unsuccessful_dudo(GameServer.callback_arg(), Game.player_id()) :: any
  @callback winner(GameServer.callback_arg(), Game.player_id(), Game.player_id()) :: any
  @callback loser(GameServer.callback_arg(), Game.player_id(), Game.player_id()) :: any

  def start_link({game_id, player}),
    do:
      GenServer.start_link(__MODULE__, {game_id, player}, name: service_name(game_id, player.id))

  @impl true
  def init({game_id, player}), do: {:ok, %{game_id: game_id, player: player}}

  @spec publish(any, any, Game.player_instruction()) :: :ok
  def publish(game_id, player_id, instruction),
    do: GenServer.cast(service_name(game_id, player_id), {:notify, instruction})

  @impl true
  def handle_cast({:notify, instruction}, state) do
    {fun, args} = decode_instruction(instruction)

    all_args = [state.player.callback_arg, state.player.id | args]

    apply(state.player.callback_mod, fun, all_args)
    {:noreply, state}
  end

  defp decode_instruction({:game_started, players}), do: {:start_game, [players]}
  defp decode_instruction({:new_hand, hand}), do: {:new_hand, [hand]}
  defp decode_instruction(:move), do: {:move, []}
  defp decode_instruction({:reveal_players_hands, hands}), do: {:reveal_players_hands, [hands]}
  defp decode_instruction(:unauthorized_move), do: {:unauthorized_move, []}
  defp decode_instruction({:new_bid, bid}), do: {:new_bid, [bid]}
  defp decode_instruction(:invalid_bid), do: {:invalid_bid, []}
  defp decode_instruction(:illegal_move), do: {:illegal_move, []}
  defp decode_instruction(:successful_calza), do: {:successful_calza, []}
  defp decode_instruction(:unsuccessful_calza), do: {:unsuccessful_calza, []}
  defp decode_instruction(:successful_dudo), do: {:successful_dudo, []}
  defp decode_instruction(:unsuccessful_dudo), do: {:unsuccessful_dudo, []}
  defp decode_instruction({:winner, winner_id}), do: {:winner, [winner_id]}
  defp decode_instruction({:loser, loser_id}), do: {:loser, [loser_id]}

  defp service_name(game_id, player_id),
    do: Perudex.service_name({__MODULE__, game_id, player_id})
end