//! Implementation of the Grisu algorithm.
//!
//! These routines are adapted from Andrea Samoljuk's `fpconv` library,
//! which is available [here](https://github.com/night-shift/fpconv).
//!
//! In addition to porting from C to Rust, this also adds format
//! precision control and other features.
//!
//! This code is therefore available under a permissive
//! Boost Software License, as is the original.
//!
//! A few modifications have been made to improve readability,
//! minimize binary size, and add additional features.
//!
//! 1. The exponent is inferred, rather than explicitly store.
//! 2. The mantissas are stored in hex, rather than decimal.
//! 3. Forcing and disabling exponent notation is now supported.
//! 4. Controlling the maximum and minimum number of significant digits is
//!    supported.
//! 5. Support for trimming floats (".0") is also included.

#![cfg(feature = "compact")]
#![doc(hidden)]

use lexical_util::algorithm::{copy_to_dst, rtrim_char_count};
#[cfg(feature = "f16")]
use lexical_util::bf16::bf16;
use lexical_util::digit::digit_to_char_const;
#[cfg(feature = "f16")]
use lexical_util::f16::f16;
use lexical_util::format::NumberFormat;
use lexical_util::num::{AsPrimitive, Float};

use crate::float::{ExtendedFloat80, RawFloat};
use crate::options::Options;
use crate::shared;
use crate::table::GRISU_POWERS_OF_TEN;

/// Compact float-to-string algorithm for decimal strings.
///
/// This is based on "Printing Floating-Point Numbers Quickly and Accurately
/// with Integers", by Florian Loitsch, available online at:
/// <https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf>.
///
/// This assumes the float is:
///     1). Non-special (NaN or Infinite).
///     2). Non-negative.
pub fn write_float<F: RawFloat, const FORMAT: u128>(
    float: F,
    bytes: &mut [u8],
    options: &Options,
) -> usize {
    // PRECONDITIONS

    // Assert no special cases remain, no negative numbers,
    // and a valid format.
    let format = NumberFormat::<{ FORMAT }> {};
    assert!(format.is_valid());
    debug_assert!(!float.is_special());
    debug_assert!(float >= F::ZERO);

    // Write our mantissa digits to a temporary buffer.
    let mut digits: [u8; 32] = [0u8; 32];
    let (digit_count, kappa, carried) = if float == F::ZERO {
        digits[0] = b'0';
        (1, 0, false)
    } else {
        let (start, k) = grisu(float, &mut digits);
        let (end, carried) = shared::truncate_and_round_decimal(&mut digits, start, options);
        (end, k + start as i32 - end as i32, carried)
    };

    let sci_exp = kappa + digit_count as i32 - 1 + carried as i32;
    write_float!(
        float,
        FORMAT,
        sci_exp,
        options,
        write_float_scientific,
        write_float_positive_exponent,
        write_float_negative_exponent,
        bytes => bytes,
        args => &mut digits, digit_count, sci_exp, options,
    )
}

/// Write float to string in scientific notation.
#[allow(clippy::comparison_chain)] // reason="logical approach for the algorithm"
pub fn write_float_scientific<const FORMAT: u128>(
    bytes: &mut [u8],
    digits: &mut [u8],
    digit_count: usize,
    sci_exp: i32,
    options: &Options,
) -> usize {
    debug_assert!(rtrim_char_count(&digits[..digit_count], b'0') == 0 || digit_count == 1);
    debug_assert!(digit_count <= 20);

    // Config options
    let format = NumberFormat::<{ FORMAT }> {};
    assert!(format.is_valid());
    let decimal_point = options.decimal_point();

    // Determine the exact number of digits to write.
    let exact_count = shared::min_exact_digits(digit_count, options);

    // Write our significant digits
    let mut cursor: usize;
    bytes[0] = digits[0];
    bytes[1] = decimal_point;
    if !format.no_exponent_without_fraction() && digit_count == 1 && options.trim_floats() {
        // No more digits and need to trim floats.
        cursor = 1;
    } else if digit_count < exact_count {
        // Write our significant digits.
        let src = &digits[1..digit_count];
        let dst = &mut bytes[2..digit_count + 1];
        copy_to_dst(dst, src);
        cursor = digit_count + 1;

        // Adjust the number of digits written, by appending zeros.
        let zeros = exact_count - digit_count;
        bytes[cursor..cursor + zeros].fill(b'0');
        cursor += zeros;
    } else if digit_count == 1 {
        // Write a single, trailing 0.
        bytes[2] = b'0';
        cursor = 3;
    } else {
        // Write our significant digits.
        let src = &digits[1..digit_count];
        let dst = &mut bytes[2..digit_count + 1];
        copy_to_dst(dst, src);
        cursor = digit_count + 1;
    }

    // Now, write our scientific notation.
    shared::write_exponent::<FORMAT>(bytes, &mut cursor, sci_exp, options.exponent());

    cursor
}

/// Write negative float to string without scientific notation.
///
/// Has a negative exponent (shift right) and no scientific notation.
#[allow(clippy::comparison_chain)] // reason="logical approach for the algorithm"
pub fn write_float_negative_exponent<const FORMAT: u128>(
    bytes: &mut [u8],
    digits: &mut [u8],
    digit_count: usize,
    sci_exp: i32,
    options: &Options,
) -> usize {
    debug_assert!(rtrim_char_count(&digits[..digit_count], b'0') == 0);
    debug_assert!(digit_count <= 20);
    debug_assert!(sci_exp < 0);

    // Config options
    let decimal_point = options.decimal_point();
    let sci_exp = sci_exp.wrapping_neg() as usize;

    // Write our 0 digits. Note that we cannot have carried, since we previously
    // adjusted for carrying and rounding before.
    bytes[0] = b'0';
    bytes[1] = decimal_point;
    bytes[2..sci_exp + 1].fill(b'0');
    let mut cursor = sci_exp + 1;

    // Write out significant digits.
    let src = &digits[..digit_count];
    let dst = &mut bytes[cursor..cursor + digit_count];
    copy_to_dst(dst, src);
    cursor += digit_count;

    // Determine the exact number of digits to write.
    let exact_count = shared::min_exact_digits(digit_count, options);

    // Adjust the number of digits written, based on the exact number of digits.
    if digit_count < exact_count {
        let zeros = exact_count - digit_count;
        bytes[cursor..cursor + zeros].fill(b'0');
        cursor += zeros;
    }

    cursor
}

/// Write positive float to string without scientific notation.
///
/// Has a positive exponent (shift left) and no scientific notation.
pub fn write_float_positive_exponent<const FORMAT: u128>(
    bytes: &mut [u8],
    digits: &mut [u8],
    mut digit_count: usize,
    sci_exp: i32,
    options: &Options,
) -> usize {
    debug_assert!(rtrim_char_count(&digits[..digit_count], b'0') == 0 || digit_count == 1);
    debug_assert!(digit_count <= 20);
    debug_assert!(sci_exp >= 0);

    // Config options
    let decimal_point = options.decimal_point();

    // Now need to write our significant digits.
    let leading_digits = sci_exp as usize + 1;
    let mut cursor: usize;
    let mut trimmed = false;
    if leading_digits >= digit_count {
        // We have more leading digits than digits we wrote: can write
        // any additional digits, and then just write the remaining ones.
        let src = &digits[..digit_count];
        let dst = &mut bytes[..digit_count];
        copy_to_dst(dst, src);
        bytes[digit_count..leading_digits].fill(b'0');
        cursor = leading_digits;
        digit_count = leading_digits;
        // Only write decimal point if we're not trimming floats.
        if !options.trim_floats() {
            bytes[cursor] = decimal_point;
            cursor += 1;
            bytes[cursor] = b'0';
            cursor += 1;
            digit_count += 1;
        } else {
            trimmed = true;
        }
    } else {
        // We have less leading digits than digits we wrote.

        // Write the digits before the decimal point.
        let src = &digits[..leading_digits];
        let dst = &mut bytes[..leading_digits];
        copy_to_dst(dst, src);
        bytes[leading_digits] = decimal_point;

        // Write the digits after the decimal point.
        let src = &digits[leading_digits..digit_count];
        let dst = &mut bytes[leading_digits + 1..digit_count + 1];
        copy_to_dst(dst, src);

        cursor = digit_count + 1;
    }

    // Determine the exact number of digits to write.
    let exact_count = shared::min_exact_digits(digit_count, options);

    // Change the number of digits written, if we need to add more or trim digits.
    if !trimmed && exact_count > digit_count {
        // Check if we need to write more trailing digits.
        let zeros = exact_count - digit_count;
        bytes[cursor..cursor + zeros].fill(b'0');
        cursor += zeros;
    }

    cursor
}

// ALGORITHM
// ---------

/// Round digit to normal approximation.
fn round_digit(
    digits: &mut [u8],
    digit_count: usize,
    delta: u64,
    mut rem: u64,
    kappa: u64,
    mant: u64,
) {
    debug_assert!((1..=digits.len()).contains(&digit_count));

    while rem < mant
        && delta - rem >= kappa
        && (rem + kappa < mant || mant - rem > rem + kappa - mant)
    {
        digits[digit_count - 1] -= 1;
        rem += kappa;
    }
}

/// Generate digits from upper and lower range on rounding of number.
pub fn generate_digits(
    fp: &ExtendedFloat80,
    upper: &ExtendedFloat80,
    lower: &ExtendedFloat80,
    digits: &mut [u8],
    mut k: i32,
) -> (usize, i32) {
    debug_assert!(fp.mant != 0);

    let wmant = upper.mant - fp.mant;
    let mut delta = upper.mant - lower.mant;

    let one = ExtendedFloat80 {
        mant: 1 << -upper.exp,
        exp: upper.exp,
    };

    let mut part1 = upper.mant >> -one.exp;
    let mut part2 = upper.mant & (one.mant - 1);

    let mut idx: usize = 0;
    let mut kappa: i32 = 10;
    let mut div = 1000000000;

    while kappa > 0 {
        let digit = part1 / div;
        if digit != 0 || idx != 0 {
            digits[idx] = digit_to_char_const(digit as u32, 10);
            idx += 1;
        }

        part1 -= digit * div;
        kappa -= 1;

        let tmp = (part1 << -one.exp) + part2;
        if tmp <= delta {
            k += kappa;
            round_digit(digits, idx, delta, tmp, div << -one.exp, wmant);
            return (idx, k);
        }
        div /= 10;
    }

    // 10
    let mut ten = 10;
    loop {
        part2 *= 10;
        delta *= 10;
        kappa -= 1;

        let digit = part2 >> -one.exp;
        if digit != 0 || idx != 0 {
            // In practice, this can't exceed 18, however, we have extra digits
            // **just** in case, since we write technically up to 29 here
            // before we underflow TENS.
            digits[idx] = digit_to_char_const(digit as u32, 10);
            idx += 1;
        }

        part2 &= one.mant - 1;
        if part2 < delta {
            k += kappa;
            round_digit(digits, idx, delta, part2, one.mant, wmant * ten);
            return (idx, k);
        }
        ten *= 10;
    }
}

/// Calculate the upper and lower boundaries, then invoke the float formatter.
///
/// # Preconditions
///
/// `float` must not be 0, because this fails with the Grisu algorithm.
pub fn grisu<F: Float>(float: F, digits: &mut [u8]) -> (usize, i32) {
    debug_assert!(float != F::ZERO);

    let mut w = from_float(float);

    let (lower, upper) = normalized_boundaries::<F>(&w);
    normalize(&mut w);
    let (cp, ki) = cached_grisu_power(upper.exp);

    let w = mul(&w, &cp);
    let mut upper = mul(&upper, &cp);
    let mut lower = mul(&lower, &cp);

    lower.mant += 1;
    upper.mant -= 1;

    let k = -ki;

    generate_digits(&w, &upper, &lower, digits, k)
}

// EXTENDED FLOAT

/// Create extended float from native float.
pub fn from_float<F: Float>(float: F) -> ExtendedFloat80 {
    ExtendedFloat80 {
        mant: float.mantissa().as_u64(),
        exp: float.exponent(),
    }
}

/// Normalize float-point number.
///
/// Shift the mantissa so the number of leading zeros is 0, or the value
/// itself is 0.
///
/// Get the number of bytes shifted.
pub fn normalize(fp: &mut ExtendedFloat80) {
    // Note:
    // Using the ctlz intrinsic via `leading_zeros` is way faster (~10x)
    // than shifting 1-bit at a time, via while loop, and also way
    // faster (~2x) than an unrolled loop that checks at 32, 16, 4,
    // 2, and 1 bit.
    //
    // Using a modulus of pow2 (which will get optimized to a bitwise
    // and with 0x3F or faster) is slightly slower than an if/then,
    // however, removing the if/then will likely optimize more branched
    // code as it removes conditional logic.

    // Calculate the number of leading zeros, and then zero-out
    // any overflowing bits, to avoid shl overflow when `self.mant == 0`.
    if fp.mant != 0 {
        let shift = fp.mant.leading_zeros() as i32;
        fp.mant <<= shift;
        fp.exp -= shift;
    }
}

/// Get normalized boundaries for float.
pub fn normalized_boundaries<F: Float>(fp: &ExtendedFloat80) -> (ExtendedFloat80, ExtendedFloat80) {
    let mut upper = ExtendedFloat80 {
        mant: (fp.mant << 1) + 1,
        exp: fp.exp - 1,
    };
    normalize(&mut upper);

    // Use a boolean hack to get 2 if they're equal, else 1, without
    // any branching.
    let is_hidden = fp.mant == F::HIDDEN_BIT_MASK.as_u64();
    let l_shift: i32 = is_hidden as i32 + 1;

    let mut lower = ExtendedFloat80 {
        mant: (fp.mant << l_shift) - 1,
        exp: fp.exp - l_shift,
    };
    lower.mant <<= lower.exp - upper.exp;
    lower.exp = upper.exp;

    (lower, upper)
}

/// Multiply two normalized extended-precision floats, as if by `a*b`.
///
/// The precision is maximal when the numbers are normalized, however,
/// decent precision will occur as long as both values have high bits
/// set. The result is not normalized.
///
/// Algorithm:
///     1. Non-signed multiplication of mantissas (requires 2x as many bits as
///        input).
///     2. Normalization of the result (not done here).
///     3. Addition of exponents.
pub fn mul(x: &ExtendedFloat80, y: &ExtendedFloat80) -> ExtendedFloat80 {
    // Logic check, values must be decently normalized prior to multiplication.
    debug_assert!(x.mant >> 32 != 0);
    debug_assert!(y.mant >> 32 != 0);

    // Extract high-and-low masks.
    const LOMASK: u64 = u32::MAX as u64;
    let x1 = x.mant >> 32;
    let x0 = x.mant & LOMASK;
    let y1 = y.mant >> 32;
    let y0 = y.mant & LOMASK;

    // Get our products
    let x1_y0 = x1 * y0;
    let x0_y1 = x0 * y1;
    let x0_y0 = x0 * y0;
    let x1_y1 = x1 * y1;

    let mut tmp = (x1_y0 & LOMASK) + (x0_y1 & LOMASK) + (x0_y0 >> 32);
    // round up
    tmp += 1 << (32 - 1);

    ExtendedFloat80 {
        mant: x1_y1 + (x1_y0 >> 32) + (x0_y1 >> 32) + (tmp >> 32),
        exp: x.exp + y.exp + 64,
    }
}

// CACHED POWERS

/// Find cached power of 10 from the exponent.
fn cached_grisu_power(exp: i32) -> (ExtendedFloat80, i32) {
    // Make the bounds 64 + 1 larger, since those will still work,
    // but the exp can be biased within that range.
    debug_assert!(((-1075 - 64 - 1)..=(1024 + 64 + 1)).contains(&exp));

    // FLOATING POINT CONSTANTS
    const ONE_LOG_TEN: f64 = 0.30102999566398114;
    const NPOWERS: i32 = 87;
    const FIRSTPOWER: i32 = -348; // 10 ^ -348
    const STEPPOWERS: i32 = 8;
    const EXPMAX: i32 = -32;
    const EXPMIN: i32 = -60;

    let approx = -((exp + NPOWERS) as f64) * ONE_LOG_TEN;
    let approx = approx as i32;
    let mut idx = ((approx - FIRSTPOWER) / STEPPOWERS) as usize;

    loop {
        let mant = f64::grisu_power(idx);
        let decexp = fast_decimal_power(idx);
        let binexp = fast_binary_power(decexp);
        let current = exp + binexp + 64;
        if current < EXPMIN {
            idx += 1;
            continue;
        }

        if current > EXPMAX {
            idx -= 1;
            continue;
        }

        let k = FIRSTPOWER + idx as i32 * STEPPOWERS;
        let power = ExtendedFloat80 {
            mant,
            exp: binexp,
        };
        return (power, k);
    }
}

/// Calculate a base 2 exponent from a decimal exponent.
/// This uses a pre-computed integer approximation for
/// log2(10), where 217706 / 2^16 is accurate for the
/// entire range of non-finite decimal exponents.
fn fast_binary_power(q: i32) -> i32 {
    (q.wrapping_mul(152_170 + 65536) >> 16) - 63
}

/// Calculate the fast decimal power from the index.
fn fast_decimal_power(index: usize) -> i32 {
    index as i32 * 8 - 348
}

// GRISU FLOAT
// -----------

/// Trait with specialized methods for the Grisu algorithm.
pub trait GrisuFloat: Float {
    /// Get the pre-computed Grisu power from the index.
    #[inline(always)]
    fn grisu_power(index: usize) -> u64 {
        debug_assert!(index <= GRISU_POWERS_OF_TEN.len());
        GRISU_POWERS_OF_TEN[index]
    }
}

macro_rules! grisu_impl {
    ($($t:ident)*) => ($(
        impl GrisuFloat for $t {}
    )*);
}

grisu_impl! { f32 f64 }

#[cfg(feature = "f16")]
macro_rules! grisu_unimpl {
    ($($t:ident)*) => ($(
        impl GrisuFloat for $t {
            #[inline(always)]
            fn grisu_power(_: usize) -> u64 {
                unimplemented!()
            }
        }
    )*);
}

#[cfg(feature = "f16")]
grisu_unimpl! { bf16 f16 }
