defmodule LangTagMatcher do
@moduledoc """
"""
@type language :: String.t()
@type languages :: list()
@type subtag_rep :: list(map())
@spec match(languages(), languages(), languages()) :: languages()
@doc """
Return a list of all languages in available that matches criteria,
in the ascending order by the index of the matching criteria. The fallback
languages are added at the back if they are not already in the list.
"""
def match(criteria, available, fallback) do
criteria_subtags = make_subtags_with_lang(criteria)
available_subtags = make_subtags_with_lang(available)
criteria_subtags
|> Enum.flat_map(fn c -> languages_matched(available_subtags, c) end)
|> Enum.map(fn {lang, _subtags} -> lang end)
|> Enum.concat(fallback)
|> Enum.uniq()
end
defp languages_matched(available_subtags, {_criterion, criterion_subtags}) do
available_subtags
|> Enum.filter(fn {_lang, lang_subtags} ->
meets_criterion?(criterion_subtags, lang_subtags)
end)
end
defp language_cmp(a, b) do
cond do
a["Subtag"] == b["Subtag"] ->
:equal
a["Record"]["Macrolanguage"] == b["Subtag"] ->
:subset
a["Subtag"] == b["Record"]["Macrolanguage"] ->
:superset
true ->
:unrelated
end
end
defp tag_reducer(idx) do
fn cur, acc ->
key = cur["Record"]["Type"]
Map.put(acc, key, (acc[key] || [nil, nil]) |> List.replace_at(idx, cur["Subtag"]))
end
end
defp subtags_cmp(as, bs) do
subtags_map = Enum.reduce(as, %{}, tag_reducer(0))
subtags_map = Enum.reduce(bs, subtags_map, tag_reducer(1))
Enum.map(subtags_map, fn {_, [a, b]} ->
cond do
a == b -> :equal
is_nil(a) and not is_nil(b) -> :superset
not is_nil(a) and is_nil(b) -> :subset
true -> :unrelated
end
end)
end
defp meets_criterion?([c_lang | c_rest] = _criterion, [a_lang | a_rest] = _available_lang) do
with r when r in [:equal, :superset] <- language_cmp(c_lang, a_lang),
rs <- subtags_cmp(c_rest, a_rest),
true <- Enum.all?(rs, fn r -> r in [:equal, :superset] end) do
true
else
_ -> false
end
end
defp make_subtags_with_lang(langs) when is_list(langs) do
Enum.map(langs, &make_subtags_with_lang/1)
end
defp make_subtags_with_lang(lang) do
{lang, make_subtags(lang)}
end
defp make_subtags(lang) do
LangTags.Tag.new(lang)
|> make_preferred()
|> LangTags.Tag.subtags()
end
defp make_preferred(tag) do
pf = LangTags.Tag.preferred(tag)
if pf, do: pf, else: tag
end
end