/* -*- 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 <dpcache.hxx>
#include <dpdimsave.hxx>
#include <dpgroup.hxx>
#include <dpobject.hxx>
#include <dputil.hxx>
#include <document.hxx>

#include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp>

#include <svl/zforlist.hxx>
#include <osl/diagnose.h>
#include <rtl/math.hxx>
#include <algorithm>

#include <globstr.hrc>
#include <scresid.hxx>

using namespace com::sun::star;

ScDPSaveGroupItem::ScDPSaveGroupItem( const OUString& rName ) :
    aGroupName(rName) {}

ScDPSaveGroupItem::~ScDPSaveGroupItem() {}

void ScDPSaveGroupItem::AddElement( const OUString& rName )
{
    aElements.push_back(rName);
}

void ScDPSaveGroupItem::AddElementsFromGroup( const ScDPSaveGroupItem& rGroup )
{
    // add all elements of the other group (used for nested grouping)

    for ( const auto& rElement : rGroup.aElements )
        aElements.push_back( rElement );
}

bool ScDPSaveGroupItem::RemoveElement( const OUString& rName )
{
    auto it = std::find(aElements.begin(), aElements.end(), rName); //TODO: ignore case
    if (it != aElements.end())
    {
        aElements.erase(it);
        return true;
    }
    return false;   // not found
}

bool ScDPSaveGroupItem::IsEmpty() const
{
    return aElements.empty();
}

size_t ScDPSaveGroupItem::GetElementCount() const
{
    return aElements.size();
}

const OUString* ScDPSaveGroupItem::GetElementByIndex(size_t nIndex) const
{
    return (nIndex < aElements.size()) ? &aElements[ nIndex ] : nullptr;
}

void ScDPSaveGroupItem::Rename( const OUString& rNewName )
{
    aGroupName = rNewName;
}

void ScDPSaveGroupItem::RemoveElementsFromGroups( ScDPSaveGroupDimension& rDimension ) const
{
    // remove this group's elements from their groups in rDimension
    // (rDimension must be a different dimension from the one which contains this)

    for ( const auto& rElement : aElements )
        rDimension.RemoveFromGroups( rElement );
}

void ScDPSaveGroupItem::ConvertElementsToItems(SvNumberFormatter* pFormatter) const
{
    maItems.reserve(aElements.size());
    for (const auto& rElement : aElements)
    {
        sal_uInt32 nFormat = 0;
        double fValue;
        ScDPItemData aData;
        if (pFormatter->IsNumberFormat(rElement, nFormat, fValue))
            aData.SetValue(fValue);
        else
            aData.SetString(rElement);

        maItems.push_back(aData);
    }
}

bool ScDPSaveGroupItem::HasInGroup(const ScDPItemData& rItem) const
{
    return std::find(maItems.begin(), maItems.end(), rItem) != maItems.end();
}

void ScDPSaveGroupItem::AddToData(ScDPGroupDimension& rDataDim) const
{
    ScDPGroupItem aGroup(aGroupName);
    for (const auto& rItem : maItems)
        aGroup.AddElement(rItem);

    rDataDim.AddItem(aGroup);
}

ScDPSaveGroupDimension::ScDPSaveGroupDimension( const OUString& rSource, const OUString& rName ) :
    aSourceDim( rSource ),
    aGroupDimName( rName ),
    nDatePart( 0 )
{
}

ScDPSaveGroupDimension::ScDPSaveGroupDimension( const OUString& rSource, const OUString& rName, const ScDPNumGroupInfo& rDateInfo, sal_Int32 nPart ) :
    aSourceDim( rSource ),
    aGroupDimName( rName ),
    aDateInfo( rDateInfo ),
    nDatePart( nPart )
{
}

void ScDPSaveGroupDimension::SetDateInfo( const ScDPNumGroupInfo& rInfo, sal_Int32 nPart )
{
    aDateInfo = rInfo;
    nDatePart = nPart;
}

void ScDPSaveGroupDimension::AddGroupItem( const ScDPSaveGroupItem& rItem )
{
    aGroups.push_back( rItem );
}

OUString ScDPSaveGroupDimension::CreateGroupName(const OUString& rPrefix)
{
    // create a name for a new group, using "Group1", "Group2" etc. (translated prefix in rPrefix)

    //TODO: look in all dimensions, to avoid clashes with automatic groups (=name of base element)?
    //TODO: (only dimensions for the same base)

    sal_Int32 nAdd = 1;                                 // first try is "Group1"
    const sal_Int32 nMaxAdd = nAdd + aGroups.size();    // limit the loop
    while ( nAdd <= nMaxAdd )
    {
        OUString aGroupName = rPrefix + OUString::number( nAdd );

        // look for existing groups
        bool bExists = std::any_of(aGroups.begin(), aGroups.end(),
            [&aGroupName](const ScDPSaveGroupItem& rGroup) {
                return rGroup.GetGroupName() == aGroupName; //TODO: ignore case
            });

        if ( !bExists )
            return aGroupName;          // found a new name

        ++nAdd;                         // continue with higher number
    }

    OSL_FAIL("CreateGroupName: no valid name found");
    return OUString();
}

const ScDPSaveGroupItem* ScDPSaveGroupDimension::GetNamedGroup( const OUString& rGroupName ) const
{
    return const_cast< ScDPSaveGroupDimension* >( this )->GetNamedGroupAcc( rGroupName );
}

ScDPSaveGroupItem* ScDPSaveGroupDimension::GetNamedGroupAcc( const OUString& rGroupName )
{
    auto aIter = std::find_if(aGroups.begin(), aGroups.end(),
        [&rGroupName](const ScDPSaveGroupItem& rGroup) {
            return rGroup.GetGroupName() == rGroupName; //TODO: ignore case
        });
    if (aIter != aGroups.end())
        return &*aIter;

    return nullptr;        // none found
}

long ScDPSaveGroupDimension::GetGroupCount() const
{
    return aGroups.size();
}

const ScDPSaveGroupItem& ScDPSaveGroupDimension::GetGroupByIndex( long nIndex ) const
{
    return aGroups[nIndex];
}


void ScDPSaveGroupDimension::RemoveFromGroups( const OUString& rItemName )
{
    //  if the item is in any group, remove it from the group,
    //  also remove the group if it is empty afterwards

    for ( ScDPSaveGroupItemVec::iterator aIter(aGroups.begin()); aIter != aGroups.end(); ++aIter )
        if ( aIter->RemoveElement( rItemName ) )
        {
            if ( aIter->IsEmpty() )         // removed last item from the group?
                aGroups.erase( aIter );     // then remove the group

            return;     // don't have to look further
        }
}

void ScDPSaveGroupDimension::RemoveGroup(const OUString& rGroupName)
{
    auto aIter = std::find_if(aGroups.begin(), aGroups.end(),
        [&rGroupName](const ScDPSaveGroupItem& rGroup) {
            return rGroup.GetGroupName() == rGroupName; //TODO: ignore case
        });
    if (aIter != aGroups.end())
        aGroups.erase( aIter );
}

bool ScDPSaveGroupDimension::IsEmpty() const
{
    return aGroups.empty();
}

bool ScDPSaveGroupDimension::HasOnlyHidden(const ScDPUniqueStringSet& rVisible)
{
    // check if there are only groups that don't appear in the list of visible names

    return std::none_of(aGroups.begin(), aGroups.end(),
        [&rVisible](const ScDPSaveGroupItem& rGroup) { return rVisible.count(rGroup.GetGroupName()) > 0; });
}

void ScDPSaveGroupDimension::Rename( const OUString& rNewName )
{
    aGroupDimName = rNewName;
}

bool ScDPSaveGroupDimension::IsInGroup(const ScDPItemData& rItem) const
{
    return std::any_of(aGroups.begin(), aGroups.end(),
        [&rItem](const ScDPSaveGroupItem& rGroup) { return rGroup.HasInGroup(rItem); });
}

namespace {

bool isInteger(double fValue)
{
    return rtl::math::approxEqual(fValue, rtl::math::approxFloor(fValue));
}

void fillDateGroupDimension(
    ScDPCache& rCache, ScDPNumGroupInfo& rDateInfo, long nSourceDim, long nGroupDim,
    sal_Int32 nDatePart, const SvNumberFormatter* pFormatter)
{
    // Auto min/max is only used for "Years" part, but the loop is always
    // needed.
    double fSourceMin = 0.0;
    double fSourceMax = 0.0;
    bool bFirst = true;

    const ScDPCache::ScDPItemDataVec& rItems = rCache.GetDimMemberValues(nSourceDim);
    for (const ScDPItemData& rItem : rItems)
    {
        if (rItem.GetType() != ScDPItemData::Value)
            continue;

        double fVal = rItem.GetValue();
        if (bFirst)
        {
            fSourceMin = fSourceMax = fVal;
            bFirst = false;
        }
        else
        {
            if (fVal < fSourceMin)
                fSourceMin = fVal;
            if ( fVal > fSourceMax )
                fSourceMax = fVal;
        }
    }

    // For the start/end values, use the same date rounding as in
    // ScDPNumGroupDimension::GetNumEntries (but not for the list of
    // available years).
    if (rDateInfo.mbAutoStart)
        rDateInfo.mfStart = rtl::math::approxFloor(fSourceMin);
    if (rDateInfo.mbAutoEnd)
        rDateInfo.mfEnd = rtl::math::approxFloor(fSourceMax) + 1;

    //TODO: if not automatic, limit fSourceMin/fSourceMax for list of year values?

    long nStart = 0, nEnd = 0; // end is inclusive

    switch (nDatePart)
    {
        case sheet::DataPilotFieldGroupBy::YEARS:
            nStart = ScDPUtil::getDatePartValue(
                fSourceMin, nullptr, sheet::DataPilotFieldGroupBy::YEARS, pFormatter);
            nEnd = ScDPUtil::getDatePartValue(fSourceMax, nullptr, sheet::DataPilotFieldGroupBy::YEARS, pFormatter);
            break;
        case sheet::DataPilotFieldGroupBy::QUARTERS: nStart = 1; nEnd = 4;   break;
        case sheet::DataPilotFieldGroupBy::MONTHS:   nStart = 1; nEnd = 12;  break;
        case sheet::DataPilotFieldGroupBy::DAYS:     nStart = 1; nEnd = 366; break;
        case sheet::DataPilotFieldGroupBy::HOURS:    nStart = 0; nEnd = 23;  break;
        case sheet::DataPilotFieldGroupBy::MINUTES:  nStart = 0; nEnd = 59;  break;
        case sheet::DataPilotFieldGroupBy::SECONDS:  nStart = 0; nEnd = 59;  break;
        default:
            OSL_FAIL("invalid date part");
    }

    // Now, populate the group items in the cache.
    rCache.ResetGroupItems(nGroupDim, rDateInfo, nDatePart);

    for (long nValue = nStart; nValue <= nEnd; ++nValue)
        rCache.SetGroupItem(nGroupDim, ScDPItemData(nDatePart, nValue));

    // add first/last entry (min/max)
    rCache.SetGroupItem(nGroupDim, ScDPItemData(nDatePart, ScDPItemData::DateFirst));
    rCache.SetGroupItem(nGroupDim, ScDPItemData(nDatePart, ScDPItemData::DateLast));
}

}

void ScDPSaveGroupDimension::AddToData( ScDPGroupTableData& rData ) const
{
    long nSourceIndex = rData.GetDimensionIndex( aSourceDim );
    if ( nSourceIndex >= 0 )
    {
        ScDPGroupDimension aDim( nSourceIndex, aGroupDimName );
        if ( nDatePart )
        {
            // date grouping

            aDim.SetDateDimension();
        }
        else
        {
            // normal (manual) grouping

            for (const auto& rGroup : aGroups)
                rGroup.AddToData(aDim);
        }

        rData.AddGroupDimension( aDim );
    }
}

void ScDPSaveGroupDimension::AddToCache(ScDPCache& rCache) const
{
    long nSourceDim = rCache.GetDimensionIndex(aSourceDim);
    if (nSourceDim < 0)
        return;

    long nDim = rCache.AppendGroupField();
    SvNumberFormatter* pFormatter = rCache.GetDoc()->GetFormatTable();

    if (nDatePart)
    {
        fillDateGroupDimension(rCache, aDateInfo, nSourceDim, nDim, nDatePart, pFormatter);
        return;
    }

    rCache.ResetGroupItems(nDim, aDateInfo, 0);
    for (const ScDPSaveGroupItem& rGI : aGroups)
    {
        rGI.ConvertElementsToItems(pFormatter);
        rCache.SetGroupItem(nDim, ScDPItemData(rGI.GetGroupName()));
    }

    const ScDPCache::ScDPItemDataVec& rItems = rCache.GetDimMemberValues(nSourceDim);
    for (const ScDPItemData& rItem : rItems)
    {
        if (!IsInGroup(rItem))
            // Not in any group.  Add as its own group.
            rCache.SetGroupItem(nDim, rItem);
    }
}

ScDPSaveNumGroupDimension::ScDPSaveNumGroupDimension( const OUString& rName, const ScDPNumGroupInfo& rInfo ) :
    aDimensionName( rName ),
    aGroupInfo( rInfo ),
    nDatePart( 0 )
{
}

ScDPSaveNumGroupDimension::ScDPSaveNumGroupDimension( const OUString& rName, const ScDPNumGroupInfo& rDateInfo, sal_Int32 nPart ) :
    aDimensionName( rName ),
    aDateInfo( rDateInfo ),
    nDatePart( nPart )
{
}

void ScDPSaveNumGroupDimension::AddToData( ScDPGroupTableData& rData ) const
{
    long nSource = rData.GetDimensionIndex( aDimensionName );
    if ( nSource >= 0 )
    {
        ScDPNumGroupDimension aDim( aGroupInfo );           // aGroupInfo: value grouping
        if ( nDatePart )
            aDim.SetDateDimension();

        rData.SetNumGroupDimension( nSource, aDim );
    }
}

void ScDPSaveNumGroupDimension::AddToCache(ScDPCache& rCache) const
{
    long nDim = rCache.GetDimensionIndex(aDimensionName);
    if (nDim < 0)
        return;

    if (aDateInfo.mbEnable)
    {
        // Date grouping
        SvNumberFormatter* pFormatter = rCache.GetDoc()->GetFormatTable();
        fillDateGroupDimension(rCache, aDateInfo, nDim, nDim, nDatePart, pFormatter);
    }
    else if (aGroupInfo.mbEnable)
    {
        // Number-range grouping

        // Look through the source entries for non-integer numbers, minimum
        // and maximum.

        // non-integer GroupInfo values count, too
        aGroupInfo.mbIntegerOnly =
            (aGroupInfo.mbAutoStart || isInteger(aGroupInfo.mfStart)) &&
            (aGroupInfo.mbAutoEnd || isInteger(aGroupInfo.mfEnd)) &&
            isInteger(aGroupInfo.mfStep);

        double fSourceMin = 0.0;
        double fSourceMax = 0.0;
        bool bFirst = true;

        const ScDPCache::ScDPItemDataVec& rItems = rCache.GetDimMemberValues(nDim);
        for (const ScDPItemData& rItem : rItems)
        {
            if (rItem.GetType() != ScDPItemData::Value)
                continue;

            double fValue = rItem.GetValue();
            if (bFirst)
            {
                fSourceMin = fSourceMax = fValue;
                bFirst = false;
                continue;
            }

            if (fValue < fSourceMin)
                fSourceMin = fValue;
            if (fValue > fSourceMax)
                fSourceMax = fValue;

            if (aGroupInfo.mbIntegerOnly && !isInteger(fValue))
            {
                // If any non-integer numbers are involved, the group labels
                // are shown including their upper limit.
                aGroupInfo.mbIntegerOnly = false;
            }
        }

        if (aGroupInfo.mbDateValues)
        {
            // special handling for dates: always integer, round down limits
            aGroupInfo.mbIntegerOnly = true;
            fSourceMin = rtl::math::approxFloor(fSourceMin);
            fSourceMax = rtl::math::approxFloor(fSourceMax) + 1;
        }

        if (aGroupInfo.mbAutoStart)
            aGroupInfo.mfStart = fSourceMin;
        if (aGroupInfo.mbAutoEnd)
            aGroupInfo.mfEnd = fSourceMax;

        //TODO: limit number of entries?

        long nLoopCount = 0;
        double fLoop = aGroupInfo.mfStart;

        rCache.ResetGroupItems(nDim, aGroupInfo, 0);

        // Use "less than" instead of "less or equal" for the loop - don't
        // create a group that consists only of the end value. Instead, the
        // end value is then included in the last group (last group is bigger
        // than the others). The first group has to be created nonetheless.
        // GetNumGroupForValue has corresponding logic.

        bool bFirstGroup = true;
        while (bFirstGroup || (fLoop < aGroupInfo.mfEnd && !rtl::math::approxEqual(fLoop, aGroupInfo.mfEnd)))
        {
            ScDPItemData aItem;
            aItem.SetRangeStart(fLoop);
            rCache.SetGroupItem(nDim, aItem);
            ++nLoopCount;
            fLoop = aGroupInfo.mfStart + nLoopCount * aGroupInfo.mfStep;
            bFirstGroup = false;

            // ScDPItemData values are compared with approxEqual
        }

        ScDPItemData aItem;
        aItem.SetRangeFirst();
        rCache.SetGroupItem(nDim, aItem);

        aItem.SetRangeLast();
        rCache.SetGroupItem(nDim, aItem);
    }
}

void ScDPSaveNumGroupDimension::SetGroupInfo( const ScDPNumGroupInfo& rNew )
{
    aGroupInfo = rNew;
}

void ScDPSaveNumGroupDimension::SetDateInfo( const ScDPNumGroupInfo& rInfo, sal_Int32 nPart )
{
    aDateInfo = rInfo;
    nDatePart = nPart;
}

namespace {

struct ScDPSaveGroupDimNameFunc
{
    OUString const       maDimName;
    explicit     ScDPSaveGroupDimNameFunc( const OUString& rDimName ) : maDimName( rDimName ) {}
    bool         operator()( const ScDPSaveGroupDimension& rGroupDim ) const { return rGroupDim.GetGroupDimName() == maDimName; }
};

struct ScDPSaveGroupSourceNameFunc
{
    OUString const       maSrcDimName;
    explicit     ScDPSaveGroupSourceNameFunc( const OUString& rSrcDimName ) : maSrcDimName( rSrcDimName ) {}
    bool         operator()( const ScDPSaveGroupDimension& rGroupDim ) const { return rGroupDim.GetSourceDimName() == maSrcDimName; }
};

} // namespace

ScDPDimensionSaveData::ScDPDimensionSaveData()
{
}

bool ScDPDimensionSaveData::operator==( const ScDPDimensionSaveData& ) const
{
    return false;
}

void ScDPDimensionSaveData::AddGroupDimension( const ScDPSaveGroupDimension& rGroupDim )
{
    OSL_ENSURE( ::std::none_of( maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupDimNameFunc( rGroupDim.GetGroupDimName() ) ),
        "ScDPDimensionSaveData::AddGroupDimension - group dimension exists already" );
    // ReplaceGroupDimension() adds new or replaces existing
    ReplaceGroupDimension( rGroupDim );
}

void ScDPDimensionSaveData::ReplaceGroupDimension( const ScDPSaveGroupDimension& rGroupDim )
{
    ScDPSaveGroupDimVec::iterator aIt = ::std::find_if(
        maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupDimNameFunc( rGroupDim.GetGroupDimName() ) );
    if( aIt == maGroupDims.end() )
        maGroupDims.push_back( rGroupDim );
    else
        *aIt = rGroupDim;
}

void ScDPDimensionSaveData::RemoveGroupDimension( const OUString& rGroupDimName )
{
    ScDPSaveGroupDimVec::iterator aIt = ::std::find_if(
        maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupDimNameFunc( rGroupDimName ) );
    if( aIt != maGroupDims.end() )
        maGroupDims.erase( aIt );
}

void ScDPDimensionSaveData::AddNumGroupDimension( const ScDPSaveNumGroupDimension& rGroupDim )
{
    OSL_ENSURE( maNumGroupDims.count( rGroupDim.GetDimensionName() ) == 0,
        "ScDPDimensionSaveData::AddNumGroupDimension - numeric group dimension exists already" );
    // ReplaceNumGroupDimension() adds new or replaces existing
    ReplaceNumGroupDimension( rGroupDim );
}

void ScDPDimensionSaveData::ReplaceNumGroupDimension( const ScDPSaveNumGroupDimension& rGroupDim )
{
    ScDPSaveNumGroupDimMap::iterator aIt = maNumGroupDims.find( rGroupDim.GetDimensionName() );
    if( aIt == maNumGroupDims.end() )
        maNumGroupDims.emplace( rGroupDim.GetDimensionName(), rGroupDim );
    else
        aIt->second = rGroupDim;
}

void ScDPDimensionSaveData::RemoveNumGroupDimension( const OUString& rGroupDimName )
{
    maNumGroupDims.erase( rGroupDimName );
}

void ScDPDimensionSaveData::WriteToData( ScDPGroupTableData& rData ) const
{
    //  rData is assumed to be empty
    //  AddToData also handles date grouping

    for( const auto& rGroupDim : maGroupDims )
        rGroupDim.AddToData( rData );

    for( const auto& rEntry : maNumGroupDims )
        rEntry.second.AddToData( rData );
}

void ScDPDimensionSaveData::WriteToCache(ScDPCache& rCache) const
{
    for (const auto& rEntry : maGroupDims)
        rEntry.AddToCache(rCache);
    for (const auto& rEntry : maNumGroupDims)
        rEntry.second.AddToCache(rCache);
}

const ScDPSaveGroupDimension* ScDPDimensionSaveData::GetGroupDimForBase( const OUString& rBaseDimName ) const
{
    return const_cast< ScDPDimensionSaveData* >( this )->GetGroupDimAccForBase( rBaseDimName );
}

const ScDPSaveGroupDimension* ScDPDimensionSaveData::GetNamedGroupDim( const OUString& rGroupDimName ) const
{
    return const_cast< ScDPDimensionSaveData* >( this )->GetNamedGroupDimAcc( rGroupDimName );
}

const ScDPSaveGroupDimension* ScDPDimensionSaveData::GetFirstNamedGroupDim( const OUString& rBaseDimName ) const
{
    return const_cast< ScDPDimensionSaveData* >( this )->GetFirstNamedGroupDimAcc( rBaseDimName );
}

const ScDPSaveGroupDimension* ScDPDimensionSaveData::GetNextNamedGroupDim( const OUString& rGroupDimName ) const
{
    return const_cast< ScDPDimensionSaveData* >( this )->GetNextNamedGroupDimAcc( rGroupDimName );
}

const ScDPSaveNumGroupDimension* ScDPDimensionSaveData::GetNumGroupDim( const OUString& rGroupDimName ) const
{
    return const_cast< ScDPDimensionSaveData* >( this )->GetNumGroupDimAcc( rGroupDimName );
}

ScDPSaveGroupDimension* ScDPDimensionSaveData::GetGroupDimAccForBase( const OUString& rBaseDimName )
{
    ScDPSaveGroupDimension* pGroupDim = GetFirstNamedGroupDimAcc( rBaseDimName );
    return pGroupDim ? pGroupDim : GetNextNamedGroupDimAcc( rBaseDimName );
}

ScDPSaveGroupDimension* ScDPDimensionSaveData::GetNamedGroupDimAcc( const OUString& rGroupDimName )
{
    ScDPSaveGroupDimVec::iterator aIt = ::std::find_if(
        maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupDimNameFunc( rGroupDimName ) );
    return (aIt == maGroupDims.end()) ? nullptr : &*aIt;
}

ScDPSaveGroupDimension* ScDPDimensionSaveData::GetFirstNamedGroupDimAcc( const OUString& rBaseDimName )
{
    ScDPSaveGroupDimVec::iterator aIt = ::std::find_if(
        maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupSourceNameFunc( rBaseDimName ) );
    return (aIt == maGroupDims.end()) ? nullptr : &*aIt;
}

ScDPSaveGroupDimension* ScDPDimensionSaveData::GetNextNamedGroupDimAcc( const OUString& rGroupDimName )
{
    // find the group dimension with the passed name
    ScDPSaveGroupDimVec::iterator aIt = ::std::find_if(
        maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupDimNameFunc( rGroupDimName ) );
    // find next group dimension based on the same source dimension name
    if( aIt != maGroupDims.end() )
        aIt = ::std::find_if( aIt + 1, maGroupDims.end(), ScDPSaveGroupSourceNameFunc( aIt->GetSourceDimName() ) );
    return (aIt == maGroupDims.end()) ? nullptr : &*aIt;
}

ScDPSaveNumGroupDimension* ScDPDimensionSaveData::GetNumGroupDimAcc( const OUString& rGroupDimName )
{
    ScDPSaveNumGroupDimMap::iterator aIt = maNumGroupDims.find( rGroupDimName );
    return (aIt == maNumGroupDims.end()) ? nullptr : &aIt->second;
}

bool ScDPDimensionSaveData::HasGroupDimensions() const
{
    return !maGroupDims.empty() || !maNumGroupDims.empty();
}

sal_Int32 ScDPDimensionSaveData::CollectDateParts( const OUString& rBaseDimName ) const
{
    sal_Int32 nParts = 0;
    // start with part of numeric group
    if( const ScDPSaveNumGroupDimension* pNumDim = GetNumGroupDim( rBaseDimName ) )
        nParts |= pNumDim->GetDatePart();
    // collect parts from all matching group dimensions
    for( const ScDPSaveGroupDimension* pGroupDim = GetFirstNamedGroupDim( rBaseDimName ); pGroupDim; pGroupDim = GetNextNamedGroupDim( pGroupDim->GetGroupDimName() ) )
        nParts |= pGroupDim->GetDatePart();

    return nParts;
}

OUString ScDPDimensionSaveData::CreateGroupDimName(
    const OUString& rSourceName, const ScDPObject& rObject, bool bAllowSource,
    const std::vector<OUString>* pDeletedNames )
{
    // create a name for the new dimension by appending a number to the original
    // dimension's name

    bool bUseSource = bAllowSource;     // if set, try the unchanged original name first

    sal_Int32 nAdd = 2;                 // first try is "Name2"
    const sal_Int32 nMaxAdd = 1000;     // limit the loop
    while ( nAdd <= nMaxAdd )
    {
        OUString aDimName( rSourceName );
        if ( !bUseSource )
            aDimName += OUString::number(nAdd);

        // look for existing group dimensions
        bool bExists = std::any_of(maGroupDims.begin(), maGroupDims.end(),
            [&aDimName](const ScDPSaveGroupDimension& rDim) {
                return rDim.GetGroupDimName() == aDimName; //TODO: ignore case
            });

        // look for base dimensions that happen to have that name
        if ( !bExists && rObject.IsDimNameInUse( aDimName ) )
        {
            if ( pDeletedNames &&
                 std::find( pDeletedNames->begin(), pDeletedNames->end(), aDimName ) != pDeletedNames->end() )
            {
                // allow the name anyway if the name is in pDeletedNames
            }
            else
                bExists = true;
        }

        if ( !bExists )
            return aDimName;            // found a new name

        if ( bUseSource )
            bUseSource = false;
        else
            ++nAdd;                     // continue with higher number
    }
    OSL_FAIL("CreateGroupDimName: no valid name found");
    return OUString();
}

namespace
{
    static const char* aDatePartIds[] =
    {
        STR_DPFIELD_GROUP_BY_SECONDS,
        STR_DPFIELD_GROUP_BY_MINUTES,
        STR_DPFIELD_GROUP_BY_HOURS,
        STR_DPFIELD_GROUP_BY_DAYS,
        STR_DPFIELD_GROUP_BY_MONTHS,
        STR_DPFIELD_GROUP_BY_QUARTERS,
        STR_DPFIELD_GROUP_BY_YEARS
    };
}

OUString ScDPDimensionSaveData::CreateDateGroupDimName(
    sal_Int32 nDatePart, const ScDPObject& rObject, bool bAllowSource,
    const std::vector<OUString>* pDeletedNames )
{
    using namespace css::sheet::DataPilotFieldGroupBy;
    OUString aPartName;
    switch( nDatePart )
    {
        case SECONDS:  aPartName = ScResId(aDatePartIds[0]); break;
        case MINUTES:  aPartName = ScResId(aDatePartIds[1]); break;
        case HOURS:    aPartName = ScResId(aDatePartIds[2]); break;
        case DAYS:     aPartName = ScResId(aDatePartIds[3]); break;
        case MONTHS:   aPartName = ScResId(aDatePartIds[4]); break;
        case QUARTERS: aPartName = ScResId(aDatePartIds[5]); break;
        case YEARS:    aPartName = ScResId(aDatePartIds[6]); break;
    }
    OSL_ENSURE(!aPartName.isEmpty(), "ScDPDimensionSaveData::CreateDateGroupDimName - invalid date part");
    return CreateGroupDimName( aPartName, rObject, bAllowSource, pDeletedNames );
}

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