///|
pub struct NormalizedElement {
  code : String
  raw_value : String
  numeric_value : Int?
  decimal_places : Int?
  date_value : String?
} derive(Debug, Eq)

///|
pub fn GS1Message::normalized_element(
  self : GS1Message,
  code : String,
) -> NormalizedElement? {
  for element in self.elements {
    if element.code() == code {
      let numeric_value = if is_all_digits(element.value) {
        Some(decimal_digits(element.value))
      } else {
        None
      }
      let decimal_places = implied_decimal_places(code)
      let date_value = if is_date_ai(code) {
        Some(yymmdd_to_iso(element.value))
      } else {
        None
      }
      return Some({
        code,
        raw_value: element.value,
        numeric_value,
        decimal_places,
        date_value,
      })
    }
  }
  None
}

///|
pub fn NormalizedElement::code(self : NormalizedElement) -> String {
  self.code
}

///|
pub fn NormalizedElement::raw(self : NormalizedElement) -> String {
  self.raw_value
}

///|
fn decimal_digits(value : String) -> Int {
  value.fold(init=0, fn(acc, ch) {
    acc * 10 + ch.to_int() - ('0' : UInt16).to_int()
  })
}

///|
fn implied_decimal_places(code : String) -> Int? {
  match code {
    "3101"
    | "3111"
    | "3121"
    | "3131"
    | "3141"
    | "3151"
    | "3161"
    | "3201"
    | "3211"
    | "3221"
    | "3231"
    | "3241"
    | "3251"
    | "3261"
    | "3271"
    | "3281"
    | "3291" => Some(1)
    "3102"
    | "3112"
    | "3122"
    | "3132"
    | "3142"
    | "3152"
    | "3162"
    | "3202"
    | "3212"
    | "3222"
    | "3232"
    | "3242"
    | "3252"
    | "3262"
    | "3272"
    | "3282"
    | "3292" => Some(2)
    "3103"
    | "3113"
    | "3123"
    | "3133"
    | "3143"
    | "3153"
    | "3163"
    | "3203"
    | "3213"
    | "3223"
    | "3233"
    | "3243"
    | "3253"
    | "3263"
    | "3273"
    | "3283"
    | "3293" => Some(3)
    "3104"
    | "3114"
    | "3124"
    | "3134"
    | "3144"
    | "3154"
    | "3164"
    | "3204"
    | "3214"
    | "3224"
    | "3234"
    | "3244"
    | "3254"
    | "3264"
    | "3274"
    | "3284"
    | "3294" => Some(4)
    "3105"
    | "3115"
    | "3125"
    | "3135"
    | "3145"
    | "3155"
    | "3165"
    | "3205"
    | "3215"
    | "3225"
    | "3235"
    | "3245"
    | "3255"
    | "3265"
    | "3275"
    | "3285"
    | "3295" => Some(5)
    "3106"
    | "3116"
    | "3126"
    | "3136"
    | "3146"
    | "3156"
    | "3166"
    | "3206"
    | "3216"
    | "3226"
    | "3236"
    | "3246"
    | "3256"
    | "3266"
    | "3276"
    | "3286"
    | "3296" => Some(6)
    _ => None
  }
}

///|
fn yymmdd_to_iso(value : String) -> String {
  if value.length() != 6 || !is_all_digits(value) {
    ""
  } else {
    let year = two_digits(value, 0) + 2000
    let month = two_digits(value, 2)
    let day = two_digits(value, 4)
    "\{year}-\{pad_two(month)}-\{pad_two(day)}"
  }
}

///|
fn pad_two(value : Int) -> String {
  if value < 10 {
    "0\{value}"
  } else {
    "\{value}"
  }
}