// Double to FixedString

///|
let gPOW10_ADDITIONAL_BITS = 120

///|
let gADDITIONAL_BITS_2 = 120

///|
fn pow10BitsForIndex(idx : Int) -> Int {
  return 16 * idx + gPOW10_ADDITIONAL_BITS
}

///|
fn indexForExponent(e : Int) -> Int {
  return (e + 15) / 16
}

///|
fn lengthForIndex(idx : Int) -> Int {
  // +1 for ceil, +16 for mantissa, +8 to round up when dividing by 9
  return (log10Pow2(16 * idx) + 1 + 16 + 8) / 9
}

///|
fn umlh(a : UInt64, b : UInt64) -> UInt64 {
  // Returns the low and high parts of the 128-bit product.
  // The low part is lo, and the high part is hi.

  let (_, hi) = umul128(a, b)
  hi
}

///|
fn div1e9(x : UInt64) -> UInt64 {
  umlh(x >> 9, 0x44B82FA09B5A53UL) >> 11
}

///|
fn mod1e9(x : UInt64) -> Int {
  (x - 1000000000 * div1e9(x)).to_int()
}

///|
fn mulShift_mod1e9(m : UInt64, mul : Array[UInt64], j : Int) -> Int {
  let (_, high0) = umul128(m, mul[0])
  let (low1, high1) = umul128(m, mul[1])
  let (low2, high2) = umul128(m, mul[2])
  let s0high = low1 + high0
  let c1 = s0high < low1
  let s1low = low2 + high1 + c1.to_uint64()
  let c2 = s1low < low2
  let s1high = high2 + c2.to_uint64()
  if j < 160 {
    let r0 = mod1e9(s1high).to_uint64()
    let r1 = mod1e9((r0 << 32) | (s1low >> 32)).to_uint64()
    let r2 = (r1 << 32) | (s1low & 0xFFFFFFFFUL)
    return mod1e9(r2 >> (j - 128))
  } else {
    let r0 = mod1e9(s1high).to_uint64()
    let r1 = mod1e9((r0 << 32) | (s1low >> 32)).to_uint64()
    return mod1e9(r1 >> (j - 160))
  }
}

// Returns the number of decimal digits in v, which must not contain more than 9 digits.
///|
fn decimalLength9(v : Int) -> Int {
  // Function precondition: v is not a 10-digit number.
  // (f2s: 9 digits are sufficient for round-tripping.)
  // (d2fixed: We print 9-digit blocks.)
  if v >= 100000000 {
    return 9
  }
  if v >= 10000000 {
    return 8
  }
  if v >= 1000000 {
    return 7
  }
  if v >= 100000 {
    return 6
  }
  if v >= 10000 {
    return 5
  }
  if v >= 1000 {
    return 4
  }
  if v >= 100 {
    return 3
  }
  if v >= 10 {
    return 2
  }
  return 1
}

///|
test "db2string" {
  inspect(
    ryu_to_string_precision(12345.123456, precision=3),
    content="12345.123",
  )
}

///|
test "db2string2" {
  inspect(ryu_to_string_precision(12345.123456, precision=0), content="12345")
}

///|
test "db2string3" {
  inspect(
    ryu_to_string_precision(12345.123456, precision=10),
    content="12345.1234560000",
  )
}

///|
test "db2string4" {
  inspect(
    ryu_to_string_precision(12345.123456, precision=17),
    content="12345.12345599999935075",
  )
}

// TODO: roundUp 
///|
test "db2string5" {
  inspect(
    ryu_to_string_precision(-8888888.1, precision=1),
    content="-8888888.1",
  )
}

///|
fn write_digits(
  array : FixedArray[Byte],
  start : Int,
  len : Int,
  digits : Int
) -> Unit {
  let mut idx = start + len - 1
  let mut digits = digits
  for i in 0.. Unit {
  write_digits(array, start, 9, digits)
}

///|
fn write_chars(
  array : FixedArray[Byte],
  start : Int,
  len : Int,
  char : Byte
) -> Unit {
  let mut idx = start
  for _ in 0.. Unit {
  let mut idx = start + len
  let mut digits = digits
  for i in 0..<(len - 1) {
    array[idx] = (48 + digits % 10).to_byte()
    digits /= 10
    idx -= 1
  }
  array[idx] = b'.'
  idx -= 1
  array[idx] = (48 + digits).to_byte()
  idx -= 1
}

///|
test "write_decimal_digits" {
  let array = FixedArray::make(10, Byte::default())
  write_decimal_digits(array, 0, 5, 12345)
  inspect(string_from_bytes(array, 0, 6), content="1.2345")
}

///|
fn write_string(array : FixedArray[Byte], start : Int, str : Bytes) -> Unit {
  let mut idx = start
  for byte in str {
    array[idx] = byte
    idx += 1
  }
}

///|
pub fn ryu_to_string_precision(val : Double, precision~ : Int = 17) -> String {
  let result = FixedArray::make(25, Byte::default())
  let bits : UInt64 = val.reinterpret_as_uint64()
  let ieeeSign = (
      (bits >> (gDOUBLE_MANTISSA_BITS + gDOUBLE_EXPONENT_BITS)) & 1UL
    ) !=
    0UL
  let ieeeMantissa : UInt64 = bits & ((1UL << gDOUBLE_MANTISSA_BITS) - 1UL)
  let ieeeExponent : Int = ((bits >> gDOUBLE_MANTISSA_BITS) &
  ((1UL << gDOUBLE_EXPONENT_BITS) - 1UL)).to_int()
  if ieeeExponent == (1 << gDOUBLE_EXPONENT_BITS) - 1 ||
    (ieeeExponent == 0 && ieeeMantissa == 0UL) {
    return copy_special_str(ieeeSign, ieeeExponent != 0, ieeeMantissa != 0UL)
  }
  if ieeeExponent == 0 && ieeeMantissa == 0 {
    let mut index = 0
    if ieeeSign {
      result[index] = b'-'
      index += 1
    }
    result[index] = b'0'
    index += 1
    if precision > 0 {
      result[index] = b'.'
      index += 1
      write_chars(result, index, precision, b'0')
      index += precision
    }
    return string_from_bytes(result, 0, index)
  }
  let (e2, m2) = if ieeeExponent == 0 {
    (1 - gDOUBLE_BIAS - gDOUBLE_MANTISSA_BITS, ieeeMantissa)
  } else {
    (
      ieeeExponent - gDOUBLE_BIAS - gDOUBLE_MANTISSA_BITS,
      (1UL << gDOUBLE_MANTISSA_BITS) | ieeeMantissa,
    )
  }
  let mut index = 0
  let mut nonzero = false
  if ieeeSign {
    result[index] = b'-'
    index += 1
  }
  if e2 >= -52 {
    let idx2 = if e2 < 0 { 0 } else { indexForExponent(e2) }
    let p10bits = pow10BitsForIndex(idx2)
    let len = lengthForIndex(idx2)
    for i = len - 1; i >= 0; i = i - 1 {
      let j = p10bits - e2
      let digits = mulShift_mod1e9(
        m2 << 8,
        gPOW10_SPLIT[gPOW10_OFFSET[idx2] + i],
        j + 8,
      )
      if nonzero {
        write_nine_digits(result, index, digits)
        index += 9
      } else if digits != 0 {
        let olength = decimalLength9(digits)
        write_digits(result, index, olength, digits)
        index += olength
        nonzero = true
      }
    }
  }
  if not(nonzero) {
    result[index] = b'0'
    index += 1
  }
  if precision > 0 {
    result[index] = b'.'
    index += 1
  }
  if e2 < 0 {
    let idx2 = -e2 / 16
    let blocks = precision / 9 + 1
    let mut roundUp = 0
    let mut i = 0
    if blocks <= gMIN_BLOCK_2[idx2] {
      i = blocks
      write_chars(result, index, precision, b'0')
      index += precision
    } else if i < gMIN_BLOCK_2[idx2] {
      i = gMIN_BLOCK_2[idx2]
      write_chars(result, index, 9 * i, b'0')
      index += i * 9
    }
    for i = i; i < blocks; i = i + 1 {
      let j = gADDITIONAL_BITS_2 + (-e2 - 16 * idx2)
      let p = gPOW10_OFFSET_2[idx2] + i - gMIN_BLOCK_2[idx2]
      if p >= gPOW10_OFFSET_2[idx2 + 1] {
        let fill = precision - 9 * i
        write_chars(result, index, fill, b'0')
        index += fill
        break
      }
      let mut digits = mulShift_mod1e9(m2 << 8, gPOW10_SPLIT_2[p], j + 8)
      // println("digits: " + digits.to_string())
      if i < blocks - 1 {
        write_nine_digits(result, index, digits)
        index += 9
      } else {
        let maximum = precision - 9 * i
        let mut lastDigit = 0
        for k in 0..<(9 - maximum) {
          lastDigit = digits % 10
          digits /= 10
        }
        if lastDigit != 5 {
          roundUp = (lastDigit > 5).to_int()
        } else {
          let requiredTwos = -e2 - precision - 1
          let trailingZeros = requiredTwos <= 0 ||
            (requiredTwos < 60 && multipleOfPowerOf2(m2, requiredTwos))
          roundUp = if trailingZeros { 2 } else { 1 }
        }
        if maximum > 0 {
          write_digits(result, index, maximum, digits)
          index += maximum
        }
        break
      }
    }

    // TODO: Implement rounding logic
    if roundUp != 0 {
      let mut roundIndex = index
      let mut dotIndex = 0
      while true {
        roundIndex -= 1
        if roundIndex == -1 || result[roundIndex] == '-' {
          result[roundIndex + 1] = b'1'
          if dotIndex > 0 {
            result[dotIndex] = b'0'
            result[dotIndex + 1] = b'.'
          }
          result[index] = b'0'
          index += 1
          break
        }
        if result[roundIndex] == b'.' {
          dotIndex = roundIndex
          continue
        } else if result[roundIndex] == b'9' {
          result[roundIndex] = b'0'
          roundUp = 1
          continue
        } else {
          if roundUp == 2 && result[roundIndex] % 2 == 0 {
            break
          }
          result[roundIndex] = result[roundIndex] + 1
          break
        }
      }
    }
  } else {
    write_chars(result, index, precision, b'0')
    index += precision
  }
  // println(result.to_string())
  // println("idx: " + idx.to_string())
  return string_from_bytes(result, 0, index)
}

///|
pub fn ryu_to_string_exp(
  val : Double,
  precision~ : Int = 17,
  upper~ : Bool = false
) -> String {
  let mut precision = precision
  let result = FixedArray::make(25, Byte::default())
  let bits : UInt64 = val.reinterpret_as_uint64()
  let ieeeSign = (
      (bits >> (gDOUBLE_MANTISSA_BITS + gDOUBLE_EXPONENT_BITS)) & 1UL
    ) !=
    0UL
  let ieeeMantissa : UInt64 = bits & ((1UL << gDOUBLE_MANTISSA_BITS) - 1UL)
  let ieeeExponent : Int = ((bits >> gDOUBLE_MANTISSA_BITS) &
  ((1UL << gDOUBLE_EXPONENT_BITS) - 1UL)).to_int()
  if ieeeExponent == (1 << gDOUBLE_EXPONENT_BITS) - 1 ||
    (ieeeExponent == 0 && ieeeMantissa == 0UL) {
    return copy_special_str(ieeeSign, ieeeExponent != 0, ieeeMantissa != 0UL)
  }
  if ieeeExponent == 0 && ieeeMantissa == 0 {
    let mut index = 0
    if ieeeSign {
      result[index] = b'-'
      index += 1
    }
    result[index] = b'0'
    index += 1
    if precision > 0 {
      result[index] = b'.'
      index += 1
      write_chars(result, index, precision, b'0')
      index += precision
    }
    write_string(result, index, if upper { b"E+00" } else { b"e+00" })
    index += 4
    return string_from_bytes(result, 0, index)
  }
  let (e2, m2) = if ieeeExponent == 0 {
    (1 - gDOUBLE_BIAS - gDOUBLE_MANTISSA_BITS, ieeeMantissa)
  } else {
    (
      ieeeExponent - gDOUBLE_BIAS - gDOUBLE_MANTISSA_BITS,
      (1UL << gDOUBLE_MANTISSA_BITS) | ieeeMantissa,
    )
  }
  let printDecimalPoint = precision > 0
  precision += 1
  let mut index = 0
  if ieeeSign {
    result[index] = b'-'
    index += 1
  }
  let mut digits = 0
  let mut printedDigits = 0
  let mut avaiableDigits = 0
  let mut exp = 0
  if e2 >= -52 {
    let idx = if e2 < 0 { 0 } else { indexForExponent(e2) }
    let p10bits = pow10BitsForIndex(idx)
    let len = lengthForIndex(idx)
    for i = len - 1; i >= 0; i = i - 1 {
      let j = p10bits - e2
      digits = mulShift_mod1e9(
        m2 << 8,
        gPOW10_SPLIT[gPOW10_OFFSET[idx] + i],
        j + 8,
      )
      if printedDigits != 0 {
        if printedDigits + 9 > precision {
          avaiableDigits = 9
          break
        }
        write_nine_digits(result, index, digits)
        index += 9
        printedDigits += 9
      } else if digits != 0 {
        avaiableDigits = decimalLength9(digits)
        exp = i * 9 + avaiableDigits - 1
        if avaiableDigits > precision {
          break
        }
        if printDecimalPoint {
          write_decimal_digits(result, index, avaiableDigits, digits)
          index += avaiableDigits + 1
        } else {
          result[index] = b'0' + digits.to_byte()
          index += 1
        }
        printedDigits = avaiableDigits
        avaiableDigits = 0
      }
    }
  }
  if e2 < 0 && avaiableDigits == 0 {
    let idx = -e2 / 16
    for i = gMIN_BLOCK_2[idx]; i < 200; i = i + 1 {
      let j = gADDITIONAL_BITS_2 + (-e2 - 16 * idx)
      let p = gPOW10_OFFSET_2[idx] + i - gMIN_BLOCK_2[idx]
      digits = if p >= gPOW10_OFFSET_2[idx + 1] {
        0
      } else {
        mulShift_mod1e9(m2 << 8, gPOW10_SPLIT_2[p], j + 8)
      }
      if printedDigits != 0 {
        if printedDigits + 9 > precision {
          avaiableDigits = 9
          break
        }
        write_nine_digits(result, index, digits)
        index += 9
        printedDigits += 9
      } else if digits != 0 {
        avaiableDigits = decimalLength9(digits)
        exp = -(i + 1) * 9 + avaiableDigits - 1
        if avaiableDigits > precision {
          break
        }
        if printDecimalPoint {
          write_decimal_digits(result, index, avaiableDigits, digits)
          index += avaiableDigits + 1
        } else {
          result[index] = b'0' + digits.to_byte()
          index += 1
        }
        printedDigits = avaiableDigits
        avaiableDigits = 0
      }
    }
  }
  let maximum = precision - printedDigits
  if avaiableDigits == 0 {
    digits = 0
  }
  let mut lastDigit = 0
  if avaiableDigits > maximum {
    for k in 0..<(avaiableDigits - maximum) {
      lastDigit = digits % 10
      digits /= 10
    }
  }
  let mut roundUp = 0
  if lastDigit != 5 {
    roundUp = (lastDigit > 5).to_int()
  } else {
    let rexp = precision - exp
    let requiredTwos = -e2 - rexp
    let mut trailingZeros = requiredTwos <= 0 ||
      (requiredTwos < 60 && multipleOfPowerOf2(m2, requiredTwos))
    if rexp < 0 {
      let requiredFives = -rexp
      trailingZeros = trailingZeros && multipleOfPowerOf5(m2, requiredFives)
    }
    roundUp = if trailingZeros { 2 } else { 1 }
  }
  if printedDigits != 0 {
    if digits == 0 {
      write_chars(result, index, maximum, b'0')
    } else {
      write_digits(result, index, maximum, digits)
    }
    index += maximum
  } else if printDecimalPoint {
    write_decimal_digits(result, index, maximum, digits)
    index += maximum + 1
  } else {
    result[index] = b'0' + digits.to_byte()
    index += 1
  }
  if roundUp != 0 {
    let mut roundIndex = index
    while true {
      roundIndex -= 1
      if roundIndex == -1 || result[roundIndex] == b'-' {
        result[roundIndex + 1] = b'1'
        exp += 1
        break
      }
      if result[roundIndex] == b'.' {
        continue
      } else if result[roundIndex] == b'9' {
        result[roundIndex] = b'0'
        roundUp = 1
        continue
      } else {
        if roundUp == 2 && result[roundIndex] % 2 == 0 {
          break
        }
        result[roundIndex] = result[roundIndex] + 1
        break
      }
    }
  }
  result[index] = if upper { b'E' } else { b'e' }
  index += 1
  if exp < 0 {
    result[index] = b'-'
    index += 1
    exp = -exp
  } else {
    result[index] = b'+'
    index += 1
  }
  if exp >= 100 {
    write_digits(result, index, 3, exp)
    index += 3
  } else {
    write_digits(result, index, 2, exp)
    index += 2
  }

  // println(result.to_string())
  // println("idx: " + idx.to_string())
  return string_from_bytes(result, 0, index)
}

///|
test "ryu_to_string_exp" {
  inspect(
    ryu_to_string_exp(12345.123, precision=3),
    content="1.235e+04",
  )
}

///|
test "ryu_to_string_exp" {
  inspect(
    ryu_to_string_exp(12345.123, precision=17),
    content="1.23451229999999996e+04",
  )
}