lib/cldr/currency/extra.ex

defmodule Cldr.Currency.Extra do
  @moduledoc """
  Extra utils for `Cldr.Currency`.

  > #### Note {: .info}
  >
  > Strictly speaking, this package doesn't rely on data provided by CLDR.
  > However, it addresses issues related to `Cldr.Currency`, so it uses the relevant naming
  > convention. If CLDR provides the related data one day, there will be no need to rename.

  ## References

  ### ISO 3166

    * [ISO 3166 from WIKIPEDIA](https://en.wikipedia.org/wiki/ISO_3166):
      * [ISO 3166-1](https://en.wikipedia.org/wiki/ISO_3166-1)
      * [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-2)
      * [ISO 3166-3](https://en.wikipedia.org/wiki/ISO_3166-3)

  ### ISO 4217

    * [ISO 4217 from WIKIPEDIA](https://en.wikipedia.org/wiki/ISO_4217)

  ### WIKIPEDIA

    * [List of circulating currencies](https://en.wikipedia.org/wiki/List_of_circulating_currencies)

  """

  alias __MODULE__.TerritoryCode

  @currency_legal_tender %{
    AED: %{symbol: "د.إ"},
    AFN: %{symbol: "؋"},
    ALL: %{symbol: "L"},
    AMD: %{symbol: "֏"},
    ANG: %{symbol: "NAƒ"},
    AOA: %{symbol: "Kz"},
    ARS: %{symbol: "Arg$"},
    AUD: %{symbol: "A$"},
    AWG: %{symbol: "Afl."},
    AZN: %{symbol: "₼"},
    BAM: %{symbol: "KM"},
    BBD: %{symbol: "Bds$"},
    BDT: %{symbol: "৳"},
    BGN: %{symbol: "лв."},
    BHD: %{symbol: "BD"},
    BIF: %{symbol: "FBu"},
    BMD: %{symbol: "BD$"},
    BND: %{symbol: "B$"},
    BOB: %{symbol: "Bs."},
    BRL: %{symbol: "R$"},
    BSD: %{symbol: "B$"},
    BTN: %{symbol: "Nu."},
    BWP: %{symbol: "P"},
    BYN: %{symbol: "Br"},
    BZD: %{symbol: "BZ$"},
    CAD: %{symbol: "C$"},
    CDF: %{symbol: "Fr"},
    CHF: %{symbol: "Fr"},
    CLP: %{symbol: "CLP$"},
    CNY: %{symbol: "¥"},
    COP: %{symbol: "COP$"},
    CRC: %{symbol: "₡"},
    CUC: %{symbol: "$"},
    CUP: %{symbol: "$MN"},
    CVE: %{symbol: "Esc"},
    CZK: %{symbol: "Kč"},
    DJF: %{symbol: "Fdj"},
    DKK: %{symbol: "Kr"},
    DOP: %{symbol: "RD$"},
    DZD: %{symbol: "دج"},
    EGP: %{symbol: "E£"},
    ERN: %{symbol: "Nfk"},
    ETB: %{symbol: "Br"},
    EUR: %{symbol: "€"},
    FJD: %{symbol: "FJ$"},
    FKP: %{symbol: "£"},
    GBP: %{symbol: "£"},
    GEL: %{symbol: "₾"},
    GHS: %{symbol: "GH₵"},
    GIP: %{symbol: "£"},
    GMD: %{symbol: "D"},
    GNF: %{symbol: "FG"},
    GTQ: %{symbol: "Q"},
    GYD: %{symbol: "G$"},
    HKD: %{symbol: "HK$"},
    HNL: %{symbol: "L"},
    HTG: %{symbol: "G"},
    HUF: %{symbol: "Ft"},
    IDR: %{symbol: "Rp"},
    ILS: %{symbol: "₪"},
    INR: %{symbol: "₹"},
    IQD: %{symbol: "د.ع"},
    IRR: %{symbol: "﷼"},
    ISK: %{symbol: "kr"},
    JMD: %{symbol: "J$"},
    JOD: %{symbol: "د.أ"},
    JPY: %{symbol: "¥"},
    KES: %{symbol: "KSh"},
    KGS: %{symbol: "⃀"},
    KHR: %{symbol: "៛"},
    KMF: %{symbol: "CF"},
    KPW: %{symbol: "₩"},
    KRW: %{symbol: "₩"},
    KWD: %{symbol: "د.ك"},
    KYD: %{symbol: "$"},
    KZT: %{symbol: "₸"},
    LAK: %{symbol: "₭"},
    LBP: %{symbol: "L£"},
    LKR: %{symbol: "Rs"},
    LRD: %{symbol: "CI$"},
    LSL: %{symbol: "L"},
    LYD: %{symbol: "ل.د"},
    MAD: %{symbol: "درهم"},
    MDL: %{symbol: "L"},
    MGA: %{symbol: "Ar"},
    MKD: %{symbol: "ден"},
    MMK: %{symbol: "K"},
    MNT: %{symbol: "₮"},
    MOP: %{symbol: "MOP$"},
    MRU: %{symbol: "UM"},
    MUR: %{symbol: "Rs"},
    MVR: %{symbol: "Rf"},
    MWK: %{symbol: "K"},
    MXN: %{symbol: "Mex$"},
    MYR: %{symbol: "RM"},
    MZN: %{symbol: "MT"},
    NAD: %{symbol: "N$"},
    NGN: %{symbol: "₦"},
    NIO: %{symbol: "C$"},
    NOK: %{symbol: "kr"},
    NPR: %{symbol: "Rs"},
    NZD: %{symbol: "$"},
    OMR: %{symbol: "ر.ع."},
    PAB: %{symbol: "B/."},
    PEN: %{symbol: "S/"},
    PGK: %{symbol: "K"},
    PHP: %{symbol: "₱"},
    PKR: %{symbol: "Rs"},
    PLN: %{symbol: "zł"},
    PYG: %{symbol: "₲"},
    QAR: %{symbol: "ر.ق"},
    RON: %{symbol: "Lei"},
    RSD: %{symbol: "дин"},
    RUB: %{symbol: "₽"},
    RWF: %{symbol: "RF"},
    SAR: %{symbol: "ر.س"},
    SBD: %{symbol: "SI$"},
    SCR: %{symbol: "SRe"},
    SDG: %{symbol: "ج.س"},
    SEK: %{symbol: "kr"},
    SGD: %{symbol: "S$"},
    SHP: %{symbol: "£"},
    SLE: %{symbol: "Le"},
    SOS: %{symbol: "Sh.So."},
    SRD: %{symbol: "Sr$"},
    SSP: %{symbol: "£"},
    STN: %{symbol: "Db"},
    SVC: %{symbol: "₡"},
    SYP: %{symbol: "£"},
    SZL: %{symbol: "L"},
    THB: %{symbol: "฿"},
    TJS: %{symbol: "SM"},
    TMT: %{symbol: "m"},
    TND: %{symbol: "د.ت"},
    TOP: %{symbol: "T$"},
    TRY: %{symbol: "₺"},
    TTD: %{symbol: "TT$"},
    TWD: %{symbol: "NT$"},
    TZS: %{symbol: "TSh"},
    UAH: %{symbol: "₴"},
    UGX: %{symbol: "USh"},
    USD: %{symbol: "$"},
    UYU: %{symbol: "$U"},
    UZS: %{symbol: "so'm"},
    VES: %{symbol: "Bs."},
    VND: %{symbol: "₫"},
    VUV: %{symbol: "VT"},
    WST: %{symbol: "WS$"},
    XAF: %{symbol: "Fr"},
    XCD: %{symbol: "EC$"},
    XOF: %{symbol: "Fr"},
    XPF: %{symbol: "F"},
    YER: %{symbol: "﷼"},
    ZAR: %{symbol: "R"},
    ZMW: %{symbol: "ZK"}
  }
  @currency_codes_legal_tender Map.keys(@currency_legal_tender)

  # these currency codes are not included in CLDR
  @currency_legal_tender_extra %{
    GGP: %{symbol: "£"},
    IMP: %{symbol: "£"},
    JEP: %{symbol: "£"}
  }
  @currency_codes_legal_tender_extra Map.keys(@currency_legal_tender_extra)

  # these currency codes are deprecated but still used
  @currency_legal_tender_to_be_deprecated %{
    HRK: %{symbol: "kn"},
    SLL: %{symbol: "Le"}
  }
  @currency_codes_legal_tender_to_be_deprecated Map.keys(@currency_legal_tender_to_be_deprecated)

  @currency_legal_tender_deprecated %{
    ADP: %{symbol: nil},
    AFA: %{symbol: nil},
    ALK: %{symbol: nil},
    AOK: %{symbol: nil},
    AON: %{symbol: nil},
    AOR: %{symbol: nil},
    ARA: %{symbol: nil},
    ARL: %{symbol: nil},
    ARM: %{symbol: nil},
    ARP: %{symbol: nil},
    ATS: %{symbol: nil},
    AZM: %{symbol: nil},
    BAD: %{symbol: nil},
    BAN: %{symbol: nil},
    BEC: %{symbol: nil},
    BEF: %{symbol: nil},
    BEL: %{symbol: nil},
    BGM: %{symbol: nil},
    BGL: %{symbol: nil},
    BGO: %{symbol: nil},
    BOL: %{symbol: nil},
    BOP: %{symbol: nil},
    BRB: %{symbol: nil},
    BRC: %{symbol: nil},
    BRE: %{symbol: nil},
    BRN: %{symbol: nil},
    BRR: %{symbol: nil},
    BRZ: %{symbol: nil},
    BUK: %{symbol: nil},
    BYB: %{symbol: nil},
    BYR: %{symbol: nil},
    CLE: %{symbol: nil},
    CNX: %{symbol: nil},
    CSD: %{symbol: nil},
    CSK: %{symbol: nil},
    CYP: %{symbol: nil},
    DDM: %{symbol: nil},
    DEM: %{symbol: nil},
    ECS: %{symbol: nil},
    ECV: %{symbol: nil},
    EEK: %{symbol: nil},
    ESA: %{symbol: nil},
    ESB: %{symbol: nil},
    ESP: %{symbol: "₧"},
    FIM: %{symbol: nil},
    FRF: %{symbol: nil},
    GEK: %{symbol: nil},
    GHC: %{symbol: nil},
    GNS: %{symbol: nil},
    GQE: %{symbol: nil},
    GRD: %{symbol: nil},
    GWE: %{symbol: nil},
    GWP: %{symbol: nil},
    HRD: %{symbol: nil},
    IEP: %{symbol: nil},
    ILP: %{symbol: nil},
    ILR: %{symbol: nil},
    ISJ: %{symbol: nil},
    ITL: %{symbol: nil},
    KRH: %{symbol: nil},
    KRO: %{symbol: nil},
    LTL: %{symbol: "Lt"},
    LTT: %{symbol: nil},
    LUC: %{symbol: nil},
    LUF: %{symbol: nil},
    LUL: %{symbol: nil},
    LVL: %{symbol: "Ls"},
    LVR: %{symbol: nil},
    MAF: %{symbol: nil},
    MCF: %{symbol: nil},
    MDC: %{symbol: nil},
    MGF: %{symbol: nil},
    MKN: %{symbol: nil},
    MLF: %{symbol: nil},
    MRO: %{symbol: nil},
    MTL: %{symbol: nil},
    MTP: %{symbol: nil},
    MVP: %{symbol: nil},
    MXP: %{symbol: nil},
    MZE: %{symbol: nil},
    MZM: %{symbol: nil},
    NIC: %{symbol: nil},
    NLG: %{symbol: nil},
    PEI: %{symbol: nil},
    PES: %{symbol: nil},
    PLZ: %{symbol: nil},
    PTE: %{symbol: nil},
    RHD: %{symbol: nil},
    ROL: %{symbol: nil},
    RUR: %{symbol: "p."},
    SDD: %{symbol: nil},
    SDP: %{symbol: nil},
    SIT: %{symbol: nil},
    SKK: %{symbol: nil},
    SRG: %{symbol: nil},
    STD: %{symbol: nil},
    SUR: %{symbol: nil},
    TJR: %{symbol: nil},
    TMM: %{symbol: nil},
    TPE: %{symbol: nil},
    TRL: %{symbol: nil},
    UAK: %{symbol: nil},
    UGS: %{symbol: nil},
    USS: %{symbol: nil},
    UYP: %{symbol: nil},
    VEB: %{symbol: nil},
    VEF: %{symbol: "Bs"},
    VNN: %{symbol: nil},
    XEU: %{symbol: nil},
    XFO: %{symbol: nil},
    XFU: %{symbol: nil},
    YDD: %{symbol: nil},
    YUD: %{symbol: nil},
    YUM: %{symbol: nil},
    YUN: %{symbol: nil},
    YUR: %{symbol: nil},
    ZAL: %{symbol: nil},
    ZMK: %{symbol: nil},
    ZRN: %{symbol: nil},
    ZRZ: %{symbol: nil},
    ZWD: %{symbol: nil},
    ZWL: %{symbol: nil},
    ZWR: %{symbol: nil}
  }
  @currency_codes_legal_tender_deprecated Map.keys(@currency_legal_tender_deprecated)

  @currency_non_legal_tender %{
    BOV: %{symbol: nil},
    CHE: %{symbol: nil},
    CHW: %{symbol: nil},
    CLF: %{symbol: nil},
    CNH: %{symbol: nil},
    COU: %{symbol: nil},
    MXV: %{symbol: nil},
    USN: %{symbol: nil},
    UYI: %{symbol: nil},
    UYW: %{symbol: nil},
    VED: %{symbol: nil}
  }
  @currency_codes_non_legal_tender Map.keys(@currency_non_legal_tender)

  @currency_metal %{
    XAG: %{symbol: nil},
    XAU: %{symbol: nil},
    XPD: %{symbol: nil},
    XPT: %{symbol: nil}
  }
  @currency_codes_metal Map.keys(@currency_metal)

  @currency_imf %{
    XDR: %{symbol: nil}
  }
  @currency_codes_imf Map.keys(@currency_imf)

  @currency_imf_internal %{
    XBA: %{symbol: nil},
    XBB: %{symbol: nil},
    XBC: %{symbol: nil},
    XBD: %{symbol: nil},
    XSU: %{symbol: nil},
    XUA: %{symbol: nil}
  }
  @currency_codes_imf_internal Map.keys(@currency_imf_internal)

  @currency_imf_deprecated %{
    XRE: %{symbol: nil}
  }
  @currency_codes_imf_deprecated Map.keys(@currency_imf_deprecated)

  @currency_testing %{
    XTS: %{symbol: nil}
  }
  @currency_codes_testing Map.keys(@currency_testing)

  @currency_misc %{
    XXX: %{symbol: nil}
  }
  @currency_codes_misc Map.keys(@currency_misc)

  @currency_codes_circulating [
                                @currency_codes_legal_tender,
                                @currency_codes_legal_tender_extra,
                                @currency_codes_legal_tender_to_be_deprecated,
                                @currency_codes_metal,
                                @currency_codes_imf
                              ]
                              |> Enum.concat()
                              |> Enum.sort()

  @currency_codes_circulating_legal_tender [
                                             @currency_codes_legal_tender,
                                             @currency_codes_legal_tender_extra,
                                             @currency_codes_legal_tender_to_be_deprecated
                                           ]
                                           |> Enum.concat()
                                           |> Enum.sort()

  @currency_all %{}
                |> Map.merge(@currency_legal_tender)
                |> Map.merge(@currency_legal_tender_extra)
                |> Map.merge(@currency_legal_tender_to_be_deprecated)
                |> Map.merge(@currency_legal_tender_deprecated)
                |> Map.merge(@currency_non_legal_tender)
                |> Map.merge(@currency_metal)
                |> Map.merge(@currency_imf)
                |> Map.merge(@currency_imf_internal)
                |> Map.merge(@currency_imf_deprecated)
                |> Map.merge(@currency_testing)
                |> Map.merge(@currency_misc)

  @currency_codes_all @currency_all
                      |> Map.keys()
                      |> Enum.sort()

  @type currency_group ::
          :legal_tender
          | :legal_tender_extra
          | :legal_tender_to_be_deprecated
          | :legal_tender_deprecated
          | :non_legal_tender
          | :metal
          | :imf
          | :imf_internal
          | :imf_deprecated
          | :testing
          | :misc
          | :circulating
          | :circulating_legal_tender
  @type currency_code :: Cldr.Currency.code()
  @type territory_code :: Cldr.Currency.territory()
  @type symbol :: String.t()
  @type message :: String.t()

  # For testing
  @doc false
  def cldr_currency_codes() do
    []
    |> Kernel.++(@currency_codes_legal_tender)
    |> Kernel.++(@currency_codes_legal_tender_to_be_deprecated)
    |> Kernel.++(@currency_codes_legal_tender_deprecated)
    |> Kernel.++(@currency_codes_non_legal_tender)
    |> Kernel.++(@currency_codes_metal)
    |> Kernel.++(@currency_codes_imf)
    |> Kernel.++(@currency_codes_imf_internal)
    |> Kernel.++(@currency_codes_imf_deprecated)
    |> Kernel.++(@currency_codes_testing)
    |> Kernel.++(@currency_codes_misc)
    |> Enum.sort()
  end

  # For testing
  @doc false
  def all_currency_codes(), do: @currency_codes_all

  @doc """
  Returns a list of currency codes belong to a group.
  """
  @spec currency_codes(currency_group()) :: [currency_code()]
  def currency_codes(:legal_tender), do: @currency_codes_legal_tender
  def currency_codes(:legal_tender_extra), do: @currency_codes_legal_tender_extra

  def currency_codes(:legal_tender_to_be_deprecated),
    do: @currency_codes_legal_tender_to_be_deprecated

  def currency_codes(:legal_tender_deprecated), do: @currency_codes_legal_tender_deprecated
  def currency_codes(:non_legal_tender), do: @currency_codes_non_legal_tender
  def currency_codes(:metal), do: @currency_codes_metal
  def currency_codes(:imf), do: @currency_codes_imf
  def currency_codes(:imf_internal), do: @currency_codes_imf_internal
  def currency_codes(:imf_deprecated), do: @currency_codes_imf_deprecated
  def currency_codes(:testing), do: @currency_codes_testing
  def currency_codes(:misc), do: @currency_codes_misc
  def currency_codes(:circulating), do: @currency_codes_circulating
  def currency_codes(:circulating_legal_tender), do: @currency_codes_circulating_legal_tender

  def currency_codes(currency_group),
    do: raise(ArgumentError, "unknown currency group - #{inspect(currency_group)}")

  @doc """
  Fetches a territory code corresponding to a given currency code.
  """
  @spec territory_code_for_code(currency_code()) ::
          {:ok, territory_code()}
          | {:error, {Cldr.UnknownCurrencyError, message()}}
          | {:error, {Cldr.UnknownTerritoryError, message()}}
  def territory_code_for_code(currency_code),
    do: TerritoryCode.fetch_territory_code(currency_code)

  @doc """
  Fetches a territory code corresponding to a given currency code.
  """
  @spec territory_code_for_code!(currency_code()) :: territory_code()
  def territory_code_for_code!(currency_code),
    do: TerritoryCode.fetch_territory_code!(currency_code)

  @doc """
  Fetches a symbol corresponding to given currency code.
  """
  @spec symbol(currency_code()) ::
          {:ok, symbol()}
          | {:error, {RuntimeError, String.t()}}
  def symbol(currency_code) do
    case get_in(@currency_all, [currency_code, :symbol]) do
      nil -> {:error, {RuntimeError, "no symbol found"}}
      symbol -> {:ok, symbol}
    end
  end

  @doc """
  Fetches a symbol corresponding to given currency code.
  """
  @spec symbol!(currency_code()) :: symbol()
  def symbol!(currency_code) do
    case get_in(@currency_all, [currency_code, :symbol]) do
      nil -> raise RuntimeError, "no symbol found"
      symbol -> symbol
    end
  end
end