    /* -*- 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 <dptabsrc.hxx>

    #include <algorithm>
    #include <set>
    #include <unordered_set>
    #include <vector>

    #include <o3tl/any.hxx>
    #include <osl/diagnose.h>
    #include <rtl/math.hxx>
    #include <sal/log.hxx>
    #include <svl/itemprop.hxx>
    #include <svl/intitem.hxx>
    #include <vcl/svapp.hxx>

    #include <scitems.hxx>
    #include <document.hxx>
    #include <docpool.hxx>
    #include <patattr.hxx>
    #include <formulacell.hxx>

    #include <dpcache.hxx>
    #include <dptabres.hxx>
    #include <dptabdat.hxx>
    #include <global.hxx>
    #include <datauno.hxx>
    #include <miscuno.hxx>
    #include <unonames.hxx>
    #include <dpitemdata.hxx>
    #include <dputil.hxx>
    #include <dpresfilter.hxx>
    #include <calcmacros.hxx>
    #include <generalfunction.hxx>

    #include <com/sun/star/beans/PropertyAttribute.hpp>
    #include <com/sun/star/sheet/DataPilotFieldFilter.hpp>
    #include <com/sun/star/sheet/DataPilotFieldReferenceType.hpp>
    #include <com/sun/star/sheet/DataPilotFieldSortMode.hpp>
    #include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp>
    #include <com/sun/star/sheet/DataPilotFieldAutoShowInfo.hpp>
    #include <com/sun/star/sheet/GeneralFunction2.hpp>

    #include <unotools/collatorwrapper.hxx>
    #include <unotools/calendarwrapper.hxx>
    #include <com/sun/star/i18n/CalendarDisplayIndex.hpp>

    using namespace com::sun::star;
    using ::std::vector;
    using ::std::set;
    using ::com::sun::star::uno::Sequence;
    using ::com::sun::star::uno::Any;
    using ::com::sun::star::sheet::DataPilotFieldAutoShowInfo;

    #define SC_MINCOUNT_LIMIT   1000000

    SC_SIMPLE_SERVICE_INFO( ScDPSource,      "ScDPSource",      "com.sun.star.sheet.DataPilotSource" )
    SC_SIMPLE_SERVICE_INFO( ScDPDimensions,  "ScDPDimensions",  "com.sun.star.sheet.DataPilotSourceDimensions" )
    SC_SIMPLE_SERVICE_INFO( ScDPDimension,   "ScDPDimension",   "com.sun.star.sheet.DataPilotSourceDimension" )
    SC_SIMPLE_SERVICE_INFO( ScDPHierarchies, "ScDPHierarchies", "com.sun.star.sheet.DataPilotSourceHierarcies" )
    SC_SIMPLE_SERVICE_INFO( ScDPHierarchy,   "ScDPHierarchy",   "com.sun.star.sheet.DataPilotSourceHierarcy" )
    SC_SIMPLE_SERVICE_INFO( ScDPLevels,      "ScDPLevels",      "com.sun.star.sheet.DataPilotSourceLevels" )
    SC_SIMPLE_SERVICE_INFO( ScDPLevel,       "ScDPLevel",       "com.sun.star.sheet.DataPilotSourceLevel" )
    SC_SIMPLE_SERVICE_INFO( ScDPMembers,     "ScDPMembers",     "com.sun.star.sheet.DataPilotSourceMembers" )
    SC_SIMPLE_SERVICE_INFO( ScDPMember,      "ScDPMember",      "com.sun.star.sheet.DataPilotSourceMember" )

    // property maps for PropertySetInfo
    //  DataDescription / NumberFormat are internal

    //TODO: move to a header?
    static bool lcl_GetBoolFromAny( const uno::Any& aAny )
    {
        auto b = o3tl::tryAccess<bool>(aAny);
        return b && *b;
    }

    ScDPSource::ScDPSource( ScDPTableData* pD ) :
        pData( pD ),
        bColumnGrand( true ),       // default is true
        bRowGrand( true ),
        bIgnoreEmptyRows( false ),
        bRepeatIfEmpty( false ),
        nDupCount( 0 ),
        bResultOverflow( false ),
        bPageFiltered( false )
    {
        pData->SetEmptyFlags( bIgnoreEmptyRows, bRepeatIfEmpty );
    }

    ScDPSource::~ScDPSource()
    {
        // free lists

        pColResults.reset();
        pRowResults.reset();

        pColResRoot.reset();
        pRowResRoot.reset();
        pResData.reset();
    }

    const boost::optional<OUString> & ScDPSource::GetGrandTotalName() const
    {
        return mpGrandTotalName;
    }

    sheet::DataPilotFieldOrientation ScDPSource::GetOrientation(long nColumn)
    {
        if (std::find(maColDims.begin(), maColDims.end(), nColumn) != maColDims.end())
            return sheet::DataPilotFieldOrientation_COLUMN;

        if (std::find(maRowDims.begin(), maRowDims.end(), nColumn) != maRowDims.end())
            return sheet::DataPilotFieldOrientation_ROW;

        if (std::find(maDataDims.begin(), maDataDims.end(), nColumn) != maDataDims.end())
            return sheet::DataPilotFieldOrientation_DATA;

        if (std::find(maPageDims.begin(), maPageDims.end(), nColumn) != maPageDims.end())
            return sheet::DataPilotFieldOrientation_PAGE;

        return sheet::DataPilotFieldOrientation_HIDDEN;
    }

    long ScDPSource::GetDataDimensionCount()
    {
        return maDataDims.size();
    }

    ScDPDimension* ScDPSource::GetDataDimension(long nIndex)
    {
        if (nIndex < 0 || static_cast<size_t>(nIndex) >= maDataDims.size())
            return nullptr;

        long nDimIndex = maDataDims[nIndex];
        return GetDimensionsObject()->getByIndex(nDimIndex);
    }

    OUString ScDPSource::GetDataDimName(long nIndex)
    {
        OUString aRet;
        ScDPDimension* pDim = GetDataDimension(nIndex);
        if (pDim)
            aRet = pDim->getName();
        return aRet;
    }

    long ScDPSource::GetPosition(long nColumn)
    {
        std::vector<long>::const_iterator it, itBeg = maColDims.begin(), itEnd = maColDims.end();
        it = std::find(itBeg, itEnd, nColumn);
        if (it != itEnd)
            return std::distance(itBeg, it);

        itBeg = maRowDims.begin();
        itEnd = maRowDims.end();
        it = std::find(itBeg, itEnd, nColumn);
        if (it != itEnd)
            return std::distance(itBeg, it);

        itBeg = maDataDims.begin();
        itEnd = maDataDims.end();
        it = std::find(itBeg, itEnd, nColumn);
        if (it != itEnd)
            return std::distance(itBeg, it);

        itBeg = maPageDims.begin();
        itEnd = maPageDims.end();
        it = std::find(itBeg, itEnd, nColumn);
        if (it != itEnd)
            return std::distance(itBeg, it);

        return 0;
    }

    namespace {

    bool testSubTotal( bool& rAllowed, long nColumn, const std::vector<long>& rDims, ScDPSource* pSource )
    {
        rAllowed = true;
        std::vector<long>::const_iterator it = rDims.begin(), itEnd = rDims.end();
        for (; it != itEnd; ++it)
        {
            if (*it != nColumn)
                continue;

            if ( pSource->IsDataLayoutDimension(nColumn) )
            {
                //  no subtotals for data layout dim, no matter where
                rAllowed = false;
                return true;
            }

            //  no subtotals if no other dim but data layout follows
            ++it;
            if (it != itEnd && pSource->IsDataLayoutDimension(*it))
                ++it;
            if (it == itEnd)
                rAllowed = false;

            return true;    // found
        }

        return false;
    }

    void removeDim( long nRemove, std::vector<long>& rDims )
    {
        std::vector<long>::iterator it = std::find(rDims.begin(), rDims.end(), nRemove);
        if (it != rDims.end())
            rDims.erase(it);
    }

    }

    bool ScDPSource::SubTotalAllowed(long nColumn)
    {
        //TODO: cache this at ScDPResultData
        bool bAllowed = true;
        if ( testSubTotal(bAllowed, nColumn, maColDims, this) )
            return bAllowed;
        if ( testSubTotal(bAllowed, nColumn, maRowDims, this) )
            return bAllowed;
        return bAllowed;
    }

    void ScDPSource::SetOrientation(long nColumn, sheet::DataPilotFieldOrientation nNew)
    {
        //TODO: change to no-op if new orientation is equal to old?

        // remove from old list
        removeDim(nColumn, maColDims);
        removeDim(nColumn, maRowDims);
        removeDim(nColumn, maDataDims);
        removeDim(nColumn, maPageDims);

        // add to new list
        switch (nNew)
        {
            case sheet::DataPilotFieldOrientation_COLUMN:
                maColDims.push_back(nColumn);
                break;
            case sheet::DataPilotFieldOrientation_ROW:
                maRowDims.push_back(nColumn);
                break;
            case sheet::DataPilotFieldOrientation_DATA:
                maDataDims.push_back(nColumn);
                break;
            case sheet::DataPilotFieldOrientation_PAGE:
                maPageDims.push_back(nColumn);
                break;
                // DataPilot Migration - Cache&&Performance
            case sheet::DataPilotFieldOrientation_HIDDEN:
                break;
            default:
                OSL_FAIL( "ScDPSource::SetOrientation: unexpected orientation" );
                break;
        }
    }

    bool ScDPSource::IsDataLayoutDimension(long nDim)
    {
        return nDim == pData->GetColumnCount();
    }

    sheet::DataPilotFieldOrientation ScDPSource::GetDataLayoutOrientation()
    {
        return GetOrientation(pData->GetColumnCount());
    }

    bool ScDPSource::IsDateDimension(long nDim)
    {
        return pData->IsDateDimension(nDim);
    }

    ScDPDimensions* ScDPSource::GetDimensionsObject()
    {
        if (!pDimensions.is())
        {
            pDimensions = new ScDPDimensions(this);
        }
        return pDimensions.get();
    }

    uno::Reference<container::XNameAccess> SAL_CALL ScDPSource::getDimensions()
    {
        return GetDimensionsObject();
    }

    void ScDPSource::SetDupCount( long nNew )
    {
        nDupCount = nNew;
    }

    ScDPDimension* ScDPSource::AddDuplicated(const OUString& rNewName)
    {
        OSL_ENSURE( pDimensions.is(), "AddDuplicated without dimensions?" );

        //  re-use

        long nOldDimCount = pDimensions->getCount();
        for (long i=0; i<nOldDimCount; i++)
        {
            ScDPDimension* pDim = pDimensions->getByIndex(i);
            if (pDim && pDim->getName() == rNewName)
            {
                //TODO: test if pDim is a duplicate of source
                return pDim;
            }
        }

        SetDupCount( nDupCount + 1 );
        pDimensions->CountChanged();        // uses nDupCount

        return pDimensions->getByIndex( pDimensions->getCount() - 1 );
    }

    long ScDPSource::GetSourceDim(long nDim)
    {
        //  original source dimension or data layout dimension?
        if ( nDim <= pData->GetColumnCount() )
            return nDim;

        if ( nDim < pDimensions->getCount() )
        {
            ScDPDimension* pDimObj = pDimensions->getByIndex( nDim );
            if ( pDimObj )
            {
                long nSource = pDimObj->GetSourceDim();
                if ( nSource >= 0 )
                    return nSource;
            }
        }

        OSL_FAIL("GetSourceDim: wrong dim");
        return nDim;
    }

    uno::Sequence< uno::Sequence<sheet::DataResult> > SAL_CALL ScDPSource::getResults()
    {
        CreateRes_Impl();       // create pColResRoot and pRowResRoot

        if ( bResultOverflow )      // set in CreateRes_Impl
        {
            //  no results available
            throw uno::RuntimeException();
        }

        long nColCount = pColResRoot->GetSize(pResData->GetColStartMeasure());
        long nRowCount = pRowResRoot->GetSize(pResData->GetRowStartMeasure());

        //  allocate full sequence
        //TODO: leave out empty rows???

        uno::Sequence< uno::Sequence<sheet::DataResult> > aSeq( nRowCount );
        uno::Sequence<sheet::DataResult>* pRowAry = aSeq.getArray();
        for (long nRow = 0; nRow < nRowCount; nRow++)
        {
            uno::Sequence<sheet::DataResult> aColSeq( nColCount );
            //  use default values of DataResult
            pRowAry[nRow] = aColSeq;
        }

        ScDPResultFilterContext aFilterCxt;
        pRowResRoot->FillDataResults(
            pColResRoot.get(), aFilterCxt, aSeq, pResData->GetRowStartMeasure());

        maResFilterSet.swap(aFilterCxt.maFilterSet); // Keep this data for GETPIVOTDATA.

        return aSeq;
    }

    uno::Sequence<double> ScDPSource::getFilteredResults(
                const uno::Sequence<sheet::DataPilotFieldFilter>& aFilters )
    {
        if (maResFilterSet.empty())
            getResults(); // Build result tree first.

        // Get result values from the tree.
        const ScDPResultTree::ValuesType* pVals = maResFilterSet.getResults(aFilters);
        if (pVals && !pVals->empty())
        {
            size_t n = pVals->size();
            uno::Sequence<double> aRet(n);
            for (size_t i = 0; i < n; ++i)
                aRet[i] = (*pVals)[i];

            return aRet;
        }

        if (aFilters.getLength() == 1)
        {
            // Try to get result from the leaf nodes.
            double fVal = maResFilterSet.getLeafResult(aFilters[0]);
            if (!rtl::math::isNan(fVal))
            {
                uno::Sequence<double> aRet(1);
                aRet[0] = fVal;
                return aRet;
            }
        }

        return uno::Sequence<double>();
    }

    void SAL_CALL ScDPSource::refresh()
    {
        disposeData();
    }

    void SAL_CALL ScDPSource::addRefreshListener( const uno::Reference<util::XRefreshListener >& )
    {
        OSL_FAIL("not implemented");    //TODO: exception?
    }

    void SAL_CALL ScDPSource::removeRefreshListener( const uno::Reference<util::XRefreshListener >& )
    {
        OSL_FAIL("not implemented");    //TODO: exception?
    }

    Sequence< Sequence<Any> > SAL_CALL ScDPSource::getDrillDownData(const Sequence<sheet::DataPilotFieldFilter>& aFilters)
    {
        long nColumnCount = GetData()->GetColumnCount();

        vector<ScDPFilteredCache::Criterion> aFilterCriteria;
        sal_Int32 nFilterCount = aFilters.getLength();
        for (sal_Int32 i = 0; i < nFilterCount; ++i)
        {
            const sheet::DataPilotFieldFilter& rFilter = aFilters[i];
            const OUString& aFieldName = rFilter.FieldName;
            for (long nCol = 0; nCol < nColumnCount; ++nCol)
            {
                if (aFieldName == pData->getDimensionName(nCol))
                {
                    ScDPDimension* pDim = GetDimensionsObject()->getByIndex( nCol );
                    ScDPMembers* pMembers = pDim->GetHierarchiesObject()->getByIndex(0)->
                                            GetLevelsObject()->getByIndex(0)->GetMembersObject();
                    sal_Int32 nIndex = pMembers->GetIndexFromName( rFilter.MatchValueName );
                    if ( nIndex >= 0 )
                    {
                        ScDPItemData aItem(pMembers->getByIndex(nIndex)->FillItemData());
                        aFilterCriteria.emplace_back( );
                        aFilterCriteria.back().mnFieldIndex = nCol;
                        aFilterCriteria.back().mpFilter.reset(
                            new ScDPFilteredCache::SingleFilter(aItem));
                    }
                }
            }
        }

        // Take into account the visibilities of field members.
        ScDPResultVisibilityData aResVisData(this);
        pRowResRoot->FillVisibilityData(aResVisData);
        pColResRoot->FillVisibilityData(aResVisData);
        aResVisData.fillFieldFilters(aFilterCriteria);

        Sequence< Sequence<Any> > aTabData;
        std::unordered_set<sal_Int32> aCatDims;
        GetCategoryDimensionIndices(aCatDims);
        pData->GetDrillDownData(aFilterCriteria, aCatDims, aTabData);
        return aTabData;
    }

    OUString ScDPSource::getDataDescription()
    {
        CreateRes_Impl();       // create pResData

        OUString aRet;
        if ( pResData->GetMeasureCount() == 1 )
        {
            bool bTotalResult = false;
            aRet = pResData->GetMeasureString(0, true, SUBTOTAL_FUNC_NONE, bTotalResult);
        }

        //  empty for more than one measure

        return aRet;
    }

    void ScDPSource::setIgnoreEmptyRows(bool bSet)
    {
        bIgnoreEmptyRows = bSet;
        pData->SetEmptyFlags( bIgnoreEmptyRows, bRepeatIfEmpty );
    }

    void ScDPSource::setRepeatIfEmpty(bool bSet)
    {
        bRepeatIfEmpty = bSet;
        pData->SetEmptyFlags( bIgnoreEmptyRows, bRepeatIfEmpty );
    }

    void ScDPSource::disposeData()
    {
        maResFilterSet.clear();

        if ( pResData )
        {
            //  reset all data...

            pColResRoot.reset();
            pRowResRoot.reset();
            pResData.reset();
            pColResults.reset();
            pRowResults.reset();
            aColLevelList.clear();
            aRowLevelList.clear();
        }

        pDimensions.clear(); // settings have to be applied (from SaveData) again!
        SetDupCount( 0 );

        maColDims.clear();
        maRowDims.clear();
        maDataDims.clear();
        maPageDims.clear();

        pData->DisposeData();   // cached entries etc.
        bPageFiltered = false;
        bResultOverflow = false;
    }

    static long lcl_CountMinMembers(const vector<ScDPDimension*>& ppDim, const vector<ScDPLevel*>& ppLevel, long nLevels )
    {
        //  Calculate the product of the member count for those consecutive levels that
        //  have the "show all" flag, one following level, and the data layout dimension.

        long nTotal = 1;
        long nDataCount = 1;
        bool bWasShowAll = true;
        long nPos = nLevels;
        while ( nPos > 0 )
        {
            --nPos;

            if ( nPos+1 < nLevels && ppDim[nPos] == ppDim[nPos+1] )
            {
                OSL_FAIL("lcl_CountMinMembers: multiple levels from one dimension not implemented");
                return 0;
            }

            bool bDo = false;
            if ( ppDim[nPos]->getIsDataLayoutDimension() )
            {
                //  data layout dim doesn't interfere with "show all" flags
                nDataCount = ppLevel[nPos]->GetMembersObject()->getCount();
                if ( nDataCount == 0 )
                    nDataCount = 1;
            }
            else if ( bWasShowAll )     // "show all" set for all following levels?
            {
                bDo = true;
                if ( !ppLevel[nPos]->getShowEmpty() )
                {
                    //  this level is counted, following ones are not
                    bWasShowAll = false;
                }
            }
            if ( bDo )
            {
                long nThisCount = ppLevel[nPos]->GetMembersObject()->getMinMembers();
                if ( nThisCount == 0 )
                {
                    nTotal = 1;         //  empty level -> start counting from here
                                        //TODO: start with visible elements in this level?
                }
                else
                {
                    if ( nTotal >= LONG_MAX / nThisCount )
                        return LONG_MAX;                        //  overflow
                    nTotal *= nThisCount;
                }
            }
        }

        //  always include data layout dim, even after restarting
        if ( nTotal >= LONG_MAX / nDataCount )
            return LONG_MAX;                        //  overflow
        nTotal *= nDataCount;

        return nTotal;
    }

    static long lcl_GetIndexFromName( const OUString& rName, const uno::Sequence<OUString>& rElements )
    {
        long nCount = rElements.getLength();
        const OUString* pArray = rElements.getConstArray();
        for (long nPos=0; nPos<nCount; nPos++)
            if (pArray[nPos] == rName)
                return nPos;

        return -1;  // not found
    }

    void ScDPSource::FillCalcInfo(bool bIsRow, ScDPTableData::CalcInfo& rInfo, bool &rHasAutoShow)
    {
        const std::vector<long>& rDims = bIsRow ? maRowDims : maColDims;
        std::vector<long>::const_iterator it = rDims.begin(), itEnd = rDims.end();
        for (; it != itEnd; ++it)
        {
            ScDPDimension* pDim = GetDimensionsObject()->getByIndex(*it);
            long nHierarchy = ScDPDimension::getUsedHierarchy();
            if ( nHierarchy >= ScDPHierarchies::getCount() )
                nHierarchy = 0;
            ScDPLevels* pLevels = pDim->GetHierarchiesObject()->getByIndex(nHierarchy)->GetLevelsObject();
            long nCount = pLevels->getCount();

            //TODO: Test
            if (pDim->getIsDataLayoutDimension() && maDataDims.size() < 2)
                nCount = 0;
            //TODO: Test

            for (long j = 0; j < nCount; ++j)
            {
                ScDPLevel* pLevel = pLevels->getByIndex(j);
                pLevel->EvaluateSortOrder();

                // no layout flags for column fields, only for row fields
                pLevel->SetEnableLayout( bIsRow );

                if ( pLevel->GetAutoShow().IsEnabled )
                    rHasAutoShow = true;

                if (bIsRow)
                {
                    rInfo.aRowLevelDims.push_back(*it);
                    rInfo.aRowDims.push_back(pDim);
                    rInfo.aRowLevels.push_back(pLevel);
                }
                else
                {
                    rInfo.aColLevelDims.push_back(*it);
                    rInfo.aColDims.push_back(pDim);
                    rInfo.aColLevels.push_back(pLevel);
                }

                pLevel->GetMembersObject();                 // initialize for groups
            }
        }
    }

    namespace {

    class CategoryDimInserter
    {
        ScDPSource& mrSource;
        std::unordered_set<sal_Int32>& mrCatDims;
    public:
        CategoryDimInserter(ScDPSource& rSource, std::unordered_set<sal_Int32>& rCatDims) :
            mrSource(rSource),
            mrCatDims(rCatDims) {}

        void operator() (long nDim)
        {
            if (!mrSource.IsDataLayoutDimension(nDim))
                mrCatDims.insert(nDim);
        }
    };

    }

    void ScDPSource::GetCategoryDimensionIndices(std::unordered_set<sal_Int32>& rCatDims)
    {
        std::unordered_set<sal_Int32> aCatDims;

        CategoryDimInserter aInserter(*this, aCatDims);
        std::for_each(maColDims.begin(), maColDims.end(), aInserter);
        std::for_each(maRowDims.begin(), maRowDims.end(), aInserter);
        std::for_each(maPageDims.begin(), maPageDims.end(), aInserter);

        rCatDims.swap(aCatDims);
    }

    void ScDPSource::FilterCacheByPageDimensions()
    {
        // #i117661# Repeated calls to ScDPFilteredCache::filterByPageDimension
        // are invalid because rows are only hidden, never shown again. If
        // FilterCacheByPageDimensions is called again, the cache table must
        // be re-initialized. Currently, CreateRes_Impl always uses a fresh cache
        // because ScDBDocFunc::DataPilotUpdate calls InvalidateData.

        if (bPageFiltered)
        {
            SAL_WARN( "sc.core","tried to apply page field filters several times");

            pData->DisposeData();
            pData->CreateCacheTable();  // re-initialize the cache table
            bPageFiltered = false;
        }

        // filter table by page dimensions.
        vector<ScDPFilteredCache::Criterion> aCriteria;
        vector<long>::const_iterator it = maPageDims.begin(), itEnd = maPageDims.end();
        for (; it != itEnd; ++it)
        {
            ScDPDimension* pDim = GetDimensionsObject()->getByIndex(*it);
            long nField = pDim->GetDimension();

            ScDPMembers* pMems = pDim->GetHierarchiesObject()->getByIndex(0)->
                GetLevelsObject()->getByIndex(0)->GetMembersObject();

            long nMemCount = pMems->getCount();
            ScDPFilteredCache::Criterion aFilter;
            aFilter.mnFieldIndex = static_cast<sal_Int32>(nField);
            aFilter.mpFilter.reset(new ScDPFilteredCache::GroupFilter);
            ScDPFilteredCache::GroupFilter* pGrpFilter =
                static_cast<ScDPFilteredCache::GroupFilter*>(aFilter.mpFilter.get());
            for (long j = 0; j < nMemCount; ++j)
            {
                ScDPMember* pMem = pMems->getByIndex(j);
                if (pMem->isVisible())
                {
                    ScDPItemData aData(pMem->FillItemData());
                    pGrpFilter->addMatchItem(aData);
                }
            }
            if (pGrpFilter->getMatchItemCount() < static_cast<size_t>(nMemCount))
                // there is at least one invisible item.  Add this filter criterion to the mix.
                aCriteria.push_back(aFilter);

            if (!pDim->HasSelectedPage())
                continue;

            const ScDPItemData& rData = pDim->GetSelectedData();
            aCriteria.emplace_back();
            ScDPFilteredCache::Criterion& r = aCriteria.back();
            r.mnFieldIndex = static_cast<sal_Int32>(nField);
            r.mpFilter.reset(new ScDPFilteredCache::SingleFilter(rData));
        }
        if (!aCriteria.empty())
        {
            std::unordered_set<sal_Int32> aCatDims;
            GetCategoryDimensionIndices(aCatDims);
            pData->FilterCacheTable(aCriteria, aCatDims);
            bPageFiltered = true;
        }
    }

    void ScDPSource::CreateRes_Impl()
    {
        if (pResData)
            return;

        sheet::DataPilotFieldOrientation nDataOrient = GetDataLayoutOrientation();
        if (maDataDims.size() > 1 && ( nDataOrient != sheet::DataPilotFieldOrientation_COLUMN &&
                                    nDataOrient != sheet::DataPilotFieldOrientation_ROW ) )
        {
            //  if more than one data dimension, data layout orientation must be set
            SetOrientation( pData->GetColumnCount(), sheet::DataPilotFieldOrientation_ROW );
            nDataOrient = sheet::DataPilotFieldOrientation_ROW;
        }

        // TODO: Aggregate pDataNames, pDataRefValues, nDataRefOrient, and
        // eDataFunctions into a structure and use vector instead of static
        // or pointer arrays.
        vector<OUString> aDataNames;
        vector<sheet::DataPilotFieldReference> aDataRefValues;
        vector<ScSubTotalFunc> aDataFunctions;
        vector<sheet::DataPilotFieldOrientation> aDataRefOrient;

        ScDPTableData::CalcInfo aInfo;

        //  LateInit (initialize only those rows/children that are used) can be used unless
        //  any data dimension needs reference values from column/row dimensions
        bool bLateInit = true;

        // Go through all data dimensions (i.e. fields) and build their meta data
        // so that they can be passed on to ScDPResultData instance later.
        // TODO: aggregate all of data dimension info into a structure.
        vector<long>::const_iterator it = maDataDims.begin(), itEnd = maDataDims.end();
        for (; it != itEnd; ++it)
        {
            // Get function for each data field.
            long nDimIndex = *it;
            ScDPDimension* pDim = GetDimensionsObject()->getByIndex(nDimIndex);
            ScGeneralFunction eUser = pDim->getFunction();
            if (eUser == ScGeneralFunction::AUTO)
            {
                //TODO: test for numeric data
                eUser = ScGeneralFunction::SUM;
            }

            // Map UNO's enum to internal enum ScSubTotalFunc.
            aDataFunctions.push_back(ScDPUtil::toSubTotalFunc(eUser));

            // Get reference field/item information.
            aDataRefValues.push_back(pDim->GetReferenceValue());
            sheet::DataPilotFieldOrientation nDataRefOrient = sheet::DataPilotFieldOrientation_HIDDEN;    // default if not used
            sal_Int32 eRefType = aDataRefValues.back().ReferenceType;
            if ( eRefType == sheet::DataPilotFieldReferenceType::ITEM_DIFFERENCE ||
                 eRefType == sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE ||
                 eRefType == sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE_DIFFERENCE ||
                 eRefType == sheet::DataPilotFieldReferenceType::RUNNING_TOTAL )
            {
                long nColumn = lcl_GetIndexFromName(
                    aDataRefValues.back().ReferenceField, GetDimensionsObject()->getElementNames());
                if ( nColumn >= 0 )
                {
                    nDataRefOrient = GetOrientation(nColumn);
                    //  need fully initialized results to find reference values
                    //  (both in column or row dimensions), so updated values or
                    //  differences to 0 can be displayed even for empty results.
                    bLateInit = false;
                }
            }

            aDataRefOrient.push_back(nDataRefOrient);

            aDataNames.push_back(pDim->getName());

            //TODO: modify user visible strings as in ScDPResultData::GetMeasureString instead!

            aDataNames.back() = ScDPUtil::getSourceDimensionName(aDataNames.back());

            //TODO: if the name is overridden by user, a flag must be set
            //TODO: so the user defined name replaces the function string and field name.

            //TODO: the complete name (function and field) must be stored at the dimension

            long nSource = pDim->GetSourceDim();
            if (nSource >= 0)
                aInfo.aDataSrcCols.push_back(nSource);
            else
                aInfo.aDataSrcCols.push_back(nDimIndex);
        }

        pResData.reset( new ScDPResultData(*this) );
        pResData->SetMeasureData(aDataFunctions, aDataRefValues, aDataRefOrient, aDataNames);
        pResData->SetDataLayoutOrientation(nDataOrient);
        pResData->SetLateInit( bLateInit );

        bool bHasAutoShow = false;

        ScDPInitState aInitState;

        // Page field selections restrict the members shown in related fields
        // (both in column and row fields). aInitState is filled with the page
        // field selections, they are kept across the data iterator loop.

        for (it = maPageDims.begin(), itEnd = maPageDims.end(); it != itEnd; ++it)
        {
            ScDPDimension* pDim = GetDimensionsObject()->getByIndex(*it);
            if ( pDim->HasSelectedPage() )
                aInitState.AddMember(*it, GetCache()->GetIdByItemData(*it, pDim->GetSelectedData()));
        }

        // Show grand total columns only when the option is set *and* there is at
        // least one column field.  Same for the grand total rows.
        sheet::DataPilotFieldOrientation nDataLayoutOrient = GetDataLayoutOrientation();
        long nColDimCount2 = maColDims.size() - (nDataLayoutOrient == sheet::DataPilotFieldOrientation_COLUMN ? 1 : 0);
        long nRowDimCount2 = maRowDims.size() - (nDataLayoutOrient == sheet::DataPilotFieldOrientation_ROW ? 1 : 0);
        bool bShowColGrand = bColumnGrand && nColDimCount2 > 0;
        bool bShowRowGrand = bRowGrand && nRowDimCount2 > 0;
        pColResRoot.reset( new ScDPResultMember(pResData.get(), bShowColGrand) );
        pRowResRoot.reset( new ScDPResultMember(pResData.get(), bShowRowGrand) );

        FillCalcInfo(false, aInfo, bHasAutoShow);
        long nColLevelCount = aInfo.aColLevels.size();

        pColResRoot->InitFrom( aInfo.aColDims, aInfo.aColLevels, 0, aInitState );
        pColResRoot->SetHasElements();

        FillCalcInfo(true, aInfo, bHasAutoShow);
        long nRowLevelCount = aInfo.aRowLevels.size();

        if ( nRowLevelCount > 0 )
        {
            // disable layout flags for the innermost row field (level)
            aInfo.aRowLevels[nRowLevelCount-1]->SetEnableLayout( false );
        }

        pRowResRoot->InitFrom( aInfo.aRowDims, aInfo.aRowLevels, 0, aInitState );
        pRowResRoot->SetHasElements();

        // initialize members object also for all page dimensions (needed for numeric groups)
        for (it = maPageDims.begin(), itEnd = maPageDims.end(); it != itEnd; ++it)
        {
            ScDPDimension* pDim = GetDimensionsObject()->getByIndex(*it);
            long nHierarchy = ScDPDimension::getUsedHierarchy();
            if ( nHierarchy >= ScDPHierarchies::getCount() )
                nHierarchy = 0;

            ScDPLevels* pLevels = pDim->GetHierarchiesObject()->getByIndex(nHierarchy)->GetLevelsObject();
            long nCount = pLevels->getCount();
            for (long j=0; j<nCount; j++)
                pLevels->getByIndex(j)->GetMembersObject();             // initialize for groups
        }

        //  pre-check: calculate minimum number of result columns / rows from
        //  levels that have the "show all" flag set

        long nMinColMembers = lcl_CountMinMembers( aInfo.aColDims, aInfo.aColLevels, nColLevelCount );
        long nMinRowMembers = lcl_CountMinMembers( aInfo.aRowDims, aInfo.aRowLevels, nRowLevelCount );

        if ( nMinColMembers > MAXCOLCOUNT/*SC_MINCOUNT_LIMIT*/ || nMinRowMembers > SC_MINCOUNT_LIMIT )
        {
            //  resulting table is too big -> abort before calculating
            //  (this relies on late init, so no members are allocated in InitFrom above)

            bResultOverflow = true;
            return;
        }

        FilterCacheByPageDimensions();

        aInfo.aPageDims.reserve(maPageDims.size());
        for (it = maPageDims.begin(), itEnd = maPageDims.end(); it != itEnd; ++it)
            aInfo.aPageDims.push_back(*it);

        aInfo.pInitState = &aInitState;
        aInfo.pColRoot   = pColResRoot.get();
        aInfo.pRowRoot   = pRowResRoot.get();
        pData->CalcResults(aInfo, false);

        pColResRoot->CheckShowEmpty();
        pRowResRoot->CheckShowEmpty();

        //  With all data processed, calculate the final results:

        //  UpdateDataResults calculates all original results from the collected values,
        //  and stores them as reference values if needed.
        pRowResRoot->UpdateDataResults( pColResRoot.get(), pResData->GetRowStartMeasure() );

        if ( bHasAutoShow )     // do the double calculation only if AutoShow is used
        {
            //  Find the desired members and set bAutoHidden flag for the others
            pRowResRoot->DoAutoShow( pColResRoot.get() );

            //  Reset all results to empty, so they can be built again with data for the
            //  desired members only.
            pColResRoot->ResetResults();
            pRowResRoot->ResetResults();
            pData->CalcResults(aInfo, true);

            //  Call UpdateDataResults again, with the new (limited) values.
            pRowResRoot->UpdateDataResults( pColResRoot.get(), pResData->GetRowStartMeasure() );
        }

        //  SortMembers does the sorting by a result dimension, using the original results,
        //  but not running totals etc.
        pRowResRoot->SortMembers( pColResRoot.get() );

        //  UpdateRunningTotals calculates running totals along column/row dimensions,
        //  differences from other members (named or relative), and column/row percentages
        //  or index values.
        //  Running totals and relative differences need to be done using the sorted values.
        //  Column/row percentages and index values must be done after sorting, because the
        //  results may no longer be in the right order (row total for percentage of row is
        //  always 1).
        ScDPRunningTotalState aRunning( pColResRoot.get(), pRowResRoot.get() );
        ScDPRowTotals aTotals;
        pRowResRoot->UpdateRunningTotals( pColResRoot.get(), pResData->GetRowStartMeasure(), aRunning, aTotals );

    #if DUMP_PIVOT_TABLE
        DumpResults();
    #endif
    }

    void ScDPSource::FillLevelList( sheet::DataPilotFieldOrientation nOrientation, std::vector<ScDPLevel*> &rList )
    {
        rList.clear();

        std::vector<long>* pDimIndex = nullptr;
        switch (nOrientation)
        {
            case sheet::DataPilotFieldOrientation_COLUMN:
                pDimIndex = &maColDims;
                break;
            case sheet::DataPilotFieldOrientation_ROW:
                pDimIndex = &maRowDims;
                break;
            case sheet::DataPilotFieldOrientation_DATA:
                pDimIndex = &maDataDims;
                break;
            case sheet::DataPilotFieldOrientation_PAGE:
                pDimIndex = &maPageDims;
                break;
            default:
                OSL_FAIL( "ScDPSource::FillLevelList: unexpected orientation" );
                break;
        }
        if (!pDimIndex)
        {
            OSL_FAIL("invalid orientation");
            return;
        }

        ScDPDimensions* pDims = GetDimensionsObject();
        std::vector<long>::const_iterator it = pDimIndex->begin(), itEnd = pDimIndex->end();
        for (; it != itEnd; ++it)
        {
            ScDPDimension* pDim = pDims->getByIndex(*it);
            OSL_ENSURE( pDim->getOrientation() == nOrientation, "orientations are wrong" );

            ScDPHierarchies* pHiers = pDim->GetHierarchiesObject();
            long nHierarchy = ScDPDimension::getUsedHierarchy();
            if ( nHierarchy >= ScDPHierarchies::getCount() )
                nHierarchy = 0;
            ScDPHierarchy* pHier = pHiers->getByIndex(nHierarchy);
            ScDPLevels* pLevels = pHier->GetLevelsObject();
            long nLevCount = pLevels->getCount();
            for (long nLev=0; nLev<nLevCount; nLev++)
            {
                ScDPLevel* pLevel = pLevels->getByIndex(nLev);
                rList.push_back(pLevel);
            }
        }
    }

    void ScDPSource::FillMemberResults()
    {
        if ( !pColResults && !pRowResults )
        {
            CreateRes_Impl();

            if ( bResultOverflow )      // set in CreateRes_Impl
            {
                //  no results available -> abort (leave empty)
                //  exception is thrown in ScDPSource::getResults
                return;
            }

            FillLevelList( sheet::DataPilotFieldOrientation_COLUMN, aColLevelList );
            long nColLevelCount = aColLevelList.size();
            if (nColLevelCount)
            {
                long nColDimSize = pColResRoot->GetSize(pResData->GetColStartMeasure());
                pColResults.reset(new uno::Sequence<sheet::MemberResult>[nColLevelCount]);
                for (long i=0; i<nColLevelCount; i++)
                    pColResults[i].realloc(nColDimSize);

                long nPos = 0;
                pColResRoot->FillMemberResults( pColResults.get(), nPos, pResData->GetColStartMeasure(),
                                                true, nullptr, nullptr );
            }

            FillLevelList( sheet::DataPilotFieldOrientation_ROW, aRowLevelList );
            long nRowLevelCount = aRowLevelList.size();
            if (nRowLevelCount)
            {
                long nRowDimSize = pRowResRoot->GetSize(pResData->GetRowStartMeasure());
                pRowResults.reset( new uno::Sequence<sheet::MemberResult>[nRowLevelCount] );
                for (long i=0; i<nRowLevelCount; i++)
                    pRowResults[i].realloc(nRowDimSize);

                long nPos = 0;
                pRowResRoot->FillMemberResults( pRowResults.get(), nPos, pResData->GetRowStartMeasure(),
                                                true, nullptr, nullptr );
            }
        }
    }

    const uno::Sequence<sheet::MemberResult>* ScDPSource::GetMemberResults( const ScDPLevel* pLevel )
    {
        FillMemberResults();

        long i = 0;
        long nColCount = aColLevelList.size();
        for (i=0; i<nColCount; i++)
        {
            ScDPLevel* pColLevel = aColLevelList[i];
            if ( pColLevel == pLevel )
                return &pColResults[i];
        }
        long nRowCount = aRowLevelList.size();
        for (i=0; i<nRowCount; i++)
        {
            ScDPLevel* pRowLevel = aRowLevelList[i];
            if ( pRowLevel == pLevel )
                return &pRowResults[i];
        }
        return nullptr;
    }

    // XPropertySet

    uno::Reference<beans::XPropertySetInfo> SAL_CALL ScDPSource::getPropertySetInfo()
    {
        SolarMutexGuard aGuard;
        using beans::PropertyAttribute::READONLY;

        static const SfxItemPropertyMapEntry aDPSourceMap_Impl[] =
        {
            { OUString(SC_UNO_DP_COLGRAND), 0,  cppu::UnoType<bool>::get(),              0, 0 },
            { OUString(SC_UNO_DP_DATADESC), 0,  cppu::UnoType<OUString>::get(),    beans::PropertyAttribute::READONLY, 0 },
            { OUString(SC_UNO_DP_IGNOREEMPTY), 0,  cppu::UnoType<bool>::get(),              0, 0 },     // for sheet data only
            { OUString(SC_UNO_DP_REPEATEMPTY), 0,  cppu::UnoType<bool>::get(),              0, 0 },     // for sheet data only
            { OUString(SC_UNO_DP_ROWGRAND), 0,  cppu::UnoType<bool>::get(),              0, 0 },
            { OUString(SC_UNO_DP_ROWFIELDCOUNT),    0, cppu::UnoType<sal_Int32>::get(), READONLY, 0 },
            { OUString(SC_UNO_DP_COLUMNFIELDCOUNT), 0, cppu::UnoType<sal_Int32>::get(), READONLY, 0 },
            { OUString(SC_UNO_DP_DATAFIELDCOUNT),   0, cppu::UnoType<sal_Int32>::get(), READONLY, 0 },
            { OUString(SC_UNO_DP_GRANDTOTAL_NAME),  0, cppu::UnoType<OUString>::get(), 0, 0 },
            { OUString(), 0, css::uno::Type(), 0, 0 }
        };
        static uno::Reference<beans::XPropertySetInfo> aRef =
            new SfxItemPropertySetInfo( aDPSourceMap_Impl );
        return aRef;
    }

    void SAL_CALL ScDPSource::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue )
    {
        if (aPropertyName == SC_UNO_DP_COLGRAND)
            bColumnGrand = lcl_GetBoolFromAny(aValue);
        else if (aPropertyName == SC_UNO_DP_ROWGRAND)
            bRowGrand = lcl_GetBoolFromAny(aValue);
        else if (aPropertyName == SC_UNO_DP_IGNOREEMPTY)
            setIgnoreEmptyRows( lcl_GetBoolFromAny( aValue ) );
        else if (aPropertyName == SC_UNO_DP_REPEATEMPTY)
            setRepeatIfEmpty( lcl_GetBoolFromAny( aValue ) );
        else if (aPropertyName == SC_UNO_DP_GRANDTOTAL_NAME)
        {
            OUString aName;
            if (aValue >>= aName)
                mpGrandTotalName = aName;
        }
        else
        {
            OSL_FAIL("unknown property");
            //TODO: THROW( UnknownPropertyException() );
        }
    }

    uno::Any SAL_CALL ScDPSource::getPropertyValue( const OUString& aPropertyName )
    {
        uno::Any aRet;
        if ( aPropertyName == SC_UNO_DP_COLGRAND )
            aRet <<= bColumnGrand;
        else if ( aPropertyName == SC_UNO_DP_ROWGRAND )
            aRet <<= bRowGrand;
        else if ( aPropertyName == SC_UNO_DP_IGNOREEMPTY )
            aRet <<= bIgnoreEmptyRows;
        else if ( aPropertyName == SC_UNO_DP_REPEATEMPTY )
            aRet <<= bRepeatIfEmpty;
        else if ( aPropertyName == SC_UNO_DP_DATADESC )             // read-only
            aRet <<= getDataDescription();
        else if ( aPropertyName == SC_UNO_DP_ROWFIELDCOUNT )        // read-only
            aRet <<= static_cast<sal_Int32>(maRowDims.size());
        else if ( aPropertyName == SC_UNO_DP_COLUMNFIELDCOUNT )     // read-only
            aRet <<= static_cast<sal_Int32>(maColDims.size());
        else if ( aPropertyName == SC_UNO_DP_DATAFIELDCOUNT )       // read-only
            aRet <<= static_cast<sal_Int32>(maDataDims.size());
        else if (aPropertyName == SC_UNO_DP_GRANDTOTAL_NAME)
        {
            if (mpGrandTotalName)
                aRet <<= *mpGrandTotalName;
        }
        else
        {
            OSL_FAIL("unknown property");
            //TODO: THROW( UnknownPropertyException() );
        }
        return aRet;
    }

    #if DUMP_PIVOT_TABLE
    void ScDPSource::DumpResults() const
    {
        std::cout << "+++++ column root" << std::endl;
        pColResRoot->Dump(1);
        std::cout << "+++++ row root" << std::endl;
        pRowResRoot->Dump(1);
    }
    #endif

    SC_IMPL_DUMMY_PROPERTY_LISTENER( ScDPSource )

    ScDPDimensions::ScDPDimensions( ScDPSource* pSrc ) :
        pSource( pSrc )
    {
        //TODO: hold pSource

        // include data layout dimension and duplicated dimensions
        nDimCount = pSource->GetData()->GetColumnCount() + 1 + pSource->GetDupCount();
    }

    ScDPDimensions::~ScDPDimensions()
    {
        //TODO: release pSource
    }

    void ScDPDimensions::CountChanged()
    {
        // include data layout dimension and duplicated dimensions
        long nNewCount = pSource->GetData()->GetColumnCount() + 1 + pSource->GetDupCount();
        if ( ppDims )
        {
            long i;
            long nCopy = std::min( nNewCount, nDimCount );
            rtl::Reference<ScDPDimension>* ppNew = new rtl::Reference<ScDPDimension>[nNewCount];

            for (i=0; i<nCopy; i++)             // copy existing dims
                ppNew[i] = ppDims[i];
            for (i=nCopy; i<nNewCount; i++)     // clear additional pointers
                ppNew[i] = nullptr;

            ppDims.reset( ppNew );
        }
        nDimCount = nNewCount;
    }

    // very simple XNameAccess implementation using getCount/getByIndex

    uno::Any SAL_CALL ScDPDimensions::getByName( const OUString& aName )
    {
        long nCount = getCount();
        for (long i=0; i<nCount; i++)
            if ( getByIndex(i)->getName() == aName )
            {
                uno::Reference<container::XNamed> xNamed = getByIndex(i);
                uno::Any aRet;
                aRet <<= xNamed;
                return aRet;
            }

        throw container::NoSuchElementException();
    //    return uno::Any();
    }

    uno::Sequence<OUString> SAL_CALL ScDPDimensions::getElementNames()
    {
        long nCount = getCount();
        uno::Sequence<OUString> aSeq(nCount);
        OUString* pArr = aSeq.getArray();
        for (long i=0; i<nCount; i++)
            pArr[i] = getByIndex(i)->getName();
        return aSeq;
    }

    sal_Bool SAL_CALL ScDPDimensions::hasByName( const OUString& aName )
    {
        long nCount = getCount();
        for (long i=0; i<nCount; i++)
            if ( getByIndex(i)->getName() == aName )
                return true;
        return false;
    }

    uno::Type SAL_CALL ScDPDimensions::getElementType()
    {
        return cppu::UnoType<container::XNamed>::get();
    }

    sal_Bool SAL_CALL ScDPDimensions::hasElements()
    {
        return ( getCount() > 0 );
    }

    // end of XNameAccess implementation

    long ScDPDimensions::getCount() const
    {
        //  in tabular data, every column of source data is a dimension

        return nDimCount;
    }

    ScDPDimension* ScDPDimensions::getByIndex(long nIndex) const
    {
        if ( nIndex >= 0 && nIndex < nDimCount )
        {
            if ( !ppDims )
            {
                const_cast<ScDPDimensions*>(this)->ppDims.reset(new rtl::Reference<ScDPDimension>[nDimCount] );
                for (long i=0; i<nDimCount; i++)
                    ppDims[i] = nullptr;
            }
            if ( !ppDims[nIndex].is() )
            {
                ppDims[nIndex] = new ScDPDimension( pSource, nIndex );
            }

            return ppDims[nIndex].get();
        }

        return nullptr;    //TODO: exception?
    }

    ScDPDimension::ScDPDimension( ScDPSource* pSrc, long nD ) :
        pSource( pSrc ),
        nDim( nD ),
        nFunction( ScGeneralFunction::SUM ),     // sum is default
        nSourceDim( -1 ),
        bHasSelectedPage( false ),
        mbHasHiddenMember(false)
    {
        //TODO: hold pSource
    }

    ScDPDimension::~ScDPDimension()
    {
        //TODO: release pSource
    }

    ScDPHierarchies* ScDPDimension::GetHierarchiesObject()
    {
        if (!mxHierarchies.is())
        {
            mxHierarchies = new ScDPHierarchies( pSource, nDim );
        }
        return mxHierarchies.get();
    }

    const boost::optional<OUString> & ScDPDimension::GetLayoutName() const
    {
        return mpLayoutName;
    }

    const boost::optional<OUString> & ScDPDimension::GetSubtotalName() const
    {
        return mpSubtotalName;
    }

    uno::Reference<container::XNameAccess> SAL_CALL ScDPDimension::getHierarchies()
    {
        return GetHierarchiesObject();
    }

    OUString SAL_CALL ScDPDimension::getName()
    {
        if (!aName.isEmpty())
            return aName;
        else
            return pSource->GetData()->getDimensionName( nDim );
    }

    void SAL_CALL ScDPDimension::setName( const OUString& rNewName )
    {
        //  used after cloning
        aName = rNewName;
    }

    sheet::DataPilotFieldOrientation ScDPDimension::getOrientation() const
    {
        return pSource->GetOrientation( nDim );
    }

    bool ScDPDimension::getIsDataLayoutDimension() const
    {
        return pSource->GetData()->getIsDataLayoutDimension( nDim );
    }

    void ScDPDimension::setFunction(ScGeneralFunction nNew)
    {
        nFunction = nNew;
    }

    ScDPDimension* ScDPDimension::CreateCloneObject()
    {
        OSL_ENSURE( nSourceDim < 0, "recursive duplicate - not implemented" );

        //TODO: set new name here, or temporary name ???
        OUString aNewName = aName;

        ScDPDimension* pNew = pSource->AddDuplicated( aNewName );

        pNew->aName = aNewName;             //TODO: here or in source?
        pNew->nSourceDim = nDim;            //TODO: recursive?

        return pNew;
    }

    uno::Reference<util::XCloneable> SAL_CALL ScDPDimension::createClone()
    {
        return CreateCloneObject();
    }

    const ScDPItemData& ScDPDimension::GetSelectedData()
    {
        if ( !pSelectedData )
        {
            // find the named member to initialize pSelectedData from it, with name and value

            long nLevel = 0;

            long nHierarchy = getUsedHierarchy();
            if ( nHierarchy >= ScDPHierarchies::getCount() )
                nHierarchy = 0;
            ScDPLevels* pLevels = GetHierarchiesObject()->getByIndex(nHierarchy)->GetLevelsObject();
            long nLevCount = pLevels->getCount();
            if ( nLevel < nLevCount )
            {
                ScDPMembers* pMembers = pLevels->getByIndex(nLevel)->GetMembersObject();

                //TODO: merge with ScDPMembers::getByName
                long nCount = pMembers->getCount();
                for (long i=0; i<nCount && !pSelectedData; i++)
                {
                    ScDPMember* pMember = pMembers->getByIndex(i);
                    if (aSelectedPage == pMember->GetNameStr(false))
                    {
                        pSelectedData.reset( new ScDPItemData(pMember->FillItemData()) );
                    }
                }
            }

            if ( !pSelectedData )
                pSelectedData.reset( new ScDPItemData(aSelectedPage) );      // default - name only
        }

        return *pSelectedData;
    }

    // XPropertySet

    uno::Reference<beans::XPropertySetInfo> SAL_CALL ScDPDimension::getPropertySetInfo()
    {
        SolarMutexGuard aGuard;

        static const SfxItemPropertyMapEntry aDPDimensionMap_Impl[] =
        {
            { OUString(SC_UNO_DP_FILTER),   0,  cppu::UnoType<uno::Sequence<sheet::TableFilterField>>::get(), 0, 0 },
            { OUString(SC_UNO_DP_FLAGS),    0,  cppu::UnoType<sal_Int32>::get(),                beans::PropertyAttribute::READONLY, 0 },
            { OUString(SC_UNO_DP_FUNCTION), 0,  cppu::UnoType<sheet::GeneralFunction>::get(),   0, 0 },
            { OUString(SC_UNO_DP_FUNCTION2), 0,  cppu::UnoType<sal_Int16>::get(),   0, 0 },
            { OUString(SC_UNO_DP_ISDATALAYOUT), 0,  cppu::UnoType<bool>::get(),                      beans::PropertyAttribute::READONLY, 0 },
            { OUString(SC_UNO_DP_NUMBERFO), 0,  cppu::UnoType<sal_Int32>::get(),                beans::PropertyAttribute::READONLY, 0 },
            { OUString(SC_UNO_DP_ORIENTATION), 0,  cppu::UnoType<sheet::DataPilotFieldOrientation>::get(), 0, 0 },
            { OUString(SC_UNO_DP_ORIGINAL), 0,  cppu::UnoType<container::XNamed>::get(), beans::PropertyAttribute::READONLY, 0 },
            { OUString(SC_UNO_DP_ORIGINAL_POS), 0, cppu::UnoType<sal_Int32>::get(),             0, 0 },
            { OUString(SC_UNO_DP_POSITION), 0,  cppu::UnoType<sal_Int32>::get(),                0, 0 },
            { OUString(SC_UNO_DP_REFVALUE), 0,  cppu::UnoType<sheet::DataPilotFieldReference>::get(), 0, 0 },
            { OUString(SC_UNO_DP_USEDHIERARCHY), 0,  cppu::UnoType<sal_Int32>::get(),                0, 0 },
            { OUString(SC_UNO_DP_LAYOUTNAME), 0, cppu::UnoType<OUString>::get(), 0, 0 },
            { OUString(SC_UNO_DP_FIELD_SUBTOTALNAME), 0, cppu::UnoType<OUString>::get(), 0, 0 },
            { OUString(SC_UNO_DP_HAS_HIDDEN_MEMBER), 0, cppu::UnoType<bool>::get(), 0, 0 },
            { OUString(), 0, css::uno::Type(), 0, 0 }
        };
        static uno::Reference<beans::XPropertySetInfo> aRef =
            new SfxItemPropertySetInfo( aDPDimensionMap_Impl );
        return aRef;
    }

    void SAL_CALL ScDPDimension::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue )
    {
        if ( aPropertyName == SC_UNO_DP_USEDHIERARCHY )
        {
            // #i52547# don't use the incomplete date hierarchy implementation - ignore the call
        }
        else if ( aPropertyName == SC_UNO_DP_ORIENTATION )
        {
            sheet::DataPilotFieldOrientation eEnum;
            if (aValue >>= eEnum)
                pSource->SetOrientation( nDim, eEnum );
        }
        else if ( aPropertyName == SC_UNO_DP_FUNCTION )
        {
            sheet::GeneralFunction eEnum;
            if (aValue >>= eEnum)
                setFunction( static_cast<ScGeneralFunction>(eEnum) );
        }
        else if ( aPropertyName == SC_UNO_DP_FUNCTION2 )
        {
            sal_Int16 eEnum;
            if (aValue >>= eEnum)
                setFunction( static_cast<ScGeneralFunction>(eEnum) );
        }
        else if ( aPropertyName == SC_UNO_DP_REFVALUE )
            aValue >>= aReferenceValue;
        else if ( aPropertyName == SC_UNO_DP_FILTER )
        {
            bool bDone = false;
            uno::Sequence<sheet::TableFilterField> aSeq;
            if (aValue >>= aSeq)
            {
                sal_Int32 nLength = aSeq.getLength();
                if ( nLength == 0 )
                {
                    aSelectedPage.clear();
                    bHasSelectedPage = false;
                    bDone = true;
                }
                else if ( nLength == 1 )
                {
                    const sheet::TableFilterField& rField = aSeq[0];
                    if ( rField.Field == 0 && rField.Operator == sheet::FilterOperator_EQUAL && !rField.IsNumeric )
                    {
                        aSelectedPage = rField.StringValue;
                        bHasSelectedPage = true;
                        bDone = true;
                    }
                }
            }
            if ( !bDone )
            {
                OSL_FAIL("Filter property is not a single string");
                throw lang::IllegalArgumentException();
            }
            pSelectedData.reset();       // invalid after changing aSelectedPage
        }
        else if (aPropertyName == SC_UNO_DP_LAYOUTNAME)
        {
            OUString aTmpName;
            if (aValue >>= aTmpName)
                mpLayoutName = aTmpName;
        }
        else if (aPropertyName == SC_UNO_DP_FIELD_SUBTOTALNAME)
        {
            OUString aTmpName;
            if (aValue >>= aTmpName)
                mpSubtotalName = aTmpName;
        }
        else if (aPropertyName == SC_UNO_DP_HAS_HIDDEN_MEMBER)
        {
            bool b = false;
            aValue >>= b;
            mbHasHiddenMember = b;
        }
        else
        {
            OSL_FAIL("unknown property");
            //TODO: THROW( UnknownPropertyException() );
        }
    }

    uno::Any SAL_CALL ScDPDimension::getPropertyValue( const OUString& aPropertyName )
    {
        uno::Any aRet;
        if ( aPropertyName == SC_UNO_DP_POSITION )
            aRet <<= static_cast<sal_Int32>(pSource->GetPosition( nDim ));
        else if ( aPropertyName == SC_UNO_DP_USEDHIERARCHY )
            aRet <<= static_cast<sal_Int32>(getUsedHierarchy());
        else if ( aPropertyName == SC_UNO_DP_ORIENTATION )
        {
            sheet::DataPilotFieldOrientation eVal = getOrientation();
            aRet <<= eVal;
        }
        else if ( aPropertyName == SC_UNO_DP_FUNCTION )
        {
            ScGeneralFunction nVal = getFunction();
            if (nVal == ScGeneralFunction::MEDIAN)
                nVal = ScGeneralFunction::NONE;
            const int nValAsInt = static_cast<int>(nVal);
            assert(nValAsInt >= int(css::sheet::GeneralFunction_NONE) &&
                   nValAsInt <= int(css::sheet::GeneralFunction_VARP));
            aRet <<= static_cast<sheet::GeneralFunction>(nValAsInt);
        }
        else if ( aPropertyName == SC_UNO_DP_FUNCTION2 )
        {
            ScGeneralFunction eVal = getFunction();
            aRet <<= static_cast<sal_Int16>(eVal);
        }
        else if ( aPropertyName == SC_UNO_DP_REFVALUE )
            aRet <<= aReferenceValue;
        else if ( aPropertyName == SC_UNO_DP_ISDATALAYOUT )                 // read-only properties
            aRet <<= getIsDataLayoutDimension();
        else if ( aPropertyName == SC_UNO_DP_NUMBERFO )
        {
            sal_Int32 nFormat = 0;
            ScGeneralFunction eFunc = getFunction();
            // #i63745# don't use source format for "count"
            if ( eFunc != ScGeneralFunction::COUNT && eFunc != ScGeneralFunction::COUNTNUMS )
                nFormat = pSource->GetData()->GetNumberFormat( ( nSourceDim >= 0 ) ? nSourceDim : nDim );

            switch ( aReferenceValue.ReferenceType )
            {
            case sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE:
            case sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE_DIFFERENCE:
            case sheet::DataPilotFieldReferenceType::ROW_PERCENTAGE:
            case sheet::DataPilotFieldReferenceType::COLUMN_PERCENTAGE:
            case sheet::DataPilotFieldReferenceType::TOTAL_PERCENTAGE:
                nFormat = pSource->GetData()->GetNumberFormatByIdx( NF_PERCENT_DEC2 );
                break;
            case sheet::DataPilotFieldReferenceType::INDEX:
                nFormat = pSource->GetData()->GetNumberFormatByIdx( NF_NUMBER_SYSTEM );
                break;
            default:
                break;
            }

            aRet <<= nFormat;
        }
        else if ( aPropertyName == SC_UNO_DP_ORIGINAL )
        {
            uno::Reference<container::XNamed> xOriginal;
            if (nSourceDim >= 0)
                xOriginal = pSource->GetDimensionsObject()->getByIndex(nSourceDim);
            aRet <<= xOriginal;
        }
        else if (aPropertyName == SC_UNO_DP_ORIGINAL_POS)
        {
            sal_Int32 nPos = static_cast<sal_Int32>(nSourceDim);
            aRet <<= nPos;
        }
        else if ( aPropertyName == SC_UNO_DP_FILTER )
        {
            if ( bHasSelectedPage )
            {
                // single filter field: first field equal to selected string
                sheet::TableFilterField aField( sheet::FilterConnection_AND, 0,
                        sheet::FilterOperator_EQUAL, false, 0.0, aSelectedPage );
                aRet <<= uno::Sequence<sheet::TableFilterField>( &aField, 1 );
            }
            else
                aRet <<= uno::Sequence<sheet::TableFilterField>(0);
        }
        else if (aPropertyName == SC_UNO_DP_LAYOUTNAME)
            aRet <<= mpLayoutName ? *mpLayoutName : OUString();
        else if (aPropertyName == SC_UNO_DP_FIELD_SUBTOTALNAME)
            aRet <<= mpSubtotalName ? *mpSubtotalName : OUString();
        else if (aPropertyName == SC_UNO_DP_HAS_HIDDEN_MEMBER)
            aRet <<= mbHasHiddenMember;
        else if (aPropertyName == SC_UNO_DP_FLAGS)
        {
            aRet <<= sal_Int32(0); // tabular data: all orientations are possible
        }
        else
        {
            OSL_FAIL("unknown property");
            //TODO: THROW( UnknownPropertyException() );
        }
        return aRet;
    }

    SC_IMPL_DUMMY_PROPERTY_LISTENER( ScDPDimension )

    ScDPHierarchies::ScDPHierarchies( ScDPSource* pSrc, long nD ) :
        pSource( pSrc ),
        nDim( nD )
    {
        //TODO: hold pSource
    }

    ScDPHierarchies::~ScDPHierarchies()
    {
        //TODO: release pSource
    }

    // very simple XNameAccess implementation using getCount/getByIndex

    uno::Any SAL_CALL ScDPHierarchies::getByName( const OUString& aName )
    {
        long nCount = getCount();
        for (long i=0; i<nCount; i++)
            if ( getByIndex(i)->getName() == aName )
            {
                uno::Reference<container::XNamed> xNamed = getByIndex(i);
                uno::Any aRet;
                aRet <<= xNamed;
                return aRet;
            }

        throw container::NoSuchElementException();
    }

    uno::Sequence<OUString> SAL_CALL ScDPHierarchies::getElementNames()
    {
        long nCount = getCount();
        uno::Sequence<OUString> aSeq(nCount);
        OUString* pArr = aSeq.getArray();
        for (long i=0; i<nCount; i++)
            pArr[i] = getByIndex(i)->getName();
        return aSeq;
    }

    sal_Bool SAL_CALL ScDPHierarchies::hasByName( const OUString& aName )
    {
        long nCount = getCount();
        for (long i=0; i<nCount; i++)
            if ( getByIndex(i)->getName() == aName )
                return true;
        return false;
    }

    uno::Type SAL_CALL ScDPHierarchies::getElementType()
    {
        return cppu::UnoType<container::XNamed>::get();
    }

    sal_Bool SAL_CALL ScDPHierarchies::hasElements()
    {
        return ( getCount() > 0 );
    }

    // end of XNameAccess implementation

    long ScDPHierarchies::getCount()
    {
        return nHierCount;
    }

    ScDPHierarchy* ScDPHierarchies::getByIndex(long nIndex) const
    {
        //  pass hierarchy index to new object in case the implementation
        //  will be extended to more than one hierarchy

        if ( nIndex >= 0 && nIndex < nHierCount )
        {
            if ( !ppHiers )
            {
                const_cast<ScDPHierarchies*>(this)->ppHiers.reset( new rtl::Reference<ScDPHierarchy>[nHierCount] );
                for (long i=0; i<nHierCount; i++)
                    ppHiers[i] = nullptr;
            }
            if ( !ppHiers[nIndex].is() )
            {
                ppHiers[nIndex] = new ScDPHierarchy( pSource, nDim, nIndex );
            }

            return ppHiers[nIndex].get();
        }

        return nullptr;    //TODO: exception?
    }

    ScDPHierarchy::ScDPHierarchy( ScDPSource* pSrc, long nD, long nH ) :
        pSource( pSrc ),
        nDim( nD ),
        nHier( nH )
    {
        //TODO: hold pSource
    }

    ScDPHierarchy::~ScDPHierarchy()
    {
        //TODO: release pSource
    }

    ScDPLevels* ScDPHierarchy::GetLevelsObject()
    {
        if (!mxLevels.is())
        {
            mxLevels = new ScDPLevels( pSource, nDim, nHier );
        }
        return mxLevels.get();
    }

    uno::Reference<container::XNameAccess> SAL_CALL ScDPHierarchy::getLevels()
    {
        return GetLevelsObject();
    }

    OUString SAL_CALL ScDPHierarchy::getName()
    {
        OUString aRet;        //TODO: globstr-ID !!!!
        switch (nHier)
        {
            case SC_DAPI_HIERARCHY_FLAT:
                aRet = "flat";
                break;  //TODO: name ???????
            case SC_DAPI_HIERARCHY_QUARTER:
                aRet = "Quarter";
                break;  //TODO: name ???????
            case SC_DAPI_HIERARCHY_WEEK:
                aRet = "Week";
                break;  //TODO: name ???????
            default:
                OSL_FAIL( "ScDPHierarchy::getName: unexpected hierarchy" );
                break;
        }
        return aRet;
    }

    void SAL_CALL ScDPHierarchy::setName( const OUString& /* rNewName */ )
    {
        OSL_FAIL("not implemented");        //TODO: exception?
    }

    ScDPLevels::ScDPLevels( ScDPSource* pSrc, long nD, long nH ) :
        pSource( pSrc ),
        nDim( nD ),
        nHier( nH )
    {
        //TODO: hold pSource

        //  text columns have only one level

        long nSrcDim = pSource->GetSourceDim( nDim );
        if ( pSource->IsDateDimension( nSrcDim ) )
        {
            switch ( nHier )
            {
                case SC_DAPI_HIERARCHY_FLAT:    nLevCount = SC_DAPI_FLAT_LEVELS;    break;
                case SC_DAPI_HIERARCHY_QUARTER: nLevCount = SC_DAPI_QUARTER_LEVELS; break;
                case SC_DAPI_HIERARCHY_WEEK:    nLevCount = SC_DAPI_WEEK_LEVELS;    break;
                default:
                    OSL_FAIL("wrong hierarchy");
                    nLevCount = 0;
            }
        }
        else
            nLevCount = 1;
    }

    ScDPLevels::~ScDPLevels()
    {
        //TODO: release pSource
    }

    // very simple XNameAccess implementation using getCount/getByIndex

    uno::Any SAL_CALL ScDPLevels::getByName( const OUString& aName )
    {
        long nCount = getCount();
        for (long i=0; i<nCount; i++)
            if ( getByIndex(i)->getName() == aName )
            {
                uno::Reference<container::XNamed> xNamed = getByIndex(i);
                uno::Any aRet;
                aRet <<= xNamed;
                return aRet;
            }

        throw container::NoSuchElementException();
    }

    uno::Sequence<OUString> SAL_CALL ScDPLevels::getElementNames()
    {
        long nCount = getCount();
        uno::Sequence<OUString> aSeq(nCount);
        OUString* pArr = aSeq.getArray();
        for (long i=0; i<nCount; i++)
            pArr[i] = getByIndex(i)->getName();
        return aSeq;
    }

    sal_Bool SAL_CALL ScDPLevels::hasByName( const OUString& aName )
    {
        long nCount = getCount();
        for (long i=0; i<nCount; i++)
            if ( getByIndex(i)->getName() == aName )
                return true;
        return false;
    }

    uno::Type SAL_CALL ScDPLevels::getElementType()
    {
        return cppu::UnoType<container::XNamed>::get();
    }

    sal_Bool SAL_CALL ScDPLevels::hasElements()
    {
        return ( getCount() > 0 );
    }

    // end of XNameAccess implementation

    long ScDPLevels::getCount() const
    {
        return nLevCount;
    }

    ScDPLevel* ScDPLevels::getByIndex(long nIndex) const
    {
        if ( nIndex >= 0 && nIndex < nLevCount )
        {
            if ( !ppLevs )
            {
                const_cast<ScDPLevels*>(this)->ppLevs.reset(new rtl::Reference<ScDPLevel>[nLevCount] );
                for (long i=0; i<nLevCount; i++)
                    ppLevs[i] = nullptr;
            }
            if ( !ppLevs[nIndex].is() )
            {
                ppLevs[nIndex] = new ScDPLevel( pSource, nDim, nHier, nIndex );
            }

            return ppLevs[nIndex].get();
        }

        return nullptr;    //TODO: exception?
    }

    class ScDPGlobalMembersOrder
    {
        ScDPLevel&  rLevel;
        bool        bAscending;

    public:
                ScDPGlobalMembersOrder( ScDPLevel& rLev, bool bAsc ) :
                    rLevel(rLev),
                    bAscending(bAsc)
                {}

        bool operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const;
    };

    bool ScDPGlobalMembersOrder::operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const
    {
        sal_Int32 nCompare = 0;
        // seems that some ::std::sort() implementations pass the same index twice
        if( nIndex1 != nIndex2 )
        {
            ScDPMembers* pMembers = rLevel.GetMembersObject();
            ScDPMember* pMember1 = pMembers->getByIndex(nIndex1);
            ScDPMember* pMember2 = pMembers->getByIndex(nIndex2);
            nCompare = pMember1->Compare( *pMember2 );
        }
        return bAscending ? (nCompare < 0) : (nCompare > 0);
    }

    ScDPLevel::ScDPLevel( ScDPSource* pSrc, long nD, long nH, long nL ) :
        pSource( pSrc ),
        nDim( nD ),
        nHier( nH ),
        nLev( nL ),
        aSortInfo( EMPTY_OUSTRING, true, sheet::DataPilotFieldSortMode::NAME ),   // default: sort by name
        nSortMeasure( 0 ),
        nAutoMeasure( 0 ),
        bShowEmpty( false ),
        bEnableLayout( false ),
        bRepeatItemLabels( false )
    {
        //TODO: hold pSource
        //  aSubTotals is empty
    }

    ScDPLevel::~ScDPLevel()
    {
        //TODO: release pSource
    }

    void ScDPLevel::EvaluateSortOrder()
    {
        switch (aSortInfo.Mode)
        {
            case sheet::DataPilotFieldSortMode::DATA:
                {
                    // find index of measure (index among data dimensions)

                    long nMeasureCount = pSource->GetDataDimensionCount();
                    for (long nMeasure=0; nMeasure<nMeasureCount; nMeasure++)
                    {
                        if (pSource->GetDataDimName(nMeasure) == aSortInfo.Field)
                        {
                            nSortMeasure = nMeasure;
                            break;
                        }
                    }

                    //TODO: error if not found?
                }
                break;
            case sheet::DataPilotFieldSortMode::MANUAL:
            case sheet::DataPilotFieldSortMode::NAME:
                {
                    ScDPMembers* pLocalMembers = GetMembersObject();
                    long nCount = pLocalMembers->getCount();

                    aGlobalOrder.resize( nCount );
                    for (long nPos=0; nPos<nCount; nPos++)
                        aGlobalOrder[nPos] = nPos;

                    // allow manual or name (manual is always ascending)
                    bool bAscending = ( aSortInfo.Mode == sheet::DataPilotFieldSortMode::MANUAL || aSortInfo.IsAscending );
                    ScDPGlobalMembersOrder aComp( *this, bAscending );
                    ::std::sort( aGlobalOrder.begin(), aGlobalOrder.end(), aComp );
                }
                break;
        }

        if ( aAutoShowInfo.IsEnabled )
        {
            // find index of measure (index among data dimensions)

            long nMeasureCount = pSource->GetDataDimensionCount();
            for (long nMeasure=0; nMeasure<nMeasureCount; nMeasure++)
            {
                if (pSource->GetDataDimName(nMeasure) == aAutoShowInfo.DataField)
                {
                    nAutoMeasure = nMeasure;
                    break;
                }
            }

            //TODO: error if not found?
        }
    }

    void ScDPLevel::SetEnableLayout(bool bSet)
    {
        bEnableLayout = bSet;
    }

    ScDPMembers* ScDPLevel::GetMembersObject()
    {
        if (!mxMembers.is())
        {
            mxMembers = new ScDPMembers( pSource, nDim, nHier, nLev );
        }
        return mxMembers.get();
    }

    uno::Reference<sheet::XMembersAccess> SAL_CALL ScDPLevel::getMembers()
    {
        return GetMembersObject();
    }

    uno::Sequence<sheet::MemberResult> SAL_CALL ScDPLevel::getResults()
    {
        const uno::Sequence<sheet::MemberResult>* pRes = pSource->GetMemberResults( this );
        if (pRes)
            return *pRes;

        return uno::Sequence<sheet::MemberResult>(0);       //TODO: Error?
    }

    OUString SAL_CALL ScDPLevel::getName()
    {
        long nSrcDim = pSource->GetSourceDim( nDim );
        if ( pSource->IsDateDimension( nSrcDim ) )
        {
            OUString aRet;        //TODO: globstr-ID !!!!

            if ( nHier == SC_DAPI_HIERARCHY_QUARTER )
            {
                switch ( nLev )
                {
                    case SC_DAPI_LEVEL_YEAR:
                        aRet = "Year";
                        break;
                    case SC_DAPI_LEVEL_QUARTER:
                        aRet = "Quarter";
                        break;
                    case SC_DAPI_LEVEL_MONTH:
                        aRet = "Month";
                        break;
                    case SC_DAPI_LEVEL_DAY:
                        aRet = "Day";
                        break;
                    default:
                        OSL_FAIL( "ScDPLevel::getName: unexpected level" );
                        break;
                }
            }
            else if ( nHier == SC_DAPI_HIERARCHY_WEEK )
            {
                switch ( nLev )
                {
                    case SC_DAPI_LEVEL_YEAR:
                        aRet = "Year";
                        break;
                    case SC_DAPI_LEVEL_WEEK:
                        aRet = "Week";
                        break;
                    case SC_DAPI_LEVEL_WEEKDAY:
                        aRet = "Weekday";
                        break;
                    default:
                        OSL_FAIL( "ScDPLevel::getName: unexpected level" );
                        break;
                }
            }
            if (!aRet.isEmpty())
                return aRet;
        }

        ScDPDimension* pDim = pSource->GetDimensionsObject()->getByIndex(nSrcDim);
        if (!pDim)
            return OUString();

        return pDim->getName();
    }

    void SAL_CALL ScDPLevel::setName( const OUString& /* rNewName */ )
    {
        OSL_FAIL("not implemented");        //TODO: exception?
    }

    uno::Sequence<sal_Int16> ScDPLevel::getSubTotals() const
    {
        //TODO: separate functions for settings and evaluation?

        long nSrcDim = pSource->GetSourceDim( nDim );
        if ( !pSource->SubTotalAllowed( nSrcDim ) )
            return uno::Sequence<sal_Int16>(0);

        return aSubTotals;
    }

    // XPropertySet

    uno::Reference<beans::XPropertySetInfo> SAL_CALL ScDPLevel::getPropertySetInfo()
    {
        SolarMutexGuard aGuard;

        static const SfxItemPropertyMapEntry aDPLevelMap_Impl[] =
        {
            //TODO: change type of AutoShow/Layout/Sorting to API struct when available
            { OUString(SC_UNO_DP_AUTOSHOW), 0,  cppu::UnoType<sheet::DataPilotFieldAutoShowInfo>::get(),     0, 0 },
            { OUString(SC_UNO_DP_LAYOUT),   0,  cppu::UnoType<sheet::DataPilotFieldLayoutInfo>::get(),       0, 0 },
            { OUString(SC_UNO_DP_SHOWEMPTY), 0, cppu::UnoType<bool>::get(),                                   0, 0 },
            { OUString(SC_UNO_DP_REPEATITEMLABELS), 0, cppu::UnoType<bool>::get(),                                   0, 0 },
            { OUString(SC_UNO_DP_SORTING),  0,  cppu::UnoType<sheet::DataPilotFieldSortInfo>::get(),         0, 0 },
            { OUString(SC_UNO_DP_SUBTOTAL), 0,  cppu::UnoType<uno::Sequence<sheet::GeneralFunction>>::get(), 0, 0 },
            { OUString(SC_UNO_DP_SUBTOTAL2), 0, cppu::UnoType<uno::Sequence<sal_Int16>>::get(), 0, 0 },
            { OUString(), 0, css::uno::Type(), 0, 0 }
        };
        static uno::Reference<beans::XPropertySetInfo> aRef =
            new SfxItemPropertySetInfo( aDPLevelMap_Impl );
        return aRef;
    }

    void SAL_CALL ScDPLevel::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue )
    {
        if ( aPropertyName == SC_UNO_DP_SHOWEMPTY )
            bShowEmpty = lcl_GetBoolFromAny(aValue);
        else if ( aPropertyName == SC_UNO_DP_REPEATITEMLABELS )
            bRepeatItemLabels = lcl_GetBoolFromAny(aValue);
        else if ( aPropertyName == SC_UNO_DP_SUBTOTAL )
        {
            uno::Sequence<sheet::GeneralFunction> aSeq;
            aValue >>= aSeq;
            aSubTotals.realloc(aSeq.getLength());
            for (sal_Int32 nIndex = 0; nIndex < aSeq.getLength(); nIndex++)
            {
                aSubTotals[nIndex] = static_cast<sal_Int16>(aSeq[nIndex]);
            }
        }
        else if ( aPropertyName == SC_UNO_DP_SUBTOTAL2 )
            aValue >>= aSubTotals;
        else if ( aPropertyName == SC_UNO_DP_SORTING )
            aValue >>= aSortInfo;
        else if ( aPropertyName == SC_UNO_DP_AUTOSHOW )
            aValue >>= aAutoShowInfo;
        else if ( aPropertyName == SC_UNO_DP_LAYOUT )
            aValue >>= aLayoutInfo;
        else
        {
            OSL_FAIL("unknown property");
        }
    }

    uno::Any SAL_CALL ScDPLevel::getPropertyValue( const OUString& aPropertyName )
    {
        uno::Any aRet;
        if ( aPropertyName == SC_UNO_DP_SHOWEMPTY )
            aRet <<= bShowEmpty;
        else if ( aPropertyName == SC_UNO_DP_REPEATITEMLABELS )
            aRet <<= bRepeatItemLabels;
        else if ( aPropertyName == SC_UNO_DP_SUBTOTAL )
        {
            uno::Sequence<sal_Int16> aSeq = getSubTotals();
            uno::Sequence<sheet::GeneralFunction> aNewSeq;
            aNewSeq.realloc(aSeq.getLength());
            for (sal_Int32 nIndex = 0; nIndex < aSeq.getLength(); nIndex++)
            {
                if (aSeq[nIndex] == sheet::GeneralFunction2::MEDIAN)
                    aNewSeq[nIndex] = sheet::GeneralFunction_NONE;
                else
                    aNewSeq[nIndex] = static_cast<sheet::GeneralFunction>(aSeq[nIndex]);
            }

            aRet <<= aNewSeq;
        }
        else if ( aPropertyName == SC_UNO_DP_SUBTOTAL2 )
        {
            uno::Sequence<sal_Int16> aSeq = getSubTotals();        //TODO: avoid extra copy?
            aRet <<= aSeq;
        }
        else if ( aPropertyName == SC_UNO_DP_SORTING )
            aRet <<= aSortInfo;
        else if ( aPropertyName == SC_UNO_DP_AUTOSHOW )
            aRet <<= aAutoShowInfo;
        else if ( aPropertyName == SC_UNO_DP_LAYOUT )
            aRet <<= aLayoutInfo;
        else if (aPropertyName == SC_UNO_DP_LAYOUTNAME)
        {
            // read only property
            long nSrcDim = pSource->GetSourceDim(nDim);
            ScDPDimension* pDim = pSource->GetDimensionsObject()->getByIndex(nSrcDim);
            if (!pDim)
                return aRet;

            const boost::optional<OUString> & pLayoutName = pDim->GetLayoutName();
            if (!pLayoutName)
                return aRet;

            aRet <<= *pLayoutName;
        }
        else
        {
            OSL_FAIL("unknown property");
        }
        return aRet;
    }

    SC_IMPL_DUMMY_PROPERTY_LISTENER( ScDPLevel )

    ScDPMembers::ScDPMembers( ScDPSource* pSrc, long nD, long nH, long nL ) :
        pSource( pSrc ),
        nDim( nD ),
        nHier( nH ),
        nLev( nL )
    {
        //TODO: hold pSource

        long nSrcDim = pSource->GetSourceDim( nDim );
        if ( pSource->IsDataLayoutDimension(nSrcDim) )
            nMbrCount = pSource->GetDataDimensionCount();
        else if ( nHier != SC_DAPI_HIERARCHY_FLAT && pSource->IsDateDimension( nSrcDim ) )
        {
            nMbrCount = 0;
            if ( nHier == SC_DAPI_HIERARCHY_QUARTER )
            {
                switch (nLev)
                {
                    case SC_DAPI_LEVEL_YEAR:
                        {
                            const ScDPItemData* pLastNumData = nullptr;
                            for ( SCROW n = 0; n < static_cast<SCROW>(pSource->GetData()->GetColumnEntries(nDim).size()); n-- )
                            {
                                const ScDPItemData* pData  = GetSrcItemDataByIndex( n );
                                if ( pData && pData->HasStringData() )
                                    break;
                                else
                                    pLastNumData = pData;
                            }

                            if ( pLastNumData )
                            {
                                const ScDPItemData*  pFirstData = GetSrcItemDataByIndex( 0 );
                                double fFirstVal = pFirstData->GetValue();
                                double fLastVal = pLastNumData->GetValue();

                                long nFirstYear = pSource->GetData()->GetDatePart(
                                            static_cast<long>(::rtl::math::approxFloor( fFirstVal )),
                                            nHier, nLev );
                                long nLastYear = pSource->GetData()->GetDatePart(
                                            static_cast<long>(::rtl::math::approxFloor( fLastVal )),
                                            nHier, nLev );

                                nMbrCount = nLastYear + 1 - nFirstYear;
                            }
                            else
                                nMbrCount = 0;      // no values
                        }
                        break;
                    case SC_DAPI_LEVEL_QUARTER: nMbrCount = 4;  break;
                    case SC_DAPI_LEVEL_MONTH:   nMbrCount = 12; break;
                    case SC_DAPI_LEVEL_DAY:     nMbrCount = 31; break;
                    default:
                        OSL_FAIL( "ScDPMembers::ScDPMembers: unexpected level" );
                        break;
                }
            }
            else if ( nHier == SC_DAPI_HIERARCHY_WEEK )
            {
                switch (nLev)
                {
                    case SC_DAPI_LEVEL_YEAR:    nMbrCount = 1;  break;      //TODO: get years from source
                    case SC_DAPI_LEVEL_WEEK:    nMbrCount = 53; break;
                    case SC_DAPI_LEVEL_WEEKDAY: nMbrCount = 7;  break;
                    default:
                        OSL_FAIL( "ScDPMembers::ScDPMembers: unexpected level" );
                        break;
                }
            }
        }
        else
            nMbrCount = pSource->GetData()->GetMembersCount( nSrcDim );
    }

    ScDPMembers::~ScDPMembers()
    {
    }

    // XNameAccess implementation using getCount/getByIndex

    sal_Int32 ScDPMembers::GetIndexFromName( const OUString& rName ) const
    {
        if ( aHashMap.empty() )
        {
            // store the index for each name

            sal_Int32 nCount = getCount();
            for (sal_Int32 i=0; i<nCount; i++)
                aHashMap[ getByIndex(i)->getName() ] = i;
        }

        ScDPMembersHashMap::const_iterator aIter = aHashMap.find( rName );
        if ( aIter != aHashMap.end() )
            return aIter->second;           // found index
        else
            return -1;                      // not found
    }

    uno::Any SAL_CALL ScDPMembers::getByName( const OUString& aName )
    {
        sal_Int32 nIndex = GetIndexFromName( aName );
        if ( nIndex >= 0 )
        {
            uno::Reference<container::XNamed> xNamed = getByIndex(nIndex);
            uno::Any aRet;
            aRet <<= xNamed;
            return aRet;
        }

        throw container::NoSuchElementException();
    }

    uno::Sequence<OUString> SAL_CALL ScDPMembers::getElementNames()
    {
        return getElementNames( false );
    }

    sal_Bool SAL_CALL ScDPMembers::hasByName( const OUString& aName )
    {
        return ( GetIndexFromName( aName ) >= 0 );
    }

    uno::Type SAL_CALL ScDPMembers::getElementType()
    {
        return cppu::UnoType<container::XNamed>::get();
    }

    sal_Bool SAL_CALL ScDPMembers::hasElements()
    {
        return ( getCount() > 0 );
    }

    // end of XNameAccess implementation

    // XMembersAccess implementation

    uno::Sequence<OUString> SAL_CALL ScDPMembers::getLocaleIndependentElementNames()
    {
        return getElementNames( true );
    }

    // end of XMembersAccess implementation

    uno::Sequence<OUString> ScDPMembers::getElementNames( bool bLocaleIndependent ) const
    {
        // Return list of names in sorted order,
        // so it's displayed in that order in the field options dialog.
        // Sorting is done at the level object (parent of this).

        ScDPLevel* pLevel = pSource->GetDimensionsObject()->getByIndex(nDim)->
            GetHierarchiesObject()->getByIndex(nHier)->GetLevelsObject()->getByIndex(nLev);
        pLevel->EvaluateSortOrder();
        const std::vector<sal_Int32>& rGlobalOrder = pLevel->GetGlobalOrder();
        bool bSort = !rGlobalOrder.empty();

        long nCount = getCount();
        uno::Sequence<OUString> aSeq(nCount);
        OUString* pArr = aSeq.getArray();
        for (long i=0; i<nCount; i++)
            pArr[i] = getByIndex(bSort ? rGlobalOrder[i] : i)->GetNameStr( bLocaleIndependent);
        return aSeq;
    }

    long ScDPMembers::getMinMembers() const
    {
        // used in lcl_CountMinMembers

        long nVisCount = 0;
        if (!maMembers.empty())
        {
            MembersType::const_iterator it = maMembers.begin(), itEnd = maMembers.end();
            for (; it != itEnd; ++it)
            {
                //  count only visible with details (default is true for both)
                const rtl::Reference<ScDPMember>& pMbr = *it;
                if (!pMbr.get() || (pMbr->isVisible() && pMbr->getShowDetails()))
                    ++nVisCount;
            }
        }
        else
            nVisCount = nMbrCount;      // default for all

        return nVisCount;
    }

    ScDPMember* ScDPMembers::getByIndex(long nIndex) const
    {
        //  result of GetColumnEntries must not change between ScDPMembers ctor
        //  and all calls to getByIndex

        if ( nIndex >= 0 && nIndex < nMbrCount )
        {
            if (maMembers.empty())
                maMembers.resize(nMbrCount);

            if (!maMembers[nIndex].get())
            {
                rtl::Reference<ScDPMember> pNew;
                long nSrcDim = pSource->GetSourceDim( nDim );
                if ( pSource->IsDataLayoutDimension(nSrcDim) )
                {
                    // empty name (never shown, not used for lookup)
                    pNew.set(new ScDPMember(pSource, nDim, nHier, nLev, 0));
                }
                else if ( nHier != SC_DAPI_HIERARCHY_FLAT && pSource->IsDateDimension( nSrcDim ) )
                {
                    sal_Int32 nGroupBy = 0;
                    sal_Int32 nVal = 0;
                    OUString aName;

                    if ( nLev == SC_DAPI_LEVEL_YEAR )   // YEAR is in both hierarchies
                    {
                        //TODO: cache year range here!

                        double fFirstVal = pSource->GetData()->GetMemberByIndex( nSrcDim, 0 )->GetValue();
                        long nFirstYear = pSource->GetData()->GetDatePart(
                                            static_cast<long>(::rtl::math::approxFloor( fFirstVal )),
                                            nHier, nLev );

                        nVal = nFirstYear + nIndex;
                    }
                    else if ( nHier == SC_DAPI_HIERARCHY_WEEK && nLev == SC_DAPI_LEVEL_WEEKDAY )
                    {
                        nVal = nIndex;              // DayOfWeek is 0-based
                        aName = ScGlobal::GetCalendar()->getDisplayName(
                            css::i18n::CalendarDisplayIndex::DAY,
                            sal::static_int_cast<sal_Int16>(nVal), 0 );
                    }
                    else if ( nHier == SC_DAPI_HIERARCHY_QUARTER && nLev == SC_DAPI_LEVEL_MONTH )
                    {
                        nVal = nIndex;              // Month is 0-based
                        aName = ScGlobal::GetCalendar()->getDisplayName(
                            css::i18n::CalendarDisplayIndex::MONTH,
                            sal::static_int_cast<sal_Int16>(nVal), 0 );
                    }
                    else
                        nVal = nIndex + 1;          // Quarter, Day, Week are 1-based

                    switch (nLev)
                    {
                        case SC_DAPI_LEVEL_YEAR:
                            nGroupBy = sheet::DataPilotFieldGroupBy::YEARS;
                        break;
                        case SC_DAPI_LEVEL_QUARTER:
                        case SC_DAPI_LEVEL_WEEK:
                            nGroupBy = sheet::DataPilotFieldGroupBy::QUARTERS;
                        break;
                        case SC_DAPI_LEVEL_MONTH:
                        case SC_DAPI_LEVEL_WEEKDAY:
                            nGroupBy = sheet::DataPilotFieldGroupBy::MONTHS;
                        break;
                        case SC_DAPI_LEVEL_DAY:
                            nGroupBy = sheet::DataPilotFieldGroupBy::DAYS;
                        break;
                        default:
                            ;
                    }
                    if (aName.isEmpty())
                        aName = OUString::number(nVal);

                    ScDPItemData aData(nGroupBy, nVal);
                    SCROW nId = pSource->GetCache()->GetIdByItemData(nDim, aData);
                    pNew.set(new ScDPMember(pSource, nDim, nHier, nLev, nId));
                }
                else
                {
                    const std::vector<SCROW>& memberIndexs = pSource->GetData()->GetColumnEntries(nSrcDim);
                    pNew.set(new ScDPMember(pSource, nDim, nHier, nLev, memberIndexs[nIndex]));
                }
                maMembers[nIndex] = pNew;
            }

            return maMembers[nIndex].get();
        }

        return nullptr;    //TODO: exception?
    }

    ScDPMember::ScDPMember(
        ScDPSource* pSrc, long nD, long nH, long nL, SCROW nIndex) :
        pSource( pSrc ),
        nDim( nD ),
        nHier( nH ),
        nLev( nL ),
        mnDataId( nIndex ),
        nPosition( -1 ),
        bVisible( true ),
        bShowDet( true )
    {
        //TODO: hold pSource
    }

    ScDPMember::~ScDPMember()
    {
        //TODO: release pSource
    }

    bool ScDPMember::IsNamedItem(SCROW nIndex) const
    {
        long nSrcDim = pSource->GetSourceDim( nDim );
        if ( nHier != SC_DAPI_HIERARCHY_FLAT && pSource->IsDateDimension( nSrcDim ) )
        {
            const ScDPItemData* pData = pSource->GetCache()->GetItemDataById(nDim, nIndex);
            if (pData->IsValue())
            {
                long nComp = pSource->GetData()->GetDatePart(
                    static_cast<long>(::rtl::math::approxFloor( pData->GetValue() )),
                    nHier, nLev );
                //  fValue is converted from integer, so simple comparison works
                const ScDPItemData* pData2 = GetItemData();
                return pData2 && nComp == pData2->GetValue();
            }
        }

        return  nIndex == mnDataId;
    }

    sal_Int32 ScDPMember::Compare( const ScDPMember& rOther ) const
    {
        if ( nPosition >= 0 )
        {
            if ( rOther.nPosition >= 0 )
            {
                OSL_ENSURE( nPosition != rOther.nPosition, "same position for two members" );
                return ( nPosition < rOther.nPosition ) ? -1 : 1;
            }
            else
            {
                // only this has a position - members with specified positions come before those without
                return -1;
            }
        }
        else if ( rOther.nPosition >= 0 )
        {
            // only rOther has a position
            return 1;
        }

        // no positions set - compare names
       return pSource->GetData()->Compare( pSource->GetSourceDim(nDim),mnDataId,rOther.GetItemDataId());
    }

    ScDPItemData ScDPMember::FillItemData() const
    {
        //TODO: handle date hierarchy...

        const ScDPItemData* pData = GetItemData();
        return (pData ? *pData : ScDPItemData());
    }

    const boost::optional<OUString> & ScDPMember::GetLayoutName() const
    {
        return mpLayoutName;
    }

    OUString ScDPMember::GetNameStr( bool bLocaleIndependent ) const
    {
        const ScDPItemData* pData = GetItemData();
        if (pData)
            return pSource->GetData()->GetFormattedString(nDim, *pData, bLocaleIndependent);
        return OUString();
    }

    OUString SAL_CALL ScDPMember::getName()
    {
        return GetNameStr( false );
    }

    void SAL_CALL ScDPMember::setName( const OUString& /* rNewName */ )
    {
        OSL_FAIL("not implemented");        //TODO: exception?
    }

    // XPropertySet

    uno::Reference<beans::XPropertySetInfo> SAL_CALL ScDPMember::getPropertySetInfo()
    {
        SolarMutexGuard aGuard;

        static const SfxItemPropertyMapEntry aDPMemberMap_Impl[] =
        {
            { OUString(SC_UNO_DP_ISVISIBLE), 0,  cppu::UnoType<bool>::get(),              0, 0 },
            { OUString(SC_UNO_DP_POSITION), 0,  cppu::UnoType<sal_Int32>::get(),        0, 0 },
            { OUString(SC_UNO_DP_SHOWDETAILS), 0,  cppu::UnoType<bool>::get(),              0, 0 },
            { OUString(SC_UNO_DP_LAYOUTNAME), 0, cppu::UnoType<OUString>::get(), 0, 0 },
            { OUString(), 0, css::uno::Type(), 0, 0 }
        };
        static uno::Reference<beans::XPropertySetInfo> aRef =
            new SfxItemPropertySetInfo( aDPMemberMap_Impl );
        return aRef;
    }

    void SAL_CALL ScDPMember::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue )
    {
        if ( aPropertyName == SC_UNO_DP_ISVISIBLE )
            bVisible = lcl_GetBoolFromAny(aValue);
        else if ( aPropertyName == SC_UNO_DP_SHOWDETAILS )
            bShowDet = lcl_GetBoolFromAny(aValue);
        else if ( aPropertyName == SC_UNO_DP_POSITION )
            aValue >>= nPosition;
        else if (aPropertyName == SC_UNO_DP_LAYOUTNAME)
        {
            OUString aName;
            if (aValue >>= aName)
                mpLayoutName = aName;
        }
        else
        {
            OSL_FAIL("unknown property");
        }
    }

    uno::Any SAL_CALL ScDPMember::getPropertyValue( const OUString& aPropertyName )
    {
        uno::Any aRet;
        if ( aPropertyName == SC_UNO_DP_ISVISIBLE )
            aRet <<= bVisible;
        else if ( aPropertyName == SC_UNO_DP_SHOWDETAILS )
            aRet <<= bShowDet;
        else if ( aPropertyName == SC_UNO_DP_POSITION )
            aRet <<= nPosition;
        else if (aPropertyName == SC_UNO_DP_LAYOUTNAME)
            aRet <<= mpLayoutName ? *mpLayoutName : OUString();
        else
        {
            OSL_FAIL("unknown property");
        }
        return aRet;
    }

    SC_IMPL_DUMMY_PROPERTY_LISTENER( ScDPMember )

    const ScDPCache* ScDPSource::GetCache()
    {
        OSL_ENSURE( GetData() , "empty ScDPTableData pointer");
        return ( GetData()!=nullptr ) ? &GetData()->GetCacheTable().getCache() : nullptr ;
    }

    const ScDPItemData* ScDPMember::GetItemData() const
    {
        const ScDPItemData* pData = pSource->GetItemDataById(nDim, mnDataId);
        SAL_WARN_IF( !pData, "sc.core", "ScDPMember::GetItemData: what data? nDim " << nDim << ", mnDataId " << mnDataId);
        return pData;
    }

    const ScDPItemData* ScDPSource::GetItemDataById(long nDim, long nId)
    {
        return GetData()->GetMemberById(nDim, nId);
    }

    const ScDPItemData* ScDPMembers::GetSrcItemDataByIndex(SCROW nIndex)
    {
        const std::vector< SCROW >& memberIds = pSource->GetData()->GetColumnEntries( nDim );
        if ( nIndex >= static_cast<long>(memberIds.size()) || nIndex < 0 )
            return nullptr;
        SCROW nId =  memberIds[ nIndex ];
        return pSource->GetItemDataById( nDim, nId );
    }

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

↑ V524 It is odd that the body of 'removeVetoableChangeListener' function is fully equivalent to the body of 'addVetoableChangeListener' function.

↑ V524 It is odd that the body of 'removePropertyChangeListener' function is fully equivalent to the body of 'addPropertyChangeListener' function.

↑ V524 It is odd that the body of 'removePropertyChangeListener' function is fully equivalent to the body of 'addPropertyChangeListener' function.

↑ V524 It is odd that the body of 'removePropertyChangeListener' function is fully equivalent to the body of 'addPropertyChangeListener' function.

↑ V524 It is odd that the body of 'removeVetoableChangeListener' function is fully equivalent to the body of 'addVetoableChangeListener' function.

↑ V524 It is odd that the body of 'removePropertyChangeListener' function is fully equivalent to the body of 'addPropertyChangeListener' function.

↑ V524 It is odd that the body of 'removeVetoableChangeListener' function is fully equivalent to the body of 'addVetoableChangeListener' function.

↑ V524 It is odd that the body of 'removeVetoableChangeListener' function is fully equivalent to the body of 'addVetoableChangeListener' function.