lib/cldr/locale_display/u.ex

defmodule Cldr.LocaleDisplay.U do
  @moduledoc false

  import Cldr.LocaleDisplay,
    only: [get_display_preference: 2, join_field_values: 2, replace_parens_with_brackets: 1]

  def display_name(locale, options) do
    {in_locale, _backend} = Cldr.locale_and_backend_from(options)
    module = Module.concat(in_locale.backend, :LocaleDisplay)
    {:ok, display_names} = module.display_names(in_locale)
    fields = Cldr.Validity.U.field_mapping() |> Enum.sort()

    for {_key, field} <- fields, !is_nil(value = Map.get(locale, field)) do
      format_key_value(field, value, locale, in_locale, display_names, options[:prefer])
    end
    |> join_field_values(display_names)
  end

  # If the value is not known then use the value
  # from the struct and display the key as well
  def format_key_value(field, value, locale, in_locale, display_names, prefer) do
    if value_name = get(field, value, display_names) do
      replace_parens_with_brackets(value_name)
    else
      key_name = get_in(display_names, [:keys, field])
      display_value(field, key_name, value, locale, in_locale, display_names, prefer)
    end
  end

  # Returns the localised value for the key. If there is
  # no available key name then just return the value.

  defp display_value(_key, nil, value, _transform, _in_locale, _display_names, _prefer)
       when is_binary(value) do
    replace_parens_with_brackets(value)
  end

  defp display_value(_key, nil, value, _transform, _in_locale, _display_names, _prefer)
       when is_atom(value) do
    value
    |> to_string()
    |> replace_parens_with_brackets()
  end

  defp display_value(key, key_name, value, locale, in_locale, display_names, prefer) do
    value_name =
      key
      |> get(key_name, value, locale, in_locale, display_names)
      |> Kernel.||(value)
      |> get_display_preference(prefer)
      |> :erlang.iolist_to_binary()
      |> replace_parens_with_brackets

    if key_name do
      display_pattern = get_in(display_names, [:locale_display_pattern, :locale_key_type_pattern])
      Cldr.Substitution.substitute([key_name, value_name], display_pattern)
    else
      replace_parens_with_brackets(value_name)
    end
  end

  defp get(:rg, _key_name, value, _locale, in_locale, display_names) do
    get_territory(value, in_locale, display_names)
  end

  defp get(:sd, _key_name, value, _locale, in_locale, _display_names) do
    get_subdivision(value, in_locale, in_locale.backend)
  end

  defp get(:dx, _key_name, value, _locale, _in_locale, display_names) do
    case get_script(value, display_names) do
      nil -> nil
      %{standard: script} -> String.downcase(script)
    end
  end

  defp get(:timezone, _key_name, value, _locale, in_locale, _display_names) do
    get_timezone(value, in_locale)
  end

  defp get(:currency, _key_name, value, _locale, in_locale, _display_names) do
    get_currency(value, in_locale)
  end

  defp get(:col_reorder, _key_name, values, _locale, _in_locale, display_names) do
    Enum.map(values, fn value ->
      get_script(value, display_names) ||
        get_in(display_names, [:types, :col_reorder, value]) ||
        to_string(value)
    end)
    |> join_field_values(display_names)
  end

  defp get(_key, key_name, value, _locale, _in_locale, display_names) do
    get_in(display_names, [:types, key_name, value])
  end

  # The only field that key the key and the type
  # with different names
  defp get(:col_reorder, [value], display_names) do
    get_in(display_names, [:types, :col_reorder, value])
  end

  defp get(field, [value], display_names) do
    get_in(display_names, [:types, field, value])
  end

  defp get(field, value, display_names) do
    get_in(display_names, [:types, field, value])
  end

  # Territory code is an atom
  defp get_territory(territory, locale, display_names) do
    get_in(display_names, [:territory, territory]) ||
      get_subdivision(territory, locale, locale.backend)
  end

  defp get_script(script, display_names) do
    script = Cldr.Validity.Script.validate(script) |> elem(1)
    get_in(display_names, [:script, script])
  end

  defp get_subdivision(subdivision, locale, backend) do
    backend_module = Module.concat(backend, Territory)
    subdivision = Cldr.Validity.Subdivision.validate(subdivision) |> elem(1)
    backend_module.known_subdivisions(locale)[subdivision]
  end

  def get_timezone(timezone, locale) do
    backend_module = Module.concat(locale.backend, LocaleDisplay)
    {:ok, territory_format} = backend_module.territory_format(locale)

    with {:ok, display_name} <- timezone_display_name(timezone, locale) do
      Cldr.Substitution.substitute([display_name], territory_format)
    end
  end

  def timezone_display_name(timezone, locale) do
    cond do
      territory = territory_has_only_this_zone(timezone) ->
        Cldr.Territory.display_name(territory, locale: locale)

      exemplar_city = exemplar_city(timezone, locale) ->
        {:ok, exemplar_city}

      derived_name = derive_zone_name(timezone, locale) ->
        {:ok, derived_name}

      true ->
        {:ok, timezone}
    end
  end

  # The time zone was not found in the time zone data. However, if the region exists
  # and there is only one other part, then form the time zone name by interpreting
  # the second part as a city name by replacing "_" with " ". This applies to zones like
  # "America/Los_Angeles", "America/New_York" and so on.

  defp derive_zone_name(zone, locale) do
    backend_module = Module.concat(locale.backend, LocaleDisplay)
    {:ok, zone_names} = backend_module.time_zone_names(locale)
    zone_parts = String.split(zone, "/")
    downcase_zone_parts = Enum.map(zone_parts, &String.downcase/1)

    if Map.get(zone_names, hd(downcase_zone_parts)) do
      zone_parts
      |> List.last()
      |> String.replace("_", " ")
    end
  end

  defp territory_has_only_this_zone(zone) do
    territory = Cldr.Timezone.territories_by_timezone[zone]

    if Cldr.Timezone.timezone_count_for_territory(territory) == 1 do
      territory
    end
  end

  defp exemplar_city(zone, locale) do
    backend_module = Module.concat(locale.backend, LocaleDisplay)
    {:ok, zone_names} = backend_module.time_zone_names(locale)
    zone_parts = String.split(zone, "/")
    downcase_zone_parts = Enum.map(zone_parts, &String.downcase/1)

    get_in(zone_names, downcase_zone_parts ++ [:exemplar_city])
  end

  def get_currency(currency, locale) do
    case Cldr.Currency.currency_for_code(currency, locale.backend) do
      {:ok, currency} -> currency.symbol
      _other -> nil
    end
  end

  defimpl Cldr.DisplayName, for: Cldr.LanguageTag.U do
    def display_name(language_tag, options) do
      Cldr.LocaleDisplay.U.display_name(language_tag, options)
    end
  end
end