/* -*- 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 <memory>
#include <dpsave.hxx>
#include <dpdimsave.hxx>
#include <miscuno.hxx>
#include <unonames.hxx>
#include <dputil.hxx>
#include <generalfunction.hxx>
#include <dptabdat.hxx>
#include <pivot/PivotTableFormats.hxx>
#include <tools/XmlWriter.hxx>

#include <sal/types.h>
#include <sal/log.hxx>
#include <osl/diagnose.h>
#include <comphelper/stl_types.hxx>
#include <unotools/charclass.hxx>

#include <com/sun/star/sheet/XDimensionsSupplier.hpp>
#include <com/sun/star/sheet/DataPilotFieldAutoShowInfo.hpp>
#include <com/sun/star/sheet/DataPilotFieldLayoutInfo.hpp>
#include <com/sun/star/sheet/DataPilotFieldReference.hpp>
#include <com/sun/star/sheet/DataPilotFieldSortInfo.hpp>
#include <com/sun/star/sheet/DataPilotFieldSortMode.hpp>
#include <com/sun/star/sheet/XHierarchiesSupplier.hpp>
#include <com/sun/star/sheet/XLevelsSupplier.hpp>
#include <com/sun/star/sheet/XMembersSupplier.hpp>
#include <com/sun/star/container/XNamed.hpp>
#include <com/sun/star/util/XCloneable.hpp>
#include <comphelper/diagnose_ex.hxx>

#include <unordered_map>
#include <algorithm>
#include <utility>

using namespace com::sun::star;
using namespace com::sun::star::sheet;

#define SC_DPSAVEMODE_DONTKNOW 2

static void lcl_SetBoolProperty( const uno::Reference<beans::XPropertySet>& xProp,
                            const OUString& rName, bool bValue )
{
    //TODO: move to ScUnoHelpFunctions?

    xProp->setPropertyValue( rName, uno::Any( bValue ) );
}

ScDPSaveMember::ScDPSaveMember(OUString _aName) :
    aName(std::move( _aName )),
    nVisibleMode( SC_DPSAVEMODE_DONTKNOW ),
    nShowDetailsMode( SC_DPSAVEMODE_DONTKNOW )
{
}

ScDPSaveMember::ScDPSaveMember(const ScDPSaveMember& r) :
    aName( r.aName ),
    mpLayoutName( r.mpLayoutName ),
    nVisibleMode( r.nVisibleMode ),
    nShowDetailsMode( r.nShowDetailsMode )
{
}

ScDPSaveMember::~ScDPSaveMember()
{
}

bool ScDPSaveMember::operator== ( const ScDPSaveMember& r ) const
{
    return aName            == r.aName           &&
           nVisibleMode     == r.nVisibleMode    &&
           nShowDetailsMode == r.nShowDetailsMode;
}

bool ScDPSaveMember::HasIsVisible() const
{
    return nVisibleMode != SC_DPSAVEMODE_DONTKNOW;
}

void ScDPSaveMember::SetIsVisible(bool bSet)
{
    nVisibleMode = sal_uInt16(bSet);
}

bool ScDPSaveMember::HasShowDetails() const
{
    return nShowDetailsMode != SC_DPSAVEMODE_DONTKNOW;
}

void ScDPSaveMember::SetShowDetails(bool bSet)
{
    nShowDetailsMode = sal_uInt16(bSet);
}

void ScDPSaveMember::SetName( const OUString& rNew )
{
    // Used only if the source member was renamed (groups).
    // For UI renaming of members, a layout name must be used.

    aName = rNew;
}

void ScDPSaveMember::SetLayoutName( const OUString& rName )
{
    mpLayoutName = rName;
}

const std::optional<OUString> & ScDPSaveMember::GetLayoutName() const
{
    return mpLayoutName;
}

void ScDPSaveMember::RemoveLayoutName()
{
    mpLayoutName.reset();
}

void ScDPSaveMember::WriteToSource( const uno::Reference<uno::XInterface>& xMember, sal_Int32 nPosition )
{
    uno::Reference<beans::XPropertySet> xMembProp( xMember, uno::UNO_QUERY );
    OSL_ENSURE( xMembProp.is(), "no properties at member" );
    if ( !xMembProp.is() )
        return;

    // exceptions are caught at ScDPSaveData::WriteToSource

    if ( nVisibleMode != SC_DPSAVEMODE_DONTKNOW )
        lcl_SetBoolProperty( xMembProp,
                SC_UNO_DP_ISVISIBLE, static_cast<bool>(nVisibleMode) );

    if ( nShowDetailsMode != SC_DPSAVEMODE_DONTKNOW )
        lcl_SetBoolProperty( xMembProp,
                SC_UNO_DP_SHOWDETAILS, static_cast<bool>(nShowDetailsMode) );

    if (mpLayoutName)
        ScUnoHelpFunctions::SetOptionalPropertyValue(xMembProp, SC_UNO_DP_LAYOUTNAME, *mpLayoutName);

    if ( nPosition >= 0 )
        ScUnoHelpFunctions::SetOptionalPropertyValue(xMembProp, SC_UNO_DP_POSITION, nPosition);
}

void ScDPSaveMember::dumpAsXml(tools::XmlWriter& rWriter) const
{
    rWriter.startElement("member");
    rWriter.attribute("name", aName);
    if (mpLayoutName)
        rWriter.attribute("layout_name", *mpLayoutName);
    else
        rWriter.attribute("layout_name", "(none)");

    if (nVisibleMode == SC_DPSAVEMODE_DONTKNOW)
        rWriter.attribute("visibility", "(unknown)");
    else
        rWriter.attribute("visibility", (nVisibleMode ? "visible" : "hidden"));
    rWriter.endElement();
}

#if DUMP_PIVOT_TABLE

void ScDPSaveMember::Dump(int nIndent) const
{
    std::string aIndent(nIndent*4, ' ');
    std::cout << aIndent << "* member name: '" << aName << "'" << std::endl;

    std::cout << aIndent << "    + layout name: ";
    if (mpLayoutName)
        std::cout << "'" << *mpLayoutName << "'";
    else
        std::cout << "(none)";
    std::cout << std::endl;

    std::cout << aIndent << "    + visibility: ";
    if (nVisibleMode == SC_DPSAVEMODE_DONTKNOW)
        std::cout << "(unknown)";
    else
        std::cout << (nVisibleMode ? "visible" : "hidden");
    std::cout << std::endl;
}

#endif

ScDPSaveDimension::ScDPSaveDimension(OUString _aName, bool bDataLayout) :
    aName(std::move( _aName )),
    bIsDataLayout( bDataLayout ),
    bDupFlag( false ),
    nOrientation( sheet::DataPilotFieldOrientation_HIDDEN ),
    nFunction( ScGeneralFunction::AUTO ),
    nUsedHierarchy( -1 ),
    nShowEmptyMode( SC_DPSAVEMODE_DONTKNOW ),
    bRepeatItemLabels( false ),
    bSubTotalDefault( true )
{
}

ScDPSaveDimension::ScDPSaveDimension(const ScDPSaveDimension& r) :
    aName( r.aName ),
    mpLayoutName( r.mpLayoutName ),
    mpSubtotalName( r.mpSubtotalName ),
    bIsDataLayout( r.bIsDataLayout ),
    bDupFlag( r.bDupFlag ),
    nOrientation( r.nOrientation ),
    nFunction( r.nFunction ),
    nUsedHierarchy( r.nUsedHierarchy ),
    nShowEmptyMode( r.nShowEmptyMode ),
    bRepeatItemLabels( r.bRepeatItemLabels ),
    bSubTotalDefault( r.bSubTotalDefault ),
    maSubTotalFuncs( r.maSubTotalFuncs )
{
    for (const ScDPSaveMember* pMem : r.maMemberList)
    {
        const OUString& rName = pMem->GetName();
        std::unique_ptr<ScDPSaveMember> pNew(new ScDPSaveMember( *pMem ));
        maMemberList.push_back( pNew.get() );
        maMemberHash[rName] = std::move(pNew);
    }
    if (r.pReferenceValue)
        pReferenceValue.reset( new sheet::DataPilotFieldReference( *(r.pReferenceValue) ) );
    if (r.pSortInfo)
        pSortInfo.reset( new sheet::DataPilotFieldSortInfo( *(r.pSortInfo) ) );
    if (r.pAutoShowInfo)
        pAutoShowInfo.reset( new sheet::DataPilotFieldAutoShowInfo( *(r.pAutoShowInfo) ) );
    if (r.pLayoutInfo)
        pLayoutInfo.reset(new sheet::DataPilotFieldLayoutInfo( *(r.pLayoutInfo) ));
}

ScDPSaveDimension::~ScDPSaveDimension()
{
    maMemberHash.clear();
    pReferenceValue.reset();
    pSortInfo.reset();
    pAutoShowInfo.reset();
    pLayoutInfo.reset();
}

bool ScDPSaveDimension::operator== ( const ScDPSaveDimension& r ) const
{
    if ( aName            != r.aName            ||
         bIsDataLayout    != r.bIsDataLayout    ||
         bDupFlag         != r.bDupFlag         ||
         nOrientation     != r.nOrientation     ||
         nFunction        != r.nFunction        ||
         nUsedHierarchy   != r.nUsedHierarchy   ||
         nShowEmptyMode   != r.nShowEmptyMode   ||
         bRepeatItemLabels!= r.bRepeatItemLabels||
         bSubTotalDefault != r.bSubTotalDefault ||
         maSubTotalFuncs  != r.maSubTotalFuncs   )
        return false;

    if (maMemberHash.size() != r.maMemberHash.size() )
        return false;

    if (!std::equal(maMemberList.begin(), maMemberList.end(), r.maMemberList.begin(), r.maMemberList.end(),
                    [](const ScDPSaveMember* a, const ScDPSaveMember* b) { return *a == *b; }))
        return false;

    if( pReferenceValue && r.pReferenceValue )
    {
        if ( *pReferenceValue != *r.pReferenceValue )
        {
            return false;
        }
    }
    else if ( pReferenceValue || r.pReferenceValue )
    {
        return false;
    }
    if( this->pSortInfo && r.pSortInfo )
    {
        if ( *this->pSortInfo != *r.pSortInfo )
        {
            return false;
        }
    }
    else if ( this->pSortInfo || r.pSortInfo )
    {
        return false;
    }
    if( this->pAutoShowInfo && r.pAutoShowInfo )
    {
        if ( *this->pAutoShowInfo != *r.pAutoShowInfo )
        {
            return false;
        }
    }
    else if ( this->pAutoShowInfo || r.pAutoShowInfo )
    {
        return false;
    }

    return true;
}

void ScDPSaveDimension::AddMember(std::unique_ptr<ScDPSaveMember> pMember)
{
    const OUString & rName = pMember->GetName();
    auto aExisting = maMemberHash.find( rName );
    auto tmp = pMember.get();
    if ( aExisting == maMemberHash.end() )
    {
        maMemberHash[rName] = std::move(pMember);
    }
    else
    {
        std::erase(maMemberList, aExisting->second.get());
        aExisting->second = std::move(pMember);
    }
    maMemberList.push_back( tmp );
}

void ScDPSaveDimension::SetName( const OUString& rNew )
{
    // Used only if the source dim was renamed (groups).
    // For UI renaming of dimensions, the layout name must be used.

    aName = rNew;
}

void ScDPSaveDimension::SetOrientation(css::sheet::DataPilotFieldOrientation nNew)
{
    nOrientation = nNew;
}

void ScDPSaveDimension::SetSubTotals(std::vector<ScGeneralFunction> && rFuncs)
{
    maSubTotalFuncs = std::move(rFuncs);
    bSubTotalDefault = false;
}

bool ScDPSaveDimension::HasShowEmpty() const
{
    return nShowEmptyMode != SC_DPSAVEMODE_DONTKNOW;
}

void ScDPSaveDimension::SetShowEmpty(bool bSet)
{
    nShowEmptyMode = sal_uInt16(bSet);
}

void ScDPSaveDimension::SetRepeatItemLabels(bool bSet)
{
    bRepeatItemLabels = bSet;
}

void ScDPSaveDimension::SetFunction(ScGeneralFunction nNew)
{
    nFunction = nNew;
}

void ScDPSaveDimension::SetUsedHierarchy(tools::Long nNew)
{
    nUsedHierarchy = nNew;
}

void ScDPSaveDimension::SetSubtotalName(const OUString& rName)
{
    mpSubtotalName = rName;
}

const std::optional<OUString> & ScDPSaveDimension::GetSubtotalName() const
{
    return mpSubtotalName;
}

void ScDPSaveDimension::RemoveSubtotalName()
{
    mpSubtotalName.reset();
}

bool ScDPSaveDimension::IsMemberNameInUse(const OUString& rName) const
{
    return std::any_of(maMemberList.begin(), maMemberList.end(), [&rName](const ScDPSaveMember* pMem) {
        if (rName.equalsIgnoreAsciiCase(pMem->GetName()))
            return true;

        const std::optional<OUString> & pLayoutName = pMem->GetLayoutName();
        return pLayoutName && rName.equalsIgnoreAsciiCase(*pLayoutName);
    });
}

void ScDPSaveDimension::SetLayoutName(const OUString& rName)
{
    mpLayoutName = rName;
}

const std::optional<OUString> & ScDPSaveDimension::GetLayoutName() const
{
    return mpLayoutName;
}

void ScDPSaveDimension::RemoveLayoutName()
{
    mpLayoutName.reset();
}

void ScDPSaveDimension::SetReferenceValue(const sheet::DataPilotFieldReference* pNew)
{
    if (pNew)
        pReferenceValue.reset( new sheet::DataPilotFieldReference(*pNew) );
    else
        pReferenceValue.reset();
}

void ScDPSaveDimension::SetSortInfo(const sheet::DataPilotFieldSortInfo* pNew)
{
    if (pNew)
        pSortInfo.reset( new sheet::DataPilotFieldSortInfo(*pNew) );
    else
        pSortInfo.reset();
}

void ScDPSaveDimension::SetAutoShowInfo(const sheet::DataPilotFieldAutoShowInfo* pNew)
{
    if (pNew)
        pAutoShowInfo.reset( new sheet::DataPilotFieldAutoShowInfo(*pNew) );
    else
        pAutoShowInfo.reset();
}

void ScDPSaveDimension::SetLayoutInfo(const sheet::DataPilotFieldLayoutInfo* pNew)
{
    if (pNew)
        pLayoutInfo.reset( new sheet::DataPilotFieldLayoutInfo(*pNew) );
    else
        pLayoutInfo.reset();
}

void ScDPSaveDimension::SetCurrentPage( const OUString* pPage )
{
    // We use member's visibility attribute to filter by page dimension.

    // pPage == nullptr -> all members visible.
    for (ScDPSaveMember* pMem : maMemberList)
    {
        bool bVisible = !pPage || pMem->GetName() == *pPage;
        pMem->SetIsVisible(bVisible);
    }
}

OUString ScDPSaveDimension::GetCurrentPage() const
{
    MemberList::const_iterator it = std::find_if(maMemberList.begin(), maMemberList.end(),
        [](const ScDPSaveMember* pMem) { return pMem->GetIsVisible(); });
    if (it != maMemberList.end())
        return (*it)->GetName();

    return OUString();
}

ScDPSaveMember* ScDPSaveDimension::GetExistingMemberByName(const OUString& rName)
{
    auto res = maMemberHash.find (rName);
    if (res != maMemberHash.end())
        return res->second.get();
    return nullptr;
}

ScDPSaveMember* ScDPSaveDimension::GetMemberByName(const OUString& rName)
{
    ScDPSaveMember* pResult = GetExistingMemberByName(rName);
    if (pResult)
        return pResult;

    pResult = new ScDPSaveMember(rName);
    maMemberHash[rName] = std::unique_ptr<ScDPSaveMember>(pResult);
    maMemberList.push_back(pResult);
    return pResult;
}

void ScDPSaveDimension::SetMemberPosition( const OUString& rName, sal_Int32 nNewPos )
{
    ScDPSaveMember* pMember = GetMemberByName( rName ); // make sure it exists and is in the hash

    std::erase(maMemberList, pMember);

    maMemberList.insert( maMemberList.begin() + nNewPos, pMember );
}

void ScDPSaveDimension::WriteToSource( const uno::Reference<uno::XInterface>& xDim )
{
    uno::Reference<beans::XPropertySet> xDimProp( xDim, uno::UNO_QUERY );
    OSL_ENSURE( xDimProp.is(), "no properties at dimension" );
    if ( xDimProp.is() )
    {
        // exceptions are caught at ScDPSaveData::WriteToSource

        sheet::DataPilotFieldOrientation eOrient = nOrientation;
        xDimProp->setPropertyValue( SC_UNO_DP_ORIENTATION, uno::Any(eOrient) );

        sal_Int16 eFunc = static_cast<sal_Int16>(nFunction);
        xDimProp->setPropertyValue( SC_UNO_DP_FUNCTION2, uno::Any(eFunc) );

        if ( nUsedHierarchy >= 0 )
        {
            xDimProp->setPropertyValue( SC_UNO_DP_USEDHIERARCHY, uno::Any(static_cast<sal_Int32>(nUsedHierarchy)) );
        }

        if ( pReferenceValue )
        {
            ;
            xDimProp->setPropertyValue( SC_UNO_DP_REFVALUE, uno::Any(*pReferenceValue) );
        }

        if (mpLayoutName)
            ScUnoHelpFunctions::SetOptionalPropertyValue(xDimProp, SC_UNO_DP_LAYOUTNAME, *mpLayoutName);

        const std::optional<OUString> & pSubTotalName = GetSubtotalName();
        if (pSubTotalName)
            // Custom subtotal name, with '?' being replaced by the visible field name later.
            ScUnoHelpFunctions::SetOptionalPropertyValue(xDimProp, SC_UNO_DP_FIELD_SUBTOTALNAME, *pSubTotalName);
    }

    // Level loop outside of maMemberList loop
    // because SubTotals have to be set independently of known members

    tools::Long nCount = maMemberHash.size();

    tools::Long nHierCount = 0;
    rtl::Reference<ScNameToIndexAccess> xHiers;
    uno::Reference<sheet::XHierarchiesSupplier> xHierSupp( xDim, uno::UNO_QUERY );
    if ( xHierSupp.is() )
    {
        xHiers = new ScNameToIndexAccess(xHierSupp->getHierarchies());
        nHierCount = xHiers->getCount();
    }

    bool bHasHiddenMember = false;

    for (tools::Long nHier=0; nHier<nHierCount; nHier++)
    {
        tools::Long nLevCount = 0;
        rtl::Reference<ScNameToIndexAccess> xLevels;
        uno::Reference<sheet::XLevelsSupplier> xLevSupp(xHiers->getByIndex(nHier), uno::UNO_QUERY);
        if ( xLevSupp.is() )
        {
            xLevels = new ScNameToIndexAccess(xLevSupp->getLevels());
            nLevCount = xLevels->getCount();
        }

        for (tools::Long nLev=0; nLev<nLevCount; nLev++)
        {
            uno::Reference<uno::XInterface> xLevel(xLevels->getByIndex(nLev), uno::UNO_QUERY);
            uno::Reference<beans::XPropertySet> xLevProp( xLevel, uno::UNO_QUERY );
            OSL_ENSURE( xLevProp.is(), "no properties at level" );
            if ( xLevProp.is() )
            {
                if ( !bSubTotalDefault )
                {
                    uno::Sequence<sal_Int16> aSeq(maSubTotalFuncs.size());
                    for(size_t i = 0; i < maSubTotalFuncs.size(); ++i)
                        aSeq.getArray()[i] = static_cast<sal_Int16>(maSubTotalFuncs[i]);
                    xLevProp->setPropertyValue( SC_UNO_DP_SUBTOTAL2, uno::Any(aSeq) );
                }
                if ( nShowEmptyMode != SC_DPSAVEMODE_DONTKNOW )
                    lcl_SetBoolProperty( xLevProp,
                        SC_UNO_DP_SHOWEMPTY, static_cast<bool>(nShowEmptyMode) );

                lcl_SetBoolProperty( xLevProp,
                    SC_UNO_DP_REPEATITEMLABELS, bRepeatItemLabels );

                if ( pSortInfo )
                    ScUnoHelpFunctions::SetOptionalPropertyValue(xLevProp, SC_UNO_DP_SORTING, *pSortInfo);

                if ( pAutoShowInfo )
                    ScUnoHelpFunctions::SetOptionalPropertyValue(xLevProp, SC_UNO_DP_AUTOSHOW, *pAutoShowInfo);

                if ( pLayoutInfo )
                    ScUnoHelpFunctions::SetOptionalPropertyValue(xLevProp, SC_UNO_DP_LAYOUT, *pLayoutInfo);

                // exceptions are caught at ScDPSaveData::WriteToSource
            }

            if ( nCount > 0 )
            {
                uno::Reference<sheet::XMembersSupplier> xMembSupp( xLevel, uno::UNO_QUERY );
                if ( xMembSupp.is() )
                {
                    uno::Reference<sheet::XMembersAccess> xMembers = xMembSupp->getMembers();
                    if ( xMembers.is() )
                    {
                        sal_Int32 nPosition = -1; // set position only in manual mode
                        if ( !pSortInfo || pSortInfo->Mode == sheet::DataPilotFieldSortMode::MANUAL )
                            nPosition = 0;

                        for (ScDPSaveMember* pMember : maMemberList)
                        {
                            if (!pMember->GetIsVisible())
                                bHasHiddenMember = true;
                            OUString aMemberName = pMember->GetName();
                            if ( xMembers->hasByName( aMemberName ) )
                            {
                                uno::Reference<uno::XInterface> xMemberInt(
                                    xMembers->getByName(aMemberName), uno::UNO_QUERY);
                                pMember->WriteToSource( xMemberInt, nPosition );

                                if ( nPosition >= 0 )
                                    ++nPosition; // increase if initialized
                            }
                            // missing member is no error
                        }
                    }
                }
            }
        }
    }

    if (xDimProp.is())
        ScUnoHelpFunctions::SetOptionalPropertyValue(xDimProp, SC_UNO_DP_HAS_HIDDEN_MEMBER, bHasHiddenMember);
}

void ScDPSaveDimension::UpdateMemberVisibility(const std::unordered_map<OUString, bool>& rData)
{
    for (ScDPSaveMember* pMem : maMemberList)
    {
        const OUString& rMemName = pMem->GetName();
        auto itr = rData.find(rMemName);
        if (itr != rData.end())
            pMem->SetIsVisible(itr->second);
    }
}

bool ScDPSaveDimension::HasInvisibleMember() const
{
    return std::any_of(maMemberList.begin(), maMemberList.end(),
        [](const ScDPSaveMember* pMem) { return !pMem->GetIsVisible(); });
}

void ScDPSaveDimension::RemoveObsoleteMembers(const MemberSetType& rMembers)
{
    MemberList aNew;
    for (ScDPSaveMember* pMem : maMemberList)
    {
        if (rMembers.count(pMem->GetName()))
        {
            // This member still exists.
            aNew.push_back(pMem);
        }
        else
        {
            maMemberHash.erase(pMem->GetName());
        }
    }

    maMemberList.swap(aNew);
}

void ScDPSaveDimension::dumpAsXml(tools::XmlWriter& rWriter) const
{
    static constexpr auto sOrientNames = std::to_array<std::string_view>({
        "hidden", "column", "row", "page", "data"
    });

    rWriter.startElement("dimension");
    rWriter.attribute("name", aName);
    if (nOrientation <= DataPilotFieldOrientation_DATA)
        rWriter.attribute("orientation", sOrientNames[sal_Int32(nOrientation)]);
    else
        rWriter.attribute("orientation", "invalid");

    if (mpLayoutName)
        rWriter.attribute("layout_name", *mpLayoutName);

    if (mpSubtotalName)
        rWriter.attribute("subtotal_name", *mpSubtotalName);
    else
        rWriter.attribute("subtotal_name", "(none)");

    rWriter.attribute("data_layout", (bIsDataLayout ? "yes" : "no"));
    rWriter.attribute("duplicate", (bDupFlag ? "yes" : "no"));

    for (ScDPSaveMember* pMember : maMemberList)
    {
        pMember->dumpAsXml(rWriter);
    }
    rWriter.endElement();
}

#if DUMP_PIVOT_TABLE

void ScDPSaveDimension::Dump(int nIndent) const
{
    static const char* const pOrientNames[] = { "hidden", "column", "row", "page", "data" };
    std::string aIndent(nIndent*4, ' ');

    std::cout << aIndent << "* dimension name: '" << aName << "'" << std::endl;

    std::cout << aIndent << "    + orientation: ";
    if (nOrientation <= DataPilotFieldOrientation_DATA)
        std::cout << pOrientNames[static_cast<int>(nOrientation)];
    else
        std::cout << "(invalid)";
    std::cout << std::endl;

    std::cout << aIndent << "    + layout name: ";
    if (mpLayoutName)
        std::cout << "'" << *mpLayoutName << "'";
    else
        std::cout << "(none)";
    std::cout << std::endl;

    std::cout << aIndent << "    + subtotal name: ";
    if (mpSubtotalName)
        std::cout << "'" << *mpSubtotalName << "'";
    else
        std::cout << "(none)";
    std::cout << std::endl;

    std::cout << aIndent << "    + is data layout: " << (bIsDataLayout ? "yes" : "no") << std::endl;
    std::cout << aIndent << "    + is duplicate: " << (bDupFlag ? "yes" : "no") << std::endl;

    for (ScDPSaveMember* pMem : maMemberList)
    {
        pMem->Dump(nIndent+1);
    }

    std::cout << std::endl; // blank line
}

#endif

ScDPSaveData::ScDPSaveData()
    : mnColumnGrandMode(SC_DPSAVEMODE_DONTKNOW)
    , mnRowGrandMode(SC_DPSAVEMODE_DONTKNOW)
    , mnIgnoreEmptyMode(SC_DPSAVEMODE_DONTKNOW)
    , mnRepeatEmptyMode(SC_DPSAVEMODE_DONTKNOW)
    , mbFilterButton(true)
    , mbDrillDown(true)
    , mbExpandCollapse(false)
    , mbDimensionMembersBuilt(false)
{
}

ScDPSaveData::ScDPSaveData(const ScDPSaveData& rOther)
    : mnColumnGrandMode(rOther.mnColumnGrandMode)
    , mnRowGrandMode(rOther.mnRowGrandMode)
    , mnIgnoreEmptyMode(rOther.mnIgnoreEmptyMode)
    , mnRepeatEmptyMode(rOther.mnRepeatEmptyMode)
    , mbFilterButton(rOther.mbFilterButton)
    , mbDrillDown(rOther.mbDrillDown)
    , mbExpandCollapse(rOther.mbExpandCollapse)
    , mbDimensionMembersBuilt(rOther.mbDimensionMembersBuilt)
    , mpGrandTotalName(rOther.mpGrandTotalName)
{
    if (rOther.mpDimensionData)
        mpDimensionData.reset(new ScDPDimensionSaveData(*rOther.mpDimensionData));
    if (rOther.mpFormats)
        mpFormats.reset(new sc::PivotTableFormats(*rOther.mpFormats));

    for (auto const& rOtherSaveDimension : rOther.m_DimList)
    {
        m_DimList.push_back(std::make_unique<ScDPSaveDimension>(*rOtherSaveDimension));
    }
}

ScDPSaveData& ScDPSaveData::operator=(const ScDPSaveData& rOther)
{
    if (&rOther != this)
    {
        this->~ScDPSaveData();
        new(this)ScDPSaveData(rOther);
    }
    return *this;
}

bool ScDPSaveData::operator== (const ScDPSaveData& rOther) const
{
    if (mnColumnGrandMode != rOther.mnColumnGrandMode ||
        mnRowGrandMode    != rOther.mnRowGrandMode    ||
        mnIgnoreEmptyMode != rOther.mnIgnoreEmptyMode ||
        mnRepeatEmptyMode != rOther.mnRepeatEmptyMode ||
        mbFilterButton    != rOther.mbFilterButton    ||
        mbDrillDown       != rOther.mbDrillDown       ||
        mbDimensionMembersBuilt != rOther.mbDimensionMembersBuilt)
        return false;

    if (mpDimensionData || rOther.mpDimensionData)
        if (!mpDimensionData || !rOther.mpDimensionData || !(*mpDimensionData == *rOther.mpDimensionData))
            return false;

    if (!(::comphelper::ContainerUniquePtrEquals(m_DimList, rOther.m_DimList)))
        return false;

    if (mpGrandTotalName)
    {
        if (!rOther.mpGrandTotalName)
            return false;
        if (*mpGrandTotalName != *rOther.mpGrandTotalName)
            return false;
    }
    else if (rOther.mpGrandTotalName)
        return false;

    return true;
}

ScDPSaveData::~ScDPSaveData()
{
}

void ScDPSaveData::setFormats(sc::PivotTableFormats const& rPivotTableFormats)
{
    mpFormats.reset(new sc::PivotTableFormats(rPivotTableFormats));
}

bool ScDPSaveData::hasFormats()
{
    return bool(mpFormats);
}

sc::PivotTableFormats const& ScDPSaveData::getFormats()
{
    return *mpFormats;
}

void ScDPSaveData::SetGrandTotalName(const OUString& rName)
{
    mpGrandTotalName = rName;
}

const std::optional<OUString> & ScDPSaveData::GetGrandTotalName() const
{
    return mpGrandTotalName;
}

namespace {

class DimOrderInserter
{
    ScDPSaveData::DimOrderType& mrNames;
public:
    explicit DimOrderInserter(ScDPSaveData::DimOrderType& rNames) : mrNames(rNames) {}

    void operator() (const ScDPSaveDimension* pDim)
    {
        size_t nRank = mrNames.size();
        mrNames.emplace(ScGlobal::getCharClass().uppercase(pDim->GetName()), nRank);
    }
};

}

const ScDPSaveData::DimOrderType& ScDPSaveData::GetDimensionSortOrder() const
{
    if (!mpDimOrder)
    {
        mpDimOrder.reset(new DimOrderType);
        std::vector<const ScDPSaveDimension*> aRowDims, aColDims;
        GetAllDimensionsByOrientation(sheet::DataPilotFieldOrientation_ROW, aRowDims);
        GetAllDimensionsByOrientation(sheet::DataPilotFieldOrientation_COLUMN, aColDims);

        std::for_each(aRowDims.begin(), aRowDims.end(), DimOrderInserter(*mpDimOrder));
        std::for_each(aColDims.begin(), aColDims.end(), DimOrderInserter(*mpDimOrder));
    }
    return *mpDimOrder;
}

void ScDPSaveData::GetAllDimensionsByOrientation(
    sheet::DataPilotFieldOrientation eOrientation, std::vector<const ScDPSaveDimension*>& rDims) const
{
    std::vector<const ScDPSaveDimension*> aDims;
    for (auto const& it : m_DimList)
    {
        const ScDPSaveDimension& rDim = *it;
        if (rDim.GetOrientation() != eOrientation)
            continue;

        aDims.push_back(&rDim);
    }

    rDims.swap(aDims);
}

void ScDPSaveData::AddDimension(ScDPSaveDimension* pDim)
{
    if (!pDim)
        return;

    CheckDuplicateName(*pDim);
    m_DimList.push_back(std::unique_ptr<ScDPSaveDimension>(pDim));

    DimensionsChanged();
}

ScDPSaveDimension* ScDPSaveData::GetDimensionByName(const OUString& rName)
{
    for (auto const& iter : m_DimList)
    {
        if (iter->GetName() == rName && !iter->IsDataLayout() )
            return &(*iter);
    }

    return AppendNewDimension(rName, false);
}

ScDPSaveDimension* ScDPSaveData::GetExistingDimensionByName(std::u16string_view rName) const
{
    for (auto const& iter : m_DimList)
    {
        if (iter->GetName() == rName && !iter->IsDataLayout() )
            return &(*iter);
    }
    return nullptr; // don't create new
}

ScDPSaveDimension* ScDPSaveData::GetNewDimensionByName(const OUString& rName)
{
    for (auto const& iter : m_DimList)
    {
        if (iter->GetName() == rName && !iter->IsDataLayout() )
            return DuplicateDimension(rName);
    }

    return AppendNewDimension(rName, false);
}

ScDPSaveDimension* ScDPSaveData::GetDataLayoutDimension()
{
    ScDPSaveDimension* pDim = GetExistingDataLayoutDimension();
    if (pDim)
        return pDim;

    return AppendNewDimension(OUString(), true);
}

ScDPSaveDimension* ScDPSaveData::GetExistingDataLayoutDimension() const
{
    for (auto const& iter : m_DimList)
    {
        if ( iter->IsDataLayout() )
            return &(*iter);
    }
    return nullptr;
}

ScDPSaveDimension* ScDPSaveData::DuplicateDimension(std::u16string_view rName)
{
    // always insert new

    ScDPSaveDimension* pOld = GetExistingDimensionByName(rName);
    if (!pOld)
        return nullptr;

    ScDPSaveDimension* pNew = new ScDPSaveDimension( *pOld );
    AddDimension(pNew);
    return pNew;
}

void ScDPSaveData::RemoveDimensionByName(const OUString& rName)
{
    auto iter = std::find_if(m_DimList.begin(), m_DimList.end(),
        [&rName](const std::unique_ptr<ScDPSaveDimension>& rxDim) {
            return rxDim->GetName() == rName && !rxDim->IsDataLayout(); });
    if (iter != m_DimList.end())
    {
        m_DimList.erase(iter);
        RemoveDuplicateNameCount(rName);
        DimensionsChanged();
    }
}

ScDPSaveDimension& ScDPSaveData::DuplicateDimension( const ScDPSaveDimension& rDim )
{
    ScDPSaveDimension* pNew = new ScDPSaveDimension( rDim );
    AddDimension(pNew);
    return *pNew;
}

ScDPSaveDimension* ScDPSaveData::GetInnermostDimension(DataPilotFieldOrientation nOrientation)
{
    // return the innermost dimension for the given orientation,
    // excluding data layout dimension

    auto iter = std::find_if(m_DimList.rbegin(), m_DimList.rend(),
        [&nOrientation](const std::unique_ptr<ScDPSaveDimension>& rxDim) {
            return rxDim->GetOrientation() == nOrientation && !rxDim->IsDataLayout(); });
    if (iter != m_DimList.rend())
        return iter->get();

    return nullptr;
}

ScDPSaveDimension* ScDPSaveData::GetFirstDimension(sheet::DataPilotFieldOrientation eOrientation)
{
    for (auto const& iter : m_DimList)
    {
        if (iter->GetOrientation() == eOrientation && !iter->IsDataLayout())
            return &(*iter);
    }
    return nullptr;
}

tools::Long ScDPSaveData::GetDataDimensionCount() const
{
    tools::Long nDataCount = 0;

    for (auto const& iter : m_DimList)
    {
        if (iter->GetOrientation() == sheet::DataPilotFieldOrientation_DATA)
            ++nDataCount;
    }

    return nDataCount;
}

void ScDPSaveData::SetPosition( ScDPSaveDimension* pDim, tools::Long nNew )
{
    // position (nNew) is counted within dimensions of the same orientation

    DataPilotFieldOrientation nOrient = pDim->GetOrientation();

    auto it = std::find_if(m_DimList.begin(), m_DimList.end(),
        [&pDim](const std::unique_ptr<ScDPSaveDimension>& rxDim) { return pDim == rxDim.get(); });
    if (it != m_DimList.end())
    {
        // Tell vector<unique_ptr> to give up ownership of this element.  Don't
        // delete this instance as it is re-inserted into the container later.
        // coverity[leaked_storage] - re-inserted into the container later
        it->release();
        m_DimList.erase(it);
    }

    auto iterInsert = std::find_if(m_DimList.begin(), m_DimList.end(),
        [&nOrient, &nNew](const std::unique_ptr<ScDPSaveDimension>& rxDim) {
            if (rxDim->GetOrientation() == nOrient )
                --nNew;
            return nNew <= 0;
        });

    m_DimList.insert(iterInsert, std::unique_ptr<ScDPSaveDimension>(pDim));
    DimensionsChanged();
}

void ScDPSaveData::SetColumnGrand(bool bSet)
{
    mnColumnGrandMode = sal_uInt16(bSet);
}

void ScDPSaveData::SetRowGrand(bool bSet)
{
    mnRowGrandMode = sal_uInt16(bSet);
}

void ScDPSaveData::SetIgnoreEmptyRows(bool bSet)
{
    mnIgnoreEmptyMode = sal_uInt16(bSet);
}

void ScDPSaveData::SetRepeatIfEmpty(bool bSet)
{
    mnRepeatEmptyMode = sal_uInt16(bSet);
}

void ScDPSaveData::SetFilterButton(bool bSet)
{
    mbFilterButton = bSet;
}

void ScDPSaveData::SetDrillDown(bool bSet)
{
    mbDrillDown = bSet;
}

void ScDPSaveData::SetExpandCollapse(bool bSet)
{
    mbExpandCollapse = bSet;
}

static void lcl_ResetOrient( const uno::Reference<sheet::XDimensionsSupplier>& xSource )
{
    uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions();
    uno::Reference<container::XIndexAccess> xIntDims = new ScNameToIndexAccess( xDimsName );
    tools::Long nIntCount = xIntDims->getCount();
    for (tools::Long nIntDim=0; nIntDim<nIntCount; nIntDim++)
    {
        uno::Reference<beans::XPropertySet> xDimProp(xIntDims->getByIndex(nIntDim), uno::UNO_QUERY);
        if (xDimProp.is())
        {
            xDimProp->setPropertyValue( SC_UNO_DP_ORIENTATION, uno::Any(sheet::DataPilotFieldOrientation_HIDDEN) );
        }
    }
}

void ScDPSaveData::WriteToSource( const uno::Reference<sheet::XDimensionsSupplier>& xSource )
{
    if (!xSource.is())
        return;

    // source options must be first!

    uno::Reference<beans::XPropertySet> xSourceProp( xSource, uno::UNO_QUERY );
    SAL_WARN_IF( !xSourceProp.is(), "sc.core", "no properties at source" );
    if ( xSourceProp.is() )
    {
        // source options are not available for external sources
        //TODO: use XPropertySetInfo to test for availability?

        try
        {
            if (mnIgnoreEmptyMode != SC_DPSAVEMODE_DONTKNOW)
                lcl_SetBoolProperty(xSourceProp, SC_UNO_DP_IGNOREEMPTY, bool(mnIgnoreEmptyMode));
            if (mnRepeatEmptyMode != SC_DPSAVEMODE_DONTKNOW)
                lcl_SetBoolProperty(xSourceProp, SC_UNO_DP_REPEATEMPTY, bool(mnRepeatEmptyMode));
        }
        catch(uno::Exception&)
        {
            // no error
        }

        const std::optional<OUString> & pGrandTotalName = GetGrandTotalName();
        if (pGrandTotalName)
            ScUnoHelpFunctions::SetOptionalPropertyValue(xSourceProp, SC_UNO_DP_GRANDTOTAL_NAME, *pGrandTotalName);
    }

    // exceptions in the other calls are errors
    try
    {
        // reset all orientations
        //TODO: "forgetSettings" or similar at source ?????
        //TODO: reset all duplicated dimensions, or reuse them below !!!
        SAL_INFO("sc.core", "ScDPSaveData::WriteToSource");

        lcl_ResetOrient( xSource );

        uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions();
        uno::Reference<container::XIndexAccess> xIntDims = new ScNameToIndexAccess( xDimsName );
        tools::Long nIntCount = xIntDims->getCount();

        for (const auto& rxDim : m_DimList)
        {
            OUString aName = rxDim->GetName();
            OUString aCoreName = ScDPUtil::getSourceDimensionName(aName);

            SAL_INFO("sc.core", aName);

            bool bData = rxDim->IsDataLayout();

            //TODO: getByName for ScDPSource, including DataLayoutDimension !!!!!!!!

            bool bFound = false;
            for (tools::Long nIntDim=0; nIntDim<nIntCount && !bFound; nIntDim++)
            {
                uno::Reference<uno::XInterface> xIntDim(xIntDims->getByIndex(nIntDim),
                                                        uno::UNO_QUERY);
                if ( bData )
                {
                    uno::Reference<beans::XPropertySet> xDimProp( xIntDim, uno::UNO_QUERY );
                    if ( xDimProp.is() )
                    {
                        bFound = ScUnoHelpFunctions::GetBoolProperty( xDimProp,
                                    SC_UNO_DP_ISDATALAYOUT );
                        //TODO: error checking -- is "IsDataLayoutDimension" property required??
                    }
                }
                else
                {
                    uno::Reference<container::XNamed> xDimName( xIntDim, uno::UNO_QUERY );
                    if (xDimName.is() && xDimName->getName() == aCoreName)
                        bFound = true;
                }

                if (bFound)
                {
                    if (rxDim->GetDupFlag())
                    {
                        uno::Reference<util::XCloneable> xCloneable(xIntDim, uno::UNO_QUERY);
                        SAL_WARN_IF(!xCloneable.is(), "sc.core", "cannot clone dimension");
                        if (xCloneable.is())
                        {
                            uno::Reference<util::XCloneable> xNew = xCloneable->createClone();
                            uno::Reference<container::XNamed> xNewName(xNew, uno::UNO_QUERY);
                            if (xNewName.is())
                            {
                                xNewName->setName(aName);
                                rxDim->WriteToSource(xNew);
                            }
                        }
                    }
                    else
                        rxDim->WriteToSource( xIntDim );
                }
            }
            SAL_WARN_IF(!bFound, "sc.core", "WriteToSource: Dimension not found: " + aName + ".");
        }

        if ( xSourceProp.is() )
        {
            if (mnColumnGrandMode != SC_DPSAVEMODE_DONTKNOW)
                lcl_SetBoolProperty(xSourceProp, SC_UNO_DP_COLGRAND, bool(mnColumnGrandMode));
            if (mnRowGrandMode != SC_DPSAVEMODE_DONTKNOW)
                lcl_SetBoolProperty(xSourceProp, SC_UNO_DP_ROWGRAND, bool(mnRowGrandMode));
        }
    }
    catch(uno::Exception const &)
    {
        TOOLS_WARN_EXCEPTION("sc.core", "WriteToSource");
    }
}

bool ScDPSaveData::IsEmpty() const
{
    for (auto const& iter : m_DimList)
    {
        if (iter->GetOrientation() != sheet::DataPilotFieldOrientation_HIDDEN && !iter->IsDataLayout())
            return false;
    }
    return true; // no entries that are not hidden
}

void ScDPSaveData::RemoveAllGroupDimensions( const OUString& rSrcDimName, std::vector<OUString>* pDeletedNames )
{
    if (!mpDimensionData)
        // No group dimensions exist. Nothing to do.
        return;

    // Remove numeric group dimension (exists once at most). No need to delete
    // anything in save data (grouping was done inplace in an existing base
    // dimension).
    mpDimensionData->RemoveNumGroupDimension(rSrcDimName);

    // Remove named group dimension(s). Dimensions have to be removed from
    // dimension save data and from save data too.
    const ScDPSaveGroupDimension* pExistingGroup = mpDimensionData->GetGroupDimForBase(rSrcDimName);
    while ( pExistingGroup )
    {
        OUString aGroupDimName = pExistingGroup->GetGroupDimName();
        mpDimensionData->RemoveGroupDimension(aGroupDimName);     // pExistingGroup is deleted

        // also remove SaveData settings for the dimension that no longer exists
        RemoveDimensionByName(aGroupDimName);

        if (pDeletedNames)
            pDeletedNames->push_back(aGroupDimName);

        // see if there are more group dimensions
        pExistingGroup = mpDimensionData->GetGroupDimForBase(rSrcDimName);

        if ( pExistingGroup && pExistingGroup->GetGroupDimName() == aGroupDimName )
        {
            // still get the same group dimension?
            OSL_FAIL("couldn't remove group dimension");
            pExistingGroup = nullptr;      // avoid endless loop
        }
    }
}

ScDPDimensionSaveData* ScDPSaveData::GetDimensionData()
{
    if (!mpDimensionData)
        mpDimensionData.reset(new ScDPDimensionSaveData);
    return mpDimensionData.get();
}

void ScDPSaveData::SetDimensionData(const ScDPDimensionSaveData* pNew)
{
    if (pNew)
        mpDimensionData.reset(new ScDPDimensionSaveData(*pNew));
    else
        mpDimensionData.reset();
}

void ScDPSaveData::BuildAllDimensionMembers(ScDPTableData* pData)
{
    if (mbDimensionMembersBuilt)
        return;

    // First, build a dimension name-to-index map.
    typedef std::unordered_map<OUString, tools::Long> NameIndexMap;
    NameIndexMap aMap;
    tools::Long nColCount = pData->GetColumnCount();
    for (tools::Long i = 0; i < nColCount; ++i)
        aMap.emplace(pData->getDimensionName(i), i);

    NameIndexMap::const_iterator itrEnd = aMap.end();

    for (auto const& iter : m_DimList)
    {
        const OUString& rDimName = iter->GetName();
        if (rDimName.isEmpty())
            // empty dimension name. It must be data layout.
            continue;

        NameIndexMap::const_iterator itr = aMap.find(rDimName);
        if (itr == itrEnd)
            // dimension name not in the data. This should never happen!
            continue;

        tools::Long nDimIndex = itr->second;
        const std::vector<SCROW>& rMembers = pData->GetColumnEntries(nDimIndex);
        size_t nMemberCount = rMembers.size();
        for (size_t j = 0; j < nMemberCount; ++j)
        {
            const ScDPItemData* pMemberData = pData->GetMemberById( nDimIndex, rMembers[j] );
            OUString aMemName = pData->GetFormattedString(nDimIndex, *pMemberData, false);
            if (iter->GetExistingMemberByName(aMemName))
                // this member instance already exists. nothing to do.
                continue;

            std::unique_ptr<ScDPSaveMember> pNewMember(new ScDPSaveMember(aMemName));
            pNewMember->SetIsVisible(true);
            iter->AddMember(std::move(pNewMember));
        }
    }

    mbDimensionMembersBuilt = true;
}

void ScDPSaveData::SyncAllDimensionMembers(ScDPTableData* pData)
{
    typedef std::unordered_map<OUString, tools::Long> NameIndexMap;

    // First, build a dimension name-to-index map.
    NameIndexMap aMap;
    tools::Long nColCount = pData->GetColumnCount();
    for (tools::Long i = 0; i < nColCount; ++i)
        aMap.emplace(pData->getDimensionName(i), i);

    NameIndexMap::const_iterator itMapEnd = aMap.end();

    for (auto const& it : m_DimList)
    {
        const OUString& rDimName = it->GetName();
        if (rDimName.isEmpty())
            // empty dimension name. It must be data layout.
            continue;

        NameIndexMap::const_iterator itMap = aMap.find(rDimName);
        if (itMap == itMapEnd)
            // dimension name not in the data. This should never happen!
            continue;

        ScDPSaveDimension::MemberSetType aMemNames;
        tools::Long nDimIndex = itMap->second;
        const std::vector<SCROW>& rMembers = pData->GetColumnEntries(nDimIndex);
        size_t nMemberCount = rMembers.size();
        for (size_t j = 0; j < nMemberCount; ++j)
        {
            const ScDPItemData* pMemberData = pData->GetMemberById(nDimIndex, rMembers[j]);
            // ScDPCache::GetItemDataById() (via
            // ScDPTableData::GetMemberById(),
            // ScDPGroupTableData::GetMemberById() through
            // GetCacheTable().getCache()) may return nullptr.
            if (pMemberData)
            {
                OUString aMemName = pData->GetFormattedString(nDimIndex, *pMemberData, false);
                aMemNames.insert(aMemName);
            }
            else
            {
                SAL_WARN("sc.core", "No pMemberData for nDimIndex " << nDimIndex << ", rMembers[j] " << rMembers[j]
                        << ", j " << j);
            }
        }

        it->RemoveObsoleteMembers(aMemNames);
    }
}

bool ScDPSaveData::HasInvisibleMember(std::u16string_view rDimName) const
{
    ScDPSaveDimension* pDim = GetExistingDimensionByName(rDimName);
    if (!pDim)
        return false;

    return pDim->HasInvisibleMember();
}

void ScDPSaveData::dumpAsXml(tools::XmlWriter& rWriter) const
{
    for (auto const& itDim: m_DimList)
    {
        const ScDPSaveDimension& rSaveDimension = *itDim;
        rSaveDimension.dumpAsXml(rWriter);
    }
}

#if DUMP_PIVOT_TABLE

void ScDPSaveData::Dump() const
{
    for (auto const& itDim : m_DimList)
    {
        const ScDPSaveDimension& rDim = *itDim;
        rDim.Dump();
    }
}

#endif

void ScDPSaveData::CheckDuplicateName(ScDPSaveDimension& rDim)
{
    const OUString aName = ScDPUtil::getSourceDimensionName(rDim.GetName());
    DupNameCountType::iterator it = maDupNameCounts.find(aName);
    if (it != maDupNameCounts.end())
    {
        rDim.SetName(ScDPUtil::createDuplicateDimensionName(aName, ++it->second));
        rDim.SetDupFlag(true);
    }
    else
        // New name.
        maDupNameCounts.emplace(aName, 0);
}

void ScDPSaveData::RemoveDuplicateNameCount(const OUString& rName)
{
    OUString aCoreName = rName;
    if (ScDPUtil::isDuplicateDimension(rName))
        aCoreName = ScDPUtil::getSourceDimensionName(rName);

    DupNameCountType::iterator it = maDupNameCounts.find(aCoreName);
    if (it == maDupNameCounts.end())
        return;

    if (!it->second)
    {
        maDupNameCounts.erase(it);
        return;
    }

    --it->second;
}

ScDPSaveDimension* ScDPSaveData::AppendNewDimension(const OUString& rName, bool bDataLayout)
{
    if (ScDPUtil::isDuplicateDimension(rName))
        // This call is for original dimensions only.
        return nullptr;

    ScDPSaveDimension* pNew = new ScDPSaveDimension(rName, bDataLayout);
    m_DimList.push_back(std::unique_ptr<ScDPSaveDimension>(pNew));
    if (!maDupNameCounts.count(rName))
        maDupNameCounts.emplace(rName, 0);

    DimensionsChanged();
    return pNew;
}

void ScDPSaveData::DimensionsChanged()
{
    mpDimOrder.reset();
}

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