/* -*- 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 <scitems.hxx>
#include <svx/algitem.hxx>
#include <editeng/brushitem.hxx>
#include <editeng/editobj.hxx>
#include <svl/itempool.hxx>
#include <svl/srchitem.hxx>
#include <editeng/langitem.hxx>
#include <o3tl/string_view.hxx>
#include <o3tl/unit_conversion.hxx>
#include <sfx2/docfile.hxx>
#include <sfx2/dispatch.hxx>
#include <sfx2/objsh.hxx>
#include <sfx2/sfxsids.hrc>
#include <sfx2/viewfrm.hxx>
#include <sfx2/viewsh.hxx>
#include <svl/intitem.hxx>
#include <svl/numformat.hxx>
#include <svl/stritem.hxx>
#include <svl/zforlist.hxx>
#include <svl/zformat.hxx>
#include <vcl/keycodes.hxx>
#include <vcl/virdev.hxx>
#include <vcl/settings.hxx>
#include <vcl/svapp.hxx>
#include <unotools/charclass.hxx>
#include <unotools/securityoptions.hxx>
#include <osl/diagnose.h>
 
#include <i18nlangtag/mslangid.hxx>
#include <comphelper/doublecheckedinit.hxx>
#include <comphelper/processfactory.hxx>
#include <comphelper/string.hxx>
#include <unotools/calendarwrapper.hxx>
#include <unotools/collatorwrapper.hxx>
#include <unotools/syslocale.hxx>
#include <unotools/transliterationwrapper.hxx>
 
#include <comphelper/lok.hxx>
 
#include <global.hxx>
#include <scresid.hxx>
#include <autoform.hxx>
#include <patattr.hxx>
#include <addincol.hxx>
#include <adiasync.hxx>
#include <userlist.hxx>
#include <interpre.hxx>
#include <unitconv.hxx>
#include <compiler.hxx>
#include <parclass.hxx>
#include <funcdesc.hxx>
#include <globstr.hrc>
#include <strings.hrc>
#include <scmod.hxx>
#include <editutil.hxx>
#include <docsh.hxx>
#include <sharedstringpoolpurge.hxx>
#include <formulaopt.hxx>
 
rtl::Reference<ScDocShell> ScGlobal::xDrawClipDocShellRef;
std::unique_ptr<SvxSearchItem> ScGlobal::xSearchItem;
std::unique_ptr<ScAutoFormat> ScGlobal::xAutoFormat;
std::atomic<LegacyFuncCollection*> ScGlobal::pLegacyFuncCollection(nullptr);
std::atomic<ScUnoAddInCollection*> ScGlobal::pAddInCollection(nullptr);
std::unique_ptr<ScUserList> ScGlobal::xUserList;
LanguageType    ScGlobal::eLnge = LANGUAGE_SYSTEM;
std::atomic<css::lang::Locale*> ScGlobal::pLocale(nullptr);
std::optional<SvtSysLocale>   ScGlobal::oSysLocale;
std::optional<CalendarWrapper> ScGlobal::oCalendar;
std::atomic<CollatorWrapper*> ScGlobal::pCollator(nullptr);
std::atomic<CollatorWrapper*> ScGlobal::pCaseCollator(nullptr);
std::atomic<::utl::TransliterationWrapper*> ScGlobal::pTransliteration(nullptr);
std::atomic<::utl::TransliterationWrapper*> ScGlobal::pCaseTransliteration(nullptr);
css::uno::Reference< css::i18n::XOrdinalSuffix> ScGlobal::xOrdinalSuffix;
OUString        ScGlobal::aStrClipDocName;
 
std::unique_ptr<SvxBrushItem> ScGlobal::xEmptyBrushItem;
std::unique_ptr<SvxBrushItem> ScGlobal::xButtonBrushItem;
 
std::unordered_map<OUString, std::unique_ptr<ScFunctionList>> ScGlobal::xStarCalcFunctionList;
std::unordered_map<OUString, std::unique_ptr<ScFunctionMgr>> ScGlobal::xStarCalcFunctionMgr;
 
std::atomic<ScUnitConverter*> ScGlobal::pUnitConverter(nullptr);
std::unique_ptr<SvNumberFormatter> ScGlobal::xEnglishFormatter;
std::unique_ptr<ScFieldEditEngine> ScGlobal::xFieldEditEngine;
std::atomic<sc::SharedStringPoolPurge*> ScGlobal::pSharedStringPoolPurge;
 
double          ScGlobal::nScreenPPTX           = 96.0;
double          ScGlobal::nScreenPPTY           = 96.0;
 
sal_uInt16          ScGlobal::nDefFontHeight        = 225;
sal_uInt16          ScGlobal::nStdRowHeight         = 256;
 
tools::Long            ScGlobal::nLastRowHeightExtra   = 0;
tools::Long            ScGlobal::nLastColWidthExtra    = STD_EXTRA_WIDTH;
 
SfxViewShell* pScActiveViewShell = nullptr; //FIXME: Make this a member
sal_uInt16 nScClickMouseModifier = 0;    //FIXME: This too
sal_uInt16 nScFillModeMouseModifier = 0; //FIXME: And this
 
bool ScGlobal::bThreadedGroupCalcInProgress = false;
 
std::unordered_map<OUString, InputHandlerFunctionNames> ScGlobal::maInputHandlerFunctionNames;
 
// Static functions
 
bool ScGlobal::HasAttrChanged( const SfxItemSet&  rNewAttrs,
                               const SfxItemSet&  rOldAttrs,
                               const sal_uInt16       nWhich )
{
    bool                bInvalidate = false;
    const SfxPoolItem*  pNewItem    = nullptr;
    const SfxItemState  eNewState   = rNewAttrs.GetItemState( nWhich, true, &pNewItem );
    const SfxPoolItem*  pOldItem    = nullptr;
    const SfxItemState  eOldState   = rOldAttrs.GetItemState( nWhich, true, &pOldItem );
 
    if ( eNewState == eOldState )
    {
        // Both Items set
        // PoolItems, meaning comparing pointers is valid
        if ( SfxItemState::SET == eOldState )
            bInvalidate = !SfxPoolItem::areSame(pNewItem, pOldItem);
    }
    else
    {
        // Contains a Default Item
        // PoolItems, meaning Item comparison necessary
        if (!pOldItem)
            pOldItem = &rOldAttrs.GetPool()->GetUserOrPoolDefaultItem( nWhich );
 
        if (!pNewItem)
            pNewItem = &rNewAttrs.GetPool()->GetUserOrPoolDefaultItem( nWhich );
 
        bInvalidate = (*pNewItem != *pOldItem);
    }
 
    return bInvalidate;
}
 
sal_uInt32 ScGlobal::GetStandardFormat( ScInterpreterContext& rContext,
        sal_uInt32 nFormat, SvNumFormatType nType )
{
    if (const SvNumberformat* pFormat = rContext.NFGetFormatEntry(nFormat))
        return rContext.NFGetStandardFormat( nFormat, nType, pFormat->GetLanguage() );
    return rContext.NFGetStandardFormat( nType, eLnge );
}
 
sal_uInt16 ScGlobal::GetStandardRowHeight()
{
    return nStdRowHeight;
}
 
SvNumberFormatter* ScGlobal::GetEnglishFormatter()
{
    assert(!bThreadedGroupCalcInProgress);
    if ( !xEnglishFormatter )
    {
        xEnglishFormatter.reset( new SvNumberFormatter(
            ::comphelper::getProcessComponentContext(), LANGUAGE_ENGLISH_US ) );
        xEnglishFormatter->SetEvalDateFormat( NF_EVALDATEFORMAT_INTL_FORMAT );
    }
    return xEnglishFormatter.get();
}
 
bool ScGlobal::CheckWidthInvalidate( bool& bNumFormatChanged,
                                     const SfxItemSet& rNewAttrs,
                                     const SfxItemSet& rOldAttrs )
{
    // Here ScPatternAttr::FastEqualPatternSets was used before. This implies that
    // the two given SfxItemSet are internal ones from ScPatternAttr, but there is
    // no guarantee here for that. Also that former method contained the comment
    //   "Actually test_tdf133629 from UITest_calc_tests9 somehow manages to have
    //   a different range (and I don't understand enough why), so better be safe and compare fully."
    // which may be based on this usage. I check for that already in
    // ScPatternAttr::operator==, seems not to be triggered there.
    // All in all: Better use SfxItemSet::operator== here, and not one specialized
    // on the SfxItemSets of ScPatternAttr
    if (rNewAttrs == rOldAttrs)
    {
        bNumFormatChanged = false;
        return false;
    }
    // Check whether attribute changes in rNewAttrs compared to rOldAttrs render
    // the text width at a cell invalid
    bNumFormatChanged =
            HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_VALUE_FORMAT );
    return ( bNumFormatChanged
        || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_LANGUAGE_FORMAT )
        || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT )
        || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CJK_FONT )
        || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CTL_FONT )
        || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_HEIGHT )
        || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CJK_FONT_HEIGHT )
        || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CTL_FONT_HEIGHT )
        || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_WEIGHT )
        || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CJK_FONT_WEIGHT )
        || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CTL_FONT_WEIGHT )
        || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_POSTURE )
        || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CJK_FONT_POSTURE )
        || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CTL_FONT_POSTURE )
        || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_UNDERLINE )
        || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_OVERLINE )
        || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_CROSSEDOUT )
        || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_CONTOUR )
        || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_SHADOWED )
        || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_STACKED )
        || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_ROTATE_VALUE )
        || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_ROTATE_MODE )
        || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_LINEBREAK )
        || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_MARGIN )
        );
}
 
const SvxSearchItem& ScGlobal::GetSearchItem()
{
    assert(!bThreadedGroupCalcInProgress);
    if (!xSearchItem)
    {
        xSearchItem.reset(new SvxSearchItem( SID_SEARCH_ITEM ));
        xSearchItem->SetAppFlag( SvxSearchApp::CALC );
    }
    return *xSearchItem;
}
 
void ScGlobal::SetSearchItem( const SvxSearchItem& rNew )
{
    assert(!bThreadedGroupCalcInProgress);
    // FIXME: An assignment operator would be nice here
    xSearchItem.reset(rNew.Clone());
 
    xSearchItem->SetWhich( SID_SEARCH_ITEM );
    xSearchItem->SetAppFlag( SvxSearchApp::CALC );
}
 
void ScGlobal::ClearAutoFormat()
{
    assert(!bThreadedGroupCalcInProgress);
    if (xAutoFormat)
    {
        //  When modified via StarOne then only the SaveLater flag is set and no saving is done.
        //  If the flag is set then save now.
        if (xAutoFormat->IsSaveLater())
            xAutoFormat->Save();
        xAutoFormat.reset();
    }
}
 
ScAutoFormat* ScGlobal::GetAutoFormat()
{
    return xAutoFormat.get();
}
 
ScAutoFormat* ScGlobal::GetOrCreateAutoFormat()
{
    assert(!bThreadedGroupCalcInProgress);
    if ( !xAutoFormat )
    {
        xAutoFormat.reset(new ScAutoFormat);
        xAutoFormat->Load();
    }
 
    return xAutoFormat.get();
}
 
LegacyFuncCollection* ScGlobal::GetLegacyFuncCollection()
{
    return comphelper::doubleCheckedInit( pLegacyFuncCollection, []() { return new LegacyFuncCollection(); });
}
 
ScUnoAddInCollection* ScGlobal::GetAddInCollection()
{
    return comphelper::doubleCheckedInit( pAddInCollection, []() { return new ScUnoAddInCollection(); });
}
 
ScUserList& ScGlobal::GetUserList()
{
    assert(!bThreadedGroupCalcInProgress);
    // Hack: Load Cfg item at the App
    global_InitAppOptions();
 
    if (!xUserList)
        xUserList.reset(new ScUserList());
    return *xUserList;
}
 
void ScGlobal::SetUserList( const ScUserList* pNewList )
{
    assert(!bThreadedGroupCalcInProgress);
    if ( pNewList )
    {
        if ( !xUserList )
            xUserList.reset( new ScUserList( *pNewList ) );
        else
            *xUserList = *pNewList;
    }
    else
    {
        xUserList.reset();
    }
}
 
OUString ScGlobal::GetErrorString(FormulaError nErr)
{
    TranslateId pErrNumber;
    switch (nErr)
    {
        case FormulaError::NoRef:
            pErrNumber = STR_NO_REF_TABLE;
            break;
        case FormulaError::NoAddin:
            pErrNumber = STR_NO_ADDIN;
            break;
        case FormulaError::NoMacro:
            pErrNumber = STR_NO_MACRO;
            break;
        case FormulaError::NotAvailable:
            return ScCompiler::GetNativeSymbol(ocErrNA);
        case FormulaError::NoName:
            return ScCompiler::GetNativeSymbol(ocErrName);
        case FormulaError::NoValue:
            return ScCompiler::GetNativeSymbol(ocErrValue);
        case FormulaError::NoCode:
            return ScCompiler::GetNativeSymbol(ocErrNull);
        case FormulaError::DivisionByZero:
            return ScCompiler::GetNativeSymbol(ocErrDivZero);
        case FormulaError::IllegalFPOperation:
            return ScCompiler::GetNativeSymbol(ocErrNum);
        default:
            return ScResId(STR_ERROR_STR) + OUString::number( static_cast<int>(nErr) );
    }
    return ScResId(pErrNumber);
}
 
OUString ScGlobal::GetLongErrorString(FormulaError nErr)
{
    TranslateId pErrNumber;
    switch (nErr)
    {
        case FormulaError::NONE:
            return OUString();
        case FormulaError::IllegalArgument:
            pErrNumber = STR_LONG_ERR_ILL_ARG;
        break;
        case FormulaError::IllegalFPOperation:
            pErrNumber = STR_LONG_ERR_ILL_FPO;
        break;
        case FormulaError::IllegalChar:
            pErrNumber = STR_LONG_ERR_ILL_CHAR;
        break;
        case FormulaError::IllegalParameter:
            pErrNumber = STR_LONG_ERR_ILL_PAR;
        break;
        case FormulaError::Pair:
        case FormulaError::PairExpected:
            pErrNumber = STR_LONG_ERR_PAIR;
        break;
        case FormulaError::OperatorExpected:
            pErrNumber = STR_LONG_ERR_OP_EXP;
        break;
        case FormulaError::VariableExpected:
        case FormulaError::ParameterExpected:
            pErrNumber = STR_LONG_ERR_VAR_EXP;
        break;
        case FormulaError::CodeOverflow:
            pErrNumber = STR_LONG_ERR_CODE_OVF;
        break;
        case FormulaError::StringOverflow:
            pErrNumber = STR_LONG_ERR_STR_OVF;
        break;
        case FormulaError::StackOverflow:
            pErrNumber = STR_LONG_ERR_STACK_OVF;
        break;
        case FormulaError::MatrixSize:
            pErrNumber = STR_LONG_ERR_MATRIX_SIZE;
        break;
        case FormulaError::UnknownState:
        case FormulaError::UnknownVariable:
        case FormulaError::UnknownOpCode:
        case FormulaError::UnknownStackVariable:
        case FormulaError::UnknownToken:
            pErrNumber = STR_LONG_ERR_SYNTAX;
        break;
        case FormulaError::NoCode:
            pErrNumber = STR_LONG_ERR_NO_CODE;
        break;
        case FormulaError::CircularReference:
            pErrNumber = STR_LONG_ERR_CIRC_REF;
        break;
        case FormulaError::NoConvergence:
            pErrNumber = STR_LONG_ERR_NO_CONV;
        break;
        case FormulaError::NoRef:
            pErrNumber = STR_LONG_ERR_NO_REF;
        break;
        case FormulaError::NoName:
            pErrNumber = STR_LONG_ERR_NO_NAME;
        break;
        case FormulaError::NoAddin:
            pErrNumber = STR_LONG_ERR_NO_ADDIN;
        break;
        case FormulaError::NoMacro:
            pErrNumber = STR_LONG_ERR_NO_MACRO;
        break;
        case FormulaError::DivisionByZero:
            pErrNumber = STR_LONG_ERR_DIV_ZERO;
        break;
        case FormulaError::NestedArray:
            pErrNumber = STR_ERR_LONG_NESTED_ARRAY;
        break;
        case FormulaError::BadArrayContent:
            pErrNumber = STR_ERR_LONG_BAD_ARRAY_CONTENT;
        break;
        case FormulaError::LinkFormulaNeedingCheck:
            pErrNumber = STR_ERR_LONG_LINK_FORMULA_NEEDING_CHECK;
        break;
        case FormulaError::NoValue:
            pErrNumber = STR_LONG_ERR_NO_VALUE;
        break;
        case FormulaError::NotAvailable:
            pErrNumber = STR_LONG_ERR_NV;
        break;
        default:
            return ScResId(STR_ERROR_STR) + OUString::number( static_cast<int>(nErr) );
    }
    return ScResId(pErrNumber);
}
 
SvxBrushItem* ScGlobal::GetButtonBrushItem()
{
    assert(!bThreadedGroupCalcInProgress);
    xButtonBrushItem->SetColor( Application::GetSettings().GetStyleSettings().GetFaceColor() );
    return xButtonBrushItem.get();
}
 
void ScGlobal::Init()
{
    // The default language for number formats (ScGlobal::eLnge) must
    // always be LANGUAGE_SYSTEM
    // FIXME: So remove this variable?
    eLnge = LANGUAGE_SYSTEM;
 
    oSysLocale.emplace();
 
    xEmptyBrushItem = std::make_unique<SvxBrushItem>( COL_TRANSPARENT, ATTR_BACKGROUND );
    xButtonBrushItem = std::make_unique<SvxBrushItem>( Color(), ATTR_BACKGROUND );
 
    InitPPT();
    //ScCompiler::InitSymbolsNative();
    // ScParameterClassification _after_ Compiler, needs function resources if
    // arguments are to be merged in, which in turn need strings of function
    // names from the compiler.
    ScParameterClassification::Init();
 
    InitAddIns();
 
    aStrClipDocName = ScResId( SCSTR_NONAME ) + "1";
 
    //  ScDocumentPool::InitVersionMaps() has been called earlier already
}
 
void ScGlobal::InitPPT()
{
    OutputDevice* pDev = Application::GetDefaultDevice();
 
    if (comphelper::LibreOfficeKit::isActive())
    {
        // LOK: the below limited precision is not enough for RowColumnHeader.
        nScreenPPTX = o3tl::convert<double>(pDev->GetDPIX(), o3tl::Length::twip, o3tl::Length::in);
        nScreenPPTY = o3tl::convert<double>(pDev->GetDPIY(), o3tl::Length::twip, o3tl::Length::in);
    }
    else
    {
        // Avoid cumulative placement errors by intentionally limiting
        // precision.
        Point aPix1000 = pDev->LogicToPixel(Point(1000, 1000), MapMode(MapUnit::MapTwip));
        nScreenPPTX = aPix1000.X() / 1000.0;
        nScreenPPTY = aPix1000.Y() / 1000.0;
    }
}
 
const OUString& ScGlobal::GetClipDocName()
{
    return aStrClipDocName;
}
 
void ScGlobal::SetClipDocName( const OUString& rNew )
{
    assert(!bThreadedGroupCalcInProgress);
    aStrClipDocName = rNew;
}
 
void ScGlobal::InitTextHeight(SfxItemPool& rPool)
{
    // this gets handed over the m_pMessagePool in ScModule::ScModule, so
    // the previously used item ScPatternAttr is unchanged. This allows to
    // just use an temporary incarnation of a CellAttributeHelper here
    const CellAttributeHelper aTempHelper(rPool);
    const ScPatternAttr& rDefaultCellAttribute(aTempHelper.getDefaultCellAttribute());
 
    OutputDevice* pDefaultDev = Application::GetDefaultDevice();
    ScopedVclPtrInstance< VirtualDevice > pVirtWindow( *pDefaultDev );
    pVirtWindow->SetMapMode(MapMode(MapUnit::MapPixel));
    vcl::Font aDefFont;
    rDefaultCellAttribute.fillFontOnly(aDefFont, pVirtWindow); // Font color doesn't matter here
    pVirtWindow->SetFont(aDefFont);
    sal_uInt16 nTest = static_cast<sal_uInt16>(
        pVirtWindow->PixelToLogic(Size(0, pVirtWindow->GetTextHeight()), MapMode(MapUnit::MapTwip)).Height());
 
    if (nTest > nDefFontHeight)
        nDefFontHeight = nTest;
 
    const SvxMarginItem& rMargin(rDefaultCellAttribute.GetItem(ATTR_MARGIN));
 
    nTest = static_cast<sal_uInt16>(nDefFontHeight + rMargin.GetTopMargin()
                                    + rMargin.GetBottomMargin() - STD_ROWHEIGHT_DIFF);
 
    if (nTest > nStdRowHeight)
        nStdRowHeight = nTest;
}
 
void ScGlobal::Clear()
{
    // Destroy asyncs _before_ ExitExternalFunc!
    theAddInAsyncTbl.clear();
    ExitExternalFunc();
    ClearAutoFormat();
    xSearchItem.reset();
    delete pLegacyFuncCollection.exchange(nullptr);
    delete pAddInCollection.exchange(nullptr);
    xUserList.reset();
    xStarCalcFunctionList.clear(); // Destroy before ResMgr!
    xStarCalcFunctionMgr.clear();
    maInputHandlerFunctionNames.clear();
    ScParameterClassification::Exit();
    ScCompiler::DeInit();
    ScInterpreter::GlobalExit(); // Delete static Stack
 
    xEmptyBrushItem.reset();
    xButtonBrushItem.reset();
    xEnglishFormatter.reset();
    delete pCaseTransliteration.exchange(nullptr);
    delete pTransliteration.exchange(nullptr);
    delete pCaseCollator.exchange(nullptr);
    delete pCollator.exchange(nullptr);
    oCalendar.reset();
    oSysLocale.reset();
    delete pLocale.exchange(nullptr);
 
    delete pUnitConverter.exchange(nullptr);
    xFieldEditEngine.reset();
    delete pSharedStringPoolPurge.exchange(nullptr);
 
    xDrawClipDocShellRef.clear();
}
 
rtl_TextEncoding ScGlobal::GetCharsetValue( std::u16string_view rCharSet )
{
    // new TextEncoding values
    if ( CharClass::isAsciiNumeric( rCharSet ) )
    {
        sal_Int32 nVal = o3tl::toInt32(rCharSet);
        if ( nVal == RTL_TEXTENCODING_DONTKNOW )
            return osl_getThreadTextEncoding();
        return static_cast<rtl_TextEncoding>(nVal);
    }
    // old CharSet values for compatibility
    else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"ANSI")     ) return RTL_TEXTENCODING_MS_1252;
    else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"MAC")      ) return RTL_TEXTENCODING_APPLE_ROMAN;
    else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"IBMPC")    ) return RTL_TEXTENCODING_IBM_850;
    else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"IBMPC_437")) return RTL_TEXTENCODING_IBM_437;
    else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"IBMPC_850")) return RTL_TEXTENCODING_IBM_850;
    else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"IBMPC_860")) return RTL_TEXTENCODING_IBM_860;
    else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"IBMPC_861")) return RTL_TEXTENCODING_IBM_861;
    else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"IBMPC_863")) return RTL_TEXTENCODING_IBM_863;
    else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"IBMPC_865")) return RTL_TEXTENCODING_IBM_865;
    // Some wrong "help" on the net mentions UTF8 and even unoconv uses it,
    // which worked accidentally if the system encoding is UTF-8 anyway, so
    // support it ;) but only when reading.
    else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"UTF8"))      return RTL_TEXTENCODING_UTF8;
    else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"UTF-8"))     return RTL_TEXTENCODING_UTF8;
    else return osl_getThreadTextEncoding();
}
 
OUString ScGlobal::GetCharsetString( rtl_TextEncoding eVal )
{
    const char* pChar;
    switch ( eVal )
    {
        // old CharSet strings for compatibility
        case RTL_TEXTENCODING_MS_1252:      pChar = "ANSI";         break;
        case RTL_TEXTENCODING_APPLE_ROMAN:  pChar = "MAC";          break;
        // IBMPC == IBMPC_850
        case RTL_TEXTENCODING_IBM_437:      pChar = "IBMPC_437";    break;
        case RTL_TEXTENCODING_IBM_850:      pChar = "IBMPC_850";    break;
        case RTL_TEXTENCODING_IBM_860:      pChar = "IBMPC_860";    break;
        case RTL_TEXTENCODING_IBM_861:      pChar = "IBMPC_861";    break;
        case RTL_TEXTENCODING_IBM_863:      pChar = "IBMPC_863";    break;
        case RTL_TEXTENCODING_IBM_865:      pChar = "IBMPC_865";    break;
        case RTL_TEXTENCODING_DONTKNOW:     pChar = "SYSTEM";       break;
        // new string of TextEncoding value
        default:
            return OUString::number( eVal );
    }
    return OUString::createFromAscii(pChar);
}
 
bool ScGlobal::HasStarCalcFunctionList()
{
    OUString lang = Translate::getLanguage(SC_MOD()->GetResLocale());
    auto list = xStarCalcFunctionList.find(lang);
    return list != xStarCalcFunctionList.end();
}
 
ScFunctionList* ScGlobal::GetStarCalcFunctionList()
{
    assert(!bThreadedGroupCalcInProgress);
    OUString lang = Translate::getLanguage(SC_MOD()->GetResLocale());
    if (auto list = xStarCalcFunctionList.find(lang); list != xStarCalcFunctionList.end())
    {
        return xStarCalcFunctionList[lang].get();
    }
    xStarCalcFunctionList.emplace(
        lang, new ScFunctionList(SC_MOD()->GetFormulaOptions().GetUseEnglishFuncName()));
    return xStarCalcFunctionList[lang].get();
}
 
ScFunctionMgr* ScGlobal::GetStarCalcFunctionMgr()
{
    assert(!bThreadedGroupCalcInProgress);
    OUString lang = Translate::getLanguage(SC_MOD()->GetResLocale());
    if (auto list = xStarCalcFunctionMgr.find(lang); list != xStarCalcFunctionMgr.end())
    {
        return xStarCalcFunctionMgr[lang].get();
    }
    xStarCalcFunctionMgr.emplace(lang, new ScFunctionMgr);
 
    return xStarCalcFunctionMgr[lang].get();
}
 
void ScGlobal::ResetFunctionList()
{
    // FunctionMgr has pointers into FunctionList, must also be updated
    xStarCalcFunctionMgr.clear();
    xStarCalcFunctionList.clear();
    // Building new names also needs InputHandler data to be refreshed.
    maInputHandlerFunctionNames.clear();
    maInputHandlerFunctionNames.emplace(Translate::getLanguage(SC_MOD()->GetResLocale()),
                                        InputHandlerFunctionNames());
}
 
const InputHandlerFunctionNames& ScGlobal::GetInputHandlerFunctionNames()
{
    OUString lang = Translate::getLanguage(SC_MOD()->GetResLocale());
    if (maInputHandlerFunctionNames.find(lang) == maInputHandlerFunctionNames.end())
    {
        maInputHandlerFunctionNames.emplace(lang, InputHandlerFunctionNames());
    }
 
    InputHandlerFunctionNames& currentInputHandlerFunctionNames = maInputHandlerFunctionNames[lang];
 
    if (currentInputHandlerFunctionNames.maFunctionData.empty())
    {
        const OUString aParenthesesReplacement( cParenthesesReplacement);
        const ScFunctionList* pFuncList = GetStarCalcFunctionList();
        const sal_uInt32 nListCount = pFuncList->GetCount();
        const CharClass* pCharClass = (pFuncList->IsEnglishFunctionNames()
                ? ScCompiler::GetCharClassEnglish()
                : ScCompiler::GetCharClassLocalized());
        for (sal_uInt32 i=0; i < nListCount; ++i)
        {
            const ScFuncDesc* pDesc = pFuncList->GetFunction( i );
            if ( pDesc->mxFuncName )
            {
                OUString aFuncName(pCharClass->uppercase(*(pDesc->mxFuncName)));
                // fdo#75264 fill maFormulaChar with all characters used in formula names
                for (sal_Int32 j = 0; j < aFuncName.getLength(); j++)
                    currentInputHandlerFunctionNames.maFunctionChar.insert(aFuncName[j]);
                currentInputHandlerFunctionNames.maFunctionData.insert(
                    ScTypedStrData(*(pDesc->mxFuncName) + aParenthesesReplacement, 0.0, 0.0,
                                   ScTypedStrData::Standard));
                pDesc->initArgumentInfo();
                OUString aEntry = pDesc->getSignature();
                currentInputHandlerFunctionNames.maFunctionDataPara.insert(
                    ScTypedStrData(aEntry, 0.0, 0.0, ScTypedStrData::Standard));
            }
        }
    }
    return currentInputHandlerFunctionNames;
}
 
ScUnitConverter* ScGlobal::GetUnitConverter()
{
    return comphelper::doubleCheckedInit( pUnitConverter,
        []() { return new ScUnitConverter; });
}
 
const sal_Unicode* ScGlobal::UnicodeStrChr( const sal_Unicode* pStr,
            sal_Unicode c )
{
    if ( !pStr )
        return nullptr;
    while ( *pStr )
    {
        if ( *pStr == c )
            return pStr;
        pStr++;
    }
    return nullptr;
}
 
OUString ScGlobal::addToken(std::u16string_view rTokenList, std::u16string_view rToken,
    sal_Unicode cSep, sal_Int32 nSepCount, bool bForceSep)
{
    OUStringBuffer aBuf(rTokenList);
    if( bForceSep || (!rToken.empty() && !rTokenList.empty()) )
        comphelper::string::padToLength(aBuf, aBuf.getLength() + nSepCount, cSep);
    aBuf.append(rToken);
    return aBuf.makeStringAndClear();
}
 
bool ScGlobal::IsQuoted( std::u16string_view rString, sal_Unicode cQuote )
{
    return (rString.size() >= 2) && (rString[0] == cQuote) && (rString[ rString.size() - 1 ] == cQuote);
}
 
void ScGlobal::AddQuotes( OUString& rString, sal_Unicode cQuote, bool bEscapeEmbedded )
{
    if (bEscapeEmbedded)
    {
        sal_Unicode pQ[3];
        pQ[0] = pQ[1] = cQuote;
        pQ[2] = 0;
        OUString aQuotes( pQ );
        rString = rString.replaceAll( OUStringChar(cQuote), aQuotes);
    }
    rString = OUStringChar( cQuote ) + rString + OUStringChar( cQuote );
}
 
void ScGlobal::EraseQuotes( OUString& rString, sal_Unicode cQuote, bool bUnescapeEmbedded )
{
    if ( IsQuoted( rString, cQuote ) )
    {
        rString = rString.copy( 1, rString.getLength() - 2 );
        if (bUnescapeEmbedded)
        {
            sal_Unicode pQ[3];
            pQ[0] = pQ[1] = cQuote;
            pQ[2] = 0;
            OUString aQuotes( pQ );
            rString = rString.replaceAll( aQuotes, OUStringChar(cQuote));
        }
    }
}
 
sal_Int32 ScGlobal::FindUnquoted( const OUString& rString, sal_Unicode cChar, sal_Int32 nStart )
{
    assert(nStart >= 0);
    const sal_Unicode cQuote = '\'';
    const sal_Unicode* const pStart = rString.getStr();
    const sal_Unicode* const pStop = pStart + rString.getLength();
    const sal_Unicode* p = pStart + nStart;
    bool bQuoted = false;
    while (p < pStop)
    {
        if (*p == cChar && !bQuoted)
            return sal::static_int_cast< sal_Int32 >( p - pStart );
        else if (*p == cQuote)
        {
            if (!bQuoted)
                bQuoted = true;
            else if (p < pStop-1 && *(p+1) == cQuote)
                ++p;
            else
                bQuoted = false;
        }
        ++p;
    }
    return -1;
}
 
const sal_Unicode* ScGlobal::FindUnquoted( const sal_Unicode* pString, sal_Unicode cChar )
{
    sal_Unicode cQuote = '\'';
    const sal_Unicode* p = pString;
    bool bQuoted = false;
    while (*p)
    {
        if (*p == cChar && !bQuoted)
            return p;
        else if (*p == cQuote)
        {
            if (!bQuoted)
                bQuoted = true;
            else if (*(p+1) == cQuote)
                ++p;
            else
                bQuoted = false;
        }
        ++p;
    }
    return nullptr;
}
 
bool ScGlobal::EETextObjEqual( const EditTextObject* pObj1,
                               const EditTextObject* pObj2 )
{
    if ( pObj1 == pObj2 ) // Both empty or the same object
        return true;
 
    if ( pObj1 && pObj2 )
        return pObj1->Equals( *pObj2);
 
    return false;
}
 
void ScGlobal::OpenURL(const OUString& rURL, const OUString& rTarget, bool bIgnoreSettings)
{
    // OpenURL is always called in the GridWindow by mouse clicks in some way or another.
    // That's why pScActiveViewShell and nScClickMouseModifier are correct.
 
    // Fragments pointing into the current document should be always opened.
    if (!bIgnoreSettings && !(ShouldOpenURL() || rURL.startsWith("#")))
        return;
 
    SfxViewFrame* pViewFrm = SfxViewFrame::Current();
    if (!pViewFrm)
        return;
 
    OUString aUrlName( rURL );
    SfxViewFrame* pFrame = nullptr;
    SfxObjectShell* pObjShell = nullptr;
    OUString aReferName;
    if ( pScActiveViewShell )
    {
        pFrame = &pScActiveViewShell->GetViewFrame();
        pObjShell = pFrame->GetObjectShell();
        const SfxMedium* pMed = pObjShell->GetMedium();
        if (pMed)
            aReferName = pMed->GetName();
    }
 
    // Don't fiddle with fragments pointing into current document.
    // Also don't mess around with a vnd.sun.star.script or service or other
    // internal "URI".
    if (!aUrlName.startsWith("#")
            && !aUrlName.startsWithIgnoreAsciiCase("vnd.sun.star.script:")
            && !aUrlName.startsWithIgnoreAsciiCase("macro:")
            && !aUrlName.startsWithIgnoreAsciiCase("slot:")
            && !aUrlName.startsWithIgnoreAsciiCase("service:")
            && !aUrlName.startsWithIgnoreAsciiCase(".uno:"))
    {
        // Any relative reference would fail with "not an absolute URL"
        // error, try to construct an absolute URI with the path relative
        // to the current document's path or work path, as usual for all
        // external references.
        // This then also, as ScGlobal::GetAbsDocName() uses
        // INetURLObject::smartRel2Abs(), supports "\\" UNC path names as
        // smb:// Samba shares and DOS path separators converted to proper
        // file:// URI.
        const OUString aNewUrlName( ScGlobal::GetAbsDocName( aUrlName, pObjShell));
        if (!aNewUrlName.isEmpty())
            aUrlName = aNewUrlName;
    }
 
    if (!SfxObjectShell::AllowedLinkProtocolFromDocument(aUrlName, pObjShell, pFrame ? pFrame->GetFrameWeld() : nullptr))
        return;
 
    SfxStringItem aUrl( SID_FILE_NAME, aUrlName );
    SfxStringItem aTarget( SID_TARGETNAME, rTarget );
    if ( nScClickMouseModifier & KEY_SHIFT )     // control-click -> into new window
        aTarget.SetValue(u"_blank"_ustr);
 
    SfxFrameItem aFrm( SID_DOCFRAME, pFrame );
    SfxStringItem aReferer( SID_REFERER, aReferName );
 
    SfxBoolItem aNewView( SID_OPEN_NEW_VIEW, false );
    SfxBoolItem aBrowsing( SID_BROWSE, true );
 
    // No SID_SILENT anymore
    pViewFrm->GetDispatcher()->ExecuteList(SID_OPENDOC,
            SfxCallMode::ASYNCHRON | SfxCallMode::RECORD,
            { &aUrl, &aTarget, &aFrm, &aReferer, &aNewView, &aBrowsing });
}
 
bool ScGlobal::ShouldOpenURL()
{
    bool bCtrlClickHappened = (nScClickMouseModifier & KEY_MOD1);
    bool bCtrlClickSecOption = SvtSecurityOptions::IsOptionSet( SvtSecurityOptions::EOption::CtrlClickHyperlink );
    if( bCtrlClickHappened && ! bCtrlClickSecOption )
    {
        // return since ctrl+click happened when the
        // ctrl+click security option was disabled, link should not open
        return false;
    }
    else if( ! bCtrlClickHappened && bCtrlClickSecOption )
    {
        // ctrl+click did not happen; only click happened maybe with some
        // other key combo. and security option is set, so return
        return false;
    }
    return true;
}
 
bool ScGlobal::IsSystemRTL()
{
    return MsLangId::isRightToLeft( Application::GetSettings().GetLanguageTag().getLanguageType() );
}
 
SvtScriptType ScGlobal::GetDefaultScriptType()
{
    // Used when text contains only WEAK characters.
    // Script type of office language is used then (same as GetEditDefaultLanguage,
    // to get consistent behavior of text in simple cells and EditEngine,
    // also same as GetAppLanguage() in Writer)
    return SvtLanguageOptions::GetScriptTypeOfLanguage( Application::GetSettings().GetLanguageTag().getLanguageType() );
}
 
LanguageType ScGlobal::GetEditDefaultLanguage()
{
    // Used for EditEngine::SetDefaultLanguage
    return Application::GetSettings().GetLanguageTag().getLanguageType();
}
 
sal_uInt16 ScGlobal::GetScriptedWhichID( SvtScriptType nScriptType, sal_uInt16 nWhich )
{
    switch ( nScriptType )
    {
        case SvtScriptType::LATIN:
        case SvtScriptType::ASIAN:
        case SvtScriptType::COMPLEX:
        break;      // take exact matches
        default:    // prefer one, first COMPLEX, then ASIAN
            if ( nScriptType & SvtScriptType::COMPLEX )
                nScriptType = SvtScriptType::COMPLEX;
            else if ( nScriptType & SvtScriptType::ASIAN )
                nScriptType = SvtScriptType::ASIAN;
    }
    switch ( nScriptType )
    {
        case SvtScriptType::COMPLEX:
        {
            switch ( nWhich )
            {
                case ATTR_FONT:
                case ATTR_CJK_FONT:
                    nWhich = ATTR_CTL_FONT;
                break;
                case ATTR_FONT_HEIGHT:
                case ATTR_CJK_FONT_HEIGHT:
                    nWhich = ATTR_CTL_FONT_HEIGHT;
                break;
                case ATTR_FONT_WEIGHT:
                case ATTR_CJK_FONT_WEIGHT:
                    nWhich = ATTR_CTL_FONT_WEIGHT;
                break;
                case ATTR_FONT_POSTURE:
                case ATTR_CJK_FONT_POSTURE:
                    nWhich = ATTR_CTL_FONT_POSTURE;
                break;
            }
        }
        break;
        case SvtScriptType::ASIAN:
        {
            switch ( nWhich )
            {
                case ATTR_FONT:
                case ATTR_CTL_FONT:
                    nWhich = ATTR_CJK_FONT;
                break;
                case ATTR_FONT_HEIGHT:
                case ATTR_CTL_FONT_HEIGHT:
                    nWhich = ATTR_CJK_FONT_HEIGHT;
                break;
                case ATTR_FONT_WEIGHT:
                case ATTR_CTL_FONT_WEIGHT:
                    nWhich = ATTR_CJK_FONT_WEIGHT;
                break;
                case ATTR_FONT_POSTURE:
                case ATTR_CTL_FONT_POSTURE:
                    nWhich = ATTR_CJK_FONT_POSTURE;
                break;
            }
        }
        break;
        default:
        {
            switch ( nWhich )
            {
                case ATTR_CTL_FONT:
                case ATTR_CJK_FONT:
                    nWhich = ATTR_FONT;
                break;
                case ATTR_CTL_FONT_HEIGHT:
                case ATTR_CJK_FONT_HEIGHT:
                    nWhich = ATTR_FONT_HEIGHT;
                break;
                case ATTR_CTL_FONT_WEIGHT:
                case ATTR_CJK_FONT_WEIGHT:
                    nWhich = ATTR_FONT_WEIGHT;
                break;
                case ATTR_CTL_FONT_POSTURE:
                case ATTR_CJK_FONT_POSTURE:
                    nWhich = ATTR_FONT_POSTURE;
                break;
            }
        }
    }
    return nWhich;
}
 
void ScGlobal::AddLanguage( SfxItemSet& rSet, const SvNumberFormatter& rFormatter )
{
    OSL_ENSURE( rSet.GetItemState( ATTR_LANGUAGE_FORMAT, false ) == SfxItemState::DEFAULT,
        "ScGlobal::AddLanguage - language already added");
 
    const SfxUInt32Item* pHardItem = rSet.GetItemIfSet( ATTR_VALUE_FORMAT, false );
    if ( !pHardItem )
        return;
 
    const SvNumberformat* pHardFormat = rFormatter.GetEntry(
        pHardItem->GetValue() );
 
    sal_uInt32 nParentFmt = 0; // Pool default
    const SfxItemSet* pParent = rSet.GetParent();
    if ( pParent )
        nParentFmt = pParent->Get( ATTR_VALUE_FORMAT ).GetValue();
    const SvNumberformat* pParFormat = rFormatter.GetEntry( nParentFmt );
 
    if ( pHardFormat && pParFormat &&
            (pHardFormat->GetLanguage() != pParFormat->GetLanguage()) )
        rSet.Put( SvxLanguageItem( pHardFormat->GetLanguage(), ATTR_LANGUAGE_FORMAT ) );
}
 
utl::TransliterationWrapper& ScGlobal::GetTransliteration()
{
    return *comphelper::doubleCheckedInit( pTransliteration,
        []()
        {
            const LanguageType eOfficeLanguage = Application::GetSettings().GetLanguageTag().getLanguageType();
            ::utl::TransliterationWrapper* p = new ::utl::TransliterationWrapper(
                ::comphelper::getProcessComponentContext(), TransliterationFlags::IGNORE_CASE );
            p->loadModuleIfNeeded( eOfficeLanguage );
            return p;
        });
}
::utl::TransliterationWrapper& ScGlobal::GetCaseTransliteration()
{
    return *comphelper::doubleCheckedInit( pCaseTransliteration,
        []()
        {
            const LanguageType eOfficeLanguage = Application::GetSettings().GetLanguageTag().getLanguageType();
            ::utl::TransliterationWrapper* p = new ::utl::TransliterationWrapper(
                ::comphelper::getProcessComponentContext(), TransliterationFlags::NONE );
            p->loadModuleIfNeeded( eOfficeLanguage );
            return p;
        });
}
utl::TransliterationWrapper& ScGlobal::GetTransliteration(bool bCaseSensitive)
{
    return bCaseSensitive ? GetCaseTransliteration() : GetTransliteration();
}
 
const LocaleDataWrapper& ScGlobal::getLocaleData()
{
    OSL_ENSURE(
        oSysLocale,
        "ScGlobal::getLocaleDataPtr() called before ScGlobal::Init()");
 
    return oSysLocale->GetLocaleData();
}
 
const CharClass& ScGlobal::getCharClass()
{
    OSL_ENSURE(
        oSysLocale,
        "ScGlobal::getCharClassPtr() called before ScGlobal::Init()");
 
    return oSysLocale->GetCharClass();
}
 
CalendarWrapper& ScGlobal::GetCalendar()
{
    assert(!bThreadedGroupCalcInProgress);
    if ( !oCalendar )
    {
        oCalendar.emplace( ::comphelper::getProcessComponentContext() );
        oCalendar->loadDefaultCalendar( GetLocale() );
    }
    return *oCalendar;
}
 
namespace {
 
struct GetMutex {
    osl::Mutex * operator ()() {
        static osl::Mutex m;
        return &m;
    }
};
 
}
 
CollatorWrapper& ScGlobal::GetCollator()
{
    return *comphelper::doubleCheckedInit( pCollator,
        []()
        {
            CollatorWrapper* p = new CollatorWrapper( ::comphelper::getProcessComponentContext() );
            p->loadDefaultCollator( GetLocale(), SC_COLLATOR_IGNORES );
            return p;
        },
        GetMutex());
}
CollatorWrapper& ScGlobal::GetCaseCollator()
{
    return *comphelper::doubleCheckedInit( pCaseCollator,
        []()
        {
            CollatorWrapper* p = new CollatorWrapper( ::comphelper::getProcessComponentContext() );
            p->loadDefaultCollator( GetLocale(), 0 );
            return p;
        },
        GetMutex());
}
CollatorWrapper& ScGlobal::GetCollator(bool bCaseSensitive)
{
    return bCaseSensitive ? GetCaseCollator() : GetCollator();
}
css::lang::Locale& ScGlobal::GetLocale()
{
    return *comphelper::doubleCheckedInit( pLocale,
        []() { return new css::lang::Locale( Application::GetSettings().GetLanguageTag().getLocale()); });
}
 
ScFieldEditEngine& ScGlobal::GetStaticFieldEditEngine()
{
    assert(!bThreadedGroupCalcInProgress);
    if (!xFieldEditEngine)
    {
        // Creating a ScFieldEditEngine with pDocument=NULL leads to document
        // specific fields not being resolvable! See
        // ScFieldEditEngine::CalcFieldValue(). pEnginePool=NULL lets
        // EditEngine internally create and delete a default pool.
        xFieldEditEngine.reset(new ScFieldEditEngine( nullptr, nullptr));
    }
    return *xFieldEditEngine;
}
 
sc::SharedStringPoolPurge& ScGlobal::GetSharedStringPoolPurge()
{
    return *comphelper::doubleCheckedInit( pSharedStringPoolPurge,
        []() { return new sc::SharedStringPoolPurge; });
}
 
OUString ScGlobal::ReplaceOrAppend( const OUString& rString,
        std::u16string_view rPlaceholder, const OUString& rReplacement )
{
    if (rString.isEmpty())
        return rReplacement;
    sal_Int32 nFound = rString.indexOf( rPlaceholder);
    if (nFound < 0)
    {
        if (rString[rString.getLength()-1] == ' ')
            return rString + rReplacement;
        return rString + " " + rReplacement;
    }
    return rString.replaceFirst( rPlaceholder, rReplacement, &nFound);
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'padToLength' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'ExecuteList' is required to be utilized.