/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the Collabora Office 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/kit.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 );
}

SvNumberFormatter* ScGlobal::GetEnglishFormatter()
{
    assert(!bThreadedGroupCalcInProgress);
    if ( !xEnglishFormatter )
    {
        xEnglishFormatter.reset( new SvNumberFormatter(
            ::comphelper::getProcessComponentContext(), LANGUAGE_ENGLISH_US ) );
        xEnglishFormatter->SetEvalDateFormat( NfEvalDateFormat::InternationalThenFormat );
    }
    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(); });
}

bool ScGlobal::IsValidExternal(const OUString& rAddIn)
{
    return (ScGlobal::GetLegacyFuncCollection()->findByName(rAddIn)
            || !ScGlobal::GetAddInCollection()->FindFunction(rAddIn, false).isEmpty());
}

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);
        case FormulaError::Spill:
            return ScCompiler::GetNativeSymbol(ocErrSpill);
        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::Spill:
            pErrNumber = STR_LONG_ERR_SPILL;
        break;
        case FormulaError::NoValue:
            pErrNumber = STR_LONG_ERR_NO_VALUE;
        break;
        case FormulaError::NotAFunction:
            pErrNumber = STR_LONG_ERR_NOT_FUNC;
        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);
}

bool ScGlobal::IsValidOOXMLError(FormulaError nErrNumber)
{
    switch (nErrNumber)
    {
        case FormulaError::DivisionByZero:
        case FormulaError::NotAvailable:
        case FormulaError::NoName:
        case FormulaError::NoCode:
        case FormulaError::IllegalFPOperation:
        case FormulaError::NoRef:
        case FormulaError::NoValue:
        case FormulaError::Spill:
            return true;
        default:
            return false;
    }
}

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::COKit::isActive())
    {
        // COKit: 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();

    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(ScModule::get()->GetResLocale());
    auto list = xStarCalcFunctionList.find(lang);
    return list != xStarCalcFunctionList.end();
}

ScFunctionList* ScGlobal::GetStarCalcFunctionList()
{
    assert(!bThreadedGroupCalcInProgress);
    ScModule* mod = ScModule::get();
    OUString lang = Translate::getLanguage(mod->GetResLocale());
    if (auto list = xStarCalcFunctionList.find(lang); list != xStarCalcFunctionList.end())
    {
        return xStarCalcFunctionList[lang].get();
    }
    xStarCalcFunctionList.emplace(
        lang, new ScFunctionList(mod->GetFormulaOptions().GetUseEnglishFuncName()));
    return xStarCalcFunctionList[lang].get();
}

ScFunctionMgr* ScGlobal::GetStarCalcFunctionMgr()
{
    assert(!bThreadedGroupCalcInProgress);
    OUString lang = Translate::getLanguage(ScModule::get()->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(ScModule::get()->GetResLocale()),
                                        InputHandlerFunctionNames());
}

const InputHandlerFunctionNames& ScGlobal::GetInputHandlerFunctionNames()
{
    OUString lang = Translate::getLanguage(ScModule::get()->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 (comphelper::COKit::isActive())
    {
        if (SfxViewShell* pCurr = SfxViewShell::Current())
        {
            return pCurr->GetKitCalendar();
        }
    }
    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()
{
    if (comphelper::COKit::isActive())
    {
        return const_cast<css::lang::Locale&>(comphelper::COKit::getLocale().getLocale());
    }
    return *comphelper::doubleCheckedInit( pLocale,
        []() { return new css::lang::Locale( Application::GetSettings().GetLanguageTag().getLocale()); });
}

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