// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
priv struct FloatInfo {
  mantissa_bits : Int
  exponent_bits : Int
  bias : Int
}

///|
let double_info : FloatInfo = {
  mantissa_bits: 52,
  exponent_bits: 11,
  bias: -1023,
}

///|
/// TODO: For `f32` it is 23, but we don't have `f32` yet.
let mantissa_explicit_bits = 52

///|
/// TODO: For `f32` it is -10, but we don't have `f32` yet.
let min_exponent_fast_path : Int64 = -22L

///|
/// TODO: For `f32` it is 10, but we don't have `f32` yet.
let max_exponent_fast_path : Int64 = 22L

///|
/// TODO: For `f32` it is 17, but we don't have `f32` yet.
let max_exponent_disguised_fast_path : Int64 = 37L

///|
let max_mantissa_fast_path : UInt64 = 2UL << mantissa_explicit_bits

///|
/// Parse a string into a double precision floating point number. The string
/// must contain at least one of:
/// - An integer part (decimal digits)
/// - A decimal point followed by a fractional part (decimal digits)
/// - An exponent part ('e' or 'E' followed by an optional sign and decimal digits)
///
/// The string may optionally start with a sign ('+' or '-').
/// For readability, underscores may appear between digits.
///
/// Examples:
/// ```mbt
///   inspect(parse_double("123"), content="123")
///   inspect(parse_double("12.34"), content="12.34")
///   inspect(parse_double(".123"), content="0.123")
///   inspect(parse_double("1e5"), content="100000")
///   inspect(parse_double("1.2e-3"), content="0.0012")
///   inspect(parse_double("1_234.5"), content="1234.5")
/// ```
///
/// An exponent value exp scales the mantissa (significand) by 10^exp.
/// For example, "1.23e2" represents 1.23 × 10² = 123.
pub fn parse_double(str : String) -> Double raise StrConvError {
  if str.length() == 0 {
    syntax_err()
  }
  if not(check_underscore(str)) {
    syntax_err()
  }
  // validate its a number
  let (num, consumed) = match parse_number(str) {
    Some(r) => r
    None =>
      match parse_inf_nan(str) {
        Some((num, consumed)) =>
          if str.length() != consumed {
            syntax_err()
          } else {
            return num
          }
        None => syntax_err()
      }
  }
  if str.length() != consumed {
    syntax_err()
  }
  // Clinger's fast path (How to read floating point numbers accurately)[https://doi.org/10.1145/989393.989430]
  match num.try_fast_path() {
    Some(value) => value
    None => {
      // fallback to slow path
      let ret = parse_decimal_priv(str)
      ret.to_double_priv()
    }
  }
}

///|
fn is_fast_path(self : Number) -> Bool {
  min_exponent_fast_path <= self.exponent &&
  self.exponent <= max_exponent_disguised_fast_path &&
  self.mantissa <= max_mantissa_fast_path &&
  not(self.many_digits)
}

///|
let table = [
  1.0, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, 10000000.0, 100000000.0,
  1000000000.0, 10000000000.0, 100000000000.0, 1000000000000.0, 10000000000000.0,
  100000000000000.0, 1000000000000000.0, 10000000000000000.0, 100000000000000000.0,
  1000000000000000000.0, 10000000000000000000.0, 100000000000000000000.0, 1000000000000000000000.0,
  10000000000000000000000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
]

///|
fn pow10_fast_path(exponent : Int) -> Double {
  table[exponent & 31]
}

///|
let int_pow10 : Array[UInt64] = [
  1UL, 10UL, 100UL, 1000UL, 10000UL, 100000UL, 1000000UL, 10000000UL, 100000000UL,
  1000000000UL, 10000000000UL, 100000000000UL, 1000000000000UL, 10000000000000UL,
  100000000000000UL, 1000000000000000UL,
]

///|
fn try_fast_path(self : Number) -> Double? {
  if self.is_fast_path() {
    let mut value = if self.exponent <= max_exponent_fast_path {
      // normal fast path
      let value = Double::convert_uint64(self.mantissa)
      if self.exponent < 0L {
        value / pow10_fast_path(-self.exponent.to_int())
      } else {
        value * pow10_fast_path(self.exponent.to_int())
      }
    } else {
      // disguised fast path
      let shift = self.exponent - max_exponent_fast_path
      let mantissa = match
        checked_mul(self.mantissa, int_pow10[shift.to_int()]) {
        Some(m) => m
        None => return None
      }
      if mantissa > max_mantissa_fast_path {
        return None
      }
      Double::convert_uint64(mantissa) *
      pow10_fast_path(max_exponent_fast_path.to_int())
    }
    if self.negative {
      value = -value
    }
    Some(value)
  } else {
    None
  }
}

///|
test "parse_double" {
  let tests : Array[(String, Result[Double, String])] = [
    ("", Err(syntax_err_str)),
    ("1x", Err(syntax_err_str)),
    ("1.1.", Err(syntax_err_str)),
    ("1e", Err(syntax_err_str)),
    ("1e-", Err(syntax_err_str)),
    (".e-1", Err(syntax_err_str)),
    ("1", Ok(1.0)),
    ("+1", Ok(1.0)),
    ("1e23", Ok(1.0e23)),
    ("1E23", Ok(1.0e23)),
    ("100000000000000000000000", Ok(1.0e23)),
    ("1e-100", Ok(1.0e-100)),
    ("123456700", Ok(1.234567e+08)),
    ("99999999999999974834176", Ok(9.999999999999997e+22)),
    ("100000000000000000000001", Ok(1.0000000000000001e+23)),
    ("100000000000000008388608", Ok(1.0000000000000001e+23)),
    ("100000000000000016777215", Ok(1.0000000000000001e+23)),
    ("100000000000000016777216", Ok(1.0000000000000003e+23)),
    ("-1", Ok(-1.0)),
    ("-0.1", Ok(-0.1)),
    ("-0", Ok(-0.0)),
    ("1e-20", Ok(1.0e-20)),
    ("625e-3", Ok(0.625)),
    ("6.62607015e-34", Ok(6.62607015e-34)),
    ("2.2250738585072012e-308", Ok(2.2250738585072014e-308)),
    ("2.2250738585072011e-308", Ok(2.225073858507201e-308)),
    ("0", Ok(0.0)),
    ("0e0", Ok(0.0)),
    ("-0e0", Ok(-0.0)),
    ("+0e0", Ok(0.0)),
    ("0e-0", Ok(0.0)),
    ("-0e-0", Ok(-0.0)),
    ("+0e-0", Ok(0.0)),
    ("0e+0", Ok(0.0)),
    ("-0e+0", Ok(-0.0)),
    ("+0e+0", Ok(0.0)),
    ("0e+01234567890123456789", Ok(0.0)),
    ("0.00e-01234567890123456789", Ok(0.0)),
    ("-0e+01234567890123456789", Ok(-0.0)),
    ("-0.00e-01234567890123456789", Ok(-0.0)),
    ("0e292", Ok(0.0)),
    ("0e347", Ok(0.0)),
    ("0e348", Ok(0.0)),
    ("-0e291", Ok(-0.0)),
    ("-0e292", Ok(-0.0)),
    ("-0e347", Ok(-0.0)),
    ("-0e348", Ok(-0.0)),
    ("1.7976931348623157e308", Ok(1.7976931348623157e308)),
    ("-1.7976931348623157e308", Ok(-1.7976931348623157e308)),
    ("1.7976931348623158e308", Ok(1.7976931348623157e308)),
    ("-1.7976931348623158e308", Ok(-1.7976931348623157e308)),
    ("1e308", Ok(1.0e308)),
    (
      "1.7976931348623159e308",
      Err(
        // zeros
        // large double
        range_err_str,
      ),
    ),
    (
      "-1.7976931348623159e308",
      Err(
        // overflow
        range_err_str,
      ),
    ),
    ("2e308", Err(range_err_str)),
    ("1e309", Err(range_err_str)),
    ("1e310", Err(range_err_str)),
    ("1e400", Err(range_err_str)),
    ("1e40000", Err(range_err_str)),
    // denormalized
    ("1e-305", Ok(1.0e-305)),
    ("1e-306", Ok(1.0e-306)),
    ("1e-307", Ok(1.0e-307)),
    ("1e-308", Ok(1.0e-308)),
    ("1e-309", Ok(1.0e-309)),
    ("1e-310", Ok(1.0e-310)),
    ("1e-322", Ok(1.0e-322)),
    // smallest denormal
    ("5e-324", Ok(5.0e-324)),
    ("4e-324", Ok(5.0e-324)),
    ("3e-324", Ok(5.0e-324)),
    // underflow
    ("2e-324", Ok(0.0)),
    ("1e-350", Ok(0.0)),
    ("1e-400000", Ok(0.0)),
    // underscores
    ("1_23.50_0_0e+1_2", Ok(1.235e+14)),
    ("-_123.5e+12", Err(syntax_err_str)),
    ("+_123.5e+12", Err(syntax_err_str)),
    ("_123.5e+12", Err(syntax_err_str)),
    ("1__23.5e+12", Err(syntax_err_str)),
    ("123_.5e+12", Err(syntax_err_str)),
    ("123._5e+12", Err(syntax_err_str)),
    ("123.5_e+12", Err(syntax_err_str)),
    ("123.5__0e+12", Err(syntax_err_str)),
    ("123.5e_+12", Err(syntax_err_str)),
    ("123.5e+_12", Err(syntax_err_str)),
    ("123.5e_-12", Err(syntax_err_str)),
    ("123.5e-_12", Err(syntax_err_str)),
    ("123.5e+1__2", Err(syntax_err_str)),
    ("123.5e+12_", Err(syntax_err_str)),
  ]
  for i in 0.. Err(err)
      },
      t.1,
    )
  }
}

///|
test "parse_double_inf" {
  assert_eq(parse_double("inf") catch { _ => panic() }, @double.infinity)
  assert_eq(parse_double("+Inf") catch { _ => panic() }, @double.infinity)
  assert_eq(parse_double("-Inf") catch { _ => panic() }, @double.neg_infinity)
  assert_eq(parse_double("+Infinity") catch { _ => panic() }, @double.infinity)
  assert_eq(
    parse_double("-Infinity") catch {
      _ => panic()
    },
    @double.neg_infinity,
  )
  assert_eq(parse_double("+INFINITY") catch { _ => panic() }, @double.infinity)
  assert_eq(
    parse_double("-INFINITY") catch {
      _ => panic()
    },
    @double.neg_infinity,
  )
}

///|
test "parse_double_nan" {
  let nan = parse_double("nan") catch { _ => panic() }
  assert_true(nan.is_nan())
  let nan = parse_double("NaN") catch { _ => panic() }
  assert_true(nan.is_nan())
  let nan = parse_double("NAN") catch { _ => panic() }
  assert_true(nan.is_nan())
}