!!!
require Logger

def normalize(input) when is_list(input) do
  to_string(input)
end

def normalize(input) do
  input
end

def normalize_list([list]) do
  normalize(list)
end

def normalize_list(list) when is_list(list) do
  Enum.map(list, &normalize/1)
end

def normalize_list(list) do
  list
end

def normalize_tuples(list) do
  Enum.map(list, fn {k, v} ->
    {normalize(k), normalize_list(v)}
  end)
end

def return_value(key, state, string_values, _values, _rule) do
  [v] = string_values
  {:ok, Map.put(state, key, normalize(v)), v}
end

def accumulate_value(key, state, string_values, _values, _rule) do
  [v] = string_values
  value = normalize(v)
  case Map.get(state, key) do
    nil ->
      {:ok, Map.put(state, key, value), value}
    [_h | _t] = state_value ->
      {:ok, Map.put(state, key, state_value ++ [value]), value}
    state_value ->
      {:ok, Map.put(state, key, [state_value, value]), value}
  end
end

def tuples(values) do
  values
  |> List.flatten
  |> Enum.reject(&is_integer/1)
end
!!!

language-tag  = langtag             ; normal language tags
              / privateuse          ; private use tag
              / grandfathered       ; grandfathered tags

langtag       = language
                ["-" script]
                ["-" region]
                *("-" variant)
                *("-" extensions)
                ["-" privateuse]

language      = 2*3ALPHA            ; shortest ISO 639 code
                ;["-" extlang]      ; sometimes followed by
                                    ; extended language subtags
                / 4ALPHA            ; or reserved for future use
                / 5*8ALPHA  !!!     # or registered language subtag
                  return_value(:language, state, string_values, values, rule)
                !!!

extlang       = 3ALPHA              ; selected ISO 639 codes
                *2("-" 3ALPHA)      ; permanently reserved

script        = 4ALPHA  !!!         # ISO 15924 code
                  return_value(:script, state, string_values, values, rule)
                !!!

region        = 2ALPHA              ; ISO 3166-1 code
                / 3DIGIT !!!        # UN M.49 code
                  return_value(:region, state, string_values, values, rule)
                !!!

variant       = 5*8alphanum         ; registered variants
                / (DIGIT 3alphanum) !!! 
                  accumulate_value(:variant, state, string_values, values, rule)
                !!!

extensions     = locale / transform / extension !!! 
                 _ = rule
                 _ = string_values
                 state = if values == [[[[]]]] do
                   state
                 else
                   [[[{extension, values}]]] =  values
                   extensions = Map.put(state.extensions, extension, normalize_list(values))
                   Map.put(state, :extensions, extensions)
                 end
                 {:ok, state, []}
              !!!

extension     = singleton 1*("-" (2*8alphanum)) !!! 
                 _ = rule
                 _ = string_values
                [extension | values] = :string.split(:lists.flatten(values), '-', :all)
                {:ok, state, {normalize(extension), values}}
              !!!

                                    ;  The following is the syntax for the extensions managed
                                    ;  by CLDR.  These are extensions "u" for locale and "t"
                                    ;  for transforms
locale        = "u" (1*("-" keyword) / 1*("-" attribute) *("-" keyword)) !!!
                _ = rule
                _ = string_values
                keywords = tuples(values) |> normalize_tuples |> Enum.into(%{})
                {:ok, %{state | locale: keywords}, []}
              !!!

transform      = "t" (1*("-" keyword)) !!!
                _ = rule
                _ = string_values
                keywords = tuples(values) |> normalize_tuples |> Enum.into(%{})
                {:ok, %{state | transform: keywords}, []}
              !!!
              
keyword       = key ["-" type] !!!
                _ = rule
                _ = string_values
                [key | values] = :string.split(:lists.flatten(values), '-', :all)
                {:ok, state, {normalize(key), values}}
              !!!
key           = 2alphanum !!! 
                _ = rule
                _ = string_values
                {:ok, state, normalize(:lists.flatten(values))}
              !!!
type          = 3*8alphanum *("-" 3*8alphanum) !!!
                _ = rule
                _ = string_values
                {:ok, state, normalize(:lists.flatten(values))}
              !!!
attribute     = 3*8alphanum !!! 
                _ = rule
                _ = string_values
                {:ok, state, {:attributes, normalize_list(:string.split(:lists.flatten(values), '-', :all))}}
              !!!

                                    ; Single alphanumerics
                                    ; "x" reserved for private use
                                    ; "u" reserved for CLDR use as locale
                                    ; "t" reserved for CLDR use as tranforms
singleton     = DIGIT               ; 0 - 9
              / %x41-53             ; A - S
              / %x56-57             ; V - W
              / %x59-5A             ; Y - Z
              / %x61-73             ; a - s
              / %x76-77             ; v - w
              / %x79-7A             ; y - z

privateuse    = "x" 1*("-" (1*8alphanum)) !!!
                 _ = rule
                 _ = string_values
                [_x | values] = :string.split(:lists.flatten(values), '-', :all)
                {:ok, %{state | private_use: normalize_list(values)}, []}
              !!!

grandfathered = irregular           ; non-redundant tags registered
              / regular             ; during the RFC 3066 era

irregular     = "en-GB-oed"         ; irregular tags do not match
              / "i-ami"             ; the 'langtag' production and
              / "i-bnn"             ; would not otherwise be
              / "i-default"         ; considered 'well-formed'
              / "i-enochian"        ; These tags are all valid,
              / "i-hak"             ; but most are deprecated
              / "i-klingon"         ; in favor of more modern
              / "i-lux"             ; subtags or subtag
              / "i-mingo"           ; combination
              / "i-navajo"
              / "i-pwn"
              / "i-tao"
              / "i-tay"
              / "i-tsu"
              / "sgn-BE-FR"
              / "sgn-BE-NL"
              / "sgn-CH-DE"

regular       = "art-lojban"        ; these tags match the 'langtag'
              / "cel-gaulish"       ; production, but their subtags
              / "no-bok"            ; are not extended language
              / "no-nyn"            ; or variant subtags: their meaning
              / "zh-guoyu"          ; is defined by their registration
              / "zh-hakka"          ; and all of these are deprecated
              / "zh-min"            ; in favor of a more modern
              / "zh-min-nan"        ; subtag or sequence of subtags
              / "zh-xiang"

alphanum      = (ALPHA / DIGIT)     ; letters and numbers

DIGIT          = %x30-39
ALPHA          = %x41-5A / %x61-7A
