lib/plugin_manager/state/plugin_ets.ex

defmodule MishkaInstaller.PluginETS do
  @moduledoc """
  This module provides a series of essential functions for storing plugins on ETS.
  """
  use GenServer
  require Logger
  @ets_table :plugin_ets_state
  @sync_with_database 100_000

  def start_link(args) do
    GenServer.start_link(__MODULE__, args, name: __MODULE__)
  end

  @doc false
  def child_spec(process_name) do
    %{
      id: __MODULE__,
      start: {__MODULE__, :start_link, [process_name]},
      restart: :transient,
      max_restarts: 4
    }
  end

  @impl true
  def init(_state) do
    Logger.info("The ETS state of plugin was staretd")
    Process.send_after(self(), :sync_with_database, @sync_with_database)

    {:ok,
     %{
       set:
         ETS.Set.new!(
           name: @ets_table,
           protection: :public,
           read_concurrency: true,
           write_concurrency: true
         )
     }}
  end

  @impl true
  def terminate(_reason, _state) do
    Logger.warn("Your ETS state of plugin was restarted by a problem")
    sync_with_database()
  end

  @impl true
  def handle_info(:sync_with_database, state) do
    Logger.info("Plugin ETS state was synced with database")
    Process.send_after(self(), :sync_with_database, @sync_with_database)
    {:noreply, state}
  end

  @doc """
  Storing a plug-in on the ETS table.

  ## Examples
  ```elixir
  event = %MishkaInstaller.PluginState{name: "MishkaUser.SuccessLogin", event: Atom.to_string(@ref), priority: 1}
  MishkaInstaller.PluginETS.push(event)
  ```
  """
  def push(%MishkaInstaller.PluginState{name: name, event: event} = state) do
    ETS.Set.put!(table(), {String.to_atom(name), event, state})
  end

  @doc """
  Getting a plug-in from the ETS table.

  ## Examples
  ```elixir
   MishkaInstaller.PluginETS.get_all(module: "TestModule.a_plugin")
  ```
  """
  def get(module: name) do
    case ETS.Set.get(table(), String.to_atom(name)) do
      {:ok, {_key, _event, data}} -> data
      _ -> {:error, :get, :not_found}
    end
  end

  @doc """
  Getting all plug-ins from the ETS table.

  ## Examples
  ```elixir
    MishkaInstaller.PluginETS.get_all(event: "test_event")
  ```
  """
  def get_all(event: event_name) do
    ETS.Set.match!(table(), {:_, event_name, :"$3"})
    |> Enum.map(&List.first/1)
  end

  @doc """
   - Deleting a plug-in from the ETS table.
   - Deleting plugins from the ETS table based on a specific event.

  ## Examples
  ```elixir
  MishkaInstaller.PluginETS.delete(module: "TestModule.a_plugin")
  # or
  MishkaInstaller.PluginETS.delete(event: "test_event")
  ```
  """
  def delete(module: module_name) do
    ETS.Set.delete(table(), String.to_atom(module_name))
  end

  def delete(event: event_name) do
    ETS.Set.match_delete(table(), {:_, event_name, :_})
  end

  defp table() do
    case ETS.Set.wrap_existing(@ets_table) do
      {:ok, set} ->
        set

      _ ->
        start_link([])
        table()
    end
  end

  @doc """
  Initializing ETS table with database

  ## Examples
  ```elixir
  MishkaInstaller.PluginETS.sync_with_database()
  ```
  """
  def sync_with_database() do
    MishkaInstaller.Plugin.plugins()
    |> Enum.reject(&(&1.status in [:stopped]))
    |> Enum.map(&push/1)
  end
end