/* -*- 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 <sal/config.h>

#include <string_view>

#include <unotools/moduleoptions.hxx>
#include <comphelper/sequenceashashmap.hxx>
#include <unotools/configitem.hxx>
#include <comphelper/processfactory.hxx>
#include <comphelper/sequence.hxx>
#include <osl/diagnose.h>
#include <o3tl/enumarray.hxx>
#include <o3tl/string_view.hxx>
#include <com/sun/star/uno/Any.hxx>
#include <com/sun/star/uno/Sequence.hxx>
#include <com/sun/star/beans/PropertyValue.hpp>
#include <com/sun/star/container/XNameAccess.hpp>
#include <com/sun/star/frame/XModel.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/document/XTypeDetection.hpp>
#include <com/sun/star/util/PathSubstitution.hpp>
#include <com/sun/star/util/XStringSubstitution.hpp>

#include "itemholder1.hxx"

/*-************************************************************************************************************
    @descr          These values are used to define necessary keys from our configuration management to support
                    all functionality of these implementation.
                    It's a fast way to make changes if some keys change its name or location!

                    Property handle are necessary to specify right position in return list of configuration
                    for asked values. We ask it with a list of properties to get its values. The returned list
                    has the same order like our given name list!
                    e.g.:
                            NAMELIST[ PROPERTYHANDLE_xxx ] => VALUELIST[ PROPERTYHANDLE_xxx ]
*//*-*************************************************************************************************************/
constexpr OUStringLiteral ROOTNODE_FACTORIES = u"Setup/Office/Factories";
#define PATHSEPARATOR                       "/"

// Attention: The property "ooSetupFactoryEmptyDocumentURL" is read from configuration but not used! There is
//            special code that uses hard coded strings to return them.
#define PROPERTYNAME_SHORTNAME              "ooSetupFactoryShortName"
#define PROPERTYNAME_TEMPLATEFILE           "ooSetupFactoryTemplateFile"
#define PROPERTYNAME_WINDOWATTRIBUTES       "ooSetupFactoryWindowAttributes"
#define PROPERTYNAME_EMPTYDOCUMENTURL       "ooSetupFactoryEmptyDocumentURL"
#define PROPERTYNAME_DEFAULTFILTER          "ooSetupFactoryDefaultFilter"
#define PROPERTYNAME_ICON                   "ooSetupFactoryIcon"

#define PROPERTYHANDLE_SHORTNAME            0
#define PROPERTYHANDLE_TEMPLATEFILE         1
#define PROPERTYHANDLE_WINDOWATTRIBUTES     2
#define PROPERTYHANDLE_EMPTYDOCUMENTURL     3
#define PROPERTYHANDLE_DEFAULTFILTER        4
#define PROPERTYHANDLE_ICON                 5

#define PROPERTYCOUNT                       6

constexpr OUStringLiteral FACTORYNAME_WRITER = u"com.sun.star.text.TextDocument";
constexpr OUStringLiteral FACTORYNAME_WRITERWEB = u"com.sun.star.text.WebDocument";
constexpr OUStringLiteral FACTORYNAME_WRITERGLOBAL = u"com.sun.star.text.GlobalDocument";
constexpr OUStringLiteral FACTORYNAME_CALC = u"com.sun.star.sheet.SpreadsheetDocument";
constexpr OUStringLiteral FACTORYNAME_DRAW = u"com.sun.star.drawing.DrawingDocument";
constexpr OUStringLiteral FACTORYNAME_IMPRESS = u"com.sun.star.presentation.PresentationDocument";
constexpr OUStringLiteral FACTORYNAME_MATH = u"com.sun.star.formula.FormulaProperties";
constexpr OUStringLiteral FACTORYNAME_CHART = u"com.sun.star.chart2.ChartDocument";
constexpr OUStringLiteral FACTORYNAME_DATABASE = u"com.sun.star.sdb.OfficeDatabaseDocument";
constexpr OUStringLiteral FACTORYNAME_STARTMODULE = u"com.sun.star.frame.StartModule";
constexpr OUStringLiteral FACTORYNAME_BASIC = u"com.sun.star.script.BasicIDE";

#define FACTORYCOUNT                        11

namespace {

/*-************************************************************************************************************
    @descr  This struct hold information about one factory. We declare a complete array which can hold infos
            for all well known factories. Values of enum "EFactory" (see header!) are directly used as index!
            So we can support a fast access on this information.
*//*-*************************************************************************************************************/
struct FactoryInfo
{
    public:

        // initialize empty struct
        FactoryInfo()
        {
            free();
        }

        // easy way to reset struct member!
        void free()
        {
            bInstalled                  = false;
            sFactory.clear();
            sTemplateFile.clear();
            sDefaultFilter.clear();
            nIcon                       = 0;
            bChangedTemplateFile        = false;
            bChangedDefaultFilter       = false;
            bDefaultFilterReadonly      = false;
        }

        // returns list of properties, which has changed only!
        // We use given value of sNodeBase to build full qualified paths ...
        // Last sign of it must be "/". because we use it directly, without any additional things!
        css::uno::Sequence< css::beans::PropertyValue > getChangedProperties( std::u16string_view sNodeBase )
        {
            // a) reserve memory for max. count of changed properties
            // b) add names and values of changed ones only and count it
            // c) resize return list by using count
            css::uno::Sequence< css::beans::PropertyValue > lProperties   ( 4 );
            auto plProperties = lProperties.getArray();
            sal_Int8                                        nRealyChanged = 0;

            if( bChangedTemplateFile )
            {
                plProperties[nRealyChanged].Name
                    = OUString::Concat(sNodeBase) + PROPERTYNAME_TEMPLATEFILE;

                if ( !sTemplateFile.isEmpty() )
                {
                    plProperties[nRealyChanged].Value
                        <<= getStringSubstitution()
                            ->reSubstituteVariables( sTemplateFile );
                }
                else
                {
                    plProperties[nRealyChanged].Value <<= sTemplateFile;
                }

                ++nRealyChanged;
            }
            if( bChangedDefaultFilter )
            {
                plProperties[nRealyChanged].Name
                    = OUString::Concat(sNodeBase) + PROPERTYNAME_DEFAULTFILTER;
                plProperties[nRealyChanged].Value <<= sDefaultFilter;
                ++nRealyChanged;
            }

            // Don't forget to reset changed flags! Otherwise we save it again and again and ...
            bChangedTemplateFile        = false;
            bChangedDefaultFilter       = false;

            lProperties.realloc( nRealyChanged );
            return lProperties;
        }

        // We must support setting AND marking of changed values.
        // That's why we can't make our member public. We must use get/set/init methods
        // to control access on it!
        bool            getInstalled        () const { return bInstalled;         };
        const OUString& getFactory          () const { return sFactory;           };
        const OUString& getTemplateFile     () const { return sTemplateFile;      };
        const OUString& getDefaultFilter    () const { return sDefaultFilter;     };
        bool            isDefaultFilterReadonly() const { return bDefaultFilterReadonly; }
        sal_Int32       getIcon             () const { return nIcon;              };

        // If you call set-methods - we check for changes of values and mark it.
        // But if you wish to set it without that... you must initialize it!
        void initInstalled        ()                                       { bInstalled        = true; }
        void initFactory          ( const OUString& sNewFactory          ) { sFactory          = sNewFactory; }
        void initDefaultFilter    ( const OUString& sNewDefaultFilter    ) { sDefaultFilter    = sNewDefaultFilter; }
        void setDefaultFilterReadonly( const bool bVal){bDefaultFilterReadonly = bVal;}
        void initIcon             ( sal_Int32              nNewIcon             ) { nIcon             = nNewIcon; }

        void initTemplateFile( const OUString& sNewTemplateFile )
        {
            if ( !sNewTemplateFile.isEmpty() )
            {
                sTemplateFile= getStringSubstitution()->substituteVariables( sNewTemplateFile, false );
            }
            else
            {
                sTemplateFile = sNewTemplateFile;
            }
        }

        void setTemplateFile( const OUString& sNewTemplateFile )
        {
            if( sTemplateFile != sNewTemplateFile )
            {
                sTemplateFile        = sNewTemplateFile;
                bChangedTemplateFile = true;
            }
        };

        void setDefaultFilter( const OUString& sNewDefaultFilter )
        {
            if( sDefaultFilter != sNewDefaultFilter )
            {
                sDefaultFilter       = sNewDefaultFilter;
                bChangedDefaultFilter = true;
            }
        };

    private:
        css::uno::Reference< css::util::XStringSubstitution > const & getStringSubstitution()
        {
            if ( !xSubstVars.is() )
            {
                xSubstVars.set( css::util::PathSubstitution::create(::comphelper::getProcessComponentContext()) );
            }
            return xSubstVars;
        }

        bool         bInstalled;
        OUString     sFactory;
        OUString     sTemplateFile;
        OUString     sDefaultFilter;
        sal_Int32    nIcon;

        bool            bChangedTemplateFile        :1;
        bool            bChangedDefaultFilter       :1;
        bool            bDefaultFilterReadonly      :1;

        css::uno::Reference< css::util::XStringSubstitution >  xSubstVars;
};

}

class SvtModuleOptions_Impl : public ::utl::ConfigItem
{

    //  public methods

    public:

        //  constructor / destructor

         SvtModuleOptions_Impl();
        virtual ~SvtModuleOptions_Impl() override;

        //  override methods of baseclass

        virtual void Notify( const css::uno::Sequence< OUString >& lPropertyNames ) override;

        //  public interface

        bool            IsModuleInstalled         (       SvtModuleOptions::EModule     eModule    ) const;
        css::uno::Sequence < OUString > GetAllServiceNames();
        OUString const & GetFactoryName           (       SvtModuleOptions::EFactory    eFactory   ) const;
        OUString const & GetFactoryStandardTemplate(      SvtModuleOptions::EFactory    eFactory   ) const;
        static OUString GetFactoryEmptyDocumentURL(       SvtModuleOptions::EFactory    eFactory   );
        OUString const & GetFactoryDefaultFilter  (       SvtModuleOptions::EFactory    eFactory   ) const;
        bool            IsDefaultFilterReadonly(          SvtModuleOptions::EFactory eFactory      ) const;
        sal_Int32       GetFactoryIcon            (       SvtModuleOptions::EFactory    eFactory   ) const;
        static bool     ClassifyFactoryByName     ( std::u16string_view sName      ,
                                                          SvtModuleOptions::EFactory&   eFactory   );
        void            SetFactoryStandardTemplate(       SvtModuleOptions::EFactory    eFactory   ,
                                                    const OUString&              sTemplate  );
        void            SetFactoryDefaultFilter   (       SvtModuleOptions::EFactory    eFactory   ,
                                                    const OUString&              sFilter    );
        void            MakeReadonlyStatesAvailable();

    //  private methods

    private:
        static css::uno::Sequence< OUString > impl_ExpandSetNames ( const css::uno::Sequence< OUString >& lSetNames );
        void impl_Read ( const css::uno::Sequence< OUString >& lSetNames );

        virtual void ImplCommit() override;

    //  private member

    private:
        o3tl::enumarray<SvtModuleOptions::EFactory, FactoryInfo> m_lFactories;
        bool            m_bReadOnlyStatesWellKnown;
};

/*-************************************************************************************************************
    @short      default ctor
    @descr      We open our configuration here and read all necessary values from it.
                These values are cached till everyone call Commit(). Then we write changed ones back to cfg.

    @seealso    baseclass ConfigItem
    @seealso    method impl_Read()
    @threadsafe no
*//*-*************************************************************************************************************/
SvtModuleOptions_Impl::SvtModuleOptions_Impl()
    :   ::utl::ConfigItem( ROOTNODE_FACTORIES )
    ,   m_bReadOnlyStatesWellKnown( false )
{
    // First initialize list of factory infos! Otherwise we couldn't guarantee right working of these class.
    for( auto & rFactory : m_lFactories )
        rFactory.free();

    // Get name list of all existing set node names in configuration to read her properties in impl_Read().
    // These list is a list of long names of our factories.
    const css::uno::Sequence< OUString > lFactories = GetNodeNames( OUString() );
    impl_Read( lFactories );

    // Enable notification for changes by using configuration directly.
    // So we can update our internal values immediately.
    EnableNotification( lFactories );
}

SvtModuleOptions_Impl::~SvtModuleOptions_Impl()
{
    assert(!IsModified()); // should have been committed
}

/*-************************************************************************************************************
    @short      called for notify of configmanager
    @descr      This method is called from the ConfigManager before application ends or from the
                PropertyChangeListener if the sub tree broadcasts changes. You must update our
                internal values.

    @attention  We are registered for pure set node names only. So we can use our internal method "impl_Read()" to
                update our info list. Because - this method expand given name list to full qualified property list
                and use it to read the values. These values are filled into our internal member list m_lFactories
                at right position.

    @seealso    method impl_Read()

    @param      "lNames" is the list of set node entries which should be updated.
    @threadsafe no
*//*-*************************************************************************************************************/
void SvtModuleOptions_Impl::Notify( const css::uno::Sequence< OUString >& )
{
    OSL_FAIL( "SvtModuleOptions_Impl::Notify() Not implemented yet!" );
}

/*-****************************************************************************************************
    @short      write changes to configuration
    @descr      This method writes the changed values into the sub tree
                and should always called in our destructor to guarantee consistency of config data.

    @attention  We clear complete set in configuration first and write it completely new! So we don't must
                distinguish between existing, added or removed elements. Our internal cached values
                are the only and right ones.

    @seealso    baseclass ConfigItem
    @threadsafe no
*//*-*****************************************************************************************************/
void SvtModuleOptions_Impl::ImplCommit()
{
    // Reserve memory for ALL possible factory properties!
    // Step over all factories and get her really changed values only.
    // Build list of these ones and use it for commit.
    css::uno::Sequence< css::beans::PropertyValue > lCommitProperties( FACTORYCOUNT*PROPERTYCOUNT );
    sal_Int32                                       nRealCount       = 0;
    OUString                                 sBasePath;
    for( FactoryInfo & rInfo : m_lFactories )
    {
        // These path is used to build full qualified property names...
        // See pInfo->getChangedProperties() for further information
        sBasePath  = PATHSEPARATOR + rInfo.getFactory() + PATHSEPARATOR;

        const css::uno::Sequence< css::beans::PropertyValue > lChangedProperties = rInfo.getChangedProperties ( sBasePath );
        std::copy(lChangedProperties.begin(), lChangedProperties.end(), std::next(lCommitProperties.getArray(), nRealCount));
        nRealCount += lChangedProperties.getLength();
    }
    // Resize commit list to real size.
    // If nothing to do - suppress calling of configuration...
    // It could be too expensive :-)
    if( nRealCount > 0 )
    {
        lCommitProperties.realloc( nRealCount );
        SetSetProperties( OUString(), lCommitProperties );
    }
}

/*-****************************************************************************************************
    @short      access method to get internal values
    @descr      These methods implement easy access to our internal values.
                You give us right enum value to specify which module interest you ... we return right information.

    @attention  Some people use any value as enum ... but we support in header specified values only!
                We use it directly as index in our internal list. If enum value isn't right - we crash with an
                "index out of range"!!! Please use me right - otherwise there is no guarantee.
    @param      "eModule"  , index in list - specify module
    @return     Queried information.

    @onerror    We return default values. (mostly "not installed"!)
    @threadsafe no
*//*-*****************************************************************************************************/
bool SvtModuleOptions_Impl::IsModuleInstalled( SvtModuleOptions::EModule eModule ) const
{
    switch( eModule )
    {
        case SvtModuleOptions::EModule::WRITER:
            return m_lFactories[SvtModuleOptions::EFactory::WRITER].getInstalled();
        case SvtModuleOptions::EModule::WEB:
            return m_lFactories[SvtModuleOptions::EFactory::WRITERWEB].getInstalled();
        case SvtModuleOptions::EModule::GLOBAL:
            return m_lFactories[SvtModuleOptions::EFactory::WRITERGLOBAL].getInstalled();
        case SvtModuleOptions::EModule::CALC:
            return m_lFactories[SvtModuleOptions::EFactory::CALC].getInstalled();
        case SvtModuleOptions::EModule::DRAW:
            return m_lFactories[SvtModuleOptions::EFactory::DRAW].getInstalled();
        case SvtModuleOptions::EModule::IMPRESS:
            return m_lFactories[SvtModuleOptions::EFactory::IMPRESS].getInstalled();
        case SvtModuleOptions::EModule::MATH:
            return m_lFactories[SvtModuleOptions::EFactory::MATH].getInstalled();
        case SvtModuleOptions::EModule::CHART:
            return m_lFactories[SvtModuleOptions::EFactory::CHART].getInstalled();
        case SvtModuleOptions::EModule::STARTMODULE:
            return m_lFactories[SvtModuleOptions::EFactory::STARTMODULE].getInstalled();
        case SvtModuleOptions::EModule::BASIC:
            return true; // Couldn't be deselected by setup yet!
        case SvtModuleOptions::EModule::DATABASE:
            return m_lFactories[SvtModuleOptions::EFactory::DATABASE].getInstalled();
    }

    return false;
}

css::uno::Sequence < OUString > SvtModuleOptions_Impl::GetAllServiceNames()
{
    std::vector<OUString> aVec;

    for( const auto & rFactory : m_lFactories )
        if( rFactory.getInstalled() )
            aVec.push_back( rFactory.getFactory() );

    return comphelper::containerToSequence(aVec);
}

OUString const & SvtModuleOptions_Impl::GetFactoryName( SvtModuleOptions::EFactory eFactory ) const
{
    return m_lFactories[eFactory].getFactory();
}

OUString SvtModuleOptions::GetFactoryShortName(SvtModuleOptions::EFactory eFactory)
{
    // Attention: Hard configured yet ... because it's not fine to make changes possible by xml file yet.
    //            But it's good to plan further possibilities!

    //return m_lFactories[eFactory].sShortName;

    OUString sShortName;
    switch( eFactory )
    {
        case SvtModuleOptions::EFactory::WRITER   :  sShortName = "swriter";
                                                       break;
        case SvtModuleOptions::EFactory::WRITERWEB:  sShortName = "swriter/web";
                                                       break;
        case SvtModuleOptions::EFactory::WRITERGLOBAL:  sShortName = "swriter/GlobalDocument";
                                                       break;
        case SvtModuleOptions::EFactory::CALC     :  sShortName = "scalc";
                                                       break;
        case SvtModuleOptions::EFactory::DRAW     :  sShortName = "sdraw";
                                                       break;
        case SvtModuleOptions::EFactory::IMPRESS  :  sShortName = "simpress";
                                                       break;
        case SvtModuleOptions::EFactory::MATH     :  sShortName = "smath";
                                                       break;
        case SvtModuleOptions::EFactory::CHART    :  sShortName = "schart";
                                                       break;
        case SvtModuleOptions::EFactory::BASIC    :  sShortName = "sbasic";
                                                       break;
        case SvtModuleOptions::EFactory::DATABASE :  sShortName = "sdatabase";
                                                       break;
        case SvtModuleOptions::EFactory::STARTMODULE :  sShortName = "startmodule";
                                                       break;
        default:
            OSL_FAIL( "unknown factory" );
            break;
    }

    return sShortName;
}

OUString const & SvtModuleOptions_Impl::GetFactoryStandardTemplate( SvtModuleOptions::EFactory eFactory ) const
{
    return m_lFactories[eFactory].getTemplateFile();
}

OUString SvtModuleOptions_Impl::GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory eFactory )
{
    // Attention: Hard configured yet ... because it's not fine to make changes possible by xml file yet.
    //            But it's good to plan further possibilities!

    //return m_lFactories[eFactory].getEmptyDocumentURL();

    OUString sURL;
    switch( eFactory )
    {
        case SvtModuleOptions::EFactory::WRITER        :  sURL = "private:factory/swriter";
                                                  break;
        case SvtModuleOptions::EFactory::WRITERWEB     :  sURL = "private:factory/swriter/web";
                                                  break;
        case SvtModuleOptions::EFactory::WRITERGLOBAL  :  sURL = "private:factory/swriter/GlobalDocument";
                                                  break;
        case SvtModuleOptions::EFactory::CALC          :  sURL = "private:factory/scalc";
                                                  break;
        case SvtModuleOptions::EFactory::DRAW          :  sURL = "private:factory/sdraw";
                                                  break;
        case SvtModuleOptions::EFactory::IMPRESS       :  sURL = "private:factory/simpress?slot=6686";
                                                  break;
        case SvtModuleOptions::EFactory::MATH          :  sURL = "private:factory/smath";
                                                  break;
        case SvtModuleOptions::EFactory::CHART         :  sURL = "private:factory/schart";
                                                  break;
        case SvtModuleOptions::EFactory::BASIC         :  sURL = "private:factory/sbasic";
                                                  break;
        case SvtModuleOptions::EFactory::DATABASE     :  sURL = "private:factory/sdatabase?Interactive";
                                                  break;
        default:
            OSL_FAIL( "unknown factory" );
            break;
    }
    return sURL;
}

OUString const & SvtModuleOptions_Impl::GetFactoryDefaultFilter( SvtModuleOptions::EFactory eFactory ) const
{
    return m_lFactories[eFactory].getDefaultFilter();
}

bool SvtModuleOptions_Impl::IsDefaultFilterReadonly( SvtModuleOptions::EFactory eFactory   ) const
{
    return m_lFactories[eFactory].isDefaultFilterReadonly();
}

sal_Int32 SvtModuleOptions_Impl::GetFactoryIcon( SvtModuleOptions::EFactory eFactory ) const
{
    return m_lFactories[eFactory].getIcon();
}

void SvtModuleOptions_Impl::SetFactoryStandardTemplate(       SvtModuleOptions::EFactory eFactory   ,
                                                        const OUString&           sTemplate  )
{
    m_lFactories[eFactory].setTemplateFile( sTemplate );
    SetModified();
}

void SvtModuleOptions_Impl::SetFactoryDefaultFilter(       SvtModuleOptions::EFactory eFactory,
                                                     const OUString&           sFilter )
{
    m_lFactories[eFactory].setDefaultFilter( sFilter );
    SetModified();
}

/*-************************************************************************************************************
    @short      return list of key names of our configuration management which represent our module tree
    @descr      You give use a list of current existing set node names .. and we expand it for all
                well known properties which are necessary for this implementation.
                These full expanded list should be used to get values of this properties.

    @seealso    ctor
    @return     List of all relative addressed properties of given set entry names.

    @onerror    List will be empty.
    @threadsafe no
*//*-*************************************************************************************************************/
css::uno::Sequence< OUString > SvtModuleOptions_Impl::impl_ExpandSetNames( const css::uno::Sequence< OUString >& lSetNames )
{
    sal_Int32 nCount     = lSetNames.getLength();
    css::uno::Sequence< OUString > lPropNames ( nCount*PROPERTYCOUNT );
    OUString* pPropNames = lPropNames.getArray();
    sal_Int32 nPropStart = 0;

    for( const auto& rSetName : lSetNames )
    {
        pPropNames[nPropStart+PROPERTYHANDLE_SHORTNAME       ] = rSetName + PATHSEPARATOR PROPERTYNAME_SHORTNAME;
        pPropNames[nPropStart+PROPERTYHANDLE_TEMPLATEFILE    ] = rSetName + PATHSEPARATOR PROPERTYNAME_TEMPLATEFILE;
        pPropNames[nPropStart+PROPERTYHANDLE_WINDOWATTRIBUTES] = rSetName + PATHSEPARATOR PROPERTYNAME_WINDOWATTRIBUTES;
        pPropNames[nPropStart+PROPERTYHANDLE_EMPTYDOCUMENTURL] = rSetName + PATHSEPARATOR PROPERTYNAME_EMPTYDOCUMENTURL;
        pPropNames[nPropStart+PROPERTYHANDLE_DEFAULTFILTER   ] = rSetName + PATHSEPARATOR PROPERTYNAME_DEFAULTFILTER;
        pPropNames[nPropStart+PROPERTYHANDLE_ICON            ] = rSetName + PATHSEPARATOR PROPERTYNAME_ICON;
        nPropStart += PROPERTYCOUNT;
    }

    return lPropNames;
}

/*-************************************************************************************************************
    @short      helper to classify given factory by name
    @descr      Every factory has its own long and short name. So we can match right enum value for internal using.

    @attention  We change in/out parameter "eFactory" in every case! But you should use it only, if return value is sal_True!
                Algorithm:  Set out-parameter to probably value ... and check the longname.
                            If it matches with these factory - break operation and return true AND right set parameter.
                            Otherwise try next one and so on. If no factory was found return false. Out parameter eFactory
                            is set to last tried value but shouldn't be used! Because our return value is false!
    @param      "sLongName" , long name of factory, which should be classified
    @return     "eFactory"  , right enum value, which match given long name
                and true for successfully classification, false otherwise

    @onerror    We return false.
    @threadsafe no
*//*-*************************************************************************************************************/
bool SvtModuleOptions_Impl::ClassifyFactoryByName( std::u16string_view sName, SvtModuleOptions::EFactory& eFactory )
{
    bool bState;

    eFactory = SvtModuleOptions::EFactory::WRITER;
    bState   = ( sName == FACTORYNAME_WRITER );

    if( !bState )
    {
        eFactory = SvtModuleOptions::EFactory::WRITERWEB;
        bState   = ( sName == FACTORYNAME_WRITERWEB );
    }
    // no else!
    if( !bState )
    {
        eFactory = SvtModuleOptions::EFactory::WRITERGLOBAL;
        bState   = ( sName == FACTORYNAME_WRITERGLOBAL );
    }
    // no else!
    if( !bState )
    {
        eFactory = SvtModuleOptions::EFactory::CALC;
        bState   = ( sName == FACTORYNAME_CALC );
    }
    // no else!
    if( !bState )
    {
        eFactory = SvtModuleOptions::EFactory::DRAW;
        bState   = ( sName == FACTORYNAME_DRAW );
    }
    // no else!
    if( !bState )
    {
        eFactory = SvtModuleOptions::EFactory::IMPRESS;
        bState   = ( sName == FACTORYNAME_IMPRESS );
    }
    // no else!
    if( !bState )
    {
        eFactory = SvtModuleOptions::EFactory::MATH;
        bState   = ( sName == FACTORYNAME_MATH );
    }
    // no else!
    if( !bState )
    {
        eFactory = SvtModuleOptions::EFactory::CHART;
        bState   = ( sName == FACTORYNAME_CHART );
    }
    // no else!
    if( !bState )
    {
        eFactory = SvtModuleOptions::EFactory::DATABASE;
        bState   = ( sName == FACTORYNAME_DATABASE );
    }
    // no else!
    if( !bState )
    {
        eFactory = SvtModuleOptions::EFactory::STARTMODULE;
        bState   = ( sName == FACTORYNAME_STARTMODULE);
    }
    // no else!
    if( !bState )
    {
        eFactory = SvtModuleOptions::EFactory::BASIC;
        bState   = ( sName == FACTORYNAME_BASIC);
    }

    return bState;
}

/*-************************************************************************************************************
    @short      read factory configuration
    @descr      Give us a list of pure factory names (long names!) which can be used as
                direct set node names... and we read her property values and fill internal list.
                These method can be used by initial reading at ctor and later updating by "Notify()".

    @seealso    ctor
    @seealso    method Notify()

    @param      "lFactories" is the list of set node entries which should be read.
    @onerror    We do nothing.
    @threadsafe no
*//*-*************************************************************************************************************/
void SvtModuleOptions_Impl::impl_Read( const css::uno::Sequence< OUString >& lFactories )
{
    // Expand every set node name in lFactories to full qualified paths to its properties
    // and get right values from configuration.
    const css::uno::Sequence< OUString > lProperties = impl_ExpandSetNames( lFactories  );
    const css::uno::Sequence< css::uno::Any >   lValues     = GetProperties( lProperties );

    // Safe impossible cases.
    // We need values from ALL configuration keys.
    // Follow assignment use order of values in relation to our list of key names!
    OSL_ENSURE( !(lProperties.getLength()!=lValues.getLength()), "SvtModuleOptions_Impl::impl_Read()\nI miss some values of configuration keys!" );

    // Algorithm:   We step over all given factory names and classify it. These enum value can be used as direct index
    //              in our member list m_lFactories! VAriable nPropertyStart marks start position of every factory
    //              and her properties in expanded property/value list. The defines PROPERTHANDLE_xxx are used as offset values
    //              added to nPropertyStart. So we can address every property relative in these lists.
    //              If we found any valid values ... we reset all existing information for corresponding m_lFactories-entry and
    //              use a pointer to these struct in memory directly to set new values.
    //              But we set it only, if bInstalled is true. Otherwise all other values of a factory can be undeclared .. They
    //              shouldn't be used then.
    // Attention:   If a propertyset of a factory will be ignored we must step to next start position of next factory infos!
    //              see "nPropertyStart += PROPERTYCOUNT" ...

    sal_Int32                   nPropertyStart  = 0;
    FactoryInfo*                pInfo           = nullptr;
    SvtModuleOptions::EFactory  eFactory;

    for( const OUString& sFactoryName : lFactories )
    {
        if( ClassifyFactoryByName( sFactoryName, eFactory ) )
        {
            OUString sTemp;
            sal_Int32       nTemp = 0;

            pInfo = &(m_lFactories[eFactory]);
            pInfo->free();

            pInfo->initInstalled();
            pInfo->initFactory  ( sFactoryName );

            if (lValues[nPropertyStart+PROPERTYHANDLE_TEMPLATEFILE] >>= sTemp)
                pInfo->initTemplateFile( sTemp );
            if (lValues[nPropertyStart+PROPERTYHANDLE_DEFAULTFILTER   ] >>= sTemp)
                pInfo->initDefaultFilter( sTemp );
            if (lValues[nPropertyStart+PROPERTYHANDLE_ICON] >>= nTemp)
                pInfo->initIcon( nTemp );
        }
        nPropertyStart += PROPERTYCOUNT;
    }
}

void SvtModuleOptions_Impl::MakeReadonlyStatesAvailable()
{
    if (m_bReadOnlyStatesWellKnown)
        return;

    css::uno::Sequence< OUString > lFactories = GetNodeNames(OUString());
    for (OUString& rFactory : asNonConstRange(lFactories))
        rFactory += PATHSEPARATOR PROPERTYNAME_DEFAULTFILTER;

    css::uno::Sequence< sal_Bool > lReadonlyStates = GetReadOnlyStates(lFactories);
    sal_Int32 c = lFactories.getLength();
    for (sal_Int32 i=0; i<c; ++i)
    {
        const OUString&            rFactoryName = std::as_const(lFactories)[i];
        SvtModuleOptions::EFactory  eFactory;

        if (!ClassifyFactoryByName(rFactoryName, eFactory))
            continue;

        FactoryInfo& rInfo = m_lFactories[eFactory];
        rInfo.setDefaultFilterReadonly(lReadonlyStates[i]);
    }

    m_bReadOnlyStatesWellKnown = true;
}

namespace {
    //global
    std::weak_ptr<SvtModuleOptions_Impl> g_pModuleOptions;

std::mutex& impl_GetOwnStaticMutex()
{
    static std::mutex s_Mutex;
    return s_Mutex;
}
}

/*-************************************************************************************************************
    @short      standard constructor and destructor
    @descr      This will initialize an instance with default values. We initialize/deinitialize our static data
                container and create a static mutex, which is used for threadsafe code in further time of this object.

    @seealso    method impl_GetOwnStaticMutex()
    @threadsafe yes
*//*-*************************************************************************************************************/
SvtModuleOptions::SvtModuleOptions()
{
    // no need to take the mutex yet, shared_ptr/weak_ptr are thread-safe
    m_pImpl = g_pModuleOptions.lock();
    if( m_pImpl )
        return;

    // take the mutex, so we don't accidentally create more than one
    std::unique_lock aGuard( impl_GetOwnStaticMutex() );

    m_pImpl = g_pModuleOptions.lock();
    if( !m_pImpl )
    {
        m_pImpl = std::make_shared<SvtModuleOptions_Impl>();
        g_pModuleOptions = m_pImpl;
        aGuard.unlock(); // because holdConfigItem will call this constructor
        ItemHolder1::holdConfigItem(EItem::ModuleOptions);
    }
}

SvtModuleOptions::~SvtModuleOptions()
{
    m_pImpl.reset();
}

/*-************************************************************************************************************
    @short      access to configuration data
    @descr      This methods allow read/write access to configuration values.
                They are threadsafe. All calls are forwarded to impl-data-container. See there for further information!

    @seealso    method impl_GetOwnStaticMutex()
    @threadsafe yes
*//*-*************************************************************************************************************/
bool SvtModuleOptions::IsModuleInstalled( EModule eModule ) const
{
    // doesn't need mutex, never modified
    return m_pImpl->IsModuleInstalled( eModule );
}

const OUString & SvtModuleOptions::GetFactoryName( EFactory eFactory ) const
{
    // doesn't need mutex, never modified
    return m_pImpl->GetFactoryName( eFactory );
}

OUString SvtModuleOptions::GetFactoryStandardTemplate( EFactory eFactory ) const
{
    std::unique_lock aGuard( impl_GetOwnStaticMutex() );
    return m_pImpl->GetFactoryStandardTemplate( eFactory );
}

OUString SvtModuleOptions::GetFactoryEmptyDocumentURL( EFactory eFactory ) const
{
    std::unique_lock aGuard( impl_GetOwnStaticMutex() );
    return SvtModuleOptions_Impl::GetFactoryEmptyDocumentURL( eFactory );
}

OUString SvtModuleOptions::GetFactoryDefaultFilter( EFactory eFactory ) const
{
    std::unique_lock aGuard( impl_GetOwnStaticMutex() );
    return m_pImpl->GetFactoryDefaultFilter( eFactory );
}

bool SvtModuleOptions::IsDefaultFilterReadonly( EFactory eFactory   ) const
{
    std::unique_lock aGuard( impl_GetOwnStaticMutex() );
    m_pImpl->MakeReadonlyStatesAvailable();
    return m_pImpl->IsDefaultFilterReadonly( eFactory );
}

sal_Int32 SvtModuleOptions::GetFactoryIcon( EFactory eFactory ) const
{
    std::unique_lock aGuard( impl_GetOwnStaticMutex() );
    return m_pImpl->GetFactoryIcon( eFactory );
}

bool SvtModuleOptions::ClassifyFactoryByName( std::u16string_view sName    ,
                                                        EFactory&        eFactory )
{
    // We don't need any mutex here ... because we don't use any member here!
    return SvtModuleOptions_Impl::ClassifyFactoryByName( sName, eFactory );
}

void SvtModuleOptions::SetFactoryStandardTemplate(       EFactory         eFactory   ,
                                                   const OUString& sTemplate  )
{
    std::unique_lock aGuard( impl_GetOwnStaticMutex() );
    m_pImpl->SetFactoryStandardTemplate( eFactory, sTemplate );
}

void SvtModuleOptions::SetFactoryDefaultFilter(       EFactory         eFactory,
                                                const OUString& sFilter )
{
    std::unique_lock aGuard( impl_GetOwnStaticMutex() );
    m_pImpl->SetFactoryDefaultFilter( eFactory, sFilter );
}

bool SvtModuleOptions::IsMath() const
{
    // doesn't need mutex, never modified
    return m_pImpl->IsModuleInstalled( EModule::MATH );
}

bool SvtModuleOptions::IsChart() const
{
    // doesn't need mutex, never modified
    return m_pImpl->IsModuleInstalled( EModule::CHART );
}

bool SvtModuleOptions::IsCalc() const
{
    // doesn't need mutex, never modified
    return m_pImpl->IsModuleInstalled( EModule::CALC );
}

bool SvtModuleOptions::IsDraw() const
{
    // doesn't need mutex, never modified
    return m_pImpl->IsModuleInstalled( EModule::DRAW );
}

bool SvtModuleOptions::IsWriter() const
{
    std::unique_lock aGuard( impl_GetOwnStaticMutex() );
    return m_pImpl->IsModuleInstalled( EModule::WRITER );
}

bool SvtModuleOptions::IsImpress() const
{
    // doesn't need mutex, never modified
    return m_pImpl->IsModuleInstalled( EModule::IMPRESS );
}

bool SvtModuleOptions::IsDataBase() const
{
    // doesn't need mutex, never modified
    return m_pImpl->IsModuleInstalled( EModule::DATABASE );
}

OUString SvtModuleOptions::GetModuleName( EModule eModule ) const
{
    switch( eModule )
    {
        case SvtModuleOptions::EModule::WRITER    :   { return "Writer"; }
        case SvtModuleOptions::EModule::WEB       :   { return "Web"; }
        case SvtModuleOptions::EModule::GLOBAL    :   { return "Global"; }
        case SvtModuleOptions::EModule::CALC      :   { return "Calc"; }
        case SvtModuleOptions::EModule::DRAW      :   { return "Draw"; }
        case SvtModuleOptions::EModule::IMPRESS   :   { return "Impress"; }
        case SvtModuleOptions::EModule::MATH      :   { return "Math"; }
        case SvtModuleOptions::EModule::CHART     :   { return "Chart"; }
        case SvtModuleOptions::EModule::BASIC     :   { return "Basic"; }
        case SvtModuleOptions::EModule::DATABASE  :   { return "Database"; }
        default:
            OSL_FAIL( "unknown module" );
            break;
    }

    return OUString();
}

SvtModuleOptions::EFactory SvtModuleOptions::ClassifyFactoryByShortName(std::u16string_view sName)
{
    if ( sName == u"swriter" )
        return EFactory::WRITER;
    if (o3tl::equalsIgnoreAsciiCase(sName, u"swriter/Web")) // sometimes they are registered for swriter/web :-(
        return EFactory::WRITERWEB;
    if (o3tl::equalsIgnoreAsciiCase(sName, u"swriter/GlobalDocument")) // sometimes they are registered for swriter/globaldocument :-(
        return EFactory::WRITERGLOBAL;
    if ( sName == u"scalc" )
        return EFactory::CALC;
    if ( sName == u"sdraw" )
        return EFactory::DRAW;
    if ( sName == u"simpress" )
        return EFactory::IMPRESS;
    if ( sName == u"schart" )
        return EFactory::CHART;
    if ( sName == u"smath" )
        return EFactory::MATH;
    if ( sName == u"sbasic" )
        return EFactory::BASIC;
    if ( sName == u"sdatabase" )
        return EFactory::DATABASE;

    return EFactory::UNKNOWN_FACTORY;
}

SvtModuleOptions::EFactory SvtModuleOptions::ClassifyFactoryByServiceName(std::u16string_view sName)
{
    if (sName == FACTORYNAME_WRITERGLOBAL)
        return EFactory::WRITERGLOBAL;
    if (sName == FACTORYNAME_WRITERWEB)
        return EFactory::WRITERWEB;
    if (sName == FACTORYNAME_WRITER)
        return EFactory::WRITER;
    if (sName == FACTORYNAME_CALC)
        return EFactory::CALC;
    if (sName == FACTORYNAME_DRAW)
        return EFactory::DRAW;
    if (sName == FACTORYNAME_IMPRESS)
        return EFactory::IMPRESS;
    if (sName == FACTORYNAME_MATH)
        return EFactory::MATH;
    if (sName == FACTORYNAME_CHART)
        return EFactory::CHART;
    if (sName == FACTORYNAME_DATABASE)
        return EFactory::DATABASE;
    if (sName == FACTORYNAME_STARTMODULE)
        return EFactory::STARTMODULE;
    if (sName == FACTORYNAME_BASIC)
        return EFactory::BASIC;

    return EFactory::UNKNOWN_FACTORY;
}

SvtModuleOptions::EFactory SvtModuleOptions::ClassifyFactoryByURL(const OUString&                                 sURL            ,
                                                                  const css::uno::Sequence< css::beans::PropertyValue >& lMediaDescriptor)
{
    css::uno::Reference< css::uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();

    css::uno::Reference< css::container::XNameAccess > xFilterCfg;
    css::uno::Reference< css::container::XNameAccess > xTypeCfg;
    try
    {
        xFilterCfg.set(
            xContext->getServiceManager()->createInstanceWithContext("com.sun.star.document.FilterFactory", xContext), css::uno::UNO_QUERY);
        xTypeCfg.set(
            xContext->getServiceManager()->createInstanceWithContext("com.sun.star.document.TypeDetection", xContext), css::uno::UNO_QUERY);
    }
    catch(const css::uno::RuntimeException&)
        { throw; }
    catch(const css::uno::Exception&)
        { return EFactory::UNKNOWN_FACTORY; }

    ::comphelper::SequenceAsHashMap stlDesc(lMediaDescriptor);

    // is there already a filter inside the descriptor?
    OUString sFilterName = stlDesc.getUnpackedValueOrDefault("FilterName", OUString());
    if (!sFilterName.isEmpty())
    {
        try
        {
            ::comphelper::SequenceAsHashMap stlFilterProps   (xFilterCfg->getByName(sFilterName));
            OUString                 sDocumentService = stlFilterProps.getUnpackedValueOrDefault("DocumentService", OUString());
            SvtModuleOptions::EFactory      eApp             = SvtModuleOptions::ClassifyFactoryByServiceName(sDocumentService);

            if (eApp != EFactory::UNKNOWN_FACTORY)
                return eApp;
        }
        catch(const css::uno::RuntimeException&)
            { throw; }
        catch(const css::uno::Exception&)
            { /* do nothing here ... may the following code can help!*/ }
    }

    // is there already a type inside the descriptor?
    OUString sTypeName = stlDesc.getUnpackedValueOrDefault("TypeName", OUString());
    if (sTypeName.isEmpty())
    {
        // no :-(
        // start flat detection of URL
        css::uno::Reference< css::document::XTypeDetection > xDetect(xTypeCfg, css::uno::UNO_QUERY);
        sTypeName = xDetect->queryTypeByURL(sURL);
    }

    if (sTypeName.isEmpty())
        return EFactory::UNKNOWN_FACTORY;

    // yes - there is a type info
    // Try to find the preferred filter.
    try
    {
        ::comphelper::SequenceAsHashMap stlTypeProps     (xTypeCfg->getByName(sTypeName));
        OUString                 sPreferredFilter = stlTypeProps.getUnpackedValueOrDefault("PreferredFilter", OUString());
        ::comphelper::SequenceAsHashMap stlFilterProps   (xFilterCfg->getByName(sPreferredFilter));
        OUString                 sDocumentService = stlFilterProps.getUnpackedValueOrDefault("DocumentService", OUString());
        SvtModuleOptions::EFactory      eApp             = SvtModuleOptions::ClassifyFactoryByServiceName(sDocumentService);

        if (eApp != EFactory::UNKNOWN_FACTORY)
            return eApp;
    }
    catch(const css::uno::RuntimeException&)
        { throw; }
    catch(const css::uno::Exception&)
        { /* do nothing here ... may the following code can help!*/ }

    // no filter/no type/no detection result => no fun :-)
    return EFactory::UNKNOWN_FACTORY;
}

SvtModuleOptions::EFactory SvtModuleOptions::ClassifyFactoryByModel(const css::uno::Reference< css::frame::XModel >& xModel)
{
    css::uno::Reference< css::lang::XServiceInfo > xInfo(xModel, css::uno::UNO_QUERY);
    if (!xInfo.is())
        return EFactory::UNKNOWN_FACTORY;

    const css::uno::Sequence< OUString > lServices = xInfo->getSupportedServiceNames();

    for (const OUString& rService : lServices)
    {
        SvtModuleOptions::EFactory eApp = SvtModuleOptions::ClassifyFactoryByServiceName(rService);
        if (eApp != EFactory::UNKNOWN_FACTORY)
            return eApp;
    }

    return EFactory::UNKNOWN_FACTORY;
}

css::uno::Sequence < OUString > SvtModuleOptions::GetAllServiceNames()
{
    std::unique_lock aGuard( impl_GetOwnStaticMutex() );
    return m_pImpl->GetAllServiceNames();
}

OUString SvtModuleOptions::GetDefaultModuleName() const
{
    OUString aModule;
    if (m_pImpl->IsModuleInstalled(SvtModuleOptions::EModule::WRITER))
        aModule = GetFactoryShortName(SvtModuleOptions::EFactory::WRITER);
    else if (m_pImpl->IsModuleInstalled(SvtModuleOptions::EModule::CALC))
        aModule = GetFactoryShortName(SvtModuleOptions::EFactory::CALC);
    else if (m_pImpl->IsModuleInstalled(SvtModuleOptions::EModule::IMPRESS))
        aModule = GetFactoryShortName(SvtModuleOptions::EFactory::IMPRESS);
    else if (m_pImpl->IsModuleInstalled(SvtModuleOptions::EModule::DATABASE))
        aModule = GetFactoryShortName(SvtModuleOptions::EFactory::DATABASE);
    else if (m_pImpl->IsModuleInstalled(SvtModuleOptions::EModule::DRAW))
        aModule = GetFactoryShortName(SvtModuleOptions::EFactory::DRAW);
    else if (m_pImpl->IsModuleInstalled(SvtModuleOptions::EModule::WEB))
        aModule = GetFactoryShortName(SvtModuleOptions::EFactory::WRITERWEB);
    else if (m_pImpl->IsModuleInstalled(SvtModuleOptions::EModule::GLOBAL))
        aModule = GetFactoryShortName(SvtModuleOptions::EFactory::WRITERGLOBAL);
    else if (m_pImpl->IsModuleInstalled(SvtModuleOptions::EModule::MATH))
        aModule = GetFactoryShortName(SvtModuleOptions::EFactory::MATH);
    return aModule;
}

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