/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you 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 .
 */

#pragma once

#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstddef>
#include <cstdlib>
#include <cstring>
#include <cwchar>
#include <limits>
#include <new>
#include <string_view>
#include <type_traits>
#include <utility>

#include "strimp.hxx"

#include <o3tl/safeint.hxx>
#include <o3tl/string_view.hxx>
#include <osl/diagnose.h>
#include <sal/log.hxx>
#include <rtl/character.hxx>
#include <rtl/math.h>
#include <rtl/string.h>
#include <rtl/ustring.h>

#include <dragonbox/dragonbox.h>

void internRelease(rtl_uString*);

namespace rtl::str
{
template <typename C> auto UChar(C c) { return std::make_unsigned_t<C>(c); }

// Wrappers around null-terminated/known-length strings, that allow to generalize algorithms
// without overhead (e.g., without need to get length of null-terminated strings).

template <typename C> struct null_terminated
{
    C* p;
    null_terminated(C* pStr)
        : p(pStr)
    {
        assert(pStr);
    }
    auto begin() const { return p; }
    struct EndDetector
    {
        friend bool operator==(EndDetector, C* iter) { return *iter == 0; }
        friend bool operator==(C* iter, EndDetector) { return *iter == 0; }
    };
    static auto end() { return EndDetector{}; }
};

template <typename C> struct with_length
{
    C* p;
    sal_Int32 len;
    with_length(C* pStr, sal_Int32 nLength)
        : p(pStr)
        , len(nLength)
    {
        assert(len >= 0);
    }
    auto begin() const { return p; }
    auto end() const { return p + len; }
};

template <bool (&fApplicable)(sal_uInt32), sal_uInt32 (&fReplace)(sal_uInt32)> struct CaseReplace
{
    static auto Applicable() { return [](auto c) { return fApplicable(UChar(c)); }; }
    template <typename C> static C Replace(C c) { return fReplace(UChar(c)); }
};
constexpr CaseReplace<rtl::isAsciiUpperCase, rtl::toAsciiLowerCase> toAsciiLower;
constexpr CaseReplace<rtl::isAsciiLowerCase, rtl::toAsciiUpperCase> toAsciiUpper;

template <typename C> struct FromTo
{
    C from;
    C to;
    FromTo(C cFrom, C cTo) : from(cFrom), to(cTo) {}
    auto Applicable() const { return [this](C c) { return c == from; }; }
    C Replace(C c) const { return c == from ? to : c; }
};

template <typename C> void Copy(C* _pDest, const C* _pSrc, sal_Int32 _nCount)
{
    // take advantage of builtin optimisations
    std::copy(_pSrc, _pSrc + _nCount, _pDest);
}

template <typename C> void CopyBackward(C* _pDest, const C* _pSrc, sal_Int32 _nCount)
{
    // take advantage of builtin optimisations
    std::copy_backward(_pSrc, _pSrc + _nCount, _pDest + _nCount);
}

inline void Copy(sal_Unicode* _pDest, const char* _pSrc, sal_Int32 _nCount)
{
    std::transform(_pSrc, _pSrc + _nCount, _pDest,
                   [](char c)
                   {
                       assert(rtl::isAscii(static_cast<unsigned char>(c)));
                       SAL_WARN_IF(c == '\0', "rtl.string", "Found embedded \\0 ASCII character");
                       return static_cast<unsigned char>(c);
                   });
}

inline sal_Int16 implGetDigit(sal_Unicode ch, sal_Int16 nRadix)
{
    sal_Int16 n = -1;
    if ((ch >= '0') && (ch <= '9'))
        n = ch - '0';
    else if ((ch >= 'a') && (ch <= 'z'))
        n = ch - 'a' + 10;
    else if ((ch >= 'A') && (ch <= 'Z'))
        n = ch - 'A' + 10;
    return (n < nRadix) ? n : -1;
}

/* ======================================================================= */
/* C-String functions which could be used without the String-Class         */
/* ======================================================================= */

template <typename T> sal_Int32 getLength( const T* pStr )
{
    assert(pStr);
    if constexpr (std::is_class_v<T>)
    {
        return pStr->length;
    }
    else
    {
        // take advantage of builtin optimisations
        return std::char_traits<T>::length(pStr);
    }
}

/* ----------------------------------------------------------------------- */

template <typename C> void warnIfCharAndNotAscii(C c)
{
    if constexpr (sizeof(c) == sizeof(char))
        SAL_WARN_IF(!rtl::isAscii(static_cast<unsigned char>(c)), "rtl.string",
                    "Found non-ASCII char");
}

template <typename C1, typename C2> void warnIfOneIsCharAndNotAscii(C1 c1, C2 c2)
{
    if constexpr (sizeof(c1) != sizeof(c2))
    {
        warnIfCharAndNotAscii(c1);
        warnIfCharAndNotAscii(c2);
    }
}

struct CompareNormal
{
    template <typename C1, typename C2> static sal_Int32 compare(C1 c1, C2 c2)
    {
        warnIfOneIsCharAndNotAscii(c1, c2);
        return static_cast<sal_Int32>(UChar(c1))
               - static_cast<sal_Int32>(UChar(c2));
    }
};

struct CompareIgnoreAsciiCase
{
    template <typename C1, typename C2> static sal_Int32 compare(C1 c1, C2 c2)
    {
        warnIfOneIsCharAndNotAscii(c1, c2);
        return rtl::compareIgnoreAsciiCase(UChar(c1), UChar(c2));
    }
};

/* ----------------------------------------------------------------------- */

struct NoShortening
{
    constexpr bool operator>=(int) { return true; } // for assert
    constexpr bool operator==(int) { return false; } // for loop break check
    constexpr void operator--() {} // for decrement in loop
} constexpr noShortening;

template <class S1, class S2, class Compare, typename Shorten_t>
sal_Int32 compare(S1 s1, S2 s2, Compare, Shorten_t shortenedLength)
{
    static_assert(std::is_same_v<Shorten_t, NoShortening> || std::is_same_v<Shorten_t, sal_Int32>);
    assert(shortenedLength >= 0);
    auto pStr1 = s1.begin();
    const auto end1 = s1.end();
    auto pStr2 = s2.begin();
    const auto end2 = s2.end();
    for (;;)
    {
        if (shortenedLength == 0)
            return 0;
        if (pStr2 == end2)
            return pStr1 == end1 ? 0 : 1;
        if (pStr1 == end1)
            return -1;
        if (const sal_Int32 nRet = Compare::compare(*pStr1, *pStr2))
            return nRet;
        --shortenedLength;
        ++pStr1;
        ++pStr2;
    }
}

// take advantage of builtin optimisations
template <typename C> requires (sizeof(C) == sizeof(wchar_t))
sal_Int32 compare(null_terminated<C> s1, null_terminated<C> s2, CompareNormal, NoShortening)
{
    return wcscmp(reinterpret_cast<wchar_t const*>(s1.p), reinterpret_cast<wchar_t const*>(s2.p));
}
template <typename C> requires (sizeof(C) == sizeof(char))
sal_Int32 compare(null_terminated<C> s1, null_terminated<C> s2, CompareNormal, NoShortening)
{
    return strcmp(reinterpret_cast<char const*>(s1.p), reinterpret_cast<char const*>(s2.p));
}
template <typename C>
sal_Int32 compare(with_length<C> s1, with_length<C> s2, CompareNormal, NoShortening)
{
    std::basic_string_view sv1(s1.p, s1.len);
    return sv1.compare(std::basic_string_view(s2.p, s2.len));
}
template <typename C1, typename C2, class Compare>
sal_Int32 compare(with_length<C1> s1, with_length<C2> s2, Compare cf, sal_Int32 nShortenedLength)
{
    assert(nShortenedLength >= 0);
    s1.len = std::min(s1.len, nShortenedLength);
    s2.len = std::min(s2.len, nShortenedLength);
    return compare(s1, s2, cf, noShortening);
}

/* ----------------------------------------------------------------------- */

template <typename C1, typename C2, class Compare>
sal_Int32 reverseCompare_WithLengths(const C1* pStr1, sal_Int32 nStr1Len,
                                     const C2* pStr2, sal_Int32 nStr2Len, Compare)
{
    assert(pStr1 || nStr1Len == 0);
    assert(nStr1Len >= 0);
    assert(pStr2 || nStr2Len == 0);
    assert(nStr2Len >= 0);
    const C1* pStr1Run = pStr1+nStr1Len;
    const C2* pStr2Run = pStr2+nStr2Len;
    while ((pStr1 < pStr1Run) && (pStr2 < pStr2Run))
    {
        pStr1Run--;
        pStr2Run--;
        if (const sal_Int32 nRet = Compare::compare(*pStr1Run, *pStr2Run))
            return nRet;
    }

    return nStr1Len - nStr2Len;
}

/* ----------------------------------------------------------------------- */

template <typename C> sal_Int32 hashCode_WithLength(const C* pStr, sal_Int32 nLen)
{
    assert(nLen >= 0);
    sal_uInt32 h = static_cast<sal_uInt32>(nLen);
    while ( nLen > 0 )
    {
        h = (h*37U) + UChar( *pStr );
        pStr++;
        nLen--;
    }
    return static_cast<sal_Int32>(h);
}

/* ----------------------------------------------------------------------- */

template <typename C> sal_Int32 hashCode(const C* pStr)
{
    return hashCode_WithLength( pStr, getLength( pStr ) );
}

/* ----------------------------------------------------------------------- */

template <typename C> sal_Int32 indexOfChar(const C* pStr, C c)
{
    assert(pStr);
    if (!c)
        return -1; // Unifies behavior of strchr/wcschr and unoptimized algorithm wrt '\0'

    if constexpr (sizeof(C) == sizeof(char))
    {
        // take advantage of builtin optimisations
        const C* p = strchr(pStr, c);
        return p ? p - pStr : -1;
    }
    else if constexpr (sizeof(C) == sizeof(wchar_t))
    {
        // take advantage of builtin optimisations
        wchar_t const * p = wcschr(reinterpret_cast<wchar_t const *>(pStr), static_cast<wchar_t>(c));
        return p ? p - reinterpret_cast<wchar_t const *>(pStr) : -1;
    }
    else
    {
        const C* pTempStr = pStr;
        while ( *pTempStr )
        {
            if ( *pTempStr == c )
                return pTempStr-pStr;

            pTempStr++;
        }

        return -1;
    }
}

/* ----------------------------------------------------------------------- */

template <typename C> sal_Int32 indexOfChar_WithLength(const C* pStr, sal_Int32 nLen, C c)
{
//    assert(nLen >= 0);
    if (nLen <= 0)
        return -1;
    // take advantage of builtin optimisations
    std::basic_string_view v(pStr, nLen);
    auto idx = v.find(c);
    return idx == v.npos ? -1 : idx;
}

/* ----------------------------------------------------------------------- */

template <typename C> sal_Int32 lastIndexOfChar_WithLength(const C* pStr, sal_Int32 nLen, C c)
{
    assert(nLen >= 0);
    // take advantage of builtin optimisations
    std::basic_string_view v(pStr, nLen);
    auto idx = v.rfind(c);
    return idx == v.npos ? -1 : idx;
}

/* ----------------------------------------------------------------------- */

template <typename C> sal_Int32 lastIndexOfChar(const C* pStr, C c)
{
    assert(pStr);
    if (!c)
        return -1; // Unifies behavior of strrchr/wcsrchr and lastIndexOfChar_WithLength wrt '\0'

    if constexpr (sizeof(C) == sizeof(char))
    {
        // take advantage of builtin optimisations
        const C* p = strrchr(pStr, c);
        return p ? p - pStr : -1;
    }
    else if constexpr (sizeof(C) == sizeof(wchar_t))
    {
        // take advantage of builtin optimisations
        wchar_t const * p = wcsrchr(reinterpret_cast<wchar_t const *>(pStr), static_cast<wchar_t>(c));
        return p ? p - reinterpret_cast<wchar_t const *>(pStr) : -1;
    }
    else
    {
        return lastIndexOfChar_WithLength( pStr, getLength( pStr ), c );
    }
}

/* ----------------------------------------------------------------------- */

template <typename C>
sal_Int32 indexOfStr_WithLength(const C* pStr, sal_Int32 nStrLen,
                                const C* pSubStr, sal_Int32 nSubLen)
{
    assert(nStrLen >= 0);
    assert(nSubLen >= 0);
    /* an empty SubString is always not findable */
    if ( nSubLen == 0 )
        return -1;
    // take advantage of builtin optimisations
    std::basic_string_view v(pStr, nStrLen);
    auto idx = nSubLen == 1 ? v.find(*pSubStr) : v.find(pSubStr, 0, nSubLen);
    return idx == v.npos ? -1 : idx;
}

inline sal_Int32 indexOfStr_WithLength(const sal_Unicode* pStr, sal_Int32 nStrLen,
                                       const char* pSubStr, sal_Int32 nSubLen)
{
    assert(nStrLen >= 0);
    assert(nSubLen >= 0);
    if (nSubLen > 0 && nSubLen <= nStrLen)
    {
        sal_Unicode const* end = pStr + nStrLen;
        sal_Unicode const* cursor = pStr;

        while (cursor < end)
        {
            cursor = std::char_traits<sal_Unicode>::find(cursor, end - cursor, *pSubStr);
            if (!cursor || (end - cursor < nSubLen))
            {
                /* no enough left to actually have a match */
                break;
            }
            /* now it is worth trying a full match */
            if (nSubLen == 1 || rtl_ustr_asciil_reverseEquals_WithLength(cursor, pSubStr, nSubLen))
            {
                return cursor - pStr;
            }
            cursor += 1;
        }
    }
    return -1;
}

/* ----------------------------------------------------------------------- */

template <typename C> sal_Int32 indexOfStr(const C* pStr, const C* pSubStr)
{
    assert(pStr);
    assert(pSubStr);
    /* an empty SubString is always not findable */
    if (*pSubStr == 0)
        return -1;
    if constexpr (sizeof(C) == sizeof(char))
    {
        // take advantage of builtin optimisations
        const C* p = strstr(pStr, pSubStr);
        return p ? p - pStr : -1;
    }
    else if constexpr (sizeof(C) == sizeof(wchar_t))
    {
        // take advantage of builtin optimisations
        wchar_t const * p = wcsstr(reinterpret_cast<wchar_t const *>(pStr), reinterpret_cast<wchar_t const *>(pSubStr));
        return p ? p - reinterpret_cast<wchar_t const *>(pStr) : -1;
    }
    else
    {
        return indexOfStr_WithLength( pStr, getLength( pStr ),
                                                         pSubStr, getLength( pSubStr ) );
    }
}

/* ----------------------------------------------------------------------- */

template <typename C>
sal_Int32 lastIndexOfStr_WithLength(const C* pStr, sal_Int32 nStrLen,
                                    const C* pSubStr, sal_Int32 nSubLen)
{
    assert(nStrLen >= 0);
    assert(nSubLen >= 0);
    /* an empty SubString is always not findable */
    if ( nSubLen == 0 )
        return -1;
    // take advantage of builtin optimisations
    std::basic_string_view v(pStr, nStrLen);
    std::basic_string_view needle(pSubStr, nSubLen);
    auto idx = v.rfind(needle);
    return idx == v.npos ? -1 : idx;
}

/* ----------------------------------------------------------------------- */

template <typename C> sal_Int32 lastIndexOfStr(const C* pStr, const C* pSubStr)
{
    return lastIndexOfStr_WithLength(pStr, getLength(pStr), pSubStr, getLength(pSubStr));
}

/* ----------------------------------------------------------------------- */

template <class S, class Replacer> void replaceChars(S str, Replacer replacer)
{
    for (auto& rChar : str)
        rChar = replacer.Replace(rChar);
}

/* ----------------------------------------------------------------------- */

template <typename C> sal_Int32 trim_WithLength(C* pStr, sal_Int32 nLen)
{
    const auto view = o3tl::trim(std::basic_string_view(pStr, nLen));

    if (static_cast<sal_Int32>(view.size()) != nLen)
    {
        nLen = static_cast<sal_Int32>(view.size());
        if (view.data() != pStr)
            Copy(pStr, view.data(), nLen);
        *(pStr+nLen) = 0;
    }

    return nLen;
}

/* ----------------------------------------------------------------------- */

template <typename C> sal_Int32 trim(C* pStr) { return trim_WithLength(pStr, getLength(pStr)); }

/* ----------------------------------------------------------------------- */

template <typename C> sal_Int32 valueOfBoolean(C* pStr, sal_Bool b)
{
    assert(pStr);
    if ( b )
    {
        *pStr = 't';
        pStr++;
        *pStr = 'r';
        pStr++;
        *pStr = 'u';
        pStr++;
        *pStr = 'e';
        pStr++;
        *pStr = 0;
        return 4;
    }
    else
    {
        *pStr = 'f';
        pStr++;
        *pStr = 'a';
        pStr++;
        *pStr = 'l';
        pStr++;
        *pStr = 's';
        pStr++;
        *pStr = 'e';
        pStr++;
        *pStr = 0;
        return 5;
    }
}

/* ----------------------------------------------------------------------- */

template <typename C> sal_Int32 valueOfChar(C* pStr, C c)
{
    assert(pStr);
    *pStr++ = c;
    *pStr = 0;
    return 1;
}

/* ----------------------------------------------------------------------- */

template <sal_Int32 maxLen, typename C, typename T>
sal_Int32 valueOfInt(C* pStr, T n, sal_Int16 nRadix)
{
    assert(pStr);
    assert( nRadix >= RTL_STR_MIN_RADIX && nRadix <= RTL_STR_MAX_RADIX );
    const auto* const pStart = pStr;
    char    aBuf[maxLen];
    char*   pBuf = aBuf;
    using uT = std::make_unsigned_t<T>;
    uT nValue;

    /* Radix must be valid */
    if ( (nRadix < RTL_STR_MIN_RADIX) || (nRadix > RTL_STR_MAX_RADIX) )
        nRadix = 10;

    if constexpr (std::is_signed_v<T>)
    {
        /* is value negative */
        if ( n < 0 )
        {
            *pStr = '-';
            pStr++;
            nValue = n == std::numeric_limits<T>::min() ? static_cast<uT>(n) : -n;
        }
        else
            nValue = n;
    }
    else
        nValue = n;

    /* create a recursive buffer with all values, except the last one */
    do
    {
        char nDigit = static_cast<char>(nValue % nRadix);
        nValue /= nRadix;
        if ( nDigit > 9 )
            *pBuf = (nDigit-10) + 'a';
        else
            *pBuf = (nDigit + '0' );
        pBuf++;
    }
    while ( nValue > 0 );

    /* copy the values in the right direction into the destination buffer */
    pStr = std::reverse_copy(aBuf, pBuf, pStr);
    *pStr = 0;

    return pStr - pStart;
}

/* ----------------------------------------------------------------------- */

template <typename C> sal_Bool toBoolean(const C* pStr)
{
    assert(pStr);
    if ( *pStr == '1' )
        return true;

    if ( (*pStr == 'T') || (*pStr == 't') )
    {
        pStr++;
        if ( (*pStr == 'R') || (*pStr == 'r') )
        {
            pStr++;
            if ( (*pStr == 'U') || (*pStr == 'u') )
            {
                pStr++;
                if ( (*pStr == 'E') || (*pStr == 'e') )
                    return true;
            }
        }
    }

    return false;
}

/* ----------------------------------------------------------------------- */

template <typename T, class Iter> inline bool HandleSignChar(Iter& iter)
{
    if constexpr (std::numeric_limits<T>::is_signed)
    {
        if (*iter == '-')
        {
            ++iter;
            return true;
        }
    }
    if (*iter == '+')
        ++iter;
    return false;
}

template <typename T> std::pair<T, sal_Int16> DivMod(sal_Int16 nRadix, [[maybe_unused]] bool bNeg)
{
    if constexpr (std::numeric_limits<T>::is_signed)
        if (bNeg)
            return { -(std::numeric_limits<T>::min() / nRadix),
                     -(std::numeric_limits<T>::min() % nRadix) };
    return { std::numeric_limits<T>::max() / nRadix, std::numeric_limits<T>::max() % nRadix };
}

template <typename T, class S> T toInt(S str, sal_Int16 nRadix)
{
    assert( nRadix >= RTL_STR_MIN_RADIX && nRadix <= RTL_STR_MAX_RADIX );

    if ( (nRadix < RTL_STR_MIN_RADIX) || (nRadix > RTL_STR_MAX_RADIX) )
        nRadix = 10;

    auto pStr = str.begin();
    const auto end = str.end();

    /* Skip whitespaces */
    while (pStr != end && o3tl::internal::implIsWhitespace(UChar(*pStr)))
        pStr++;
    if (pStr == end)
        return 0;

    const bool bNeg = HandleSignChar<T>(pStr);
    const auto& [nDiv, nMod] = DivMod<T>(nRadix, bNeg);
    assert(nDiv > 0);

    std::make_unsigned_t<T> n = 0;
    while (pStr != end)
    {
        sal_Int16 nDigit = implGetDigit(UChar(*pStr), nRadix);
        if ( nDigit < 0 )
            break;
        if (static_cast<std::make_unsigned_t<T>>(nMod < nDigit ? nDiv - 1 : nDiv) < n)
            return 0;

        n *= nRadix;
        n += nDigit;

        pStr++;
    }

    if constexpr (std::numeric_limits<T>::is_signed)
        if (bNeg)
            return n == static_cast<std::make_unsigned_t<T>>(std::numeric_limits<T>::min())
                       ? std::numeric_limits<T>::min()
                       : -static_cast<T>(n);
    return static_cast<T>(n);
}

/* ======================================================================= */
/* Internal String-Class help functions                                    */
/* ======================================================================= */

template <class rtl_tString> using Char_T = std::remove_extent_t<decltype(rtl_tString::buffer)>;

template <typename rtl_tString> rtl_tString* Alloc(sal_Int32 nLen)
{
    constexpr auto fix = offsetof(rtl_tString, buffer) + sizeof rtl_tString::buffer;
    rtl_tString * pData
        = (o3tl::make_unsigned(nLen)
           <= ((std::numeric_limits<std::size_t>::max() - fix)
               / sizeof (Char_T<rtl_tString>)))
        ? static_cast<rtl_tString *>(rtl_allocateString(
            fix + nLen * sizeof (Char_T<rtl_tString>)))
        : nullptr;
    if (pData != nullptr) {
        pData->refCount = 1;
        pData->length = nLen;
        pData->buffer[nLen] = 0;
    }
    return pData;
}

/* ======================================================================= */
/* String-Class functions                                                  */
/* ======================================================================= */

template <typename rtl_tString> void acquire(rtl_tString* pThis)
{
    if (!SAL_STRING_IS_STATIC (pThis))
        osl_atomic_increment( &((pThis)->refCount) );
}

/* ----------------------------------------------------------------------- */

template <typename rtl_tString> void release(rtl_tString* pThis)
{
    if (SAL_UNLIKELY(SAL_STRING_IS_STATIC (pThis)))
        return;

    /* OString doesn't have an 'intern' */
    if constexpr (sizeof(Char_T<rtl_tString>) == sizeof(sal_Unicode))
    {
        if (SAL_STRING_IS_INTERN (pThis))
        {
            internRelease (pThis);
            return;
        }
    }

    if ( !osl_atomic_decrement( &(pThis->refCount) ) )
    {
        RTL_LOG_STRING_DELETE( pThis );
        rtl_freeString( pThis );
    }
}

/* ----------------------------------------------------------------------- */

/* static data to be referenced by all empty strings
 * the refCount is predefined to 1 and must never become 0 !
 */
template <typename rtl_tString> struct EmptyStringImpl
{
    static rtl_tString data;
};

template <>
inline rtl_uString EmptyStringImpl<rtl_uString>::data = {
    sal_Int32(SAL_STRING_INTERN_FLAG | SAL_STRING_STATIC_FLAG | 1), /* sal_Int32   refCount;  */
    0,                                                              /* sal_Int32   length;    */
    { 0 }                                                           /* sal_Unicode buffer[1]; */
};

template <>
inline rtl_String EmptyStringImpl<rtl_String>::data = {
    SAL_STRING_STATIC_FLAG | 1, /* sal_Int32 refCount;  */
    0,                          /* sal_Int32 length;    */
    { 0 }                       /* char      buffer[1]; */
};

template <typename rtl_tString> void new_(rtl_tString** ppThis)
{
    assert(ppThis);
    if ( *ppThis)
        release( *ppThis );

    *ppThis = &EmptyStringImpl<rtl_tString>::data;
}

/* ----------------------------------------------------------------------- */

template <typename rtl_tString> void new_WithLength(rtl_tString** ppThis, sal_Int32 nLen)
{
    assert(ppThis);
    assert(nLen >= 0);
    if ( nLen <= 0 )
        new_( ppThis );
    else
    {
        if ( *ppThis)
            release( *ppThis );

        *ppThis = Alloc<rtl_tString>( nLen );
        assert(*ppThis != nullptr);
        (*ppThis)->length   = 0;
        (*ppThis)->buffer[0] = 0;
    }
}

/* ----------------------------------------------------------------------- */

template <typename rtl_tString, typename C>
void newFromStr_WithLength(rtl_tString** ppThis, const C* pCharStr, sal_Int32 nLen,
                           sal_Int32 allocExtra = 0)
{
    assert(ppThis);
    assert(nLen >= 0);
    assert(pCharStr || nLen == 0);
    assert(allocExtra >= 0);

    if (nLen + allocExtra == 0)
        return new_(ppThis);

    rtl_tString* pOrg = *ppThis;
    *ppThis = Alloc<rtl_tString>(nLen + allocExtra);
    assert(*ppThis != nullptr);
    if (nLen > 0)
        Copy((*ppThis)->buffer, pCharStr, nLen);
    if (allocExtra > 0)
    {
        (*ppThis)->length = nLen;
        (*ppThis)->buffer[nLen] = 0;
    }

    RTL_LOG_STRING_NEW(*ppThis);

    /* must be done last, if pCharStr belongs to *ppThis */
    if (pOrg)
        release(pOrg);
}

template <typename rtl_tString> void newFromString(rtl_tString** ppThis, const rtl_tString* pStr)
{
    assert(pStr);

    newFromStr_WithLength(ppThis, pStr->buffer, pStr->length);
}

/* ----------------------------------------------------------------------- */

template <typename rtl_tString>
void newFromStr(rtl_tString** ppThis, const Char_T<rtl_tString>* pCharStr)
{
    newFromStr_WithLength(ppThis, pCharStr, getLength(pCharStr));
}

/* ----------------------------------------------------------------------- */

template <typename rtl_tString> void assign(rtl_tString** ppThis, rtl_tString* pStr)
{
    assert(ppThis);
    /* must be done at first, if pStr == *ppThis */
    acquire( pStr );

    if ( *ppThis )
        release( *ppThis );

    *ppThis = pStr;
}

/* ----------------------------------------------------------------------- */

template <typename rtl_tString>
void newFromSubString(rtl_tString** ppThis, const rtl_tString* pFrom, sal_Int32 beginIndex,
                      sal_Int32 count)
{
    assert(ppThis);
    if ( beginIndex == 0 && count == pFrom->length )
        return assign(ppThis, const_cast<rtl_tString*>(pFrom));
    if ( count < 0 || beginIndex < 0 || beginIndex + count > pFrom->length )
    {
        assert(false); // fail fast at least in debug builds
        return newFromStr_WithLength(ppThis, "!!br0ken!!", 10);
    }

    newFromStr_WithLength( ppThis, pFrom->buffer + beginIndex, count );
}

/* ----------------------------------------------------------------------- */

template <typename rtl_tString> auto* getStr(rtl_tString* pThis)
{
    assert(pThis);
    return pThis->buffer;
}

/* ----------------------------------------------------------------------- */

enum ThrowPolicy { NoThrow, Throw };

template <ThrowPolicy throwPolicy, typename rtl_tString, typename C1, typename C2>
void newConcat(rtl_tString** ppThis, const C1* pLeft, sal_Int32 nLeftLength,
               const C2* pRight, sal_Int32 nRightLength)
{
    assert(ppThis);
    assert(nLeftLength >= 0);
    assert(pLeft || nLeftLength == 0);
    assert(nRightLength >= 0);
    assert(pRight || nRightLength == 0);
    rtl_tString* pOrg = *ppThis;

    if (nLeftLength > std::numeric_limits<sal_Int32>::max() - nRightLength)
    {
        if constexpr (throwPolicy == NoThrow)
            *ppThis = nullptr;
        else
        {
#if !defined(__COVERITY__) || __COVERITY_MAJOR__ > 2023
            throw std::length_error("newConcat");
#else
            //coverity doesn't report std::bad_alloc as an unhandled exception when
            //potentially thrown from destructors but does report std::length_error
            throw std::bad_alloc();
#endif
        }
    }
    else
    {
        auto* pTempStr = Alloc<rtl_tString>(nLeftLength + nRightLength);
        OSL_ASSERT(pTempStr != nullptr);
        *ppThis = pTempStr;
        if (*ppThis != nullptr) {
            if (nLeftLength)
                Copy( pTempStr->buffer, pLeft, nLeftLength );
            if (nRightLength)
                Copy( pTempStr->buffer+nLeftLength, pRight, nRightLength );

            RTL_LOG_STRING_NEW( *ppThis );
        }
    }

    /* must be done last, if left or right == *ppThis */
    if ( pOrg )
        release( pOrg );
}

template <typename rtl_tString, typename C>
void newConcat(rtl_tString** ppThis, rtl_tString* pLeft, const C* pRight, sal_Int32 nRightLength)
{
    assert(pLeft != nullptr);
    if (nRightLength == 0)
        assign(ppThis, pLeft);
    else
        newConcat<Throw>(ppThis, pLeft->buffer, pLeft->length, pRight, nRightLength);
}

template <typename rtl_tString>
void newConcat(rtl_tString** ppThis, rtl_tString* pLeft, rtl_tString* pRight)
{
    /* Test for 0-Pointer - if not, change newReplaceStrAt! */
    if ( !pRight || !pRight->length )
    {
        assert(pLeft != nullptr);
        assign(ppThis, pLeft);
    }
    else if ( !pLeft || !pLeft->length )
        assign(ppThis, pRight);
    else
        newConcat<NoThrow>(ppThis, pLeft->buffer, pLeft->length, pRight->buffer, pRight->length);
}

/* ----------------------------------------------------------------------- */

template <typename rtl_tString> void ensureCapacity(rtl_tString** ppThis, sal_Int32 size)
{
    assert(ppThis);
    rtl_tString* const pOrg = *ppThis;
    if ( pOrg->refCount == 1 && pOrg->length >= size )
        return;
    assert( pOrg->length <= size ); // do not truncate
    auto* pTempStr = Alloc<rtl_tString>( size );
    Copy( pTempStr->buffer, pOrg->buffer, pOrg->length );
    // right now the length is still the same as of the original
    pTempStr->length = pOrg->length;
    pTempStr->buffer[ pOrg->length ] = '\0';
    *ppThis = pTempStr;
    RTL_LOG_STRING_NEW( *ppThis );

    release( pOrg );
}

/* ----------------------------------------------------------------------- */

template <typename rtl_tString, typename C>
void newReplaceStrAt(rtl_tString** ppThis, rtl_tString* pStr, sal_Int32 nIndex, sal_Int32 nCount,
                     const C* pNewSubStr, sal_Int32 nNewSubStrLen)
{
    assert(ppThis);
    assert(nIndex >= 0 && nIndex <= pStr->length);
    assert(nCount >= 0);
    assert(nCount <= pStr->length - nIndex);
    assert(pNewSubStr != nullptr || nNewSubStrLen == 0);
    assert(nNewSubStrLen >= 0);
    /* Append? */
    if ( nIndex >= pStr->length )
        return newConcat(ppThis, pStr, pNewSubStr, nNewSubStrLen);

    /* not more than the String length could be deleted */
    if ( nCount >= pStr->length-nIndex )
    {
        /* Assign of NewSubStr? */
        if (nIndex == 0)
            return newFromStr_WithLength( ppThis, pNewSubStr, nNewSubStrLen );

        nCount = pStr->length - nIndex;
    }

    /* Assign of Str? */
    if ( !nCount && !nNewSubStrLen )
        return assign(ppThis, pStr);

    rtl_tString*    pOrg = *ppThis;

    /* Alloc New Buffer */
    *ppThis = Alloc<rtl_tString>(pStr->length - nCount + nNewSubStrLen);
    assert(*ppThis != nullptr);
    auto* pBuffer = (*ppThis)->buffer;
    if ( nIndex )
    {
        Copy( pBuffer, pStr->buffer, nIndex );
        pBuffer += nIndex;
    }
    if ( nNewSubStrLen )
    {
        Copy( pBuffer, pNewSubStr, nNewSubStrLen );
        pBuffer += nNewSubStrLen;
    }
    Copy( pBuffer, pStr->buffer+nIndex+nCount, pStr->length-nIndex-nCount );

    RTL_LOG_STRING_NEW( *ppThis );
    /* must be done last, if pStr or pNewSubStr == *ppThis */
    if ( pOrg )
        release( pOrg );
}

/* ----------------------------------------------------------------------- */

template <typename rtl_tString>
void newReplaceStrAt(rtl_tString** ppThis, rtl_tString* pStr, sal_Int32 nIndex, sal_Int32 nCount,
                     rtl_tString* pNewSubStr)
{
    assert(ppThis);
    assert(nIndex >= 0 && nIndex <= pStr->length);
    assert(nCount >= 0);
    assert(nCount <= pStr->length - nIndex);
    /* Append? */
    if (nIndex >= pStr->length)
    {
        /* newConcat test, if pNewSubStr is 0 */
        newConcat(ppThis, pStr, pNewSubStr);
        return;
    }

    /* not more than the String length could be deleted */
    if (nCount >= pStr->length-nIndex)
    {
        /* Assign of NewSubStr? */
        if (nIndex == 0)
        {
            if (!pNewSubStr)
                return new_(ppThis);
            else
                return assign(ppThis, pNewSubStr);
        }
        nCount = pStr->length - nIndex;
    }

    const auto* pNewSubStrBuf = pNewSubStr ? pNewSubStr->buffer : nullptr;
    const sal_Int32 nNewSubStrLength = pNewSubStr ? pNewSubStr->length : 0;
    newReplaceStrAt(ppThis, pStr, nIndex, nCount, pNewSubStrBuf, nNewSubStrLength);
}

/* ----------------------------------------------------------------------- */

template <typename rtl_tString, class Replacer>
void newReplaceChars(rtl_tString** ppThis, rtl_tString* pStr, Replacer replacer)
{
    assert(ppThis);
    assert(pStr);

    const auto pEnd = pStr->buffer + pStr->length;
    auto pCharStr = std::find_if(pStr->buffer, pEnd, replacer.Applicable());
    if (pCharStr != pEnd)
    {
        rtl_tString* pOrg = *ppThis;
        *ppThis = Alloc<rtl_tString>(pStr->length);
        assert(*ppThis != nullptr);
        auto* pNewCharStr = (*ppThis)->buffer;
        /* Copy String */
        const sal_Int32 nCount = pCharStr - pStr->buffer;
        Copy(pNewCharStr, pStr->buffer, nCount);
        pNewCharStr += nCount;
        /* replace/copy rest of the string */
        do
        {
            *pNewCharStr = replacer.Replace(*pCharStr);
            pNewCharStr++;
            pCharStr++;
        } while (pCharStr != pEnd);

        RTL_LOG_STRING_NEW(*ppThis);
        /* must be done last, if pStr == *ppThis */
        if (pOrg)
            release(pOrg);
    }
    else
        assign(ppThis, pStr);
}

/* ----------------------------------------------------------------------- */

template <typename rtl_tString> void newTrim(rtl_tString** ppThis, rtl_tString* pStr)
{
    assert(pStr);
    const auto view = o3tl::trim(std::basic_string_view(pStr->buffer, pStr->length));

    if (static_cast<sal_Int32>(view.size()) == pStr->length)
        assign(ppThis, pStr);
    else
        newFromStr_WithLength(ppThis, view.data(), view.size());
}

/* ----------------------------------------------------------------------- */

template <typename rtl_tString>
sal_Int32 getToken(rtl_tString** ppThis, rtl_tString* pStr, sal_Int32 nToken,
                   Char_T<rtl_tString> cTok, sal_Int32 nIndex)
{
    assert(ppThis);
    assert(pStr);
    assert(nIndex <= pStr->length);

    // Set ppThis to an empty string and return -1 if either nToken or nIndex is
    // negative:
    if (nIndex >= 0 && nToken >= 0)
    {
        const auto* pOrgCharStr = pStr->buffer;
        const auto* pCharStr = pOrgCharStr + nIndex;
        sal_Int32 nLen = pStr->length - nIndex;
        sal_Int32 nTokCount = 0;
        const auto* pCharStrStart = pCharStr;
        while (nLen > 0)
        {
            if (*pCharStr == cTok)
            {
                nTokCount++;

                if (nTokCount > nToken)
                    break;
                if (nTokCount == nToken)
                    pCharStrStart = pCharStr + 1;
            }

            pCharStr++;
            nLen--;
        }
        if (nTokCount >= nToken)
        {
            newFromStr_WithLength(ppThis, pCharStrStart, pCharStr - pCharStrStart);
            if (nLen > 0)
                return pCharStr - pOrgCharStr + 1;
            else
                return -1;
        }
    }

    new_(ppThis);
    return -1;
}

/* ======================================================================= */
/* String buffer help functions                                            */
/* ======================================================================= */

template <class rtl_tString>
void stringbuffer_newFromStr_WithLength(rtl_tString** ppThis,
                                        const Char_T<rtl_tString>* pStr, sal_Int32 count)
{
    assert(ppThis);
    assert(count >= 0);
    if (!pStr)
        count = 0; // Because old code didn't care about count when !pStr

    newFromStr_WithLength(ppThis, pStr, count, 16);
}

template <class rtl_tString>
sal_Int32 stringbuffer_newFromStringBuffer(rtl_tString** ppThis, sal_Int32 capacity,
                                           rtl_tString* pStr)
{
    assert(capacity >= 0);
    assert(pStr);

    if (capacity < pStr->length)
        capacity = pStr->length;

    newFromStr_WithLength(ppThis, pStr->buffer, pStr->length, capacity - pStr->length);
    return capacity;
}

template <class rtl_tString>
void stringbuffer_ensureCapacity(rtl_tString** ppThis, sal_Int32* capacity,
                                 sal_Int32 minimumCapacity)
{
    assert(ppThis);
    assert(capacity && *capacity >= 0);
    // assert(minimumCapacity >= 0); // It was commented out in rtl_stringbuffer_ensureCapacity
    if (minimumCapacity <= *capacity)
        return;

    const auto nLength = (*ppThis)->length;
    *capacity = (nLength + 1) * 2;
    if (minimumCapacity > *capacity)
        *capacity = minimumCapacity;

    newFromStr_WithLength(ppThis, (*ppThis)->buffer, nLength, *capacity - nLength);
}

template <class rtl_tString, typename C>
void stringbuffer_insert(rtl_tString** ppThis, sal_Int32* capacity, sal_Int32 offset,
                         const C* pStr, sal_Int32 len)
{
    assert(ppThis);
    assert(capacity && *capacity >= 0);
    assert(offset >= 0 && offset <= (*ppThis)->length);
    assert(len >= 0);
    if (len == 0)
        return;
    if (len > std::numeric_limits<sal_Int32>::max() - (*ppThis)->length) {
        throw std::bad_alloc();
    }

    stringbuffer_ensureCapacity(ppThis, capacity, (*ppThis)->length + len);

    sal_Int32 nOldLen = (*ppThis)->length;
    auto* pBuf = (*ppThis)->buffer;

    /* copy the tail */
    const sal_Int32 n = nOldLen - offset;
    if (n > 0)
        CopyBackward(pBuf + offset + len, pBuf + offset, n);

    /* insert the new characters */
    if (pStr != nullptr)
        Copy(pBuf + offset, pStr, len);

    (*ppThis)->length = nOldLen + len;
    pBuf[nOldLen + len] = 0;
}

template <class rtl_tString>
void stringbuffer_remove(rtl_tString** ppThis, sal_Int32 start, sal_Int32 len)
{
    assert(ppThis);
    assert(start >= 0 && start <= (*ppThis)->length);
    assert(len >= 0);

    if (len > (*ppThis)->length - start)
        len = (*ppThis)->length - start;

    //remove nothing
    if (!len)
        return;

    auto* pBuf = (*ppThis)->buffer;
    const sal_Int32 nTailLen = (*ppThis)->length - (start + len);

    if (nTailLen)
    {
        /* move the tail */
        Copy(pBuf + start, pBuf + start + len, nTailLen);
    }

    (*ppThis)->length -= len;
    pBuf[(*ppThis)->length] = 0;
}

template <class S, typename CharTypeFrom, typename CharTypeTo>
void newReplaceAllFromIndex(S** s, S* s1, CharTypeFrom const* from, sal_Int32 fromLength,
                            CharTypeTo const* to, sal_Int32 toLength, sal_Int32 fromIndex)
{
    assert(s != nullptr);
    assert(s1 != nullptr);
    assert(fromLength >= 0);
    assert(from != nullptr || fromLength == 0);
    assert(toLength >= 0);
    assert(to != nullptr || toLength == 0);
    assert(fromIndex >= 0 && fromIndex <= s1->length);
    sal_Int32 i = indexOfStr_WithLength(s1->buffer + fromIndex, s1->length - fromIndex,
                                        from, fromLength);
    if (i >= 0)
    {
        if (s1->length - fromLength > SAL_MAX_INT32 - toLength)
            std::abort();
        i += fromIndex;
        sal_Int32 nCapacity = s1->length + (toLength - fromLength);
        if (fromLength < toLength)
        {
            // Pre-allocate up to 16 replacements more
            const sal_Int32 nMaxMoreFinds = (s1->length - i - fromLength) / fromLength;
            const sal_Int32 nIncrease = toLength - fromLength;
            const sal_Int32 nMoreReplacements = std::min(
                { nMaxMoreFinds, (SAL_MAX_INT32 - nCapacity) / nIncrease, sal_Int32(16) });
            nCapacity += nMoreReplacements * nIncrease;
        }
        const auto pOld = *s;
        *s = Alloc<S>(nCapacity);
        (*s)->length = 0;
        fromIndex = 0;
        do
        {
            stringbuffer_insert(s, &nCapacity, (*s)->length, s1->buffer + fromIndex, i);
            stringbuffer_insert(s, &nCapacity, (*s)->length, to, toLength);
            fromIndex += i + fromLength;
            i = indexOfStr_WithLength(s1->buffer + fromIndex, s1->length - fromIndex,
                                      from, fromLength);
        } while (i >= 0);
        // the rest
        stringbuffer_insert(s, &nCapacity, (*s)->length,
                            s1->buffer + fromIndex, s1->length - fromIndex);
        if (pOld)
            release(pOld); // Must be last in case *s == s1
    }
    else
        assign(s, s1);

    RTL_LOG_STRING_NEW(*s);
}

template <class rtl_tString, typename C1, typename C2>
void newReplaceFirst(rtl_tString** s, rtl_tString* s1, C1 const* from, sal_Int32 fromLength,
                     C2 const* to, sal_Int32 toLength, sal_Int32& fromIndex)
{
    assert(s != nullptr);
    assert(s1 != nullptr);
    assert(fromLength >= 0);
    assert(from != nullptr || fromLength == 0);
    assert(toLength >= 0);
    assert(to != nullptr || toLength == 0);
    assert(fromIndex >= 0 && fromIndex <= s1->length);
    sal_Int32 i = indexOfStr_WithLength(s1->buffer + fromIndex, s1->length - fromIndex,
                                        from, fromLength);
    if (i >= 0)
    {
        if (s1->length - fromLength > SAL_MAX_INT32 - toLength)
            std::abort();
        i += fromIndex;
        newReplaceStrAt(s, s1, i, fromLength, to, toLength);
    }
    else
        assign(s, s1);

    fromIndex = i;
}

// doubleToString implementation

static inline constexpr sal_uInt64 eX[] = { 10ull,
                                            100ull,
                                            1000ull,
                                            10000ull,
                                            100000ull,
                                            1000000ull,
                                            10000000ull,
                                            100000000ull,
                                            1000000000ull,
                                            10000000000ull,
                                            100000000000ull,
                                            1000000000000ull,
                                            10000000000000ull,
                                            100000000000000ull,
                                            1000000000000000ull,
                                            10000000000000000ull,
                                            100000000000000000ull,
                                            1000000000000000000ull,
                                            10000000000000000000ull };

template <typename rtl_tString>
void doubleToString(rtl_tString** pResult, sal_Int32* pResultCapacity, sal_Int32 nResultOffset,
                    double fValue, rtl_math_StringFormat eFormat, sal_Int32 nDecPlaces,
                    Char_T<rtl_tString> cDecSeparator, sal_Int32 const* pGroups,
                    Char_T<rtl_tString> cGroupSeparator, bool bEraseTrailingDecZeros)
{
    auto decimalDigits = [](sal_uInt64 n) {
        return std::distance(std::begin(eX), std::upper_bound(std::begin(eX), std::end(eX), n)) + 1;
    };

    auto roundToPow10 = [](sal_uInt64 n, int e) {
        assert(e > 0 && o3tl::make_unsigned(e) <= std::size(eX));
        const sal_uInt64 d = eX[e - 1];
        return (n + d / 2) / d * d;
    };

    auto append = [](rtl_tString** s, sal_Int32* pCapacity, sal_Int32 rOffset, auto sv)
    {
        if (!pCapacity)
            newFromStr_WithLength(s, sv.data(), sv.size());
        else
            stringbuffer_insert(s, pCapacity, rOffset, sv.data(), sv.size());
    };

    if (std::isnan(fValue))
    {
        // #i112652# XMLSchema-2
        static constexpr std::string_view nan{ "NaN" };
        return append(pResult, pResultCapacity, nResultOffset, nan);
    }

    // sign adjustment, instead of testing for fValue<0.0 this will also fetch -0.0
    bool bSign = std::signbit(fValue);

    if (std::isinf(fValue))
    {
        // #i112652# XMLSchema-2
        std::string_view inf = bSign ? std::string_view("-INF") : std::string_view("INF");
        return append(pResult, pResultCapacity, nResultOffset, inf);
    }

    if (bSign)
        fValue = -fValue;

    decltype(jkj::dragonbox::to_decimal(fValue, jkj::dragonbox::policy::sign::ignore,
                                        jkj::dragonbox::policy::trailing_zero::ignore)) aParts{};
    if (fValue) // to_decimal is documented to only handle non-zero finite numbers
        aParts = jkj::dragonbox::to_decimal(fValue, jkj::dragonbox::policy::sign::ignore,
                                            jkj::dragonbox::policy::trailing_zero::ignore);

    int nOrigDigits = decimalDigits(aParts.significand);
    int nExp = nOrigDigits + aParts.exponent - 1;
    int nRoundDigits = 15;

    // Unfortunately the old rounding below writes 1.79769313486232e+308 for
    // DBL_MAX and 4 subsequent nextafter(...,0).
    static const double fB1 = std::nextafter(std::numeric_limits<double>::max(), 0);
    static const double fB2 = std::nextafter(fB1, 0);
    static const double fB3 = std::nextafter(fB2, 0);
    static const double fB4 = std::nextafter(fB3, 0);
    if ((fValue >= fB4) && eFormat != rtl_math_StringFormat_F)
    {
        // 1.7976931348623157e+308 instead of rounded 1.79769313486232e+308
        // that can't be converted back as out of range. For rounded values if
        // they exceed range they should not be written to exchange strings or
        // file formats.

        eFormat = rtl_math_StringFormat_E;
        nDecPlaces = std::clamp<sal_Int32>(nDecPlaces, 0, 16);
        nRoundDigits = 17;
    }

    // Use integer representation for integer values that fit into the
    // mantissa (1.((2^53)-1)) with a precision of 1 for highest accuracy.
    if ((eFormat == rtl_math_StringFormat_Automatic || eFormat == rtl_math_StringFormat_F)
        && aParts.exponent >= 0 && fValue < 0x1p53)
    {
        eFormat = rtl_math_StringFormat_F;
        if (nDecPlaces == rtl_math_DecimalPlaces_Max)
            nDecPlaces = 0;
        else
            nDecPlaces = std::clamp<sal_Int32>(nDecPlaces, -15, 15);

        if (bEraseTrailingDecZeros && nDecPlaces > 0)
            nDecPlaces = 0;

        nRoundDigits = nOrigDigits; // no rounding
    }

    switch (eFormat)
    {
        case rtl_math_StringFormat_Automatic:
            // E or F depending on exponent magnitude
            if (nExp <= -15 || nExp >= 15)
            {
                if (nDecPlaces == rtl_math_DecimalPlaces_Max)
                    nDecPlaces = 14;
                eFormat = rtl_math_StringFormat_E;
            }
            else
            {
                if (nDecPlaces == rtl_math_DecimalPlaces_Max)
                    nDecPlaces = (nExp < 14) ? 15 - nExp - 1 : 15;
                eFormat = rtl_math_StringFormat_F;
            }
            break;

        case rtl_math_StringFormat_G:
        case rtl_math_StringFormat_G1:
        case rtl_math_StringFormat_G2:
            // G-Point, similar to sprintf %G
            if (nDecPlaces == rtl_math_DecimalPlaces_DefaultSignificance)
                nDecPlaces = 6;

            if (nExp < -4 || nExp >= nDecPlaces)
            {
                nDecPlaces = std::max<sal_Int32>(1, nDecPlaces - 1);

                if (eFormat == rtl_math_StringFormat_G)
                    eFormat = rtl_math_StringFormat_E;
                else if (eFormat == rtl_math_StringFormat_G2)
                    eFormat = rtl_math_StringFormat_E2;
                else if (eFormat == rtl_math_StringFormat_G1)
                    eFormat = rtl_math_StringFormat_E1;
            }
            else
            {
                if (nOrigDigits <= nDecPlaces && aParts.exponent >= 0 && fValue < 0x1p53)
                {
                    // Use integer representation with highest accuracy.
                    nRoundDigits = nOrigDigits; // no rounding
                }
                nDecPlaces = std::max<sal_Int32>(0, nDecPlaces - nExp - 1);
                eFormat = rtl_math_StringFormat_F;
            }
            break;

        default:
            break;
    }

    // Too large values for nDecPlaces make no sense; it might also be
    // rtl_math_DecimalPlaces_Max was passed with rtl_math_StringFormat_F or
    // others, but we don't want to allocate/deallocate 2GB just to fill it
    // with trailing '0' characters..
    nDecPlaces = std::clamp<sal_Int32>(nDecPlaces, -309, 309);

    sal_Int32 nDigits = nDecPlaces + 1;

    if (eFormat == rtl_math_StringFormat_F)
        nDigits += nExp;

    // Round the number
    nRoundDigits = std::min<int>(nDigits, nRoundDigits);
    if (nDigits >= 0 && nOrigDigits > nRoundDigits)
    {
        aParts.significand = roundToPow10(aParts.significand, nOrigDigits - nRoundDigits);
        assert(aParts.significand <= eX[nOrigDigits - 1]);
        if (aParts.significand == eX[nOrigDigits - 1]) // up-rounding to the next decade
        {
            nOrigDigits++;
            nExp++;

            if (eFormat == rtl_math_StringFormat_F)
                nDigits++;
        }
    }

    sal_Int32 nBuf
        = (nDigits <= 0 ? std::max<sal_Int32>(nDecPlaces, std::abs(nExp)) : nDigits + nDecPlaces)
          + 10 + (pGroups ? std::abs(nDigits) * 2 : 0);
    // max(nDigits) = max(nDecPlaces) + 1 + max(nExp) + 1 = 309 + 1 + 308 + 1 = 619
    // max(nBuf) = max(nDigits) + max(nDecPlaces) + 10 + max(nDigits) * 2 = 619 * 3 + 309 + 10 = 2176
    assert(nBuf <= 2176);
    auto* const pBuf = static_cast<Char_T<rtl_tString>*>(alloca(nBuf * sizeof(Char_T<rtl_tString>)));
    auto* p = pBuf;
    if (bSign)
        *p++ = '-';

    bool bHasDec = false;

    int nDecPos;
    // Check for F format and number < 1
    if (eFormat == rtl_math_StringFormat_F)
    {
        if (nExp < 0)
        {
            *p++ = '0';
            if (nDecPlaces > 0)
            {
                *p++ = cDecSeparator;
                bHasDec = true;
            }

            sal_Int32 i = (nDigits <= 0 ? nDecPlaces : -nExp - 1);

            while ((i--) > 0)
                *p++ = '0';

            nDecPos = 0;
        }
        else
            nDecPos = nExp + 1;
    }
    else
        nDecPos = 1;

    int nGrouping = 0, nGroupSelector = 0, nGroupExceed = 0;
    if (nDecPos > 1 && pGroups && pGroups[0] && cGroupSeparator)
    {
        while (nGrouping + pGroups[nGroupSelector] < nDecPos)
        {
            nGrouping += pGroups[nGroupSelector];
            if (pGroups[nGroupSelector + 1])
            {
                if (nGrouping + pGroups[nGroupSelector + 1] >= nDecPos)
                    break; // while

                ++nGroupSelector;
            }
            else if (!nGroupExceed)
                nGroupExceed = nGrouping;
        }
    }

    // print the number
    if (nDigits > 0)
    {
        for (int nCurExp = nOrigDigits - 1;;)
        {
            int nDigit;
            if (aParts.significand > 0 && nCurExp > 0)
            {
                --nCurExp;
                nDigit = aParts.significand / eX[nCurExp];
                aParts.significand %= eX[nCurExp];
            }
            else
            {
                nDigit = aParts.significand;
                aParts.significand = 0;
            }
            assert(nDigit >= 0 && nDigit < 10);
            *p++ = nDigit + '0';

            if (!--nDigits)
                break; // for

            if (nDecPos)
            {
                if (!--nDecPos)
                {
                    *p++ = cDecSeparator;
                    bHasDec = true;
                }
                else if (nDecPos == nGrouping)
                {
                    *p++ = cGroupSeparator;
                    nGrouping -= pGroups[nGroupSelector];

                    if (nGroupSelector && nGrouping < nGroupExceed)
                        --nGroupSelector;
                }
            }
        }
    }

    if (!bHasDec && eFormat == rtl_math_StringFormat_F)
    { // nDecPlaces < 0 did round the value
        while (--nDecPos > 0)
        { // fill before decimal point
            if (nDecPos == nGrouping)
            {
                *p++ = cGroupSeparator;
                nGrouping -= pGroups[nGroupSelector];

                if (nGroupSelector && nGrouping < nGroupExceed)
                    --nGroupSelector;
            }

            *p++ = '0';
        }
    }

    if (bEraseTrailingDecZeros && bHasDec)
    {
        while (*(p - 1) == '0')
            p--;

        if (*(p - 1) == cDecSeparator)
            p--;
    }

    // Print the exponent ('E', followed by '+' or '-', followed by exactly
    // three digits for rtl_math_StringFormat_E). The code in
    // rtl_[u]str_valueOf{Float|Double} relies on this format.
    if (eFormat == rtl_math_StringFormat_E || eFormat == rtl_math_StringFormat_E2
        || eFormat == rtl_math_StringFormat_E1)
    {
        if (p == pBuf)
            *p++ = '1';
        // maybe no nDigits if nDecPlaces < 0

        *p++ = 'E';
        if (nExp < 0)
        {
            nExp = -nExp;
            *p++ = '-';
        }
        else
            *p++ = '+';

        if (eFormat == rtl_math_StringFormat_E || nExp >= 100)
            *p++ = nExp / 100 + '0';

        nExp %= 100;

        if (eFormat == rtl_math_StringFormat_E || eFormat == rtl_math_StringFormat_E2 || nExp >= 10)
            *p++ = nExp / 10 + '0';

        *p++ = nExp % 10 + '0';
    }

    append(pResult, pResultCapacity, nResultOffset, std::basic_string_view(pBuf, p - pBuf));
}

template <sal_Int32 maxLen, typename C, typename T> sal_Int32 SAL_CALL valueOfFP(C* pStr, T f)
{
    assert(pStr);
    rtl_String* pResult = nullptr;
    doubleToString(&pResult, nullptr, 0, f, rtl_math_StringFormat_G,
                   maxLen - std::size("-x.E-xxx") + 1, '.', nullptr, 0, true);
    const sal_Int32 nLen = pResult->length;
    assert(nLen < maxLen);
    Copy(pStr, pResult->buffer, nLen + 1);
    release(pResult);
    return nLen;
}

}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
