///|
pub(all) struct IonDecimal {
  coefficient : BigInt
  exponent : Int
} derive(Eq, Debug)

///|
pub fn decimal_from_int(value : Int) -> IonDecimal {
  { coefficient: bigint_from_int(value), exponent: 0, }
}

///|
pub fn decimal_from_parts(coefficient : BigInt, exponent : Int) -> IonDecimal {
  { coefficient, exponent, }
}

///|
pub fn IonDecimal::is_zero(self : IonDecimal) -> Bool {
  self.coefficient.is_zero()
}

///|
pub fn IonDecimal::to_ion_text(self : IonDecimal) -> String {
  let coeff = self.coefficient.to_decimal_string()
  if self.exponent == 0 {
    coeff + "d0"
  } else {
    coeff + "d" + self.exponent.to_string()
  }
}

///|
pub fn parse_decimal_text(text : String) -> Result[IonDecimal, IonError] {
  let mut i = 0
  if i < text.length() && (text[i].to_int() == 43 || text[i].to_int() == 45) {
    i += 1
  }
  let mut seen_digit = false
  let mut seen_dot = false
  let mut seen_d = false
  let mut j = i
  while j < text.length() {
    let c = text[j].to_int()
    if c == 95 {
      j += 1
    } else if is_digit(c) {
      seen_digit = true
      j += 1
    } else if c == 46 && !seen_dot && !seen_d {
      seen_dot = true
      j += 1
    } else if (c == 100 || c == 68) && !seen_d {
      seen_d = true
      j += 1
      if j < text.length() && (text[j].to_int() == 43 || text[j].to_int() == 45) {
        j += 1
      }
    } else {
      return Err(InvalidDecimal(text))
    }
  }
  if !seen_digit {
    return Err(InvalidDecimal(text))
  }
  let mut sign = ""
  let mut k = 0
  if text[0].to_int() == 45 {
    sign = "-"
    k = 1
  } else if text[0].to_int() == 43 {
    k = 1
  }
  let digits = StringBuilder()
  let mut frac = 0
  let exp_part = StringBuilder()
  let mut in_exp = false
  let mut after_dot = false
  while k < text.length() {
    let c = text[k].to_int()
    if c == 95 {
      k += 1
    } else if c == 46 && !in_exp {
      after_dot = true
      k += 1
    } else if c == 100 || c == 68 {
      in_exp = true
      k += 1
    } else if in_exp {
      exp_part.write_char(c.unsafe_to_char())
      k += 1
    } else {
      digits.write_char(c.unsafe_to_char())
      if after_dot {
        frac += 1
      }
      k += 1
    }
  }
  let coeff_text = sign + digits.to_string()
  let coeff = match bigint_from_decimal_string(coeff_text) {
    Ok(v) => v
    Err(_) => return Err(InvalidDecimal(text))
  }
  let mut exp = 0
  let exp_text = exp_part.to_string()
  if exp_text.length() > 0 {
    match parse_signed_int(exp_text) {
      Ok(v) => exp = v
      Err(_) => return Err(InvalidDecimal(text))
    }
  }
  Ok({ coefficient: coeff, exponent: exp - frac, })
}

///|
fn parse_signed_int(text : String) -> Result[Int, IonError] {
  if text.length() == 0 {
    return Err(InvalidNumber(text))
  }
  let mut i = 0
  let mut negative = false
  if text[0].to_int() == 45 {
    negative = true
    i = 1
  } else if text[0].to_int() == 43 {
    i = 1
  }
  if i >= text.length() {
    return Err(InvalidNumber(text))
  }
  let mut n = 0
  while i < text.length() {
    let c = text[i].to_int()
    if c == 95 {
      i += 1
      continue
    }
    if !is_digit(c) {
      return Err(InvalidNumber(text))
    }
    n = n * 10 + (c - 48)
    i += 1
  }
  if negative {
    Ok(0 - n)
  } else {
    Ok(n)
  }
}