lib/cadastre/country.ex

defmodule Cadastre.Country do
  @moduledoc """
  Country implementation
  """
  alias Cadastre.Language

  @external_resource Application.app_dir(:cadastre, "priv/data/countries.etf")

  @enforce_keys [:id]
  defstruct [:id]

  @type id :: <<_::16>>
  @type t :: %__MODULE__{id: id}

  external_data = @external_resource |> File.read!() |> :erlang.binary_to_term()
  ids = external_data |> Enum.map(&elem(&1, 0))

  @doc """
  Return all ids (ISO_3166-1)

  ## Examples
  ```
  iex> Cadastre.Country.ids() |> Enum.take(10)
  ["AD", "AE", "AF", "AG", "AI", "AL", "AM", "AO", "AQ", "AR"]
  ```
  """
  @spec ids :: [id]
  def ids, do: unquote(ids)

  @doc """
  Returns all countries

  ## Examples
  ```
  iex> Cadastre.Country.all() |> Enum.take(3)
  [
    %Cadastre.Country{id: "AD"},
    %Cadastre.Country{id: "AE"},
    %Cadastre.Country{id: "AF"}
  ]

  iex> Cadastre.Country.all() |> Enum.count()
  249
  ```
  """
  @spec all :: [t]
  def all, do: ids() |> Enum.map(&%__MODULE__{id: &1})

  @doc """
  Returns `%Cadastre.Country{}` for valid `id`.
  Returns `nil` for invalid `id`.

  ## Examples
  ```
  iex> Cadastre.Country.new("NL")
  %Cadastre.Country{id: "NL"}

  iex> Cadastre.Country.new("nl")
  %Cadastre.Country{id: "NL"}

  iex> Cadastre.Country.new("xx")
  nil
  ```
  """
  @spec new(id | any) :: t | nil
  def new(id) when id in unquote(ids), do: %__MODULE__{id: id}

  def new(str) when is_binary(str) do
    id = str |> String.upcase()

    case id in ids() do
      true -> %__MODULE__{id: id}
      _ -> nil
    end
  end

  def new(_), do: nil

  @doc """
  Returns country name translation for `locale`

  ## Examples
  ```
  iex> Cadastre.Country.new("NL") |> Cadastre.Country.name("be")
  "Нідэрланды"

  iex> Cadastre.Country.new("NL") |> Cadastre.Country.name(":)")
  "Netherlands"

  iex> Cadastre.Country.name("something wrong", "be")
  nil
  ```
  """
  @spec name(t, Language.id()) :: String.t()
  def name(country, locale)

  external_data
  |> Enum.each(fn {id, translations} ->
    translations = translations |> Map.new()
    en = translations |> Map.fetch!("en")

    def name(%__MODULE__{id: unquote(id)}, locale) do
      unquote(Macro.escape(translations)) |> Map.get(locale, unquote(en))
    end
  end)

  def name(_, _), do: nil
end