// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include <cctype>
#include <clocale>
#include <cstdlib>

#include "xmath.hpp"

_EXTERN_C_UNLESS_PURE

constexpr int _Base   = 10; // decimal
constexpr int _Ndig   = 9; // decimal digits per long word
constexpr int _Maxsig = 5 * _Ndig; // maximum significant digits to keep

_In_range_(0, maxsig) int _Stoflt(const char* s0, const char* s, char** endptr, _Out_writes_(maxsig) long lo[],
    _In_range_(1, 4) int maxsig) noexcept {
    // convert string to array of long plus exponent
    char buf[_Maxsig + 1]; // worst case, with room for rounding digit
    int nsig   = 0; // number of significant digits seen
    int seen   = 0; // any valid field characters seen
    int sticky = 0; // note any nonzero digits discarded
    int word   = 0; // just before current long word to fill

    maxsig *= _Ndig; // convert word count to digit count
    if (_Maxsig < maxsig) {
        maxsig = _Maxsig; // protect against bad call
    }

    lo[0] = 0; // power of ten exponent
    lo[1] = 0; // first _Ndig-digit word of fraction

    while (*s == '0') { // strip leading zeros
        seen = 1;
        ++s;
    }

    while (isdigit(static_cast<unsigned char>(*s))) {
        if (nsig < maxsig) {
            buf[nsig++] = static_cast<char>(*s - '0'); // accumulate a digit
        } else { // just scale exponent and note nonzero digits
            ++lo[0];
            if (*s != '0') {
                sticky = 1;
            }
        }

        ++s;
        seen = 1;
    }

    if (*s == localeconv()->decimal_point[0]) {
        ++s;
    }

    if (nsig == 0) {
        for (; *s == '0'; ++s, seen = 1) {
            --lo[0]; // scale for stripped zeros after point
        }
    }

    for (; isdigit(static_cast<unsigned char>(*s)); ++s, seen = 1) {
        if (nsig < maxsig) { // accumulate a fraction digit
            buf[nsig++] = static_cast<char>(*s - '0');
            --lo[0];
        } else if (*s != '0') {
            sticky = 1;
        }
    }

    if (sticky) {
        buf[maxsig - 1] += 1; // okay if digit becomes _Base
    }

    for (; 0 < nsig && buf[nsig - 1] == '\0'; --nsig) {
        ++lo[0]; // discard trailing zeros
    }

    if (nsig == 0) {
        buf[nsig++] = '\0'; // ensure at least one digit
    }

    if (seen) { // convert digit sequence to words
        int bufidx  = 0; // next digit in buffer
        int wordidx = _Ndig - nsig % _Ndig; // next digit in word (% _Ndig)

        word = wordidx % _Ndig == 0 ? 0 : 1;
        for (; bufidx < nsig; ++wordidx, ++bufidx) {
            if (wordidx % _Ndig == 0) {
                lo[++word] = buf[bufidx];
            } else {
                lo[word] = lo[word] * _Base + buf[bufidx];
            }
        }

        if (*s == 'e' || *s == 'E') { // parse exponent
            const char* ssav = s;
            const char esign = *++s == '+' || *s == '-' ? *s++ : '+';
            int eseen        = 0;
            long lexp        = 0;

            for (; isdigit(static_cast<unsigned char>(*s)); ++s, eseen = 1) {
                if (lexp < 100000000) { // else overflow
                    lexp = lexp * 10 + static_cast<unsigned char>(*s) - '0';
                }
            }

            if (esign == '-') {
                lexp = -lexp;
            }

            lo[0] += lexp;
            if (!eseen) {
                s = ssav; // roll back if incomplete exponent
            }
        }
    }

    if (!seen) {
        word = 0; // return zero if bad parse
    }

    if (endptr) {
        *endptr = const_cast<char*>(seen ? s : s0); // roll back if bad parse
    }

    return word;
}

_END_EXTERN_C_UNLESS_PURE
