lib/phone.ex

defmodule Phone do
  @moduledoc ~S"""
  Phone is a real telephone number parser, that will help you get useful information from numbers.

  ## How to use

  Very simple to use:
      iex> Phone.parse("555132345678")
      {:ok, %{a2: "BR", a3: "BRA", country: "Brazil", international_code: "55", area_code: "51", number: "32345678", area_abbreviation: "RS", area_type: "state", area_name: "Rio Grande do Sul"}}
  """

  import Helper.Parser

  @doc """
  Parses a string or integer and returns a map with information about that number.

  ```
    iex> Phone.parse("555132345678")
    {:ok, %{a2: "BR", a3: "BRA", country: "Brazil", international_code: "55", area_code: "51", number: "32345678", area_abbreviation: "RS", area_type: "state", area_name: "Rio Grande do Sul"}}

    iex> Phone.parse("+55(51)3234-5678")
    {:ok, %{a2: "BR", a3: "BRA", country: "Brazil", international_code: "55", area_code: "51", number: "32345678", area_abbreviation: "RS", area_type: "state", area_name: "Rio Grande do Sul"}}

    iex> Phone.parse("55 51 3234-5678")
    {:ok, %{a2: "BR", a3: "BRA", country: "Brazil", international_code: "55", area_code: "51", number: "32345678", area_abbreviation: "RS", area_type: "state", area_name: "Rio Grande do Sul"}}

    iex> Phone.parse(555132345678)
    {:ok, %{a2: "BR", a3: "BRA", country: "Brazil", international_code: "55", area_code: "51", number: "32345678", area_abbreviation: "RS", area_type: "state", area_name: "Rio Grande do Sul"}}

  ```
  """
  @spec parse(String.t() | pos_integer()) :: {:ok, map()} | {:error, String.t()}
  def parse(number) when is_bitstring(number) do
    number
    |> prepare_number
    |> Phone.Countries.build()
  end

  def parse(number) when is_integer(number) do
    number = Integer.to_string(number)
    parse(number)
  end

  def parse(_) do
    {:error, "Not a valid parameter, only string or integer."}
  end

  defp prepare_number(number) do
    number = clear(number)

    try do
      number |> String.to_integer() |> Integer.to_string()
    rescue
      _ -> ""
    end
  end

  defp clear(number) when is_bitstring(number) do
    number
    |> String.graphemes()
    |> Enum.filter(fn n -> valid_char(n) end)
    |> Enum.join("")
  end

  defp valid_char("+"), do: false
  defp valid_char("("), do: false
  defp valid_char(")"), do: false
  defp valid_char("-"), do: false
  defp valid_char(" "), do: false
  defp valid_char(_), do: true

  @doc """
  Same as `parse/1`, except it raises on error.

  ```
  iex> Phone.parse!("555132345678")
  %{a2: "BR", a3: "BRA", country: "Brazil", international_code: "55", area_code: "51", number: "32345678", area_abbreviation: "RS", area_type: "state", area_name: "Rio Grande do Sul"}

  iex> Phone.parse!("+55(51)3234-5678")
  %{a2: "BR", a3: "BRA", country: "Brazil", international_code: "55", area_code: "51", number: "32345678", area_abbreviation: "RS", area_type: "state", area_name: "Rio Grande do Sul"}

  iex> Phone.parse!("55 51 3234-5678")
  %{a2: "BR", a3: "BRA", country: "Brazil", international_code: "55", area_code: "51", number: "32345678", area_abbreviation: "RS", area_type: "state", area_name: "Rio Grande do Sul"}

  iex> Phone.parse!(555132345678)
  %{a2: "BR", a3: "BRA", country: "Brazil", international_code: "55", area_code: "51", number: "32345678", area_abbreviation: "RS", area_type: "state", area_name: "Rio Grande do Sul"}

  ```
  """
  @spec parse!(String.t() | pos_integer()) :: map()
  def parse!(number) when is_bitstring(number) do
    number = clear(number)

    number =
      try do
        number |> String.to_integer() |> Integer.to_string()
      rescue
        _ -> ""
      end

    Phone.Countries.build!(number)
  end

  def parse!(number) when is_integer(number) do
    number = Integer.to_string(number)
    parse!(number)
  end

  def parse!(_) do
    raise ArgumentError, message: "Not a valid parameter, only string or integer."
  end

  @spec parse(String.t() | pos_integer(), atom()) :: {:ok, map()} | {:error, String.t()}
  @spec parse!(String.t() | pos_integer(), atom()) :: map()
  country_parser()

  @doc """
  Returns `true` if the number can be parsed, otherwise returns `false`.

  ```
  iex> Phone.valid?("555132345678")
  true

  iex> Phone.valid?("+55(51)3234-5678")
  true

  iex> Phone.valid?("55 51 3234-5678")
  true

  iex> Phone.valid?(555132345678)
  true

  ```
  """
  @spec valid?(String.t() | pos_integer()) :: boolean()
  def valid?(number) when is_bitstring(number) do
    number
    |> prepare_number
    |> Phone.Countries.match?()
  end

  def valid?(number) when is_integer(number) do
    number
    |> to_string
    |> valid?
  end
end