// Number parsing — integers and scientific notation

///|
pub fn parse_int(input : ParseInput) -> (ParseInput, Int)? {
  let (rest, sign) = match tag("-", input) {
    Some((r, _)) => (r, true)
    None => (input, false)
  }
  match take_while1(is_digit, rest) {
    Some((rest2, digits)) => {
      let val = str_to_int(digits, 0, 0)
      let final_val = if sign { -val } else { val }
      Some((rest2, final_val))
    }
    None => None
  }
}

///|
fn str_to_int(s : String, idx : Int, acc : Int) -> Int {
  if idx >= s.length() {
    acc
  } else {
    let ch = s.substring(start=idx, end=idx + 1)
    let digit = match ch {
      "0" => 0
      "1" => 1
      "2" => 2
      "3" => 3
      "4" => 4
      "5" => 5
      "6" => 6
      "7" => 7
      "8" => 8
      "9" => 9
      _ => 0
    }
    str_to_int(s, idx + 1, acc * 10 + digit)
  }
}

///|
pub fn parse_u64(input : ParseInput) -> (ParseInput, Int)? {
  match take_while1(is_digit, input) {
    Some((rest, digits)) => Some((rest, str_to_int(digits, 0, 0)))
    None => None
  }
}

///|
pub fn parse_float(input : ParseInput) -> (ParseInput, Float)? {
  let (rest, sign) = match tag("-", input) {
    Some((r, _)) => (r, true)
    None => (input, false)
  }
  let (r1, whole_str) = match take_while1(is_digit, rest) {
    Some((r, d)) => (r, d)
    None => return None
  }
  let whole = Float::from_int(str_to_int(whole_str, 0, 0))

  match tag(".", r1) {
    Some((r2, _)) =>
      match take_while1(is_digit, r2) {
        Some((r3, frac_str)) => {
          let frac_val = Float::from_int(str_to_int(frac_str, 0, 0))
          let div = pow10_float(frac_str.length())
          let result = whole + frac_val / div
          let final_val = if sign { -result } else { result }
          Some((r3, final_val))
        }
        None => {
          let final_val = if sign { -whole } else { whole }
          Some((r1, final_val))
        }
      }
    None => {
      let final_val = if sign { -whole } else { whole }
      Some((r1, final_val))
    }
  }
}

///|
fn pow10_float(n : Int) -> Float {
  if n <= 0 {
    1.0
  } else {
    10.0 * pow10_float(n - 1)
  }
}

///|
pub fn parse_hex_int(input : ParseInput) -> (ParseInput, Int)? {
  let (rest, _) = match tag("0x", input) {
    Some((r, _)) => (r, "")
    None =>
      match tag("0X", input) {
        Some((r, _)) => (r, "")
        None => return None
      }
  }
  match take_while1(is_hex, rest) {
    Some((rest2, hex_str)) => Some((rest2, hex_to_int(hex_str, 0, 0)))
    None => None
  }
}

///|
fn hex_to_int(s : String, idx : Int, acc : Int) -> Int {
  if idx >= s.length() {
    acc
  } else {
    let ch = s.substring(start=idx, end=idx + 1)
    let digit = match ch {
      "0" => 0
      "1" => 1
      "2" => 2
      "3" => 3
      "4" => 4
      "5" => 5
      "6" => 6
      "7" => 7
      "8" => 8
      "9" => 9
      "a" | "A" => 10
      "b" | "B" => 11
      "c" | "C" => 12
      "d" | "D" => 13
      "e" | "E" => 14
      "f" | "F" => 15
      _ => 0
    }
    hex_to_int(s, idx + 1, acc * 16 + digit)
  }
}

///|
pub fn parse_scientific(input : ParseInput) -> (ParseInput, Float)? {
  match parse_float(input) {
    Some((r1, base)) =>
      match tag_ci("e", r1) {
        Some((r2, _)) => {
          let (r3, exp_sign) = match tag("+", r2) {
            Some((r4, _)) => (r4, 1)
            None =>
              match tag("-", r2) {
                Some((r4, _)) => (r4, -1)
                None => (r2, 1)
              }
          }
          match parse_u64(r3) {
            Some((r5, exp)) => {
              let factor = pow10_float(exp)
              let result = if exp_sign == 1 {
                base * factor
              } else {
                base / factor
              }
              Some((r5, result))
            }
            None => None
          }
        }
        None => None
      }
    None => None
  }
}

///|
pub fn parse_bool(input : ParseInput) -> (ParseInput, Bool)? {
  match tag("true", input) {
    Some((r, _)) => Some((r, true))
    None =>
      match tag("false", input) {
        Some((r, _)) => Some((r, false))
        None => None
      }
  }
}