lib/fluent/store.ex

defmodule Fluent.Store do
  @moduledoc """
  This module hande references to active bundles, that are using during runtimne
  of the program
  """
  alias Fluent.Assembly.Source

  @behaviour Access
  defstruct bundles: %{}

  @type t :: %__MODULE__{}

  #############################################################################
  ### Access behaviour implementation
  #############################################################################

  @impl Access
  defdelegate fetch(term, key), to: Map

  @impl Access
  defdelegate get_and_update(data, key, function), to: Map

  @impl Access
  defdelegate pop(data, key), to: Map

  #############################################################################
  ### Initialization tree
  #############################################################################

  @spec get_store(assembly :: Fluent.Assembly.t()) :: Fluent.Store.t()
  def get_store(assembly) do
    case :persistent_term.get(assembly, nil) do
      assembly = %__MODULE__{} -> assembly
      _ -> initialize_store(assembly)
    end
  end

  @spec initialize_store(assembly :: Fluent.Assembly.t()) :: {:ok, t()}
  def initialize_store(assembly) do
    assembly
    |> Source.locales()
    |> Enum.each(&initialize_locale(assembly, &1))

    :persistent_term.get(assembly)
  end

  @doc """

  Example:

  iex> Fluent.Store.initialize_bundle(MyApp.Fluent, "en-US")
  """
  @spec initialize_locale(assembly :: Fluent.Assembly.t(), locale :: Fluent.locale()) ::
          {:ok, Fluent.bundle()}
  def initialize_locale(assembly, locale) do
    with {:ok, bundle_ref} when is_reference(bundle_ref) <-
           Fluent.Native.init(locale, use_isolating: assembly.__config__(:use_isolating)),
         :ok <- persist_bundle(assembly, locale, bundle_ref),
         :ok <- add_resources(bundle_ref, assembly, locale) do
      {:ok, bundle_ref}
    end
  end

  @spec format_pattern(Fluent.Store.t(), Fluent.locale(), String.t(), Keyword.t()) :: any
  def format_pattern(store, locale, message, args \\ []) do
    case Map.get(store.bundles, locale) do
      nil -> {:error, :bad_locale}
      bundle -> Fluent.Native.format_pattern(bundle, message, args)
    end
  end

  @spec known_locales(store :: t()) :: [Fluent.locale()]
  def known_locales(store) do
    Map.keys(store[:bundles])
  end

  @spec add_resources(
          bundle_reference :: Fluent.bundle(),
          assembly :: Fluent.Assembly.t(),
          locale :: Fluent.locale()
        ) :: :ok
  defp add_resources(bundle_reference, assembly, locale) do
    assembly
    |> Source.ftl_files_pathes(locale)
    |> Enum.each(fn path ->
      case File.read(path) do
        {:ok, raw_data} -> Fluent.Native.with_resource(bundle_reference, raw_data)
        # May be here is needed to handle errors
        {:error, _} -> :skip_this_file
      end
    end)
  end

  @spec persist_bundle(
          assembly :: Fluent.Assembly.t(),
          locale :: Fluent.locale(),
          bundle_reference :: reference()
        ) :: :ok
  defp persist_bundle(assembly, locale, bundle_reference) do
    store =
      put_in(
        :persistent_term.get(assembly, %__MODULE__{}),
        [:bundles, locale],
        bundle_reference
      )

    :persistent_term.put(assembly, store)
  end
end