/* -*- 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 .
 */

#include <i18nlangtag/languagetag.hxx>
#include <i18nlangtag/mslangid.hxx>
#include <rtl/ustrbuf.hxx>
#include <sal/macros.h>
#include <nativenumbersupplier.hxx>
#include <localedata.hxx>
#include "data/numberchar.h"
#include <comphelper/processfactory.hxx>
#include <cppuhelper/supportsservice.hxx>
#include <o3tl/string_view.hxx>
#include <cstddef>
#include <map>
#include <mutex>
#include <memory>
#include <string_view>
#include <unordered_map>
#include <com/sun/star/i18n/CharacterClassification.hpp>
#include <com/sun/star/i18n/NativeNumberMode.hpp>
#include <com/sun/star/linguistic2/NumberText.hpp>

using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::i18n;
using namespace ::com::sun::star::lang;

namespace {

struct Number {
    sal_Int16 number;
    const sal_Unicode *multiplierChar;
    sal_Int16 numberFlag;
    sal_Int16 exponentCount;
    const sal_Int16 *multiplierExponent;
};

}

#define NUMBER_OMIT_ZERO (1 << 0)
#define NUMBER_OMIT_ONLY_ZERO  (1 << 1)
#define NUMBER_OMIT_ONE_1  (1 << 2)
#define NUMBER_OMIT_ONE_2  (1 << 3)
#define NUMBER_OMIT_ONE_3  (1 << 4)
#define NUMBER_OMIT_ONE_4  (1 << 5)
#define NUMBER_OMIT_ONE_5  (1 << 6)
#define NUMBER_OMIT_ONE_6  (1 << 7)
#define NUMBER_OMIT_ONE_7  (1 << 8)
#define NUMBER_OMIT_ONE  (NUMBER_OMIT_ONE_1|NUMBER_OMIT_ONE_2|NUMBER_OMIT_ONE_3|NUMBER_OMIT_ONE_4|NUMBER_OMIT_ONE_5|NUMBER_OMIT_ONE_6|NUMBER_OMIT_ONE_7)
#define NUMBER_OMIT_ONE_CHECK(bit)  (1 << (2 + bit))
#define NUMBER_OMIT_ALL ( NUMBER_OMIT_ZERO|NUMBER_OMIT_ONE|NUMBER_OMIT_ONLY_ZERO )
#define NUMBER_OMIT_ZERO_ONE ( NUMBER_OMIT_ZERO|NUMBER_OMIT_ONE )
#define NUMBER_OMIT_ONE_67 (NUMBER_OMIT_ONE_6|NUMBER_OMIT_ONE_7)
#define NUMBER_OMIT_ZERO_ONE_67 ( NUMBER_OMIT_ZERO|NUMBER_OMIT_ONE_67 )

namespace i18npool {

namespace {

std::mutex theNatNumMutex;

}

static OUString getHebrewNativeNumberString(const OUString& aNumberString, bool useGeresh);

static OUString getCyrillicNativeNumberString(const OUString& aNumberString);

/// @throws RuntimeException
static OUString AsciiToNativeChar( const OUString& inStr, sal_Int32 nCount,
        Sequence< sal_Int32 >* pOffset, sal_Int16 number )
{
    const sal_Unicode *src = inStr.getStr();
    rtl_uString *newStr = rtl_uString_alloc(nCount);
    if (pOffset)
        pOffset->realloc(nCount);
    auto ppOffset = pOffset ? pOffset->getArray() : nullptr;

    for (sal_Int32 i = 0; i < nCount; i++)
    {
        sal_Unicode ch = src[i];
        if (isNumber(ch))
            newStr->buffer[i] = NumberChar[number][ ch - NUMBER_ZERO ];
        else if (i+1 < nCount && isNumber(src[i+1])) {
            if (i > 0 && isNumber(src[i-1]) && isSeparator(ch))
                newStr->buffer[i] = SeparatorChar[number] ? SeparatorChar[number] : ch;
            else
                newStr->buffer[i] = isDecimal(ch) ? (DecimalChar[number] ? DecimalChar[number] : ch) :
                    isMinus(ch) ? (MinusChar[number] ? MinusChar[number] : ch) : ch;
        }
        else
            newStr->buffer[i] = ch;
        if (ppOffset)
            ppOffset[i] = i;
    }
    return OUString(newStr, SAL_NO_ACQUIRE); // take ownership
}

static bool AsciiToNative_numberMaker(const sal_Unicode *str, sal_Int32 begin, sal_Int32 len,
        sal_Unicode *dst, sal_Int32& count, sal_Int16 multiChar_index, Sequence< sal_Int32 >* pOffset, sal_Int32 startPos,
 const Number *number, const sal_Unicode* numberChar)
{
    sal_Unicode multiChar = (multiChar_index == -1 ? 0 : number->multiplierChar[multiChar_index]);
    auto ppOffset = pOffset ? pOffset->getArray() : nullptr;
    if ( len <= number->multiplierExponent[number->exponentCount-1] ) {
        if (number->multiplierExponent[number->exponentCount-1] > 1) {
            bool bNotZero = false;
            for (const sal_Int32 end = begin+len; begin < end; begin++) {
                if (bNotZero || str[begin] != NUMBER_ZERO) {
                    dst[count] = numberChar[str[begin] - NUMBER_ZERO];
                    if (ppOffset)
                        ppOffset[count] = begin + startPos;
                    count++;
                    bNotZero = true;
                }
            }
            if (bNotZero && multiChar > 0) {
                dst[count] = multiChar;
                if (ppOffset)
                    ppOffset[count] = begin + startPos;
                count++;
            }
            return bNotZero;
        } else if (str[begin] != NUMBER_ZERO) {
            if (!(number->numberFlag & (multiChar_index < 0 ? 0 : NUMBER_OMIT_ONE_CHECK(multiChar_index))) || str[begin] != NUMBER_ONE) {
                dst[count] = numberChar[str[begin] - NUMBER_ZERO];
                if (ppOffset)
                    ppOffset[count] = begin + startPos;
                count++;
            }
            if (multiChar > 0) {
                dst[count] = multiChar;
                if (ppOffset)
                    ppOffset[count] = begin + startPos;
                count++;
            }
        } else if (!(number->numberFlag & NUMBER_OMIT_ZERO) && count > 0 && dst[count-1] != numberChar[0]) {
            dst[count] = numberChar[0];
            if (ppOffset)
                ppOffset[count] = begin + startPos;
            count++;
        }
        return str[begin] != NUMBER_ZERO;
    } else {
        bool bPrintPower = false;
        // sal_Int16 last = 0;
        for (sal_Int16 i = 1; i <= number->exponentCount; i++) {
            sal_Int32 tmp = len - (i == number->exponentCount ? 0 : number->multiplierExponent[i]);
            if (tmp > 0) {
                bPrintPower |= AsciiToNative_numberMaker(str, begin, tmp, dst, count,
                        (i == number->exponentCount ? -1 : i), pOffset, startPos, number, numberChar);
                begin += tmp;
                len -= tmp;
            }
        }
        if (bPrintPower) {
            if (count > 0 && number->multiplierExponent[number->exponentCount-1] == 1 &&
                    dst[count-1] == numberChar[0])
                count--;
            if (multiChar > 0) {
                dst[count] = multiChar;
                if (ppOffset)
                    ppOffset[count] = begin + startPos;
                count++;
            }
        }
        return bPrintPower;
    }
}

/// @throws RuntimeException
static OUString AsciiToNative( const OUString& inStr, sal_Int32 nCount,
        Sequence< sal_Int32 >* pOffset, const Number* number )
{
    OUString aRet;

    sal_Int32 strLen = inStr.getLength();
    const sal_Unicode *numberChar = NumberChar[number->number];

    if (nCount > strLen)
        nCount = strLen;

    if (nCount > 0)
    {
        const sal_Unicode *str = inStr.getStr();
        std::unique_ptr<sal_Unicode[]> newStr(new sal_Unicode[nCount * 2 + 1]);
        std::unique_ptr<sal_Unicode[]> srcStr(new sal_Unicode[nCount + 1]); // for keeping number without comma
        sal_Int32 i, len = 0, count = 0;

        if (pOffset)
            pOffset->realloc( nCount * 2 );
        auto ppOffset = pOffset ? pOffset->getArray() : nullptr;
        bool bDoDecimal = false;

        for (i = 0; i <= nCount; i++)
        {
            if (i < nCount && isNumber(str[i])) {
                if (bDoDecimal) {
                    newStr[count] = numberChar[str[i] - NUMBER_ZERO];
                    if (ppOffset)
                        ppOffset[count] = i;
                    count++;
                }
                else
                    srcStr[len++] = str[i];
            } else {
                if (len > 0) {
                    if (i < nCount-1 && isSeparator(str[i]) && isNumber(str[i+1]))
                        continue; // skip comma inside number string
                    bool bNotZero = false;
                    for (sal_Int32 begin = 0, end = len % number->multiplierExponent[0];
                            end <= len; begin = end, end += number->multiplierExponent[0]) {
                        if (end == 0) continue;
                        sal_Int32 _count = count;
                        bNotZero |= AsciiToNative_numberMaker(srcStr.get(), begin, end - begin, newStr.get(), count,
                                end == len ? -1 : 0, pOffset, i - len, number, numberChar);
                        if (count > 0 && number->multiplierExponent[number->exponentCount-1] == 1 &&
                                newStr[count-1] == numberChar[0])
                            count--;
                        if (bNotZero && _count == count && end != len) {
                            newStr[count] = number->multiplierChar[0];
                            if (ppOffset)
                                ppOffset[count] = i - len;
                            count++;
                        }
                    }
                    if (! bNotZero && ! (number->numberFlag & NUMBER_OMIT_ONLY_ZERO)) {
                        newStr[count] = numberChar[0];
                        if (ppOffset)
                            ppOffset[count] = i - len;
                        count++;
                    }
                    len = 0;
                }
                if (i < nCount) {
                    bDoDecimal = (!bDoDecimal && i < nCount-1 && isDecimal(str[i]) && isNumber(str[i+1]));
                    if (bDoDecimal)
                        newStr[count] = (DecimalChar[number->number] ? DecimalChar[number->number] : str[i]);
                    else if (i < nCount-1 && isMinus(str[i]) && isNumber(str[i+1]))
                        newStr[count] = (MinusChar[number->number] ? MinusChar[number->number] : str[i]);
                    else if (i < nCount-1 && isSeparator(str[i]) && isNumber(str[i+1]))
                        newStr[count] = (SeparatorChar[number->number] ? SeparatorChar[number->number] : str[i]);
                    else
                        newStr[count] = str[i];
                    if (ppOffset)
                        ppOffset[count] = i;
                    count++;
                }
            }
        }

        if (pOffset)
            pOffset->realloc(count);
        aRet = OUString(newStr.get(), count);
    }
    return aRet;
}

namespace
{
void NativeToAscii_numberMaker(sal_Int16 max, sal_Int16 prev, const sal_Unicode *str,
        sal_Int32& i, sal_Int32 nCount, sal_Unicode *dst, sal_Int32& count, Sequence< sal_Int32 >* pOffset,
        OUString& numberChar, OUString& multiplierChar)
{
    auto ppOffset = pOffset ? pOffset->getArray() : nullptr;
    sal_Int16 curr = 0, num = 0, end = 0, shift = 0;
    while (++i < nCount) {
        if ((curr = sal::static_int_cast<sal_Int16>( numberChar.indexOf(str[i]) )) >= 0) {
            if (num > 0)
                break;
            num = curr % 10;
        } else if ((curr = sal::static_int_cast<sal_Int16>( multiplierChar.indexOf(str[i]) )) >= 0) {
            curr = MultiplierExponent_7_CJK[curr % ExponentCount_7_CJK];
            if (prev > curr && num == 0) num = 1; // One may be omitted in informal format
            shift = end = 0;
            if (curr >= max)
                max = curr;
            else if (curr > prev)
                shift = max - curr;
            else
                end = curr;
            while (end++ < prev) {
                dst[count] = NUMBER_ZERO + (end == prev ? num : 0);
                if (ppOffset)
                    ppOffset[count] = i;
                count++;
            }
            if (shift) {
                count -= max;
                for (const sal_Int32 countEnd = count+shift; count < countEnd; count++) {
                    dst[count] = dst[count + curr];
                    if (ppOffset)
                        ppOffset[count] = ppOffset[count + curr];
                }
                max = curr;
            }
            NativeToAscii_numberMaker(max, curr, str, i, nCount, dst,
                    count, pOffset, numberChar, multiplierChar);
            return;
        } else
            break;
    }
    while (end++ < prev) {
        dst[count] = NUMBER_ZERO + (end == prev ? num : 0);
        if (ppOffset)
            ppOffset[count] = i - 1;
        count++;
    }
}

/// @throws RuntimeException
OUString NativeToAscii(const OUString& inStr,
        sal_Int32 nCount, Sequence< sal_Int32 >* pOffset )
{
    OUString aRet;

    sal_Int32 strLen = inStr.getLength();

    if (nCount > strLen)
        nCount = strLen;

    if (nCount > 0) {
        const sal_Unicode *str = inStr.getStr();
        std::unique_ptr<sal_Unicode[]> newStr(new sal_Unicode[nCount * MultiplierExponent_7_CJK[0] + 2]);
        if (pOffset)
            pOffset->realloc( nCount * MultiplierExponent_7_CJK[0] + 1 );
        auto ppOffset = pOffset ? pOffset->getArray() : nullptr;
        sal_Int32 count = 0, index;
        sal_Int32 i;

        OUString numberChar, multiplierChar, decimalChar, separatorChar;
        numberChar = OUString(NumberChar[0], 10*NumberChar_Count);
        multiplierChar = OUString(MultiplierChar_7_CJK[0], ExponentCount_7_CJK*Multiplier_Count);
        decimalChar = OUString(DecimalChar, NumberChar_Count);
        std::u16string_view const minusChar(MinusChar, NumberChar_Count);
        separatorChar = OUString(
            reinterpret_cast<sal_Unicode *>(SeparatorChar), NumberChar_Count);

        for ( i = 0; i < nCount; i++) {
            if ((index = multiplierChar.indexOf(str[i])) >= 0) {
                if (count == 0 || !isNumber(newStr[count-1])) { // add 1 in front of multiplier
                    newStr[count] = NUMBER_ONE;
                    if (ppOffset)
                        ppOffset[count] = i;
                    count++;
                }
                index = MultiplierExponent_7_CJK[index % ExponentCount_7_CJK];
                NativeToAscii_numberMaker(
                        sal::static_int_cast<sal_Int16>( index ), sal::static_int_cast<sal_Int16>( index ),
                        str, i, nCount, newStr.get(), count, pOffset,
                        numberChar, multiplierChar);
            } else {
                if ((index = numberChar.indexOf(str[i])) >= 0)
                    newStr[count] = sal::static_int_cast<sal_Unicode>( (index % 10) + NUMBER_ZERO );
                else if (separatorChar.indexOf(str[i]) >= 0 &&
                        (i < nCount-1 && (numberChar.indexOf(str[i+1]) >= 0 ||
                                          multiplierChar.indexOf(str[i+1]) >= 0)))
                    newStr[count] = SeparatorChar[NumberChar_HalfWidth];
                else if (decimalChar.indexOf(str[i]) >= 0 &&
                        (i < nCount-1 && (numberChar.indexOf(str[i+1]) >= 0 ||
                                          multiplierChar.indexOf(str[i+1]) >= 0)))
                    // Only when decimal point is followed by numbers,
                    // it will be convert to ASCII decimal point
                    newStr[count] = DecimalChar[NumberChar_HalfWidth];
                else if (minusChar.find(str[i]) != std::u16string_view::npos &&
                        (i < nCount-1 && (numberChar.indexOf(str[i+1]) >= 0 ||
                                          multiplierChar.indexOf(str[i+1]) >= 0)))
                    // Only when minus is followed by numbers,
                    // it will be convert to ASCII minus sign
                    newStr[count] = MinusChar[NumberChar_HalfWidth];
                else
                    newStr[count] = str[i];
                if (ppOffset)
                    ppOffset[count] = i;
                count++;
            }
        }

        if (pOffset) {
            pOffset->realloc(count);
        }
        aRet = OUString(newStr.get(), count);
    }
    return aRet;
}

const Number natnum4[4] = {
        { NumberChar_Lower_zh, MultiplierChar_6_CJK[Multiplier_Lower_zh], 0,
                ExponentCount_6_CJK, MultiplierExponent_6_CJK },
        { NumberChar_Lower_zh, MultiplierChar_6_CJK[Multiplier_Lower_zh_TW], 0,
                ExponentCount_6_CJK, MultiplierExponent_6_CJK },
        { NumberChar_Modern_ja, MultiplierChar_7_CJK[Multiplier_Modern_ja], NUMBER_OMIT_ZERO_ONE_67,
                ExponentCount_7_CJK, MultiplierExponent_7_CJK },
        { NumberChar_Lower_ko, MultiplierChar_6_CJK[Multiplier_Lower_ko], NUMBER_OMIT_ZERO,
                ExponentCount_6_CJK, MultiplierExponent_6_CJK },
};

const Number natnum5[4] = {
        { NumberChar_Upper_zh, MultiplierChar_6_CJK[Multiplier_Upper_zh], 0,
                ExponentCount_6_CJK, MultiplierExponent_6_CJK },
        { NumberChar_Upper_zh_TW, MultiplierChar_6_CJK[Multiplier_Upper_zh_TW], 0,
                ExponentCount_6_CJK, MultiplierExponent_6_CJK },
        { NumberChar_Traditional_ja, MultiplierChar_7_CJK[Multiplier_Traditional_ja], NUMBER_OMIT_ZERO_ONE_67,
                ExponentCount_7_CJK, MultiplierExponent_7_CJK },
        { NumberChar_Upper_ko, MultiplierChar_6_CJK[Multiplier_Upper_ko], 0,
                ExponentCount_6_CJK, MultiplierExponent_6_CJK },
};

const Number natnum6[4] = {
        { NumberChar_FullWidth, MultiplierChar_6_CJK[Multiplier_Lower_zh], 0,
                ExponentCount_6_CJK, MultiplierExponent_6_CJK },
        { NumberChar_FullWidth, MultiplierChar_6_CJK[Multiplier_Lower_zh_TW], 0,
                ExponentCount_6_CJK, MultiplierExponent_6_CJK },
        { NumberChar_FullWidth, MultiplierChar_7_CJK[Multiplier_Modern_ja], NUMBER_OMIT_ZERO_ONE_67,
                ExponentCount_7_CJK, MultiplierExponent_7_CJK },
        { NumberChar_FullWidth, MultiplierChar_6_CJK[Multiplier_Hangul_ko], NUMBER_OMIT_ZERO,
                ExponentCount_6_CJK, MultiplierExponent_6_CJK },
};

const Number natnum7[4] = {
        { NumberChar_Lower_zh, MultiplierChar_6_CJK[Multiplier_Lower_zh], NUMBER_OMIT_ALL,
                ExponentCount_6_CJK, MultiplierExponent_6_CJK },
        { NumberChar_Lower_zh, MultiplierChar_6_CJK[Multiplier_Lower_zh_TW], NUMBER_OMIT_ALL,
                ExponentCount_6_CJK, MultiplierExponent_6_CJK },
        { NumberChar_Modern_ja, MultiplierChar_2_CJK[Multiplier_Modern_ja], NUMBER_OMIT_ZERO_ONE,
                ExponentCount_2_CJK, MultiplierExponent_2_CJK },
        { NumberChar_Lower_ko, MultiplierChar_6_CJK[Multiplier_Lower_ko], NUMBER_OMIT_ALL,
                ExponentCount_6_CJK, MultiplierExponent_6_CJK },
};

const Number natnum8[4] = {
        { NumberChar_Upper_zh, MultiplierChar_6_CJK[Multiplier_Upper_zh], NUMBER_OMIT_ALL,
                ExponentCount_6_CJK, MultiplierExponent_6_CJK },
        { NumberChar_Upper_zh_TW, MultiplierChar_6_CJK[Multiplier_Upper_zh_TW], NUMBER_OMIT_ALL,
                ExponentCount_6_CJK, MultiplierExponent_6_CJK },
        { NumberChar_Traditional_ja, MultiplierChar_2_CJK[Multiplier_Traditional_ja], NUMBER_OMIT_ZERO_ONE,
                ExponentCount_2_CJK, MultiplierExponent_2_CJK },
        { NumberChar_Upper_ko, MultiplierChar_6_CJK[Multiplier_Upper_ko], NUMBER_OMIT_ALL,
                ExponentCount_6_CJK, MultiplierExponent_6_CJK },
};

const Number natnum10 = { NumberChar_Hangul_ko, MultiplierChar_6_CJK[Multiplier_Hangul_ko], NUMBER_OMIT_ZERO,
                ExponentCount_6_CJK, MultiplierExponent_6_CJK };
const Number natnum11 = { NumberChar_Hangul_ko, MultiplierChar_6_CJK[Multiplier_Hangul_ko], NUMBER_OMIT_ALL,
                ExponentCount_6_CJK, MultiplierExponent_6_CJK };

//! ATTENTION: Do not change order of elements!
//! Append new languages to the end of the list!
const char* const natnum1Locales[] = {
    "zh_CN",
    "zh_TW",
    "ja",
    "ko",
    "he",
    "ar",
    "th",
    "hi",
    "or",
    "mr",
    "bn",
    "pa",
    "gu",
    "ta",
    "te",
    "kn",
    "ml",
    "lo",
    "bo",
    "my",
    "km",
    "mn",
    "ne",
    "dz",
    "fa",
    "cu"
};
const sal_Int16 nbOfLocale = SAL_N_ELEMENTS(natnum1Locales);

//! ATTENTION: Do not change order of elements!
//! Number and order must match elements of natnum1Locales!
const sal_Int16 natnum1[] = {
    NumberChar_Lower_zh,
    NumberChar_Lower_zh,
    NumberChar_Modern_ja,
    NumberChar_Lower_ko,
    NumberChar_he,
    NumberChar_Indic_ar,
    NumberChar_th,
    NumberChar_hi,
    NumberChar_or,
    NumberChar_mr,
    NumberChar_bn,
    NumberChar_pa,
    NumberChar_gu,
    NumberChar_ta,
    NumberChar_te,
    NumberChar_kn,
    NumberChar_ml,
    NumberChar_lo,
    NumberChar_bo,
    NumberChar_my,
    NumberChar_km,
    NumberChar_mn,
    NumberChar_ne,
    NumberChar_dz,
    NumberChar_EastIndic_ar,
    NumberChar_cu
};
const sal_Int16 sizeof_natnum1 = SAL_N_ELEMENTS(natnum1);

//! ATTENTION: Do not change order of elements!
//! Order must match first elements of natnum1Locales!
const sal_Int16 natnum2[] = {
    NumberChar_Upper_zh,
    NumberChar_Upper_zh_TW,
    NumberChar_Traditional_ja,
    NumberChar_Upper_ko,
    NumberChar_he
};
const sal_Int16 sizeof_natnum2 = SAL_N_ELEMENTS(natnum2);

sal_Int16 getLanguageNumber( const Locale& rLocale)
{
    // return zh_TW for TW, HK and MO, return zh_CN for other zh locales.
    if (rLocale.Language == "zh") return MsLangId::isTraditionalChinese(rLocale) ? 1 : 0;

    for (sal_Int16 i = 2; i < nbOfLocale; i++)
        if (rLocale.Language.equalsAsciiL(natnum1Locales[i], 2))
            return i;

    return -1;
}

struct Separators
{
    sal_Unicode DecimalSeparator;
    sal_Unicode ThousandSeparator;
    Separators(const Locale& rLocale)
    {
        LocaleDataItem aLocaleItem = LocaleDataImpl::get()->getLocaleItem(rLocale);
        DecimalSeparator = aLocaleItem.decimalSeparator.toChar();
        ThousandSeparator = aLocaleItem.thousandSeparator.toChar();
    }
};

Separators getLocaleSeparators(const Locale& rLocale, const OUString& rLocStr)
{
    // Guard the static variable below.
    std::scoped_lock aGuard(theNatNumMutex);
    // Maximum a couple hundred of pairs with 4-byte structs - so no need for smart managing
    static std::unordered_map<OUString, Separators> aLocaleSeparatorsBuf;
    auto it = aLocaleSeparatorsBuf.find(rLocStr);
    if (it == aLocaleSeparatorsBuf.end())
    {
        it = aLocaleSeparatorsBuf.emplace(rLocStr, Separators(rLocale)).first;
    }
    return it->second;
}

OUString getNumberText(const Locale& rLocale, const OUString& rNumberString,
                       std::u16string_view sNumberTextParams)
{
    sal_Int32 i, count = 0;
    const sal_Int32 len = rNumberString.getLength();
    const sal_Unicode* src = rNumberString.getStr();

    OUString aLoc = LanguageTag::convertToBcp47(rLocale);
    Separators aSeparators = getLocaleSeparators(rLocale, aLoc);

    OUStringBuffer sBuf(len);
    for (i = 0; i < len; i++)
    {
        sal_Unicode ch = src[i];
        if (isNumber(ch) || ch == aSeparators.DecimalSeparator)
        {
            ++count;
            sBuf.append(ch);
        }
        else if (ch == aSeparators.ThousandSeparator && count > 0)
            continue;
        else if (isMinus(ch) && count == 0)
            sBuf.append(ch);
        else
            break;
    }

    // Handle also month and day names for NatNum12 date formatting
    const OUString aNumberStr = (count == 0) ? rNumberString : sBuf.makeStringAndClear();

    static auto xNumberText
        = css::linguistic2::NumberText::create(comphelper::getProcessComponentContext());

    // Guard the static variables below.
    std::scoped_lock aGuard( theNatNumMutex );

    OUString numbertext_prefix;
    // default "cardinal" gets empty prefix
    if (!sNumberTextParams.empty() && sNumberTextParams != u"cardinal")
        numbertext_prefix = OUString::Concat(sNumberTextParams) + " ";
    // Several hundreds of headings could result typing lags because
    // of the continuous update of the multiple number names during typing.
    // We fix this by buffering the result of the conversion.
    static std::unordered_map<OUString, std::map<OUString, OUString>> aBuff;
    auto& rItems = aBuff[aNumberStr];
    auto& rItem = rItems[numbertext_prefix + aLoc];
    if (rItem.isEmpty())
    {
        rItem = xNumberText->getNumberText(numbertext_prefix + aNumberStr, rLocale);
        // use number at missing number to text conversion
        if (rItem.isEmpty())
            rItem = aNumberStr;
    }
    OUString sResult = rItem;
    if (i != 0 && i < len)
        sResult += rNumberString.subView(i);
    return sResult;
}
}

OUString NativeNumberSupplierService::getNativeNumberString(const OUString& aNumberString, const Locale& rLocale,
                                                            sal_Int16 nNativeNumberMode,
                                                            Sequence<sal_Int32>* pOffset,
                                                            std::u16string_view rNativeNumberParams)
{
    if (!isValidNatNumImpl(rLocale, nNativeNumberMode))
        return aNumberString;

    if (nNativeNumberMode == NativeNumberMode::NATNUM12)
    {
        // handle capitalization prefixes "capitalize", "upper", "lower" and "title"

        enum WhichCasing
        {
            CAPITALIZE,
            UPPER,
            LOWER,
            TITLE
        };

        struct CasingEntry
        {
            std::u16string_view aLiteral;
            WhichCasing     eCasing;
        };

        static const CasingEntry Casings[] =
        {
            { std::u16string_view(u"capitalize"), CAPITALIZE },
            { std::u16string_view(u"upper"), UPPER },
            { std::u16string_view(u"lower"), LOWER },
            { std::u16string_view(u"title"), TITLE }
        };

        std::size_t nStripCase = 0;
        size_t nCasing;
        for (nCasing = 0; nCasing < std::size(Casings); ++nCasing)
        {
            if (o3tl::starts_with(rNativeNumberParams, Casings[nCasing].aLiteral))
            {
                nStripCase = Casings[nCasing].aLiteral.size();
                break;
            }
        }

        if (nStripCase > 0 && (rNativeNumberParams.size() == nStripCase ||
                    rNativeNumberParams[nStripCase++] == ' '))
        {
            OUString aStr = getNumberText(rLocale, aNumberString, rNativeNumberParams.substr(nStripCase));

            if (!xCharClass.is())
                xCharClass = CharacterClassification::create(comphelper::getProcessComponentContext());

            switch (Casings[nCasing].eCasing)
            {
                case CAPITALIZE:
                    return xCharClass->toTitle(aStr, 0, 1, aLocale) +
                        (aStr.getLength() > 1 ? aStr.subView(1) : u"");
                case UPPER:
                    return xCharClass->toUpper(aStr, 0, aStr.getLength(), aLocale);
                case LOWER:
                    return xCharClass->toLower(aStr, 0, aStr.getLength(), aLocale);
                case TITLE:
                {
                    if ( rLocale.Language == "en" )
                    {
                        // title case is common in English, so fix bugs of toTitle():
                        // not "One Dollar *And* *Twenty-two* Cents", but
                        // "One Dollar *and* *Twenty-Two* Cents".

                        // Add spaces after hyphens to separate the elements of the
                        // hyphenated compound words temporarily, allowing their
                        // capitalization by toTitle()
                        aStr = aStr.replaceAll("-", "- ");
                        aStr = xCharClass->toTitle(aStr, 0, aStr.getLength(), aLocale);
                        return aStr.replaceAll("- ", "-").replaceAll(" And ", " and ");
                    }
                    else
                        return xCharClass->toTitle(aStr, 0, aStr.getLength(), aLocale);
               }
            }
        }
        else
        {
            return getNumberText(rLocale, aNumberString, rNativeNumberParams);
        }
    }

    sal_Int16 langnum = getLanguageNumber(rLocale);
    if (langnum == -1)
        return aNumberString;

    const Number *number = nullptr;
    sal_Int16 num = -1;

    switch (nNativeNumberMode)
    {
        case NativeNumberMode::NATNUM0: // Ascii
            return NativeToAscii(aNumberString, aNumberString.getLength(), pOffset);
        case NativeNumberMode::NATNUM1: // Char, Lower
            num = natnum1[langnum];
            break;
        case NativeNumberMode::NATNUM2: // Char, Upper
            num = natnum2[langnum];
            break;
        case NativeNumberMode::NATNUM3: // Char, FullWidth
            num = NumberChar_FullWidth;
            break;
        case NativeNumberMode::NATNUM4: // Text, Lower, Long
            number = &natnum4[langnum];
            break;
        case NativeNumberMode::NATNUM5: // Text, Upper, Long
            number = &natnum5[langnum];
            break;
        case NativeNumberMode::NATNUM6: // Text, FullWidth
            number = &natnum6[langnum];
            break;
        case NativeNumberMode::NATNUM7: // Text. Lower, Short
            number = &natnum7[langnum];
            break;
        case NativeNumberMode::NATNUM8: // Text, Upper, Short
            number = &natnum8[langnum];
            break;
        case NativeNumberMode::NATNUM9: // Char, Hangul
            num = NumberChar_Hangul_ko;
            break;
        case NativeNumberMode::NATNUM10:        // Text, Hangul, Long
            number = &natnum10;
            break;
        case NativeNumberMode::NATNUM11:        // Text, Hangul, Short
            number = &natnum11;
            break;
        default:
            break;
    }

    if (number || num >= 0) {
        if (aLocale.Language != rLocale.Language ||
                aLocale.Country != rLocale.Country ||
                aLocale.Variant != rLocale.Variant) {
            LocaleDataItem item = LocaleDataImpl::get()->getLocaleItem( rLocale );
            aLocale = rLocale;
            DecimalChar[NumberChar_HalfWidth]=item.decimalSeparator.toChar();
            if (DecimalChar[NumberChar_HalfWidth] > 0x7E || DecimalChar[NumberChar_HalfWidth] < 0x21)
                DecimalChar[NumberChar_FullWidth]=0xFF0E;
            else
                DecimalChar[NumberChar_FullWidth]=DecimalChar[NumberChar_HalfWidth]+0xFEE0;
            SeparatorChar[NumberChar_HalfWidth]=item.thousandSeparator.toChar();
            if (SeparatorChar[NumberChar_HalfWidth] > 0x7E || SeparatorChar[NumberChar_HalfWidth] < 0x21)
                SeparatorChar[NumberChar_FullWidth]=0xFF0C;
            else
                SeparatorChar[NumberChar_FullWidth]=SeparatorChar[NumberChar_HalfWidth]+0xFEE0;
        }
        if (number)
            return AsciiToNative( aNumberString, aNumberString.getLength(), pOffset, number );
        else if (num == NumberChar_he)
            return getHebrewNativeNumberString(aNumberString,
                    nNativeNumberMode == NativeNumberMode::NATNUM2);
        else if (num == NumberChar_cu)
            return getCyrillicNativeNumberString(aNumberString);
        else
            return AsciiToNativeChar(aNumberString, aNumberString.getLength(), pOffset, num);
    }
    else
        return aNumberString;
}

OUString SAL_CALL NativeNumberSupplierService::getNativeNumberString(const OUString& aNumberString, const Locale& rLocale,
                sal_Int16 nNativeNumberMode)
{
    return getNativeNumberString(aNumberString, rLocale, nNativeNumberMode, nullptr);
}

OUString SAL_CALL NativeNumberSupplierService::getNativeNumberStringParams(
    const OUString& rNumberString, const css::lang::Locale& rLocale, sal_Int16 nNativeNumberMode,
    const OUString& rNativeNumberParams)
{
    return getNativeNumberString(rNumberString, rLocale, nNativeNumberMode, nullptr, rNativeNumberParams);
}

sal_Unicode NativeNumberSupplierService::getNativeNumberChar( const sal_Unicode inChar, const Locale& rLocale, sal_Int16 nNativeNumberMode )
{
    if (nNativeNumberMode == NativeNumberMode::NATNUM0) { // Ascii
        for (const auto & i : NumberChar)
            for (sal_Int16 j = 0; j < 10; j++)
                if (inChar == i[j])
                    return j;
        return inChar;
    }

    if (!isNumber(inChar))
        return inChar;

    if (!isValidNatNumImpl(rLocale, nNativeNumberMode))
        return inChar;

    sal_Int16 langnum = getLanguageNumber(rLocale);
    if (langnum == -1)
        return inChar;

    switch (nNativeNumberMode)
    {
        case NativeNumberMode::NATNUM1: // Char, Lower
        case NativeNumberMode::NATNUM4: // Text, Lower, Long
        case NativeNumberMode::NATNUM7: // Text. Lower, Short
            return NumberChar[natnum1[langnum]][inChar - NUMBER_ZERO];
        case NativeNumberMode::NATNUM2: // Char, Upper
        case NativeNumberMode::NATNUM5: // Text, Upper, Long
        case NativeNumberMode::NATNUM8: // Text, Upper, Short
            return NumberChar[natnum2[langnum]][inChar - NUMBER_ZERO];
        case NativeNumberMode::NATNUM3: // Char, FullWidth
        case NativeNumberMode::NATNUM6: // Text, FullWidth
            return NumberChar[NumberChar_FullWidth][inChar - NUMBER_ZERO];
        case NativeNumberMode::NATNUM9: // Char, Hangul
        case NativeNumberMode::NATNUM10:        // Text, Hangul, Long
        case NativeNumberMode::NATNUM11:        // Text, Hangul, Short
            return NumberChar[NumberChar_Hangul_ko][inChar - NUMBER_ZERO];
        default:
            break;
    }

    return inChar;
}

bool NativeNumberSupplierService::isValidNatNumImpl( const Locale& rLocale, sal_Int16 nNativeNumberMode )
{
    sal_Int16 langnum = getLanguageNumber(rLocale);

    switch (nNativeNumberMode) {
        case NativeNumberMode::NATNUM0:     // Ascii
        case NativeNumberMode::NATNUM3:     // Char, FullWidth
        case NativeNumberMode::NATNUM12:    // spell out numbers, dates and money amounts
            return true;
        case NativeNumberMode::NATNUM1:     // Char, Lower
            return (langnum >= 0);
        case NativeNumberMode::NATNUM2:     // Char, Upper
            if (langnum == 4) // Hebrew numbering
                return true;
            [[fallthrough]];
        case NativeNumberMode::NATNUM4:     // Text, Lower, Long
        case NativeNumberMode::NATNUM5:     // Text, Upper, Long
        case NativeNumberMode::NATNUM6:     // Text, FullWidth
        case NativeNumberMode::NATNUM7:     // Text. Lower, Short
        case NativeNumberMode::NATNUM8:     // Text, Upper, Short
            return (langnum >= 0 && langnum < 4); // CJK numbering
        case NativeNumberMode::NATNUM9:     // Char, Hangul
        case NativeNumberMode::NATNUM10:    // Text, Hangul, Long
        case NativeNumberMode::NATNUM11:    // Text, Hangul, Short
            return (langnum == 3); // Korean numbering
    }
    return false;
}

NativeNumberXmlAttributes SAL_CALL NativeNumberSupplierService::convertToXmlAttributes( const Locale& rLocale, sal_Int16 nNativeNumberMode )
{
    static const sal_Int16 attShort         = 0;
    static const sal_Int16 attMedium        = 1;
    static const sal_Int16 attLong          = 2;
    static const char * const attType[] = { "short", "medium", "long" };

    sal_Int16 number = NumberChar_HalfWidth, type = attShort;

    sal_Int16 langnum = -1;
    if (isValidNatNum(rLocale, nNativeNumberMode)) {
        langnum = getLanguageNumber(rLocale);
    }
    if (langnum != -1) {
        switch (nNativeNumberMode) {
            case NativeNumberMode::NATNUM0: // Ascii
                number = NumberChar_HalfWidth;
                type = attShort;
                break;
            case NativeNumberMode::NATNUM1: // Char, Lower
                number = natnum1[langnum];
                type = attShort;
                break;
            case NativeNumberMode::NATNUM2: // Char, Upper
                number = natnum2[langnum];
                type = number == NumberChar_he ? attMedium : attShort;
                break;
            case NativeNumberMode::NATNUM3: // Char, FullWidth
                number = NumberChar_FullWidth;
                type = attShort;
                break;
            case NativeNumberMode::NATNUM4: // Text, Lower, Long
                number = natnum1[langnum];
                type = attLong;
                break;
            case NativeNumberMode::NATNUM5: // Text, Upper, Long
                number = natnum2[langnum];
                type = attLong;
                break;
            case NativeNumberMode::NATNUM6: // Text, FullWidth
                number = NumberChar_FullWidth;
                type = attLong;
                break;
            case NativeNumberMode::NATNUM7: // Text. Lower, Short
                number = natnum1[langnum];
                type = attMedium;
                break;
            case NativeNumberMode::NATNUM8: // Text, Upper, Short
                number = natnum2[langnum];
                type = attMedium;
                break;
            case NativeNumberMode::NATNUM9: // Char, Hangul
                number = NumberChar_Hangul_ko;
                type = attShort;
                break;
            case NativeNumberMode::NATNUM10:        // Text, Hangul, Long
                number = NumberChar_Hangul_ko;
                type = attLong;
                break;
            case NativeNumberMode::NATNUM11:        // Text, Hangul, Short
                number = NumberChar_Hangul_ko;
                type = attMedium;
                break;
            default:
                break;
        }
    }
    return NativeNumberXmlAttributes(rLocale, OUString(&NumberChar[number][1], 1),
            OUString::createFromAscii(attType[type]));
}

static bool natNumIn(sal_Int16 num, const sal_Int16 natnum[], sal_Int16 len)
{
    for (sal_Int16 i = 0; i < len; i++)
        if (natnum[i] == num)
            return true;
    return false;
}

sal_Int16 SAL_CALL NativeNumberSupplierService::convertFromXmlAttributes( const NativeNumberXmlAttributes& aAttr )
{
    sal_Unicode numberChar[NumberChar_Count];
    for (sal_Int16 i = 0; i < NumberChar_Count; i++)
        numberChar[i] = NumberChar[i][1];
    OUString number(numberChar, NumberChar_Count);

    sal_Int16 num = sal::static_int_cast<sal_Int16>( number.indexOf(aAttr.Format) );

    if ( aAttr.Style == "short" ) {
        if (num == NumberChar_FullWidth)
            return NativeNumberMode::NATNUM3;
        else if (num == NumberChar_Hangul_ko)
            return NativeNumberMode::NATNUM9;
        else if (natNumIn(num, natnum1, sizeof_natnum1))
            return NativeNumberMode::NATNUM1;
        else if (natNumIn(num, natnum2, sizeof_natnum2))
            return NativeNumberMode::NATNUM2;
    } else if ( aAttr.Style == "medium" ) {
        if (num == NumberChar_Hangul_ko)
            return NativeNumberMode::NATNUM11;
        else if (num == NumberChar_he)
            return NativeNumberMode::NATNUM2;
        else if (natNumIn(num, natnum1, sizeof_natnum1))
            return NativeNumberMode::NATNUM7;
        else if (natNumIn(num, natnum2, sizeof_natnum2))
            return NativeNumberMode::NATNUM8;
    } else if ( aAttr.Style == "long" ) {
        if (num == NumberChar_FullWidth)
            return NativeNumberMode::NATNUM6;
        else if (num == NumberChar_Hangul_ko)
            return NativeNumberMode::NATNUM10;
        else if (natNumIn(num, natnum1, sizeof_natnum1))
            return NativeNumberMode::NATNUM4;
        else if (natNumIn(num, natnum2, sizeof_natnum2))
            return NativeNumberMode::NATNUM5;
    } else {
        throw RuntimeException();
    }
    return NativeNumberMode::NATNUM0;
}


// Following code generates Hebrew Number,
// see numerical system in the Hebrew Numbering System in following link for details,
// http://smontagu.org/writings/HebrewNumbers.html

namespace {

struct HebrewNumberChar {
    sal_Unicode code;
    sal_Int16 value;
};

}

HebrewNumberChar const HebrewNumberCharArray[] = {
    { 0x05ea, 400 },
    { 0x05ea, 400 },
    { 0x05e9, 300 },
    { 0x05e8, 200 },
    { 0x05e7, 100 },
    { 0x05e6, 90 },
    { 0x05e4, 80 },
    { 0x05e2, 70 },
    { 0x05e1, 60 },
    { 0x05e0, 50 },
    { 0x05de, 40 },
    { 0x05dc, 30 },
    { 0x05db, 20 },
    { 0x05d9, 10 },
    { 0x05d8, 9 },
    { 0x05d7, 8 },
    { 0x05d6, 7 },
    { 0x05d5, 6 },
    { 0x05d4, 5 },
    { 0x05d3, 4 },
    { 0x05d2, 3 },
    { 0x05d1, 2 },
    { 0x05d0, 1 }
};

const sal_Unicode thousand[] = {0x05d0, 0x05dc, 0x05e3, 0x0};
const sal_Unicode thousands[] = {0x05d0, 0x05dc, 0x05e4, 0x05d9, 0x0};
const sal_Unicode thousands_last[] = {0x05d0, 0x05dc, 0x05e4, 0x05d9, 0x05dd, 0x0};
const sal_Unicode geresh = 0x05f3;
const sal_Unicode gershayim = 0x05f4;

static void makeHebrewNumber(sal_Int64 value, OUStringBuffer& output, bool isLast, bool useGeresh)
{
    sal_Int16 num = sal::static_int_cast<sal_Int16>(value % 1000);

    if (value > 1000) {
        makeHebrewNumber(value / 1000, output, num != 0, useGeresh);
        output.append(" ");
    }
    if (num == 0) {
        output.append(value == 1000 ? thousand : isLast ? thousands_last : thousands);
    } else {
        sal_Int16 nbOfChar = 0;
        for (sal_Int32 j = 0; num > 0 && j < sal_Int32(std::size(HebrewNumberCharArray)); j++) {
            if (num - HebrewNumberCharArray[j].value >= 0) {
                nbOfChar++;
                // https://en.wikipedia.org/wiki/Hebrew_numerals#Key_exceptions
                // By convention, the numbers 15 and 16 are represented as 9 + 6 and 9 + 7
                if (num == 15 || num == 16) // substitution for 15 and 16
                    j++;
                assert(j < sal_Int32(SAL_N_ELEMENTS(HebrewNumberCharArray)));
                num = sal::static_int_cast<sal_Int16>( num - HebrewNumberCharArray[j].value );
                output.append(HebrewNumberCharArray[j].code);
            }
        }
        if (useGeresh) {
            if (nbOfChar > 1)   // a number is written as more than one character
                output.insert(output.getLength() - 1, gershayim);
            else if (nbOfChar == 1) // a number is written as a single character
                output.append(geresh);
        }
    }
}

OUString getHebrewNativeNumberString(const OUString& aNumberString, bool useGeresh)
{
    sal_Int64 value = 0;
    sal_Int32 i, count = 0, len = aNumberString.getLength();
    const sal_Unicode *src = aNumberString.getStr();

    for (i = 0; i < len; i++) {
        sal_Unicode ch = src[i];
        if (isNumber(ch)) {
            if (++count >= 20) // Number is too long, could not be handled.
                return aNumberString;
            value = value * 10 + (ch - NUMBER_ZERO);
        }
        else if (isSeparator(ch) && count > 0) continue;
        else if (isMinus(ch) && count == 0) continue;
        else break;
    }

    if (value > 0) {
        OUStringBuffer output(count*2 + 2 + len - i);

        makeHebrewNumber(value, output, true, useGeresh);

        if (i < len)
            output.append(aNumberString.subView(i));

        return output.makeStringAndClear();
    }
    else
        return aNumberString;
}

// Support for Cyrillic Numerals
// See UTN 41 for implementation information
// http://www.unicode.org/notes/tn41/

const sal_Unicode cyrillicThousandsMark = 0x0482;
const sal_Unicode cyrillicTitlo = 0x0483;
const sal_Unicode cyrillicTen = 0x0456;

namespace {

struct CyrillicNumberChar {
    sal_Unicode code;
    sal_Int16 value;
};

}

CyrillicNumberChar const CyrillicNumberCharArray[] = {
    { 0x0446, 900 },
    { 0x047f, 800 },
    { 0x0471, 700 },
    { 0x0445, 600 },
    { 0x0444, 500 },
    { 0x0443, 400 },
    { 0x0442, 300 },
    { 0x0441, 200 },
    { 0x0440, 100 },
    { 0x0447, 90 },
    { 0x043f, 80 },
    { 0x047b, 70 },
    { 0x046f, 60 },
    { 0x043d, 50 },
    { 0x043c, 40 },
    { 0x043b, 30 },
    { 0x043a, 20 },
    { 0x0456, 10 },
    { 0x0473, 9 },
    { 0x0438, 8 },
    { 0x0437, 7 },
    { 0x0455, 6 },
    { 0x0454, 5 },
    { 0x0434, 4 },
    { 0x0433, 3 },
    { 0x0432, 2 },
    { 0x0430, 1 }
};

static void makeCyrillicNumber(sal_Int64 value, OUStringBuffer& output, bool addTitlo)
{
    sal_Int16 num = sal::static_int_cast<sal_Int16>(value % 1000);
    if (value >= 1000) {
        output.append(cyrillicThousandsMark);
        makeCyrillicNumber(value / 1000, output, false);
        if (value >= 10000 && (value - 10000) % 1000 != 0) {
            output.append(" ");
        }
        if (value % 1000 == 0)
            addTitlo = false;
    }

    for (sal_Int32 j = 0; num > 0 && j < sal_Int32(std::size(CyrillicNumberCharArray)); j++) {
        if (num < 20 && num > 10) {
            num -= 10;
            makeCyrillicNumber(num, output, false);
            output.append(cyrillicTen);
            break;
        }

        if (CyrillicNumberCharArray[j].value <= num) {
            output.append(CyrillicNumberCharArray[j].code);
            num = sal::static_int_cast<sal_Int16>( num - CyrillicNumberCharArray[j].value );
        }
    }

    if (!addTitlo)
        return;

    if (output.getLength() == 1) {
        output.append(cyrillicTitlo);
    } else if (output.getLength() == 2) {
        if (value > 800 && value < 900) {
            output.append(cyrillicTitlo);
        } else {
            output.insert(1, cyrillicTitlo);
        }
    } else if (output.getLength() > 2) {
        if (output.indexOf(" ") == output.getLength() - 2) {
            output.append(cyrillicTitlo);
        } else {
            output.insert(output.getLength() - 1, cyrillicTitlo);
        }
    }
}

OUString getCyrillicNativeNumberString(const OUString& aNumberString)
{
    sal_Int64 value = 0;
    sal_Int32 i, count = 0, len = aNumberString.getLength();
    const sal_Unicode *src = aNumberString.getStr();

    for (i = 0; i < len; i++) {
        sal_Unicode ch = src[i];
        if (isNumber(ch)) {
            if (++count >= 8) // Number is too long, could not be handled.
                return aNumberString;
            value = value * 10 + (ch - NUMBER_ZERO);
        }
        else if (isSeparator(ch) && count > 0) continue;
        else if (isMinus(ch) && count == 0) continue;
        else break;
    }

    if (value > 0) {
        OUStringBuffer output(count*2 + 2 + len - i);

        makeCyrillicNumber(value, output, true);

        if (i < len)
            output.append(aNumberString.subView(i));

        return output.makeStringAndClear();
    }
    else
        return aNumberString;
}

constexpr OUString implementationName = u"com.sun.star.i18n.NativeNumberSupplier"_ustr;

OUString SAL_CALL NativeNumberSupplierService::getImplementationName()
{
    return implementationName;
}

sal_Bool SAL_CALL
NativeNumberSupplierService::supportsService(const OUString& rServiceName)
{
    return cppu::supportsService(this, rServiceName);
}

Sequence< OUString > SAL_CALL
NativeNumberSupplierService::getSupportedServiceNames()
{
    return {implementationName, u"com.sun.star.i18n.NativeNumberSupplier2"_ustr};
}

}

extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
com_sun_star_i18n_NativeNumberSupplier_get_implementation(
    css::uno::XComponentContext *,
    css::uno::Sequence<css::uno::Any> const &)
{
    return cppu::acquire(new i18npool::NativeNumberSupplierService());
}

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