/* -*- 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 <comphelper/processfactory.hxx>
#include <i18nlangtag/languagetag.hxx>
#include <utility>
#include <vcl/svapp.hxx>
#include <vcl/settings.hxx>
#include <sfx2/objsh.hxx>
#include <unotools/charclass.hxx>
#include <sal/log.hxx>
#include <o3tl/string_view.hxx>
#include <osl/diagnose.h>

#include <com/sun/star/container/XContentEnumerationAccess.hpp>
#include <com/sun/star/frame/XModel.hpp>
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/lang/XServiceName.hpp>
#include <com/sun/star/lang/XSingleServiceFactory.hpp>
#include <com/sun/star/lang/XSingleComponentFactory.hpp>
#include <com/sun/star/reflection/XIdlClass.hpp>
#include <com/sun/star/beans/XIntrospectionAccess.hpp>
#include <com/sun/star/beans/theIntrospection.hpp>
#include <com/sun/star/beans/MethodConcept.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/beans/PropertyValue.hpp>
#include <com/sun/star/table/XCellRange.hpp>
#include <com/sun/star/lang/Locale.hpp>
#include <com/sun/star/sheet/XCompatibilityNames.hpp>
#include <com/sun/star/sheet/NoConvergenceException.hpp>
#include <com/sun/star/sheet/XAddIn.hpp>
#include <com/sun/star/sheet/XVolatileResult.hpp>

#include <addincol.hxx>
#include <addinhelpid.hxx>
#include <scmatrix.hxx>
#include <formula/errorcodes.hxx>
#include <formula/funcvarargs.h>
#include <optutil.hxx>
#include <addincfg.hxx>
#include <scmod.hxx>
#include <rangeseq.hxx>
#include <funcdesc.hxx>
#include <svl/sharedstring.hxx>
#include <formulaopt.hxx>
#include <compiler.hxx>
#include <document.hxx>
#include <memory>

using namespace com::sun::star;

#define SC_CALLERPOS_NONE   (-1)

ScUnoAddInFuncData::ScUnoAddInFuncData( const OUString& rNam, const OUString& rLoc,
                                        OUString aDesc,
                                        sal_uInt16 nCat, OUString sHelp,
                                        uno::Reference<reflection::XIdlMethod> xFunc,
                                        uno::Any aO,
                                        sal_Int32 nAC, const ScAddInArgDesc* pAD,
                                        sal_Int32 nCP ) :
    aOriginalName( rNam ),
    aLocalName( rLoc ),
    aUpperName( rNam ),
    aUpperLocal( rLoc ),
    aDescription(std::move( aDesc )),
    xFunction(std::move( xFunc )),
    aObject(std::move( aO )),
    nArgCount( nAC ),
    nCallerPos( nCP ),
    nCategory( nCat ),
    sHelpId(std::move( sHelp )),
    bCompInitialized( false )
{
    if ( nArgCount )
    {
        pArgDescs.reset( new ScAddInArgDesc[nArgCount] );
        for (sal_Int32 i=0; i<nArgCount; i++)
            pArgDescs[i] = pAD[i];
    }

    aUpperName = aUpperName.toAsciiUpperCase();  // programmatic name
    aUpperLocal = ScGlobal::getCharClass().uppercase(aUpperLocal);
}

ScUnoAddInFuncData::~ScUnoAddInFuncData()
{
}

const ::std::vector<ScUnoAddInFuncData::LocalizedName>& ScUnoAddInFuncData::GetCompNames() const
{
    if ( !bCompInitialized )
    {
        //  read sequence of compatibility names on demand

        uno::Reference<sheet::XAddIn> xAddIn;
        if ( aObject >>= xAddIn )
        {
            uno::Reference<sheet::XCompatibilityNames> xComp( xAddIn, uno::UNO_QUERY );
            if ( xComp.is() && xFunction.is() )
            {
                OUString aMethodName = xFunction->getName();
                const uno::Sequence< sheet::LocalizedName> aCompNames( xComp->getCompatibilityNames( aMethodName ));
                maCompNames.clear();
                for (const sheet::LocalizedName& rCompName : aCompNames)
                {
                    maCompNames.emplace_back(
                                LanguageTag::convertToBcp47( rCompName.Locale, false),
                                rCompName.Name);
                }
            }
        }

        bCompInitialized = true;        // also if not successful
    }
    return maCompNames;
}

void ScUnoAddInFuncData::SetCompNames( ::std::vector< ScUnoAddInFuncData::LocalizedName >&& rNew )
{
    OSL_ENSURE( !bCompInitialized, "SetCompNames after initializing" );

    maCompNames = std::move(rNew);

    bCompInitialized = true;
}

void ScUnoAddInFuncData::SetEnglishName( const OUString& rEnglishName )
{
    if (!rEnglishName.isEmpty())
        aUpperEnglish = ScCompiler::GetCharClassEnglish()->uppercase(rEnglishName);
    else
    {
        // A dumb fallback to not have an empty name, mainly just for the
        // assignment to ScFuncDesc::mxFuncName for the Function Wizard and
        // formula input tooltips.
        aUpperEnglish = aUpperLocal;
    }
}

bool ScUnoAddInFuncData::GetExcelName( const LanguageTag& rDestLang, OUString& rRetExcelName, bool bFallbackToAny ) const
{
    const ::std::vector<LocalizedName>& rCompNames = GetCompNames();
    if ( !rCompNames.empty() )
    {
        const OUString& aSearch( rDestLang.getBcp47());

        // First, check exact match without fallback overhead.
        ::std::vector<LocalizedName>::const_iterator itNames = std::find_if(rCompNames.begin(), rCompNames.end(),
            [&aSearch](const LocalizedName& rName) { return rName.maLocale == aSearch; });
        if (itNames != rCompNames.end())
        {
            rRetExcelName = (*itNames).maName;
            return true;
        }

        // For "en-US" try the most likely fallback of "en".
        if (aSearch == "en-US")
        {
            itNames = std::find_if(rCompNames.begin(), rCompNames.end(),
                    [](const LocalizedName& rName) { return rName.maLocale == "en"; });
            if (itNames != rCompNames.end())
            {
                rRetExcelName = (*itNames).maName;
                return true;
            }
        }

        // Try match of fallback search with fallback locales,
        // appending also 'en-US' and 'en' to search if not queried.
        ::std::vector< OUString > aFallbackSearch( rDestLang.getFallbackStrings( true));
        if (aSearch != "en-US")
        {
            aFallbackSearch.emplace_back("en-US");
            if (aSearch != "en")
            {
                aFallbackSearch.emplace_back("en");
            }
        }
        for (const auto& rSearch : aFallbackSearch)
        {
            for (const auto& rCompName : rCompNames)
            {
                ::std::vector< OUString > aFallbackLocales( LanguageTag( rCompName.maLocale).getFallbackStrings(true));
                if (std::find(aFallbackLocales.begin(), aFallbackLocales.end(), rSearch) != aFallbackLocales.end())
                {
                    rRetExcelName = rCompName.maName;
                    return true;
                }
            }
        }

        if (bFallbackToAny)
        {
            // Last resort, use first (default) entry.
            rRetExcelName = rCompNames[0].maName;
            return true;
        }
    }
    return false;
}

void ScUnoAddInFuncData::SetFunction( const uno::Reference< reflection::XIdlMethod>& rNewFunc, const uno::Any& rNewObj )
{
    xFunction = rNewFunc;
    aObject = rNewObj;
}

void ScUnoAddInFuncData::SetArguments( sal_Int32 nNewCount, const ScAddInArgDesc* pNewDescs )
{
    nArgCount = nNewCount;
    if ( nArgCount )
    {
        pArgDescs.reset( new ScAddInArgDesc[nArgCount] );
        for (sal_Int32 i=0; i<nArgCount; i++)
            pArgDescs[i] = pNewDescs[i];
    }
    else
        pArgDescs.reset();
}

void ScUnoAddInFuncData::SetCallerPos( sal_Int32 nNewPos )
{
    nCallerPos = nNewPos;
}

ScUnoAddInCollection::ScUnoAddInCollection() :
    nFuncCount( 0 ),
    bInitialized( false )
{
}

ScUnoAddInCollection::~ScUnoAddInCollection()
{
}

void ScUnoAddInCollection::Clear()
{
    pExactHashMap.reset();
    pNameHashMap.reset();
    pLocalHashMap.reset();
    pEnglishHashMap.reset();
    ppFuncData.reset();
    nFuncCount = 0;

    bInitialized = false;
}

void ScUnoAddInCollection::Initialize()
{
    OSL_ENSURE( !bInitialized, "Initialize twice?" );

    uno::Reference<lang::XMultiServiceFactory> xManager = comphelper::getProcessServiceFactory();
    uno::Reference<container::XContentEnumerationAccess> xEnAc( xManager, uno::UNO_QUERY );
    if ( xEnAc.is() )
    {
        uno::Reference<container::XEnumeration> xEnum =
                        xEnAc->createContentEnumeration( u"com.sun.star.sheet.AddIn"_ustr );
        if ( xEnum.is() )
        {
            //  loop through all AddIns
            while ( xEnum->hasMoreElements() )
            {
                uno::Any aAddInAny = xEnum->nextElement();

                try
                {
                    uno::Reference<uno::XInterface> xIntFac;
                    aAddInAny >>= xIntFac;
                    if ( xIntFac.is() )
                    {
                        // #i59984# try XSingleComponentFactory in addition to (old) XSingleServiceFactory,
                        // passing the context to the component

                        uno::Reference<uno::XInterface> xInterface;
                        uno::Reference<uno::XComponentContext> xCtx(
                            comphelper::getComponentContext(xManager));
                        uno::Reference<lang::XSingleComponentFactory> xCFac( xIntFac, uno::UNO_QUERY );
                        if (xCFac.is())
                        {
                            xInterface = xCFac->createInstanceWithContext(xCtx);
                            if (xInterface.is())
                                ReadFromAddIn( xInterface );
                        }

                        if (!xInterface.is())
                        {
                            uno::Reference<lang::XSingleServiceFactory> xFac( xIntFac, uno::UNO_QUERY );
                            if ( xFac.is() )
                            {
                                xInterface = xFac->createInstance();
                                if (xInterface.is())
                                    ReadFromAddIn( xInterface );
                            }
                        }
                    }
                } catch ( const uno::Exception& ) {
                    SAL_WARN ( "sc", "Failed to initialize create instance of sheet.AddIn" );
                }
            }
        }
    }

    // ReadConfiguration is called after looking at the AddIn implementations.
    // Duplicated are skipped (by using the service information, they don't have to be updated again
    // when argument information is needed).
    ReadConfiguration();

    bInitialized = true;        // with or without functions
}

static sal_uInt16 lcl_GetCategory( std::u16string_view rName )
{
    static const char* const aFuncNames[SC_FUNCGROUP_COUNT] =
    {
        //  array index = ID - 1 (ID starts at 1)
        //  all upper case
        "Database",         // ID_FUNCTION_GRP_DATABASE
        "Date&Time",        // ID_FUNCTION_GRP_DATETIME
        "Financial",        // ID_FUNCTION_GRP_FINANCIAL
        "Information",      // ID_FUNCTION_GRP_INFO
        "Logical",          // ID_FUNCTION_GRP_LOGIC
        "Mathematical",     // ID_FUNCTION_GRP_MATH
        "Matrix",           // ID_FUNCTION_GRP_MATRIX
        "Statistical",      // ID_FUNCTION_GRP_STATISTIC
        "Spreadsheet",      // ID_FUNCTION_GRP_TABLE
        "Text",             // ID_FUNCTION_GRP_TEXT
        "Add-In"            // ID_FUNCTION_GRP_ADDINS
    };
    for (sal_uInt16 i=0; i<SC_FUNCGROUP_COUNT; i++)
        if ( o3tl::equalsAscii( rName, aFuncNames[i] ) )
            return i+1;                             // IDs start at 1

    return ID_FUNCTION_GRP_ADDINS;  // if not found, use Add-In group
}

constexpr OUStringLiteral CFGPATH_ADDINS = u"Office.CalcAddIns/AddInInfo";
constexpr OUStringLiteral CFGSTR_ADDINFUNCTIONS = u"AddInFunctions";

#define CFG_FUNCPROP_DISPLAYNAME    0
#define CFG_FUNCPROP_DESCRIPTION    1
#define CFG_FUNCPROP_CATEGORY       2
#define CFG_FUNCPROP_COUNT          3
constexpr OUString CFGSTR_DISPLAYNAME = u"DisplayName"_ustr;
constexpr OUString CFGSTR_DESCRIPTION = u"Description"_ustr;
constexpr OUString CFGSTR_CATEGORY = u"Category"_ustr;
// CategoryDisplayName is ignored for now

constexpr OUStringLiteral CFGSTR_COMPATIBILITYNAME = u"CompatibilityName";
constexpr OUStringLiteral CFGSTR_PARAMETERS = u"Parameters";

void ScUnoAddInCollection::ReadConfiguration()
{
    // called only from Initialize

    ScAddInCfg& rAddInConfig = ScModule::get()->GetAddInCfg();

    // Additional, temporary config item for the display names and
    // compatibility names.
    ScLinkConfigItem aAllLocalesConfig( CFGPATH_ADDINS, ConfigItemMode::AllLocales );
    // CommitLink is not used (only reading values)

    const OUString sSlash('/');

    // get the list of add-ins (services)
    const uno::Sequence<OUString> aServiceNames = rAddInConfig.GetNodeNames( u""_ustr );

    for ( const OUString& aServiceName : aServiceNames )
    {
        ScUnoAddInHelpIdGenerator aHelpIdGenerator( aServiceName );

        OUString aFunctionsPath(aServiceName + sSlash + CFGSTR_ADDINFUNCTIONS);

        uno::Sequence<OUString> aFunctionNames = rAddInConfig.GetNodeNames( aFunctionsPath );
        sal_Int32 nNewCount = aFunctionNames.getLength();

        // allocate pointers

        sal_Int32 nOld = nFuncCount;
        nFuncCount = nNewCount+nOld;
        if ( nOld )
        {
            std::unique_ptr<std::unique_ptr<ScUnoAddInFuncData>[]> ppNew(new std::unique_ptr<ScUnoAddInFuncData>[nFuncCount]);
            for (sal_Int32 i=0; i<nOld; i++)
                ppNew[i] = std::move(ppFuncData[i]);
            ppFuncData = std::move(ppNew);
        }
        else
            ppFuncData.reset( new std::unique_ptr<ScUnoAddInFuncData>[nFuncCount] );

        //TODO: adjust bucket count?
        if ( !pExactHashMap )
            pExactHashMap.reset( new ScAddInHashMap );
        if ( !pNameHashMap )
            pNameHashMap.reset( new ScAddInHashMap );
        if ( !pLocalHashMap )
            pLocalHashMap.reset( new ScAddInHashMap );
        if ( !pEnglishHashMap )
            pEnglishHashMap.reset( new ScAddInHashMap );

        //TODO: get the function information in a single call for all functions?

        const OUString* pFuncNameArray = aFunctionNames.getConstArray();
        for ( sal_Int32 nFuncPos = 0; nFuncPos < nNewCount; nFuncPos++ )
        {
            ppFuncData[nFuncPos+nOld] = nullptr;

            // stored function name: (service name).(function)
            OUString aFuncName = aServiceName + "." + pFuncNameArray[nFuncPos];

            // skip the function if already known (read from old AddIn service)

            if ( pExactHashMap->find( aFuncName ) == pExactHashMap->end() )
            {
                OUString aEnglishName;
                OUString aLocalName;
                OUString aDescription;
                sal_uInt16 nCategory = ID_FUNCTION_GRP_ADDINS;

                // get direct information on the function

                OUString aFuncPropPath = aFunctionsPath + sSlash + pFuncNameArray[nFuncPos] + sSlash;

                uno::Sequence<OUString> aFuncPropNames{
                    (aFuncPropPath + CFGSTR_DISPLAYNAME), // CFG_FUNCPROP_DISPLAYNAME
                    (aFuncPropPath + CFGSTR_DESCRIPTION), // CFG_FUNCPROP_DESCRIPTION
                    (aFuncPropPath + CFGSTR_CATEGORY)};   // CFG_FUNCPROP_CATEGORY

                uno::Sequence<uno::Any> aFuncProperties = rAddInConfig.GetProperties( aFuncPropNames );
                if ( aFuncProperties.getLength() == CFG_FUNCPROP_COUNT )
                {
                    aFuncProperties[CFG_FUNCPROP_DISPLAYNAME] >>= aLocalName;
                    aFuncProperties[CFG_FUNCPROP_DESCRIPTION] >>= aDescription;

                    OUString aCategoryName;
                    aFuncProperties[CFG_FUNCPROP_CATEGORY] >>= aCategoryName;
                    nCategory = lcl_GetCategory( aCategoryName );
                }

                // get English display name

                OUString aDisplayNamePath(aFuncPropPath + CFGSTR_DISPLAYNAME);
                uno::Sequence<OUString> aDisplayNamePropNames( &aDisplayNamePath, 1 );

                uno::Sequence<uno::Any> aDisplayNameProperties = aAllLocalesConfig.GetProperties( aDisplayNamePropNames );
                if ( aDisplayNameProperties.getLength() == 1 )
                {
                    uno::Sequence<beans::PropertyValue> aLocalEntries;
                    if ( aDisplayNameProperties[0] >>= aLocalEntries )
                    {
                        for (const beans::PropertyValue& rConfig : aLocalEntries)
                        {
                            // PropertyValue name is the locale ("convert" from
                            // string to canonicalize).
                            OUString aLocale( LanguageTag( rConfig.Name, true).getBcp47( false));
                            // PropertyValue value is the localized value (string in this case).
                            OUString aName;
                            rConfig.Value >>= aName;
                            // Accept 'en' and 'en-...' but prefer 'en-US'.
                            if (aLocale == "en-US" && !aName.isEmpty())
                                aEnglishName = aName;
                            else if (aEnglishName.isEmpty() && (aLocale == "en" || aLocale.startsWith("en-")))
                                aEnglishName = aName;
                        }
                    }
                }
                bool bNeedEnglish = aEnglishName.isEmpty();

                // get compatibility names

                ::std::vector<ScUnoAddInFuncData::LocalizedName> aCompNames;

                OUString aCompPath(aFuncPropPath + CFGSTR_COMPATIBILITYNAME);
                uno::Sequence<OUString> aCompPropNames( &aCompPath, 1 );

                uno::Sequence<uno::Any> aCompProperties = aAllLocalesConfig.GetProperties( aCompPropNames );
                if ( aCompProperties.getLength() == 1 )
                {
                    uno::Sequence<beans::PropertyValue> aLocalEntries;
                    if ( aCompProperties[0] >>= aLocalEntries )
                    {
                        for (const beans::PropertyValue& rConfig : aLocalEntries)
                        {
                            // PropertyValue name is the locale ("convert" from
                            // string to canonicalize).
                            OUString aLocale( LanguageTag( rConfig.Name, true).getBcp47( false));
                            // PropertyValue value is the localized value (string in this case).
                            OUString aName;
                            rConfig.Value >>= aName;
                            if (!aName.isEmpty())
                            {
                                aCompNames.emplace_back( aLocale, aName);
                                if (bNeedEnglish)
                                {
                                    // Accept 'en' and 'en-...' but prefer 'en-US'.
                                    if (aLocale == "en-US")
                                        aEnglishName = aName;
                                    else if (aEnglishName.isEmpty() && (aLocale == "en" || aLocale.startsWith("en-")))
                                        aEnglishName = aName;
                                }
                            }
                        }
                    }
                }

                // get argument info

                std::unique_ptr<ScAddInArgDesc[]> pVisibleArgs;
                sal_Int32 nVisibleCount = 0;

                OUString aArgumentsPath(aFuncPropPath + CFGSTR_PARAMETERS);

                const uno::Sequence<OUString> aArgumentNames = rAddInConfig.GetNodeNames( aArgumentsPath );
                sal_Int32 nArgumentCount = aArgumentNames.getLength();
                if ( nArgumentCount )
                {
                    // get DisplayName and Description for each argument
                    uno::Sequence<OUString> aArgPropNames( nArgumentCount * 2 );
                    OUString* pPropNameArray = aArgPropNames.getArray();

                    sal_Int32 nIndex = 0;
                    for ( const OUString& rArgName : aArgumentNames )
                    {
                        OUString aOneArgPath = aArgumentsPath + sSlash + rArgName + sSlash;

                        pPropNameArray[nIndex++] = aOneArgPath
                            + CFGSTR_DISPLAYNAME;
                        pPropNameArray[nIndex++] = aOneArgPath
                            + CFGSTR_DESCRIPTION;
                    }

                    uno::Sequence<uno::Any> aArgProperties = rAddInConfig.GetProperties( aArgPropNames );
                    if ( aArgProperties.getLength() == aArgPropNames.getLength() )
                    {
                        const OUString* pArgNameArray = aArgumentNames.getConstArray();
                        const uno::Any* pPropArray = aArgProperties.getConstArray();
                        OUString sDisplayName;
                        OUString sDescription;

                        ScAddInArgDesc aDesc;
                        aDesc.eType = SC_ADDINARG_NONE;     // arg type is not in configuration
                        aDesc.bOptional = false;

                        nVisibleCount = nArgumentCount;
                        pVisibleArgs.reset(new ScAddInArgDesc[nVisibleCount]);

                        nIndex = 0;
                        for ( sal_Int32 nArgument = 0; nArgument < nArgumentCount; nArgument++ )
                        {
                            pPropArray[nIndex++] >>= sDisplayName;
                            pPropArray[nIndex++] >>= sDescription;

                            aDesc.aInternalName = pArgNameArray[nArgument];
                            aDesc.aName         = sDisplayName;
                            aDesc.aDescription  = sDescription;

                            pVisibleArgs[nArgument] = aDesc;
                        }
                    }
                }

                OUString sHelpId = aHelpIdGenerator.GetHelpId( pFuncNameArray[nFuncPos] );

                uno::Reference<reflection::XIdlMethod> xFunc;       // remains empty
                uno::Any aObject;                                   // also empty

                // create and insert into the array

                ScUnoAddInFuncData* pData = new ScUnoAddInFuncData(
                    aFuncName, aLocalName, aDescription,
                    nCategory, sHelpId,
                    xFunc, aObject,
                    nVisibleCount, pVisibleArgs.get(), SC_CALLERPOS_NONE );

                pData->SetCompNames( std::move(aCompNames) );

                ppFuncData[nFuncPos+nOld].reset(pData);

                pExactHashMap->emplace(
                            pData->GetOriginalName(),
                            pData );
                pNameHashMap->emplace(
                            pData->GetUpperName(),
                            pData );
                pLocalHashMap->emplace(
                            pData->GetUpperLocal(),
                            pData );

                if (aEnglishName.isEmpty())
                    SAL_WARN("sc.core", "no English name for " << aLocalName << " " << aFuncName);
                else
                {
                    pEnglishHashMap->emplace(
                            ScCompiler::GetCharClassEnglish()->uppercase(aEnglishName),
                            pData );
                }
                pData->SetEnglishName(aEnglishName);    // takes care of handling empty
            }
        }
    }
}

void ScUnoAddInCollection::LoadComponent( const ScUnoAddInFuncData& rFuncData )
{
    const OUString& aFullName = rFuncData.GetOriginalName();
    sal_Int32 nPos = aFullName.lastIndexOf( '.' );
    if ( nPos <= 0 )
        return;

    OUString aServiceName = aFullName.copy( 0, nPos );

    try
    {
        uno::Reference<lang::XMultiServiceFactory> xServiceFactory = comphelper::getProcessServiceFactory();
        uno::Reference<uno::XInterface> xInterface( xServiceFactory->createInstance( aServiceName ) );

        if (xInterface.is())
            UpdateFromAddIn( xInterface, aServiceName );
    }
    catch (const uno::Exception &)
    {
        SAL_WARN ("sc", "Failed to create addin component '"
                  << aServiceName << "'");
    }
}

bool ScUnoAddInCollection::GetExcelName( const OUString& rCalcName,
                                        LanguageType eDestLang, OUString& rRetExcelName )
{
    const ScUnoAddInFuncData* pFuncData = GetFuncData( rCalcName );
    if ( pFuncData )
        return pFuncData->GetExcelName( LanguageTag( eDestLang), rRetExcelName);
    return false;
}

bool ScUnoAddInCollection::GetCalcName( const OUString& rExcelName, OUString& rRetCalcName )
{
    if (!bInitialized)
        Initialize();

    OUString aUpperCmp = ScGlobal::getCharClass().uppercase(rExcelName);

    for (sal_Int32 i=0; i<nFuncCount; i++)
    {
        ScUnoAddInFuncData* pFuncData = ppFuncData[i].get();
        if ( pFuncData )
        {
            const ::std::vector<ScUnoAddInFuncData::LocalizedName>& rNames = pFuncData->GetCompNames();
            auto bFound = std::any_of(rNames.begin(), rNames.end(),
                [&aUpperCmp](const ScUnoAddInFuncData::LocalizedName& rName) {
                    return ScGlobal::getCharClass().uppercase( rName.maName ) == aUpperCmp; });
            if (bFound)
            {
                //TODO: store upper case for comparing?

                //  use the first function that has this name for any language
                rRetCalcName = pFuncData->GetOriginalName();
                return true;
            }
        }
    }
    return false;
}

static bool IsTypeName( std::u16string_view rName, const uno::Type& rType )
{
    return rName == rType.getTypeName();
}

static bool lcl_ValidReturnType( const uno::Reference<reflection::XIdlClass>& xClass )
{
    //  this must match with ScUnoAddInCall::SetResult

    if ( !xClass.is() ) return false;

    switch (xClass->getTypeClass())
    {
        case uno::TypeClass_ANY:                // variable type
        case uno::TypeClass_ENUM:               //TODO: ???
        case uno::TypeClass_BOOLEAN:
        case uno::TypeClass_CHAR:
        case uno::TypeClass_BYTE:
        case uno::TypeClass_SHORT:
        case uno::TypeClass_UNSIGNED_SHORT:
        case uno::TypeClass_LONG:
        case uno::TypeClass_UNSIGNED_LONG:
        case uno::TypeClass_FLOAT:
        case uno::TypeClass_DOUBLE:
        case uno::TypeClass_STRING:
            return true;                        // values or string

        case uno::TypeClass_INTERFACE:
            {
                //  return type XInterface may contain a XVolatileResult
                //TODO: XIdlClass needs getType() method!

                OUString sName = xClass->getName();
                return (
                    IsTypeName( sName, cppu::UnoType<sheet::XVolatileResult>::get()) ||
                    IsTypeName( sName, cppu::UnoType<uno::XInterface>::get()) );
            }

        default:
            {
                //  nested sequences for arrays
                //TODO: XIdlClass needs getType() method!

                OUString sName = xClass->getName();
                return (
                    IsTypeName( sName, cppu::UnoType<uno::Sequence< uno::Sequence<sal_Int32> >>::get() ) ||
                    IsTypeName( sName, cppu::UnoType<uno::Sequence< uno::Sequence<double> >>::get() ) ||
                    IsTypeName( sName, cppu::UnoType<uno::Sequence< uno::Sequence<OUString> >>::get() ) ||
                    IsTypeName( sName, cppu::UnoType<uno::Sequence< uno::Sequence<uno::Any> >>::get() ) );
            }
    }
}

static ScAddInArgumentType lcl_GetArgType( const uno::Reference<reflection::XIdlClass>& xClass )
{
    if (!xClass.is())
        return SC_ADDINARG_NONE;

    uno::TypeClass eType = xClass->getTypeClass();

    if ( eType == uno::TypeClass_LONG )             //TODO: other integer types?
        return SC_ADDINARG_INTEGER;

    if ( eType == uno::TypeClass_DOUBLE )
        return SC_ADDINARG_DOUBLE;

    if ( eType == uno::TypeClass_STRING )
        return SC_ADDINARG_STRING;

    //TODO: XIdlClass needs getType() method!
    OUString sName = xClass->getName();

    if (IsTypeName( sName, cppu::UnoType<uno::Sequence< uno::Sequence<sal_Int32> >>::get() ))
        return SC_ADDINARG_INTEGER_ARRAY;

    if (IsTypeName( sName, cppu::UnoType<uno::Sequence< uno::Sequence<double> >>::get() ))
        return SC_ADDINARG_DOUBLE_ARRAY;

    if (IsTypeName( sName, cppu::UnoType<uno::Sequence< uno::Sequence<OUString> >>::get() ))
        return SC_ADDINARG_STRING_ARRAY;

    if (IsTypeName( sName, cppu::UnoType<uno::Sequence< uno::Sequence<uno::Any> >>::get() ))
        return SC_ADDINARG_MIXED_ARRAY;

    if (IsTypeName( sName, cppu::UnoType<uno::Any>::get()))
        return SC_ADDINARG_VALUE_OR_ARRAY;

    if (IsTypeName( sName, cppu::UnoType<table::XCellRange>::get()))
        return SC_ADDINARG_CELLRANGE;

    if (IsTypeName( sName, cppu::UnoType<beans::XPropertySet>::get()))
        return SC_ADDINARG_CALLER;

    if (IsTypeName( sName, cppu::UnoType<uno::Sequence<uno::Any>>::get() ))
        return SC_ADDINARG_VARARGS;

    return SC_ADDINARG_NONE;
}

void ScUnoAddInCollection::ReadFromAddIn( const uno::Reference<uno::XInterface>& xInterface )
{
    uno::Reference<sheet::XAddIn> xAddIn( xInterface, uno::UNO_QUERY );
    uno::Reference<lang::XServiceName> xName( xInterface, uno::UNO_QUERY );
    if ( !(xAddIn.is() && xName.is()) )
        return;

    // Even if GetUseEnglishFunctionName() would return true, do not set the
    // locale to en-US to get English function names as that also would mix in
    // English descriptions and parameter names. Also, setting a locale will
    // reinitialize the Add-In completely, so switching back and forth isn't a
    // good idea either.
    xAddIn->setLocale( Application::GetSettings().GetUILanguageTag().getLocale());

    // Instead, in a second run with 'en-US' obtain English names.
    struct FuncNameData
    {
        OUString            aFuncU;
        ScUnoAddInFuncData* pData;
    };
    std::vector<FuncNameData> aFuncNameData;

    OUString aServiceName( xName->getServiceName() );
    ScUnoAddInHelpIdGenerator aHelpIdGenerator( aServiceName );

    //TODO: pass XIntrospection to ReadFromAddIn

    const uno::Reference<uno::XComponentContext>& xContext = comphelper::getProcessComponentContext();

    uno::Reference<beans::XIntrospection> xIntro = beans::theIntrospection::get( xContext );
    uno::Any aObject;
    aObject <<= xAddIn;
    uno::Reference<beans::XIntrospectionAccess> xAcc = xIntro->inspect(aObject);
    if (!xAcc.is())
        return;

    uno::Sequence< uno::Reference<reflection::XIdlMethod> > aMethods =
            xAcc->getMethods( beans::MethodConcept::ALL );
    sal_Int32 nNewCount = aMethods.getLength();
    if ( !nNewCount )
        return;

    sal_Int32 nOld = nFuncCount;
    nFuncCount = nNewCount+nOld;
    if ( nOld )
    {
        std::unique_ptr<std::unique_ptr<ScUnoAddInFuncData>[]> ppNew(new std::unique_ptr<ScUnoAddInFuncData>[nFuncCount]);
        for (sal_Int32 i=0; i<nOld; i++)
            ppNew[i] = std::move(ppFuncData[i]);
        ppFuncData = std::move(ppNew);
    }
    else
        ppFuncData.reset(new std::unique_ptr<ScUnoAddInFuncData>[nFuncCount]);

    //TODO: adjust bucket count?
    if ( !pExactHashMap )
        pExactHashMap.reset( new ScAddInHashMap );
    if ( !pNameHashMap )
        pNameHashMap.reset( new ScAddInHashMap );
    if ( !pLocalHashMap )
        pLocalHashMap.reset( new ScAddInHashMap );
    if ( !pEnglishHashMap )
        pEnglishHashMap.reset( new ScAddInHashMap );

    const uno::Reference<reflection::XIdlMethod>* pArray = aMethods.getConstArray();
    for (sal_Int32 nFuncPos=0; nFuncPos<nNewCount; nFuncPos++)
    {
        ppFuncData[nFuncPos+nOld] = nullptr;

        uno::Reference<reflection::XIdlMethod> xFunc = pArray[nFuncPos];
        if (xFunc.is())
        {
            //  leave out internal functions
            uno::Reference<reflection::XIdlClass> xClass =
                            xFunc->getDeclaringClass();
            bool bSkip = true;
            if ( xClass.is() )
            {
                //TODO: XIdlClass needs getType() method!
                OUString sName = xClass->getName();
                bSkip = (
                    IsTypeName( sName,
                        cppu::UnoType<uno::XInterface>::get()) ||
                    IsTypeName( sName,
                        cppu::UnoType<lang::XServiceName>::get()) ||
                    IsTypeName( sName,
                        cppu::UnoType<lang::XServiceInfo>::get()) ||
                    IsTypeName( sName,
                        cppu::UnoType<sheet::XAddIn>::get()) );
            }
            if (!bSkip)
            {
                uno::Reference<reflection::XIdlClass> xReturn =
                            xFunc->getReturnType();
                if ( !lcl_ValidReturnType( xReturn ) )
                    bSkip = true;
            }
            if (!bSkip)
            {
                OUString aFuncU = xFunc->getName();

                // stored function name: (service name).(function)
                OUString aFuncName = aServiceName + "." + aFuncU;

                bool bValid = true;
                sal_Int32 nVisibleCount = 0;
                sal_Int32 nCallerPos = SC_CALLERPOS_NONE;

                uno::Sequence<reflection::ParamInfo> aParams =
                        xFunc->getParameterInfos();
                sal_Int32 nParamCount = aParams.getLength();
                const reflection::ParamInfo* pParArr = aParams.getConstArray();
                sal_Int32 nParamPos;
                for (nParamPos=0; nParamPos<nParamCount; nParamPos++)
                {
                    if ( pParArr[nParamPos].aMode != reflection::ParamMode_IN )
                        bValid = false;
                    uno::Reference<reflection::XIdlClass> xParClass =
                                pParArr[nParamPos].aType;
                    ScAddInArgumentType eArgType = lcl_GetArgType( xParClass );
                    if ( eArgType == SC_ADDINARG_NONE )
                        bValid = false;
                    else if ( eArgType == SC_ADDINARG_CALLER )
                        nCallerPos = nParamPos;
                    else
                        ++nVisibleCount;
                }
                if (bValid)
                {
                    sal_uInt16 nCategory = lcl_GetCategory(
                            xAddIn->getProgrammaticCategoryName( aFuncU ) );

                    OUString sHelpId = aHelpIdGenerator.GetHelpId( aFuncU );

                    OUString aLocalName;
                    try
                    {
                        aLocalName = xAddIn->
                            getDisplayFunctionName( aFuncU );
                    }
                    catch(uno::Exception&)
                    {
                        aLocalName = "###";
                    }

                    OUString aDescription;
                    try
                    {
                        aDescription = xAddIn->
                            getFunctionDescription( aFuncU );
                    }
                    catch(uno::Exception&)
                    {
                        aDescription = "###";
                    }

                    std::unique_ptr<ScAddInArgDesc[]> pVisibleArgs;
                    if ( nVisibleCount > 0 )
                    {
                        ScAddInArgDesc aDesc;
                        pVisibleArgs.reset(new ScAddInArgDesc[nVisibleCount]);
                        sal_Int32 nDestPos = 0;
                        for (nParamPos=0; nParamPos<nParamCount; nParamPos++)
                        {
                            uno::Reference<reflection::XIdlClass> xParClass =
                                pParArr[nParamPos].aType;
                            ScAddInArgumentType eArgType = lcl_GetArgType( xParClass );
                            if ( eArgType != SC_ADDINARG_CALLER )
                            {
                                OUString aArgName;
                                try
                                {
                                    aArgName = xAddIn->
                                        getDisplayArgumentName( aFuncU, nParamPos );
                                }
                                catch(uno::Exception&)
                                {
                                    aArgName = "###";
                                }
                                OUString aArgDesc;
                                try
                                {
                                    aArgDesc = xAddIn->
                                        getArgumentDescription( aFuncU, nParamPos );
                                }
                                catch(uno::Exception&)
                                {
                                    aArgDesc = "###";
                                }

                                bool bOptional =
                                    ( eArgType == SC_ADDINARG_VALUE_OR_ARRAY ||
                                      eArgType == SC_ADDINARG_VARARGS );

                                aDesc.eType = eArgType;
                                aDesc.aName = aArgName;
                                aDesc.aDescription = aArgDesc;
                                aDesc.bOptional = bOptional;
                                //TODO: initialize aInternalName only from config?
                                aDesc.aInternalName = pParArr[nParamPos].aName;

                                pVisibleArgs[nDestPos++] = aDesc;
                            }
                        }
                        OSL_ENSURE( nDestPos==nVisibleCount, "wrong count" );
                    }

                    ppFuncData[nFuncPos+nOld].reset( new ScUnoAddInFuncData(
                        aFuncName, aLocalName, aDescription,
                        nCategory, sHelpId,
                        xFunc, aObject,
                        nVisibleCount, pVisibleArgs.get(), nCallerPos ) );

                    ScUnoAddInFuncData* pData = ppFuncData[nFuncPos+nOld].get();
                    pExactHashMap->emplace(
                                pData->GetOriginalName(),
                                pData );
                    pNameHashMap->emplace(
                                pData->GetUpperName(),
                                pData );
                    pLocalHashMap->emplace(
                                pData->GetUpperLocal(),
                                pData );

                    aFuncNameData.push_back({aFuncU, pData});
                }
            }
        }
    }

    const LanguageTag aEnglishLanguageTag(LANGUAGE_ENGLISH_US);
    xAddIn->setLocale( aEnglishLanguageTag.getLocale());
    for (const auto& rFunc : aFuncNameData)
    {
        OUString aEnglishName;
        try
        {
            aEnglishName = xAddIn->getDisplayFunctionName( rFunc.aFuncU );
        }
        catch(uno::Exception&)
        {
        }
        if (aEnglishName.isEmpty()
                && rFunc.pData->GetExcelName( aEnglishLanguageTag, aEnglishName, false /*bFallbackToAny*/))
        {
            // Check our known suffixes and append if not present. Note this
            // depends on localization (that should not add such suffix, but..)
            // and is really only a last resort.
            if (rFunc.pData->GetLocalName().endsWith("_ADD") && !aEnglishName.endsWith("_ADD"))
                aEnglishName += "_ADD";
            else if (rFunc.pData->GetLocalName().endsWith("_EXCEL2003") && !aEnglishName.endsWith("_EXCEL2003"))
                aEnglishName += "_EXCEL2003";
            SAL_WARN("sc.core", "obtaining English name for " << rFunc.pData->GetLocalName() << " "
                    << rFunc.pData->GetOriginalName() << " as ExcelName '" << aEnglishName << "'");
        }
        SAL_WARN_IF(aEnglishName.isEmpty(), "sc.core", "no English name for "
                << rFunc.pData->GetLocalName() << " " << rFunc.pData->GetOriginalName());
        rFunc.pData->SetEnglishName(aEnglishName);  // takes care of handling empty
        pEnglishHashMap->emplace( rFunc.pData->GetUpperEnglish(), rFunc.pData);
    }
}

static void lcl_UpdateFunctionList( const ScFunctionList& rFunctionList, const ScUnoAddInFuncData& rFuncData,
        bool bEnglishFunctionNames )
{
    // as used in FillFunctionDescFromData
    const OUString& aCompare = (bEnglishFunctionNames ? rFuncData.GetUpperEnglish() : rFuncData.GetUpperLocal());

    sal_uLong nCount = rFunctionList.GetCount();
    for (sal_uLong nPos=0; nPos<nCount; nPos++)
    {
        const ScFuncDesc* pDesc = rFunctionList.GetFunction( nPos );
        if ( pDesc && pDesc->mxFuncName && *pDesc->mxFuncName == aCompare )
        {
            ScUnoAddInCollection::FillFunctionDescFromData( rFuncData, *const_cast<ScFuncDesc*>(pDesc),
                    bEnglishFunctionNames);
            break;
        }
    }
}

static const ScAddInArgDesc* lcl_FindArgDesc( const ScUnoAddInFuncData& rFuncData, std::u16string_view rArgIntName )
{
    sal_Int32 nArgCount = rFuncData.GetArgumentCount();
    const ScAddInArgDesc* pArguments = rFuncData.GetArguments();
    for (sal_Int32 nPos=0; nPos<nArgCount; nPos++)
    {
        if ( pArguments[nPos].aInternalName == rArgIntName )
            return &pArguments[nPos];
    }
    return nullptr;
}

void ScUnoAddInCollection::UpdateFromAddIn( const uno::Reference<uno::XInterface>& xInterface,
                                            std::u16string_view rServiceName )
{
    const bool bEnglishFunctionNames = ScModule::get()->GetFormulaOptions().GetUseEnglishFuncName();
    uno::Reference<lang::XLocalizable> xLoc( xInterface, uno::UNO_QUERY );
    if ( xLoc.is() )        // optional in new add-ins
        xLoc->setLocale( Application::GetSettings().GetUILanguageTag().getLocale());

    // if function list was already initialized, it must be updated

    ScFunctionList* pFunctionList = nullptr;
    if ( ScGlobal::HasStarCalcFunctionList() )
        pFunctionList = ScGlobal::GetStarCalcFunctionList();

    // only get the function information from Introspection

    const uno::Reference<uno::XComponentContext>& xContext = comphelper::getProcessComponentContext();

    uno::Reference<beans::XIntrospection> xIntro = beans::theIntrospection::get(xContext);
    uno::Any aObject;
    aObject <<= xInterface;
    uno::Reference<beans::XIntrospectionAccess> xAcc = xIntro->inspect(aObject);
    if (!xAcc.is())
        return;

    const uno::Sequence< uno::Reference<reflection::XIdlMethod> > aMethods =
            xAcc->getMethods( beans::MethodConcept::ALL );
    for (const uno::Reference<reflection::XIdlMethod>& xFunc : aMethods)
    {
        if (xFunc.is())
        {
            OUString aFuncU = xFunc->getName();

            // stored function name: (service name).(function)
            OUString aFuncName = OUString::Concat(rServiceName) + "." + aFuncU;

            // internal names are skipped because no FuncData exists
            ScUnoAddInFuncData* pOldData = const_cast<ScUnoAddInFuncData*>( GetFuncData( aFuncName ) );
            if ( pOldData )
            {
                // Create new (complete) argument info.
                // As in ReadFromAddIn, the reflection information is authoritative.
                // Local names and descriptions from pOldData are looked up using the
                // internal argument name.

                bool bValid = true;
                sal_Int32 nVisibleCount = 0;
                sal_Int32 nCallerPos = SC_CALLERPOS_NONE;

                const uno::Sequence<reflection::ParamInfo> aParams =
                        xFunc->getParameterInfos();
                sal_Int32 nParamCount = aParams.getLength();
                const reflection::ParamInfo* pParArr = aParams.getConstArray();
                for (sal_Int32 nParamPos=0; nParamPos<nParamCount; nParamPos++)
                {
                    if ( pParArr[nParamPos].aMode != reflection::ParamMode_IN )
                        bValid = false;
                    uno::Reference<reflection::XIdlClass> xParClass =
                                pParArr[nParamPos].aType;
                    ScAddInArgumentType eArgType = lcl_GetArgType( xParClass );
                    if ( eArgType == SC_ADDINARG_NONE )
                        bValid = false;
                    else if ( eArgType == SC_ADDINARG_CALLER )
                        nCallerPos = nParamPos;
                    else
                        ++nVisibleCount;
                }
                if (bValid)
                {
                    std::unique_ptr<ScAddInArgDesc[]> pVisibleArgs;
                    if ( nVisibleCount > 0 )
                    {
                        ScAddInArgDesc aDesc;
                        pVisibleArgs.reset(new ScAddInArgDesc[nVisibleCount]);
                        sal_Int32 nDestPos = 0;
                        for (const auto& rParam : aParams)
                        {
                            uno::Reference<reflection::XIdlClass> xParClass =
                                rParam.aType;
                            ScAddInArgumentType eArgType = lcl_GetArgType( xParClass );
                            if ( eArgType != SC_ADDINARG_CALLER )
                            {
                                const ScAddInArgDesc* pOldArgDesc =
                                    lcl_FindArgDesc( *pOldData, rParam.aName );
                                if ( pOldArgDesc )
                                {
                                    aDesc.aName = pOldArgDesc->aName;
                                    aDesc.aDescription = pOldArgDesc->aDescription;
                                }
                                else
                                    aDesc.aName = aDesc.aDescription = "###";

                                bool bOptional =
                                    ( eArgType == SC_ADDINARG_VALUE_OR_ARRAY ||
                                      eArgType == SC_ADDINARG_VARARGS );

                                aDesc.eType = eArgType;
                                aDesc.bOptional = bOptional;
                                //TODO: initialize aInternalName only from config?
                                aDesc.aInternalName = rParam.aName;

                                pVisibleArgs[nDestPos++] = aDesc;
                            }
                        }
                        OSL_ENSURE( nDestPos==nVisibleCount, "wrong count" );
                    }

                    pOldData->SetFunction( xFunc, aObject );
                    pOldData->SetArguments( nVisibleCount, pVisibleArgs.get() );
                    pOldData->SetCallerPos( nCallerPos );

                    if ( pFunctionList )
                        lcl_UpdateFunctionList( *pFunctionList, *pOldData, bEnglishFunctionNames );
                }
            }
        }
    }
}

OUString ScUnoAddInCollection::FindFunction( const OUString& rUpperName, bool bLocalFirst )
{
    if (!bInitialized)
        Initialize();

    if (nFuncCount == 0)
        return OUString();

    if ( bLocalFirst )
    {
        // Only scan local names (used for entering formulas).

        ScAddInHashMap::const_iterator iLook( pLocalHashMap->find( rUpperName ) );
        if ( iLook != pLocalHashMap->end() )
            return iLook->second->GetOriginalName();
    }
    else
    {
        // First scan international programmatic names (used when calling a
        // function).

        ScAddInHashMap::const_iterator iLook( pNameHashMap->find( rUpperName ) );
        if ( iLook != pNameHashMap->end() )
            return iLook->second->GetOriginalName();

        // Then scan English names (as FunctionAccess API could expect).

        iLook = pEnglishHashMap->find( rUpperName );
        if ( iLook != pEnglishHashMap->end() )
            return iLook->second->GetOriginalName();

        // After that, scan all local names; either to allow replacing old
        // AddIns with Uno, or for functions where the AddIn did not provide an
        // English name.

        iLook = pLocalHashMap->find( rUpperName );
        if ( iLook != pLocalHashMap->end() )
            return iLook->second->GetOriginalName();
    }

    return OUString();
}

const ScUnoAddInFuncData* ScUnoAddInCollection::GetFuncData( const OUString& rName, bool bComplete )
{
    if (!bInitialized)
        Initialize();

    //  rName must be the exact internal name

    ScAddInHashMap::const_iterator iLook( pExactHashMap->find( rName ) );
    if ( iLook != pExactHashMap->end() )
    {
        const ScUnoAddInFuncData* pFuncData = iLook->second;

        if ( bComplete && !pFuncData->GetFunction().is() )           //TODO: extra flag?
            LoadComponent( *pFuncData );

        return pFuncData;
    }

    return nullptr;
}

const ScUnoAddInFuncData* ScUnoAddInCollection::GetFuncData( sal_Int32 nIndex )
{
    if (!bInitialized)
        Initialize();

    if (nIndex < nFuncCount)
        return ppFuncData[nIndex].get();
    return nullptr;
}

void ScUnoAddInCollection::LocalizeString( OUString& rName )
{
    if (!bInitialized)
        Initialize();

    //  modify rName - input: exact name

    ScAddInHashMap::const_iterator iLook( pExactHashMap->find( rName ) );
    if ( iLook != pExactHashMap->end() )
        rName = iLook->second->GetUpperLocal();         //TODO: upper?
}

sal_Int32 ScUnoAddInCollection::GetFuncCount()
{
    if (!bInitialized)
        Initialize();

    return nFuncCount;
}

bool ScUnoAddInCollection::FillFunctionDesc( sal_Int32 nFunc, ScFuncDesc& rDesc, bool bEnglishFunctionNames )
{
    if (!bInitialized)
        Initialize();

    if (nFunc >= nFuncCount || !ppFuncData[nFunc])
        return false;

    const ScUnoAddInFuncData& rFuncData = *ppFuncData[nFunc];

    return FillFunctionDescFromData( rFuncData, rDesc, bEnglishFunctionNames );
}

bool ScUnoAddInCollection::FillFunctionDescFromData( const ScUnoAddInFuncData& rFuncData, ScFuncDesc& rDesc,
        bool bEnglishFunctionNames )
{
    rDesc.Clear();

    bool bIncomplete = !rFuncData.GetFunction().is();       //TODO: extra flag?

    sal_Int32 nArgCount = rFuncData.GetArgumentCount();
    if ( nArgCount > SAL_MAX_UINT16 )
        return false;

    if ( bIncomplete )
        nArgCount = 0;      // if incomplete, fill without argument info (no wrong order)

    // nFIndex is set from outside

    rDesc.mxFuncName = (bEnglishFunctionNames ? rFuncData.GetUpperEnglish() : rFuncData.GetUpperLocal());
    rDesc.nCategory = rFuncData.GetCategory();
    rDesc.sHelpId = rFuncData.GetHelpId();

    OUString aDesc = rFuncData.GetDescription();
    if (aDesc.isEmpty())
        aDesc = rFuncData.GetLocalName();      // use name if no description is available
    rDesc.mxFuncDesc = aDesc ;

    // AddInArgumentType_CALLER is already left out in FuncData

    rDesc.nArgCount = static_cast<sal_uInt16>(nArgCount);
    if ( nArgCount )
    {
        bool bMultiple = false;
        const ScAddInArgDesc* pArgs = rFuncData.GetArguments();

        rDesc.maDefArgNames.clear();
        rDesc.maDefArgNames.resize(nArgCount);
        rDesc.maDefArgDescs.clear();
        rDesc.maDefArgDescs.resize(nArgCount);
        rDesc.pDefArgFlags   = new ScFuncDesc::ParameterFlags[nArgCount];
        for ( sal_Int32 nArg=0; nArg<nArgCount; nArg++ )
        {
            rDesc.maDefArgNames[nArg] = pArgs[nArg].aName;
            rDesc.maDefArgDescs[nArg] = pArgs[nArg].aDescription;
            rDesc.pDefArgFlags[nArg].bOptional = pArgs[nArg].bOptional;

            // no empty names...
            if (rDesc.maDefArgNames[nArg].isEmpty())
                rDesc.maDefArgNames[nArg] = "arg" + OUString::number(nArg + 1);

            //  last argument repeated?
            if ( nArg+1 == nArgCount && ( pArgs[nArg].eType == SC_ADDINARG_VARARGS ) )
                bMultiple = true;
        }

        if ( bMultiple )
            rDesc.nArgCount += VAR_ARGS - 1;    // VAR_ARGS means just one repeated arg
    }

    rDesc.bIncomplete = bIncomplete;

    return true;
}

ScUnoAddInCall::ScUnoAddInCall( ScDocument& rDoc, ScUnoAddInCollection& rColl, const OUString& rName,
                                sal_Int32 nParamCount ) :
    mrDoc( rDoc ),
    bValidCount( false ),
    nErrCode( FormulaError::NoCode ),      // before function was called
    bHasString( true ),
    fValue( 0.0 ),
    xMatrix( nullptr )
{
    pFuncData = rColl.GetFuncData( rName, true );           // need fully initialized data
    OSL_ENSURE( pFuncData, "Function Data missing" );
    if ( !pFuncData )
        return;

    sal_Int32 nDescCount = pFuncData->GetArgumentCount();
    const ScAddInArgDesc* pArgs = pFuncData->GetArguments();

    //  is aVarArg sequence needed?
    if ( nParamCount >= nDescCount && nDescCount > 0 &&
         pArgs[nDescCount-1].eType == SC_ADDINARG_VARARGS )
    {
        sal_Int32 nVarCount = nParamCount - ( nDescCount - 1 );  // size of last argument
        aVarArg.realloc( nVarCount );
        bValidCount = true;
    }
    else if ( nParamCount <= nDescCount )
    {
        //  all args behind nParamCount must be optional
        bValidCount = true;
        for (sal_Int32 i=nParamCount; i<nDescCount; i++)
            if ( !pArgs[i].bOptional )
                bValidCount = false;
    }
    // else invalid (too many arguments)

    if ( bValidCount )
        aArgs.realloc( nDescCount );    // sequence must always match function signature
}

ScUnoAddInCall::~ScUnoAddInCall()
{
    // pFuncData is deleted with ScUnoAddInCollection
}

ScAddInArgumentType ScUnoAddInCall::GetArgType( sal_Int32 nPos )
{
    if ( pFuncData )
    {
        sal_Int32 nCount = pFuncData->GetArgumentCount();
        const ScAddInArgDesc* pArgs = pFuncData->GetArguments();

        // if last arg is sequence, use "any" type
        if ( nCount > 0 && nPos >= nCount-1 && pArgs[nCount-1].eType == SC_ADDINARG_VARARGS )
            return SC_ADDINARG_VALUE_OR_ARRAY;

        if ( nPos < nCount )
            return pArgs[nPos].eType;
    }
    return SC_ADDINARG_VALUE_OR_ARRAY;      //TODO: error code !!!!
}

bool ScUnoAddInCall::NeedsCaller() const
{
    return pFuncData && pFuncData->GetCallerPos() != SC_CALLERPOS_NONE;
}

void ScUnoAddInCall::SetCaller( const uno::Reference<uno::XInterface>& rInterface )
{
    xCaller = rInterface;
}

void ScUnoAddInCall::SetCallerFromObjectShell( const SfxObjectShell* pObjSh )
{
    if (pObjSh)
    {
        uno::Reference<uno::XInterface> xInt( pObjSh->GetBaseModel(), uno::UNO_QUERY );
        SetCaller( xInt );
    }
}

void ScUnoAddInCall::SetParam( sal_Int32 nPos, const uno::Any& rValue )
{
    if ( !pFuncData )
        return;

    sal_Int32 nCount = pFuncData->GetArgumentCount();
    const ScAddInArgDesc* pArgs = pFuncData->GetArguments();
    if ( nCount > 0 && nPos >= nCount-1 && pArgs[nCount-1].eType == SC_ADDINARG_VARARGS )
    {
        sal_Int32 nVarPos = nPos-(nCount-1);
        if ( nVarPos < aVarArg.getLength() )
            aVarArg.getArray()[nVarPos] = rValue;
        else
        {
            OSL_FAIL("wrong argument number");
        }
    }
    else if ( nPos < aArgs.getLength() )
        aArgs.getArray()[nPos] = rValue;
    else
    {
        OSL_FAIL("wrong argument number");
    }
}

void ScUnoAddInCall::ExecuteCall()
{
    if ( !pFuncData )
        return;

    sal_Int32 nCount = pFuncData->GetArgumentCount();
    const ScAddInArgDesc* pArgs = pFuncData->GetArguments();
    if ( nCount > 0 && pArgs[nCount-1].eType == SC_ADDINARG_VARARGS )
    {
        //  insert aVarArg as last argument
        //TODO: after inserting caller (to prevent copying twice)?

        OSL_ENSURE( aArgs.getLength() == nCount, "wrong argument count" );
        aArgs.getArray()[nCount-1] <<= aVarArg;
    }

    if ( pFuncData->GetCallerPos() != SC_CALLERPOS_NONE )
    {
        uno::Any aCallerAny;
        aCallerAny <<= xCaller;

        sal_Int32 nUserLen = aArgs.getLength();
        sal_Int32 nCallPos = pFuncData->GetCallerPos();
        if (nCallPos>nUserLen)                          // should not happen
        {
            OSL_FAIL("wrong CallPos");
            nCallPos = nUserLen;
        }

        sal_Int32 nDestLen = nUserLen + 1;
        uno::Sequence<uno::Any> aRealArgs( nDestLen );
        uno::Any* pDest = aRealArgs.getArray();

        pDest = std::copy_n(std::cbegin(aArgs), nCallPos, pDest);
        *pDest = std::move(aCallerAny);
        std::copy(std::next(std::cbegin(aArgs), nCallPos), std::cend(aArgs), std::next(pDest));

        ExecuteCallWithArgs( aRealArgs );
    }
    else
        ExecuteCallWithArgs( aArgs );
}

void ScUnoAddInCall::ExecuteCallWithArgs(uno::Sequence<uno::Any>& rCallArgs)
{
    //  rCallArgs may not match argument descriptions (because of caller)

    uno::Reference<reflection::XIdlMethod> xFunction;
    uno::Any aObject;
    if ( pFuncData )
    {
        xFunction = pFuncData->GetFunction();
        aObject = pFuncData->GetObject();
    }

    if ( !xFunction.is() )
        return;

    uno::Any aAny;
    nErrCode = FormulaError::NONE;

    try
    {
        aAny = xFunction->invoke( aObject, rCallArgs );
    }
    catch(lang::IllegalArgumentException&)
    {
        nErrCode = FormulaError::IllegalArgument;
    }
    catch(const reflection::InvocationTargetException& rWrapped)
    {
        if ( rWrapped.TargetException.getValueType().equals(
                cppu::UnoType<lang::IllegalArgumentException>::get()) )
            nErrCode = FormulaError::IllegalArgument;
        else if ( rWrapped.TargetException.getValueType().equals(
                cppu::UnoType<sheet::NoConvergenceException>::get()) )
            nErrCode = FormulaError::NoConvergence;
        else
            nErrCode = FormulaError::NoValue;
    }
    catch(uno::Exception&)
    {
        nErrCode = FormulaError::NoValue;
    }

    if (nErrCode == FormulaError::NONE)
        SetResult( aAny );      // convert result to Calc types
}

template <typename T>
static sal_Int32 lcl_GetMaxColCount(const uno::Sequence< uno::Sequence<T> >* pRowSeq)
{
    if (!pRowSeq->hasElements())
        return 0;

    auto pRow = std::max_element(pRowSeq->begin(), pRowSeq->end(),
        [](const uno::Sequence<T>& a, const uno::Sequence<T>& b) {
            return a.getLength() < b.getLength(); });
    return pRow->getLength();
}

void ScUnoAddInCall::SetResult( const uno::Any& rNewRes )
{
    nErrCode = FormulaError::NONE;
    xVarRes = nullptr;

    // Reflection* pRefl = rNewRes.getReflection();

    uno::TypeClass eClass = rNewRes.getValueTypeClass();
    const uno::Type& aType = rNewRes.getValueType();
    switch (eClass)
    {
        case uno::TypeClass_VOID:
            nErrCode = FormulaError::NotAvailable;         // #NA
            break;

        case uno::TypeClass_ENUM:
        case uno::TypeClass_BOOLEAN:
        case uno::TypeClass_CHAR:
        case uno::TypeClass_BYTE:
        case uno::TypeClass_SHORT:
        case uno::TypeClass_UNSIGNED_SHORT:
        case uno::TypeClass_LONG:
        case uno::TypeClass_UNSIGNED_LONG:
        case uno::TypeClass_FLOAT:
        case uno::TypeClass_DOUBLE:
            {
                uno::TypeClass eMyClass;
                ScApiTypeConversion::ConvertAnyToDouble( fValue, eMyClass, rNewRes);
                bHasString = false;
            }
            break;

        case uno::TypeClass_STRING:
            {
                rNewRes >>= aString;
                bHasString = true;
            }
            break;

        case uno::TypeClass_INTERFACE:
            {
                //TODO: directly extract XVolatileResult from any?
                uno::Reference<uno::XInterface> xInterface;
                rNewRes >>= xInterface;
                if ( xInterface.is() )
                    xVarRes.set( xInterface, uno::UNO_QUERY );

                if (!xVarRes.is())
                    nErrCode = FormulaError::NoValue;          // unknown interface
            }
            break;

        default:
            if ( aType.equals( cppu::UnoType<uno::Sequence< uno::Sequence<sal_Int32> >>::get() ) )
            {
                const uno::Sequence< uno::Sequence<sal_Int32> >* pRowSeq = nullptr;

                //TODO: use pointer from any!
                uno::Sequence< uno::Sequence<sal_Int32> > aSequence;
                if ( rNewRes >>= aSequence )
                    pRowSeq = &aSequence;

                if ( pRowSeq )
                {
                    sal_Int32 nRowCount = pRowSeq->getLength();
                    sal_Int32 nMaxColCount = lcl_GetMaxColCount(pRowSeq);
                    if ( nMaxColCount && nRowCount )
                    {
                        const uno::Sequence<sal_Int32>* pRowArr = pRowSeq->getConstArray();
                        xMatrix = new ScMatrix(
                                static_cast<SCSIZE>(nMaxColCount),
                                static_cast<SCSIZE>(nRowCount), 0.0);
                        for (sal_Int32 nRow=0; nRow<nRowCount; nRow++)
                        {
                            sal_Int32 nColCount = pRowArr[nRow].getLength();
                            const sal_Int32* pColArr = pRowArr[nRow].getConstArray();
                            for (sal_Int32 nCol=0; nCol<nColCount; nCol++)
                                xMatrix->PutDouble( pColArr[nCol],
                                        static_cast<SCSIZE>(nCol),
                                        static_cast<SCSIZE>(nRow) );
                            for (sal_Int32 nCol=nColCount; nCol<nMaxColCount; nCol++)
                                xMatrix->PutDouble( 0.0,
                                        static_cast<SCSIZE>(nCol),
                                        static_cast<SCSIZE>(nRow) );
                        }
                    }
                }
            }
            else if ( aType.equals( cppu::UnoType<uno::Sequence< uno::Sequence<double> >>::get() ) )
            {
                const uno::Sequence< uno::Sequence<double> >* pRowSeq = nullptr;

                //TODO: use pointer from any!
                uno::Sequence< uno::Sequence<double> > aSequence;
                if ( rNewRes >>= aSequence )
                    pRowSeq = &aSequence;

                if ( pRowSeq )
                {
                    sal_Int32 nRowCount = pRowSeq->getLength();
                    sal_Int32 nMaxColCount = lcl_GetMaxColCount(pRowSeq);
                    if ( nMaxColCount && nRowCount )
                    {
                        const uno::Sequence<double>* pRowArr = pRowSeq->getConstArray();
                        xMatrix = new ScMatrix(
                                static_cast<SCSIZE>(nMaxColCount),
                                static_cast<SCSIZE>(nRowCount), 0.0);
                        for (sal_Int32 nRow=0; nRow<nRowCount; nRow++)
                        {
                            sal_Int32 nColCount = pRowArr[nRow].getLength();
                            const double* pColArr = pRowArr[nRow].getConstArray();
                            for (sal_Int32 nCol=0; nCol<nColCount; nCol++)
                                xMatrix->PutDouble( pColArr[nCol],
                                        static_cast<SCSIZE>(nCol),
                                        static_cast<SCSIZE>(nRow) );
                            for (sal_Int32 nCol=nColCount; nCol<nMaxColCount; nCol++)
                                xMatrix->PutDouble( 0.0,
                                        static_cast<SCSIZE>(nCol),
                                        static_cast<SCSIZE>(nRow) );
                        }
                    }
                }
            }
            else if ( aType.equals( cppu::UnoType<uno::Sequence< uno::Sequence<OUString> >>::get() ) )
            {
                const uno::Sequence< uno::Sequence<OUString> >* pRowSeq = nullptr;

                //TODO: use pointer from any!
                uno::Sequence< uno::Sequence<OUString> > aSequence;
                if ( rNewRes >>= aSequence )
                    pRowSeq = &aSequence;

                if ( pRowSeq )
                {
                    sal_Int32 nRowCount = pRowSeq->getLength();
                    sal_Int32 nMaxColCount = lcl_GetMaxColCount(pRowSeq);
                    if ( nMaxColCount && nRowCount )
                    {
                        const uno::Sequence<OUString>* pRowArr = pRowSeq->getConstArray();
                        xMatrix = new ScMatrix(
                                static_cast<SCSIZE>(nMaxColCount),
                                static_cast<SCSIZE>(nRowCount), 0.0);
                        for (sal_Int32 nRow=0; nRow<nRowCount; nRow++)
                        {
                            sal_Int32 nColCount = pRowArr[nRow].getLength();
                            const OUString* pColArr = pRowArr[nRow].getConstArray();
                            for (sal_Int32 nCol=0; nCol<nColCount; nCol++)
                            {
                                xMatrix->PutString(
                                    mrDoc.GetSharedStringPool().intern(pColArr[nCol]),
                                    static_cast<SCSIZE>(nCol), static_cast<SCSIZE>(nRow));
                            }
                            for (sal_Int32 nCol=nColCount; nCol<nMaxColCount; nCol++)
                            {
                                xMatrix->PutString(
                                    svl::SharedString::getEmptyString(),
                                    static_cast<SCSIZE>(nCol), static_cast<SCSIZE>(nRow));
                            }
                        }
                    }
                }
            }
            else if ( aType.equals( cppu::UnoType<uno::Sequence< uno::Sequence<uno::Any> >>::get() ) )
            {
                xMatrix = ScSequenceToMatrix::CreateMixedMatrix( rNewRes );
            }

            if (!xMatrix)                       // no array found
                nErrCode = FormulaError::NoValue;          //TODO: code for error in return type???
    }
}

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