     end
 
     test "unknown characters raise" do
-      assert_raise ArgumentError, "Unknown digit(s): r", fn -> NumbersToWords.parse("r") end
-      assert_raise ArgumentError, "Unknown digit(s): r5", fn -> NumbersToWords.parse("1r5") end
+      assert_raise ArgumentError, "r is not an integer", fn -> NumbersToWords.parse("r") end
+      assert_raise ArgumentError, "1r5 is not an integer", fn -> NumbersToWords.parse("1r5") end
     end
 
   end

# numbers to words

defmodule NumbersToWords do

  @places ~w[ thousand million billion trillion quadrillion quintillion sextillion septillion octillion nonillion decillion ]

  # the default entry point, when the argument is an int and not a binary string
  def parse(int) when is_integer(int) do
    parse(Integer.to_string(int)) |> String.replace("  ", " ") |> String.rstrip
  end

  # nought case
  def parse(""), do: ""

  # negative integer case
  def parse(<<?-, the_rest::binary>>), do: "negative " <> parse(the_rest)

  # ones
  def parse("0"), do: "zero"
  def parse("1"), do: "one"
  def parse("2"), do: "two"
  def parse("3"), do: "three"
  def parse("4"), do: "four"
  def parse("5"), do: "five"
  def parse("6"), do: "six"
  def parse("7"), do: "seven"
  def parse("8"), do: "eight"
  def parse("9"), do: "nine"

  # tens
  def parse("00"), do: ""
  def parse("10"), do: "ten"
  def parse("11"), do: "eleven"
  def parse("12"), do: "twelve"
  def parse("13"), do: "thirteen"
  def parse("14"), do: "fourteen"
  def parse("15"), do: "fifteen"
  def parse("16"), do: "sixteen"
  def parse("17"), do: "seventeen"
  def parse("18"), do: "eighteen"
  def parse("19"), do: "nineteen"
  def parse("20"), do: "twenty"

  # tens, finally with a pattern
  def parse(<<?2, n::utf8>>), do: "twenty #{parse(<<n>>)}"
  def parse("30"), do: "thirty"
  def parse(<<?3, n::utf8>>), do: "thirty #{parse(<<n>>)}"
  def parse("40"), do: "forty"
  def parse(<<?4, n::utf8>>), do: "forty #{parse(<<n>>)}"
  def parse("50"), do: "fifty"
  def parse(<<?5, n::utf8>>), do: "fifty #{parse(<<n>>)}"
  def parse("60"), do: "sixty"
  def parse(<<?6, n::utf8>>), do: "sixty #{parse(<<n>>)}"
  def parse("70"), do: "seventy"
  def parse(<<?7, n::utf8>>), do: "seventy #{parse(<<n>>)}"
  def parse("80"), do: "eighty"
  def parse(<<?8, n::utf8>>), do: "eighty #{parse(<<n>>)}"
  def parse("90"), do: "ninety"
  def parse(<<?9, n::utf8>>), do: "ninety #{parse(<<n>>)}"

  # tens with a leading zero... drop it
  def parse(<<?0, n::utf8>>), do: parse(<<n>>)

  # hundreds with a leading zero... drop it
  def parse(<<?0, tens::utf8, ones::utf8>>), do: parse(<<tens, ones>>)

  # regular hundreds
  def parse(<<hundreds::utf8, tens::utf8, ones::utf8>>), do: "#{parse(<<hundreds>>)} hundred #{parse(<<tens, ones>>)}"

  #### Thousands and up. now we get fancy ####

  # General case for splitting up digits 4 or longer, start an "every third digit" state and pass back
  # Since all binaries fall through to here that also haven't already matched, we have to validate too
  def parse(<<long_binary::binary>>) when is_binary(long_binary) do
    if validate?(long_binary) do
      parse(<<long_binary::binary>>, @places)
    else
      raise ArgumentError, message: "Unknown digit(s): #{long_binary}"
    end
  end

  # End case where we run out of words. Oops.
  def parse(_, []) do
    raise ArgumentError, message: "Dude. That number is too long. I don't know how to say it."
  end

  # End cases (length is 3 or less). no more to parse, drop "every third digit" state, fallback to digit-trio parsing
  def parse(<<ones::utf8>>, _), do: parse(<<ones::utf8>>)
  def parse(<<tens::utf8, ones::utf8>>, _), do: parse(<<tens::utf8, ones::utf8>>)
  def parse(<<hundreds::utf8, tens::utf8, ones::utf8>>, _), do: parse(<<hundreds::utf8, tens::utf8, ones::utf8>>)

  # thousands and up here... recursive call... this is most of the magic
  def parse(<<long_binary::binary>>, [illion | rest]) do
    { further_digits, these_digits } = String.split_at(long_binary, -3)
    if String.match?(further_digits, ~r/0{3}$/) do
      # we have to skip the trio join word if the next trio of parseable digits to the left is all zeroes, basically
      # Otherwise you'd get stuff like "six trillion billion million thousand"
      parse(<<further_digits::binary>>, rest) <> parse(<<these_digits::binary>>)
    else
      parse(<<further_digits::binary>>, rest) <> " #{illion} " <> parse(<<these_digits::binary>>)
    end
  end

  # numeric string validation
  defp validate?(str) do
    String.match?(str, ~r/^-?[0-9]+$/)
  end

end



  defmodule NumbersToWordsTest do
    use ExUnit.Case, async: true

    test "digits to words" do
      %{
        zero: 0, one: 1, two: 2, three: 3, four: 4, five: 5, six: 6, seven: 7, eight: 8, nine: 9, ten: 10,
        eleven: 11, twelve: 12, thirteen: 13, fourteen: 14, fifteen: 15, sixteen: 16, seventeen: 17,
        eighteen: 18, nineteen: 19
      }
      |>
      Enum.each(fn {word, num} ->
        assert to_string(word) == NumbersToWords.parse(num)
