/* -*- 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 <tools/color.hxx>
#include <o3tl/any.hxx>
#include <osl/mutex.hxx>
#include <osl/diagnose.h>
#include <rtl/ustring.hxx>

#include <com/sun/star/util/Date.hpp>
#include <com/sun/star/util/MalformedNumberFormatException.hpp>
#include <com/sun/star/util/NotNumericException.hpp>
#include <com/sun/star/beans/PropertyAttribute.hpp>
#include <comphelper/propertysequence.hxx>
#include <cppuhelper/supportsservice.hxx>

#include "numfmuno.hxx"
#include <svl/numformat.hxx>
#include <svl/numuno.hxx>
#include <svl/zforlist.hxx>
#include <svl/zformat.hxx>
#include <svl/itemprop.hxx>
#include <utility>

using namespace com::sun::star;

constexpr OUStringLiteral PROPERTYNAME_FMTSTR = u"FormatString";
constexpr OUStringLiteral PROPERTYNAME_LOCALE = u"Locale";
constexpr OUStringLiteral PROPERTYNAME_TYPE = u"Type";
constexpr OUStringLiteral PROPERTYNAME_COMMENT = u"Comment";
constexpr OUStringLiteral PROPERTYNAME_CURREXT = u"CurrencyExtension";
constexpr OUStringLiteral PROPERTYNAME_CURRSYM = u"CurrencySymbol";
constexpr OUStringLiteral PROPERTYNAME_CURRABB = u"CurrencyAbbreviation";
constexpr OUStringLiteral PROPERTYNAME_DECIMALS = u"Decimals";
constexpr OUStringLiteral PROPERTYNAME_LEADING = u"LeadingZeros";
constexpr OUStringLiteral PROPERTYNAME_NEGRED = u"NegativeRed";
constexpr OUStringLiteral PROPERTYNAME_STDFORM = u"StandardFormat";
constexpr OUStringLiteral PROPERTYNAME_THOUS = u"ThousandsSeparator";
constexpr OUStringLiteral PROPERTYNAME_USERDEF = u"UserDefined";

constexpr OUStringLiteral PROPERTYNAME_NOZERO = u"NoZero";
constexpr OUStringLiteral PROPERTYNAME_NULLDATE = u"NullDate";
constexpr OUStringLiteral PROPERTYNAME_STDDEC = u"StandardDecimals";
constexpr OUStringLiteral PROPERTYNAME_TWODIGIT = u"TwoDigitDateStart";

// All without a Which-ID, Map only for PropertySetInfo

static const SfxItemPropertyMapEntry* lcl_GetNumberFormatPropertyMap()
{
    static const SfxItemPropertyMapEntry aNumberFormatPropertyMap_Impl[] =
    {
        {PROPERTYNAME_FMTSTR,   0, cppu::UnoType<OUString>::get(),    beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
        {PROPERTYNAME_LOCALE,   0, cppu::UnoType<lang::Locale>::get(),beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
        {PROPERTYNAME_TYPE,     0, cppu::UnoType<sal_Int16>::get(),   beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
        {PROPERTYNAME_COMMENT,  0, cppu::UnoType<OUString>::get(),    beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
        {PROPERTYNAME_CURREXT,  0, cppu::UnoType<OUString>::get(),    beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
        {PROPERTYNAME_CURRSYM,  0, cppu::UnoType<OUString>::get(),    beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
        {PROPERTYNAME_DECIMALS, 0, cppu::UnoType<sal_Int16>::get(),   beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
        {PROPERTYNAME_LEADING,  0, cppu::UnoType<sal_Int16>::get(),   beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
        {PROPERTYNAME_NEGRED,   0, cppu::UnoType<bool>::get(),         beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
        {PROPERTYNAME_STDFORM,  0, cppu::UnoType<bool>::get(),         beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
        {PROPERTYNAME_THOUS,    0, cppu::UnoType<bool>::get(),         beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
        {PROPERTYNAME_USERDEF,  0, cppu::UnoType<bool>::get(),         beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
        {PROPERTYNAME_CURRABB,  0, cppu::UnoType<OUString>::get(),    beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
        { u"", 0, css::uno::Type(), 0, 0 }
    };
    return aNumberFormatPropertyMap_Impl;
}

static const SfxItemPropertyMapEntry* lcl_GetNumberSettingsPropertyMap()
{
    static const SfxItemPropertyMapEntry aNumberSettingsPropertyMap_Impl[] =
    {
        {PROPERTYNAME_NOZERO,   0, cppu::UnoType<bool>::get(),         beans::PropertyAttribute::BOUND, 0},
        {PROPERTYNAME_NULLDATE, 0, cppu::UnoType<util::Date>::get(),  beans::PropertyAttribute::BOUND, 0},
        {PROPERTYNAME_STDDEC,   0, cppu::UnoType<sal_Int16>::get(),   beans::PropertyAttribute::BOUND, 0},
        {PROPERTYNAME_TWODIGIT, 0, cppu::UnoType<sal_Int16>::get(),   beans::PropertyAttribute::BOUND, 0},
        { u"", 0, css::uno::Type(), 0, 0 }
    };
    return aNumberSettingsPropertyMap_Impl;
}

static LanguageType lcl_GetLanguage( const lang::Locale& rLocale )
{
    LanguageType eRet = LanguageTag::convertToLanguageTypeWithFallback( rLocale );
    if ( eRet == LANGUAGE_NONE )
        eRet = LANGUAGE_SYSTEM; //! or throw an exception?

    return eRet;
}

SvNumberFormatterServiceObj::SvNumberFormatterServiceObj()
{
}

SvNumberFormatterServiceObj::~SvNumberFormatterServiceObj()
{
}

// XNumberFormatter

void SAL_CALL SvNumberFormatterServiceObj::attachNumberFormatsSupplier( const uno::Reference<util::XNumberFormatsSupplier>& _xSupplier )
{
    ::rtl::Reference< SvNumberFormatsSupplierObj > xAutoReleaseOld;

    // SYNCHRONIZED ->
    {
        ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex() );

        SvNumberFormatsSupplierObj* pNew = comphelper::getFromUnoTunnel<SvNumberFormatsSupplierObj>( _xSupplier );
        if (!pNew)
            throw uno::RuntimeException(); // wrong object

        xAutoReleaseOld = xSupplier;

        xSupplier = pNew;
        m_aMutex = xSupplier->getSharedMutex();
    }
    // <- SYNCHRONIZED
}

uno::Reference<util::XNumberFormatsSupplier> SAL_CALL SvNumberFormatterServiceObj::getNumberFormatsSupplier()
{
    ::osl::MutexGuard aGuard( m_aMutex );
    return xSupplier;
}

sal_Int32 SAL_CALL SvNumberFormatterServiceObj::detectNumberFormat( sal_Int32 nKey, const OUString& aString )
{
    ::osl::MutexGuard aGuard( m_aMutex );

    SvNumberFormatter* pFormatter = xSupplier.is() ? xSupplier->GetNumberFormatter() : nullptr;
    if (!pFormatter)
        throw uno::RuntimeException();

    sal_uInt32 nUKey = nKey;
    double fValue = 0.0;
    if ( !pFormatter->IsNumberFormat(aString, nUKey, fValue) )
        throw util::NotNumericException();

    return nUKey;
}

double SAL_CALL SvNumberFormatterServiceObj::convertStringToNumber( sal_Int32 nKey, const OUString& aString )
{
    ::osl::MutexGuard aGuard( m_aMutex );

    SvNumberFormatter* pFormatter = xSupplier.is() ? xSupplier->GetNumberFormatter() : nullptr;
    if (!pFormatter)
        throw uno::RuntimeException();

    sal_uInt32 nUKey = nKey;
    double fValue = 0.0;
    if ( !pFormatter->IsNumberFormat(aString, nUKey, fValue) )
        throw util::NotNumericException();

    return fValue;
}

OUString SAL_CALL SvNumberFormatterServiceObj::convertNumberToString( sal_Int32 nKey, double fValue )
{
    ::osl::MutexGuard aGuard( m_aMutex );

    OUString aRet;
    SvNumberFormatter* pFormatter = xSupplier.is() ? xSupplier->GetNumberFormatter() : nullptr;
    if (!pFormatter)
        throw uno::RuntimeException();

    const Color* pColor = nullptr;
    pFormatter->GetOutputString(fValue, nKey, aRet, &pColor);

    return aRet;
}

sal_Int32 SAL_CALL SvNumberFormatterServiceObj::queryColorForNumber( sal_Int32 nKey,
                                                                       double fValue,
                                                                       sal_Int32 aDefaultColor )
{
    ::osl::MutexGuard aGuard( m_aMutex );

    util::Color nRet = aDefaultColor; // color = sal_Int32
    SvNumberFormatter* pFormatter = xSupplier.is() ? xSupplier->GetNumberFormatter() : nullptr;
    if (!pFormatter)
        throw uno::RuntimeException();

    OUString aStr;
    const Color* pColor = nullptr;
    pFormatter->GetOutputString(fValue, nKey, aStr, &pColor);
    if (pColor)
        nRet = sal_uInt32(*pColor);
    // Else keep Default

    return nRet;
}

OUString SAL_CALL SvNumberFormatterServiceObj::formatString( sal_Int32 nKey,
                                                             const OUString& aString )
{
    ::osl::MutexGuard aGuard( m_aMutex );

    OUString aRet;
    SvNumberFormatter* pFormatter = xSupplier.is() ? xSupplier->GetNumberFormatter() : nullptr;
    if (!pFormatter)
    {
        throw uno::RuntimeException();
    }

    const Color* pColor = nullptr;
    pFormatter->GetOutputString(aString, nKey, aRet, &pColor);

    return aRet;
}

sal_Int32 SAL_CALL SvNumberFormatterServiceObj::queryColorForString( sal_Int32 nKey,
                                                                       const OUString& aString,
                                                                       sal_Int32 aDefaultColor )
{
    ::osl::MutexGuard aGuard( m_aMutex );

    util::Color nRet = aDefaultColor; // color = sal_Int32
    SvNumberFormatter* pFormatter = xSupplier.is() ? xSupplier->GetNumberFormatter() : nullptr;
    if (!pFormatter)
    {
        throw uno::RuntimeException();
    }

    OUString aStr;
    const Color* pColor = nullptr;
    pFormatter->GetOutputString(aString, nKey, aStr, &pColor);
    if (pColor)
    {
        nRet = sal_uInt32(*pColor);
    }
    // Else keep Default

    return nRet;
}

OUString SAL_CALL SvNumberFormatterServiceObj::getInputString( sal_Int32 nKey, double fValue )
{
    ::osl::MutexGuard aGuard( m_aMutex );

    OUString aRet;
    SvNumberFormatter* pFormatter = xSupplier.is() ? xSupplier->GetNumberFormatter() : nullptr;
    if (!pFormatter)
        throw uno::RuntimeException();

    pFormatter->GetInputLineString(fValue, nKey, aRet);

    return aRet;
}

// XNumberFormatPreviewer

OUString SAL_CALL SvNumberFormatterServiceObj::convertNumberToPreviewString( const OUString& aFormat,
                                                                             double fValue,
                                                                             const lang::Locale& nLocale,
                                                                             sal_Bool bAllowEnglish )
{
    ::osl::MutexGuard aGuard( m_aMutex );

    OUString aRet;
    SvNumberFormatter* pFormatter = xSupplier.is() ? xSupplier->GetNumberFormatter() : nullptr;
    if (!pFormatter)
        throw uno::RuntimeException();

    LanguageType eLang = lcl_GetLanguage( nLocale );
    const Color* pColor = nullptr;

    bool bOk;
    if ( bAllowEnglish )
        bOk = pFormatter->GetPreviewStringGuess( aFormat, fValue, aRet, &pColor, eLang );
    else
        bOk = pFormatter->GetPreviewString( aFormat, fValue, aRet, &pColor, eLang );

    if (!bOk)
        throw util::MalformedNumberFormatException();

    return aRet;
}

sal_Int32 SAL_CALL SvNumberFormatterServiceObj::queryPreviewColorForNumber( const OUString& aFormat,
                                                                              double fValue,
                                                                              const lang::Locale& nLocale,
                                                                              sal_Bool bAllowEnglish,
                                                                              sal_Int32 aDefaultColor )
{
    ::osl::MutexGuard aGuard( m_aMutex );

    util::Color nRet = aDefaultColor; // color = sal_Int32
    SvNumberFormatter* pFormatter = xSupplier.is() ? xSupplier->GetNumberFormatter() : nullptr;
    if (!pFormatter)
        throw uno::RuntimeException();

    OUString aOutString;
    LanguageType eLang = lcl_GetLanguage( nLocale );
    const Color* pColor = nullptr;

    bool bOk;
    if ( bAllowEnglish )
        bOk = pFormatter->GetPreviewStringGuess( aFormat, fValue, aOutString, &pColor, eLang );
    else
        bOk = pFormatter->GetPreviewString( aFormat, fValue, aOutString, &pColor, eLang );

    if (!bOk)
        throw util::MalformedNumberFormatException();

    if (pColor)
        nRet = sal_uInt32(*pColor);
    // Else keep Default

    return nRet;
}

// XServiceInfo

OUString SAL_CALL SvNumberFormatterServiceObj::getImplementationName()
{
    return "com.sun.star.uno.util.numbers.SvNumberFormatterServiceObject";
}

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

uno::Sequence<OUString> SAL_CALL SvNumberFormatterServiceObj::getSupportedServiceNames()
{
    return { "com.sun.star.util.NumberFormatter" };
}

SvNumberFormatsObj::SvNumberFormatsObj( SvNumberFormatsSupplierObj& _rParent, ::comphelper::SharedMutex  _aMutex )
    :m_xSupplier( &_rParent )
    ,m_aMutex(std::move( _aMutex ))
{
}

SvNumberFormatsObj::~SvNumberFormatsObj()
{
}

// XNumberFormats

uno::Reference<beans::XPropertySet> SAL_CALL SvNumberFormatsObj::getByKey( sal_Int32 nKey )
{
    ::osl::MutexGuard aGuard( m_aMutex );

    SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter();
    const SvNumberformat* pFormat = pFormatter ? pFormatter->GetEntry(nKey) : nullptr;
    if (!pFormat)
        throw uno::RuntimeException();

    return new SvNumberFormatObj( *m_xSupplier, nKey, m_aMutex );
}

uno::Sequence<sal_Int32> SAL_CALL SvNumberFormatsObj::queryKeys( sal_Int16 nType,
                                                                 const lang::Locale& nLocale,
                                                                 sal_Bool bCreate )
{
    ::osl::MutexGuard aGuard( m_aMutex );

    SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter();
    if ( !pFormatter )
        throw uno::RuntimeException();

    sal_uInt32 nIndex = 0;
    LanguageType eLang = lcl_GetLanguage( nLocale );
    SvNumberFormatTable& rTable = bCreate ?
                                    pFormatter->ChangeCL( static_cast<SvNumFormatType>(nType), nIndex, eLang ) :
                                    pFormatter->GetEntryTable( static_cast<SvNumFormatType>(nType), nIndex, eLang );
    sal_uInt32 nCount = rTable.size();
    uno::Sequence<sal_Int32> aSeq(nCount);
    sal_Int32* pAry = aSeq.getArray();
    sal_uInt32 i=0;
    for (const auto& rEntry : rTable)
    {
        pAry[i] = rEntry.first;
        ++i;
    }
    return aSeq;
}

sal_Int32 SAL_CALL SvNumberFormatsObj::queryKey( const OUString& aFormat,
                                                 const lang::Locale& nLocale,
                                                 sal_Bool bScan )
{
    ::osl::MutexGuard aGuard( m_aMutex );

    SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter();
    if (!pFormatter)
        throw uno::RuntimeException();

    LanguageType eLang = lcl_GetLanguage( nLocale );
    if (bScan)
    {
        //! FIXME: Something still needs to happen here ...
    }
    sal_uInt32 nRet = pFormatter->GetEntryKey( aFormat, eLang );
    if (nRet == NUMBERFORMAT_ENTRY_NOT_FOUND)
    {
        // This seems to be a workaround for what maybe the bScan option was
        // intended for? Tokenize the format code?

        // The format string based search is vague and fuzzy, as it is case
        // sensitive, but the format string is only half way cased, the
        // keywords (except the "General" keyword) are uppercased and literals
        // of course are not. Clients using this queryKey() and if not
        // successful addNew() may still fail if the result of PutEntry() is
        // false because the format actually existed, just with a different
        // casing. The only clean way is to just use PutEntry() and ignore the
        // duplicate case, which clients can't because the API doesn't provide
        // the information.
        // Try just another possibilty here, without any guarantee.

        // Use only ASCII upper, because keywords are only those.
        // Do not transliterate any quoted literals.
        const sal_Int32 nLen = aFormat.getLength();
        OUStringBuffer aBuf(0);
        sal_Unicode* p = aBuf.appendUninitialized( nLen + 1);
        memcpy( p, aFormat.getStr(), (nLen + 1) * sizeof(sal_Unicode));   // including 0-char
        aBuf.setLength( nLen);
        assert(p == aBuf.getStr());
        sal_Unicode const * const pStop = p + aBuf.getLength();
        bool bQuoted = false;
        for ( ; p < pStop; ++p)
        {
            if (bQuoted)
            {
                // Format codes don't have embedded doubled quotes, i.e. "a""b"
                // is two literals displayed as `ab`.
                if (*p == '"')
                    bQuoted = false;
            }
            else if (*p == '"')
                bQuoted = true;
            else if ('a' <= *p && *p <= 'z')
                *p -= 0x20;     // upper
            else if (*p == '\\')
                ++p;            // skip escaped next char
                // Theoretically that should cater for UTF-32 with
                // iterateCodePoints(), but such character would not match any
                // of [a-z\"] in its UTF-16 units.
        }
        nRet = pFormatter->GetEntryKey( aBuf, eLang );
    }
    return static_cast<sal_Int32>(nRet);
}

sal_Int32 SAL_CALL SvNumberFormatsObj::addNew( const OUString& aFormat,
                                               const lang::Locale& nLocale )
{
    ::osl::MutexGuard aGuard( m_aMutex );

    sal_Int32 nRet = 0;
    SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter();
    if (!pFormatter)
        throw uno::RuntimeException();

    OUString aFormStr = aFormat;
    LanguageType eLang = lcl_GetLanguage( nLocale );
    sal_uInt32 nKey = 0;
    sal_Int32 nCheckPos = 0;
    SvNumFormatType nType = SvNumFormatType::ALL;
    bool bOk = pFormatter->PutEntry( aFormStr, nCheckPos, nType, nKey, eLang );
    if (bOk)
        nRet = nKey;
    else if (nCheckPos)
    {
        throw util::MalformedNumberFormatException(); // Invalid Format
    }
    else if (aFormStr != aFormat)
    {
        // The format exists but with a different format code string, and if it
        // was only uppercase vs lowercase keywords; but also syntax extensions
        // are possible like resulting embedded LCIDs and what not the client
        // doesn't know about. Silently accept instead of throwing an error.
        nRet = nKey;
    }
    else
        throw uno::RuntimeException(); // Other error (e.g. already added)

    return nRet;
}

sal_Int32 SAL_CALL SvNumberFormatsObj::addNewConverted( const OUString& aFormat,
                                                        const lang::Locale& nLocale,
                                                        const lang::Locale& nNewLocale )
{
    ::osl::MutexGuard aGuard( m_aMutex );

    sal_Int32 nRet = 0;
    SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter();
    if (!pFormatter)
        throw uno::RuntimeException();

    OUString aFormStr = aFormat;
    LanguageType eLang = lcl_GetLanguage( nLocale );
    LanguageType eNewLang = lcl_GetLanguage( nNewLocale );
    sal_uInt32 nKey = 0;
    sal_Int32 nCheckPos = 0;
    SvNumFormatType nType = SvNumFormatType::ALL;
    // This is used also when reading OOXML documents, there's no indicator
    // whether to convert date particle order as well, so don't. See tdf#119013
    bool bOk = pFormatter->PutandConvertEntry( aFormStr, nCheckPos, nType, nKey, eLang, eNewLang, false);
    if (bOk || nKey > 0)
        nRet = nKey;
    else if (nCheckPos)
    {
        throw util::MalformedNumberFormatException();       // Invalid format
    }
    else
        throw uno::RuntimeException(); // Other error (e.g. already added)

    return nRet;
}

void SAL_CALL SvNumberFormatsObj::removeByKey( sal_Int32 nKey )
{
    ::osl::MutexGuard aGuard( m_aMutex );
    SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter();

    if (pFormatter)
    {
        pFormatter->DeleteEntry(nKey);
    }
}

OUString SAL_CALL SvNumberFormatsObj::generateFormat( sal_Int32 nBaseKey,
                                                      const lang::Locale& nLocale,
                                                      sal_Bool bThousands,
                                                      sal_Bool bRed, sal_Int16 nDecimals,
                                                      sal_Int16 nLeading )
{
    ::osl::MutexGuard aGuard( m_aMutex );

    SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter();
    if (!pFormatter)
        throw uno::RuntimeException();

    LanguageType eLang = lcl_GetLanguage( nLocale );
    OUString aRet = pFormatter->GenerateFormat(nBaseKey, eLang, bThousands, bRed, nDecimals, nLeading);
    return aRet;
}

// XNumberFormatTypes

sal_Int32 SAL_CALL SvNumberFormatsObj::getStandardIndex( const lang::Locale& nLocale )
{
    ::osl::MutexGuard aGuard( m_aMutex );

    SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter();
    if (!pFormatter)
        throw uno::RuntimeException();

    LanguageType eLang = lcl_GetLanguage( nLocale );
    sal_Int32 nRet = pFormatter->GetStandardIndex(eLang);
    return nRet;
}

sal_Int32 SAL_CALL SvNumberFormatsObj::getStandardFormat( sal_Int16 nType, const lang::Locale& nLocale )
{
    ::osl::MutexGuard aGuard( m_aMutex );

    SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter();
    if (!pFormatter)
        throw uno::RuntimeException();

    LanguageType eLang = lcl_GetLanguage( nLocale );
    // Mask out "defined" bit, so type from an existing number format
    // can directly be used for getStandardFormat
    SvNumFormatType nType2 = static_cast<SvNumFormatType>(nType);
    nType2 &= ~SvNumFormatType::DEFINED;
    sal_Int32 nRet = pFormatter->GetStandardFormat(nType2, eLang);
    return nRet;
}

sal_Int32 SAL_CALL SvNumberFormatsObj::getFormatIndex( sal_Int16 nIndex, const lang::Locale& nLocale )
{
    ::osl::MutexGuard aGuard( m_aMutex );

    SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter();
    if (!pFormatter)
        throw uno::RuntimeException();

    LanguageType eLang = lcl_GetLanguage( nLocale );
    sal_Int32 nRet = pFormatter->GetFormatIndex( static_cast<NfIndexTableOffset>(nIndex), eLang );
    return nRet;
}

sal_Bool SAL_CALL SvNumberFormatsObj::isTypeCompatible( sal_Int16 nOldType, sal_Int16 nNewType )
{
    ::osl::MutexGuard aGuard( m_aMutex );

    return SvNumberFormatter::IsCompatible( static_cast<SvNumFormatType>(nOldType), static_cast<SvNumFormatType>(nNewType) );
}

sal_Int32 SAL_CALL SvNumberFormatsObj::getFormatForLocale( sal_Int32 nKey, const lang::Locale& nLocale )
{
    ::osl::MutexGuard aGuard( m_aMutex );

    SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter();
    if (!pFormatter)
        throw uno::RuntimeException();

    LanguageType eLang = lcl_GetLanguage( nLocale );
    sal_Int32 nRet = pFormatter->GetFormatForLanguageIfBuiltIn(nKey, eLang);
    return nRet;
}

// XServiceInfo

OUString SAL_CALL SvNumberFormatsObj::getImplementationName()
{
    return "SvNumberFormatsObj";
}

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

uno::Sequence<OUString> SAL_CALL SvNumberFormatsObj::getSupportedServiceNames()
{
    return { "com.sun.star.util.NumberFormats" };
}

SvNumberFormatObj::SvNumberFormatObj( SvNumberFormatsSupplierObj& rParent, sal_uLong nK, ::comphelper::SharedMutex _aMutex )
    :m_xSupplier( &rParent )
    ,nKey( nK )
    ,m_aMutex(std::move( _aMutex ))
{
}

SvNumberFormatObj::~SvNumberFormatObj()
{
}

// XPropertySet

uno::Reference<beans::XPropertySetInfo> SAL_CALL SvNumberFormatObj::getPropertySetInfo()
{
    static uno::Reference<beans::XPropertySetInfo> aRef =
        new SfxItemPropertySetInfo( lcl_GetNumberFormatPropertyMap() );
    return aRef;
}

void SAL_CALL SvNumberFormatObj::setPropertyValue( const OUString&,
                                                   const uno::Any& )
{
    throw beans::UnknownPropertyException(); // Everything is read-only
}

uno::Any SAL_CALL SvNumberFormatObj::getPropertyValue( const OUString& aPropertyName )
{
    ::osl::MutexGuard aGuard( m_aMutex );

    uno::Any aRet;
    SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter();
    const SvNumberformat* pFormat = pFormatter ? pFormatter->GetEntry(nKey) : nullptr;
    if (!pFormat)
        throw uno::RuntimeException();

    bool bThousand, bRed;
    sal_uInt16 nDecimals, nLeading;

    if (aPropertyName == PROPERTYNAME_FMTSTR)
    {
        aRet <<= pFormat->GetFormatstring();
    }
    else if (aPropertyName == PROPERTYNAME_LOCALE)
    {
        lang::Locale aLocale( LanguageTag::convertToLocale( pFormat->GetLanguage(), false));
        aRet <<= aLocale;
    }
    else if (aPropertyName == PROPERTYNAME_TYPE)
    {
        aRet <<= static_cast<sal_Int16>( pFormat->GetType() );
    }
    else if (aPropertyName == PROPERTYNAME_COMMENT)
    {
        aRet <<= pFormat->GetComment();
    }
    else if (aPropertyName == PROPERTYNAME_STDFORM)
    {
        //! Pass through SvNumberformat Member bStandard?
        aRet <<= ( ( nKey % SV_COUNTRY_LANGUAGE_OFFSET ) == 0 );
    }
    else if (aPropertyName == PROPERTYNAME_USERDEF)
    {
        aRet <<= bool( pFormat->GetType() & SvNumFormatType::DEFINED );
    }
    else if (aPropertyName == PROPERTYNAME_DECIMALS)
    {
        pFormat->GetFormatSpecialInfo( bThousand, bRed, nDecimals, nLeading );
        aRet <<= static_cast<sal_Int16>(nDecimals);
    }
    else if (aPropertyName == PROPERTYNAME_LEADING)
    {
        pFormat->GetFormatSpecialInfo( bThousand, bRed, nDecimals, nLeading );
        aRet <<= static_cast<sal_Int16>(nLeading);
    }
    else if (aPropertyName == PROPERTYNAME_NEGRED)
    {
        pFormat->GetFormatSpecialInfo( bThousand, bRed, nDecimals, nLeading );
        aRet <<= bRed;
    }
    else if (aPropertyName == PROPERTYNAME_THOUS)
    {
        pFormat->GetFormatSpecialInfo( bThousand, bRed, nDecimals, nLeading );
        aRet <<= bThousand;
    }
    else if (aPropertyName == PROPERTYNAME_CURRSYM)
    {
        OUString aSymbol, aExt;
        pFormat->GetNewCurrencySymbol( aSymbol, aExt );
        aRet <<= aSymbol;
    }
    else if (aPropertyName == PROPERTYNAME_CURREXT)
    {
        OUString aSymbol, aExt;
        pFormat->GetNewCurrencySymbol( aSymbol, aExt );
        aRet <<= aExt;
    }
    else if (aPropertyName == PROPERTYNAME_CURRABB)
    {
        OUString aSymbol, aExt;
        bool bBank = false;
        pFormat->GetNewCurrencySymbol( aSymbol, aExt );
        const NfCurrencyEntry* pCurr = SvNumberFormatter::GetCurrencyEntry( bBank,
            aSymbol, aExt, pFormat->GetLanguage() );
        if ( pCurr )
            aRet <<= pCurr->GetBankSymbol();
        else
            aRet <<= OUString();
    }
    else
        throw beans::UnknownPropertyException(aPropertyName);

    return aRet;
}

void SAL_CALL SvNumberFormatObj::addPropertyChangeListener( const OUString&,
                                                            const uno::Reference<beans::XPropertyChangeListener>&)
{
    OSL_FAIL("not implemented");
}

void SAL_CALL SvNumberFormatObj::removePropertyChangeListener( const OUString&,
                                                               const uno::Reference<beans::XPropertyChangeListener>&)
{
    OSL_FAIL("not implemented");
}

void SAL_CALL SvNumberFormatObj::addVetoableChangeListener( const OUString&,
                                                            const uno::Reference<beans::XVetoableChangeListener>&)
{
    OSL_FAIL("not implemented");
}

void SAL_CALL SvNumberFormatObj::removeVetoableChangeListener( const OUString&,
                                                               const uno::Reference<beans::XVetoableChangeListener>&)
{
    OSL_FAIL("not implemented");
}

// XPropertyAccess

uno::Sequence<beans::PropertyValue> SAL_CALL SvNumberFormatObj::getPropertyValues()
{
    ::osl::MutexGuard aGuard( m_aMutex );

    SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter();
    const SvNumberformat* pFormat = pFormatter ? pFormatter->GetEntry(nKey) : nullptr;
    if (!pFormat)
        throw uno::RuntimeException();

    OUString aSymbol, aExt;
    OUString aAbb;
    bool bBank = false;
    pFormat->GetNewCurrencySymbol( aSymbol, aExt );
    const NfCurrencyEntry* pCurr = SvNumberFormatter::GetCurrencyEntry( bBank,
        aSymbol, aExt, pFormat->GetLanguage() );
    if ( pCurr )
        aAbb = pCurr->GetBankSymbol();

    OUString aFmtStr = pFormat->GetFormatstring();
    OUString aComment = pFormat->GetComment();
    bool bStandard = ( ( nKey % SV_COUNTRY_LANGUAGE_OFFSET ) == 0 );
    //! Pass through SvNumberformat Member bStandard?
    bool bUserDef( pFormat->GetType() & SvNumFormatType::DEFINED );
    bool bThousand, bRed;
    sal_uInt16 nDecimals, nLeading;
    pFormat->GetFormatSpecialInfo( bThousand, bRed, nDecimals, nLeading );
    lang::Locale aLocale( LanguageTag( pFormat->GetLanguage()).getLocale());

    uno::Sequence<beans::PropertyValue> aSeq( comphelper::InitPropertySequence({
            { PROPERTYNAME_FMTSTR, uno::Any(aFmtStr) },
            { PROPERTYNAME_LOCALE, uno::Any(aLocale) },
            { PROPERTYNAME_TYPE, uno::Any(sal_Int16( pFormat->GetType() )) },
            { PROPERTYNAME_COMMENT, uno::Any(aComment) },
            { PROPERTYNAME_STDFORM, uno::Any(bStandard) },
            { PROPERTYNAME_USERDEF, uno::Any(bUserDef) },
            { PROPERTYNAME_DECIMALS, uno::Any(sal_Int16( nDecimals )) },
            { PROPERTYNAME_LEADING, uno::Any(sal_Int16( nLeading )) },
            { PROPERTYNAME_NEGRED, uno::Any(bRed) },
            { PROPERTYNAME_THOUS, uno::Any(bThousand) },
            { PROPERTYNAME_CURRSYM, uno::Any(aSymbol) },
            { PROPERTYNAME_CURREXT, uno::Any(aExt) },
            { PROPERTYNAME_CURRABB, uno::Any(aAbb) }
        }));

    return aSeq;
}

void SAL_CALL SvNumberFormatObj::setPropertyValues( const uno::Sequence<beans::PropertyValue>& )
{
    throw beans::UnknownPropertyException(); // Everything is read-only
}

// XServiceInfo

OUString SAL_CALL SvNumberFormatObj::getImplementationName()
{
    return "SvNumberFormatObj";
}

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

uno::Sequence<OUString> SAL_CALL SvNumberFormatObj::getSupportedServiceNames()
{
    return { "com.sun.star.util.NumberFormatProperties" };
}

SvNumberFormatSettingsObj::SvNumberFormatSettingsObj( SvNumberFormatsSupplierObj& rParent, ::comphelper::SharedMutex _aMutex )
    :m_xSupplier( &rParent )
    ,m_aMutex(std::move( _aMutex ))
{
}

SvNumberFormatSettingsObj::~SvNumberFormatSettingsObj()
{
}

// XPropertySet

uno::Reference<beans::XPropertySetInfo> SAL_CALL SvNumberFormatSettingsObj::getPropertySetInfo()
{
    static uno::Reference<beans::XPropertySetInfo> aRef =
        new SfxItemPropertySetInfo( lcl_GetNumberSettingsPropertyMap() );
    return aRef;
}

void SAL_CALL SvNumberFormatSettingsObj::setPropertyValue( const OUString& aPropertyName,
                                                           const uno::Any& aValue )
{
    ::osl::MutexGuard aGuard( m_aMutex );

    SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter();
    if (!pFormatter)
        throw uno::RuntimeException();

    if (aPropertyName == PROPERTYNAME_NOZERO)
    {
        //  operator >>= shouldn't be used for bool (?)
        if ( auto b = o3tl::tryAccess<bool>(aValue) )
            pFormatter->SetNoZero( *b );
    }
    else if (aPropertyName == PROPERTYNAME_NULLDATE)
    {
        util::Date aDate;
        if ( aValue >>= aDate )
            pFormatter->ChangeNullDate( aDate.Day, aDate.Month, aDate.Year );
    }
    else if (aPropertyName == PROPERTYNAME_STDDEC)
    {
        sal_Int16 nInt16 = sal_Int16();
        if ( aValue >>= nInt16 )
            pFormatter->ChangeStandardPrec( nInt16 );
    }
    else if (aPropertyName == PROPERTYNAME_TWODIGIT)
    {
        sal_Int16 nInt16 = sal_Int16();
        if ( aValue >>= nInt16 )
            pFormatter->SetYear2000( nInt16 );
    }
    else
        throw beans::UnknownPropertyException(aPropertyName);

}

uno::Any SAL_CALL SvNumberFormatSettingsObj::getPropertyValue( const OUString& aPropertyName )
{
    ::osl::MutexGuard aGuard( m_aMutex );

    uno::Any aRet;
    SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter();
    if (!pFormatter)
        throw uno::RuntimeException();

    if (aPropertyName == PROPERTYNAME_NOZERO)
    {
        aRet <<= pFormatter->GetNoZero();
    }
    else if (aPropertyName == PROPERTYNAME_NULLDATE)
    {
        const Date& rDate = pFormatter->GetNullDate();
        aRet <<= rDate.GetUNODate();
    }
    else if (aPropertyName == PROPERTYNAME_STDDEC)
        aRet <<= static_cast<sal_Int16>( pFormatter->GetStandardPrec() );
    else if (aPropertyName == PROPERTYNAME_TWODIGIT)
        aRet <<= static_cast<sal_Int16>( pFormatter->GetYear2000() );
    else
        throw beans::UnknownPropertyException(aPropertyName);

    return aRet;
}

void SAL_CALL SvNumberFormatSettingsObj::addPropertyChangeListener( const OUString&,
                                                                    const uno::Reference<beans::XPropertyChangeListener>&)
{
    OSL_FAIL("not implemented");
}

void SAL_CALL SvNumberFormatSettingsObj::removePropertyChangeListener( const OUString&,
                                                                       const uno::Reference<beans::XPropertyChangeListener>&)
{
    OSL_FAIL("not implemented");
}

void SAL_CALL SvNumberFormatSettingsObj::addVetoableChangeListener( const OUString&,
                                                                    const uno::Reference<beans::XVetoableChangeListener>&)
{
    OSL_FAIL("not implemented");
}

void SAL_CALL SvNumberFormatSettingsObj::removeVetoableChangeListener( const OUString&,
                                                                       const uno::Reference<beans::XVetoableChangeListener>&)
{
    OSL_FAIL("not implemented");
}

// XServiceInfo

OUString SAL_CALL SvNumberFormatSettingsObj::getImplementationName()
{
    return "SvNumberFormatSettingsObj";
}

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

uno::Sequence<OUString> SAL_CALL SvNumberFormatSettingsObj::getSupportedServiceNames()
{
    return { "com.sun.star.util.NumberFormatSettings" };
}


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


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