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

#include <limits>
#include <memory>
#include <sal/config.h>

#include <PivotTableDataProvider.hxx>
#include <PivotTableDataSource.hxx>
#include <PivotTableDataSequence.hxx>

#include <miscuno.hxx>
#include <document.hxx>
#include <docsh.hxx>
#include <unonames.hxx>
#include <scresid.hxx>
#include <globstr.hrc>
#include <strings.hrc>
#include <dpobject.hxx>
#include <hints.hxx>

#include <o3tl/safeint.hxx>
#include <vcl/svapp.hxx>
#include <comphelper/propertysequence.hxx>
#include <comphelper/sequence.hxx>
#include <comphelper/processfactory.hxx>

#include <com/sun/star/chart2/data/LabeledDataSequence.hpp>
#include <com/sun/star/chart/ChartDataRowSource.hpp>
#include <com/sun/star/frame/XModel.hpp>

#include <com/sun/star/sheet/XDataPilotResults.hpp>
#include <com/sun/star/sheet/DataResultFlags.hpp>

#include <com/sun/star/sheet/XDimensionsSupplier.hpp>
#include <com/sun/star/sheet/XHierarchiesSupplier.hpp>
#include <com/sun/star/sheet/XLevelsSupplier.hpp>
#include <com/sun/star/sheet/XDataPilotMemberResults.hpp>
#include <com/sun/star/sheet/MemberResultFlags.hpp>
#include <com/sun/star/sheet/XMembersSupplier.hpp>

#include <com/sun/star/chart/ChartDataChangeEvent.hpp>
#include <com/sun/star/container/XNamed.hpp>

#include <unordered_map>

using namespace css;

namespace sc
{
namespace
{
constexpr OUStringLiteral constIdCategories(u"categories");
constexpr OUStringLiteral constIdLabel(u"label");
constexpr OUStringLiteral constIdData(u"data");

std::span<const SfxItemPropertyMapEntry> lcl_GetDataProviderPropertyMap()
{
    static const SfxItemPropertyMapEntry aDataProviderPropertyMap_Impl[] =
    {
        { SC_UNONAME_INCLUDEHIDDENCELLS, 0, cppu::UnoType<bool>::get(), 0, 0 },
        { SC_UNONAME_USE_INTERNAL_DATA_PROVIDER, 0, cppu::UnoType<bool>::get(), 0, 0 },
    };
    return aDataProviderPropertyMap_Impl;
}

uno::Reference<frame::XModel> lcl_GetXModel(const ScDocument * pDoc)
{
    uno::Reference<frame::XModel> xModel;
    ScDocShell* pObjSh(pDoc ? pDoc->GetDocumentShell() : nullptr);
    if (pObjSh)
        xModel.set(pObjSh->GetModel());
    return xModel;
}

OUString lcl_identifierForData(sal_Int32 index)
{
    return "PT@" + constIdData + " " + OUString::number(index);
}

OUString lcl_identifierForLabel(sal_Int32 index)
{
    return "PT@" + constIdLabel + " " + OUString::number(index);
}

OUString lcl_identifierForCategories()
{
    return "PT@" + constIdCategories;
}

std::vector<OUString> lcl_getVisiblePageMembers(const uno::Reference<uno::XInterface> & xLevel)
{
    std::vector<OUString> aResult;
    if (!xLevel.is())
        return aResult;

    uno::Reference<sheet::XMembersSupplier> xMembersSupplier(xLevel, uno::UNO_QUERY);
    if (!xMembersSupplier.is())
        return aResult;

    uno::Reference<sheet::XMembersAccess> xMembersAccess = xMembersSupplier->getMembers();
    if (!xMembersAccess.is())
        return aResult;

    const css::uno::Sequence<OUString> aMembersNames = xMembersAccess->getElementNames();
    for (OUString const & rMemberNames : aMembersNames)
    {
        uno::Reference<beans::XPropertySet> xProperties(xMembersAccess->getByName(rMemberNames), uno::UNO_QUERY);
        if (!xProperties.is())
            continue;

        OUString aCaption = ScUnoHelpFunctions::GetStringProperty(xProperties, SC_UNO_DP_LAYOUTNAME, OUString());
        if (aCaption.isEmpty())
            aCaption = rMemberNames;

        bool bVisible = ScUnoHelpFunctions::GetBoolProperty(xProperties, SC_UNO_DP_ISVISIBLE);

        if (bVisible)
            aResult.push_back(aCaption);
    }

    return aResult;
}

} // end anonymous namespace

SC_SIMPLE_SERVICE_INFO(PivotTableDataProvider, u"PivotTableDataProvider"_ustr, SC_SERVICENAME_CHART_PIVOTTABLE_DATAPROVIDER)

// DataProvider ==============================================================

PivotTableDataProvider::PivotTableDataProvider(ScDocument& rDoc)
    : m_pDocument(&rDoc)
    , m_aPropSet(lcl_GetDataProviderPropertyMap())
    , m_bIncludeHiddenCells(true)
    , m_bNeedsUpdate(true)
    , m_xContext(comphelper::getProcessComponentContext())
{
    rDoc.AddUnoObject(*this);
}

PivotTableDataProvider::~PivotTableDataProvider()
{
    SolarMutexGuard g;

    if (m_pDocument)
        m_pDocument->RemoveUnoObject( *this);
}

void PivotTableDataProvider::Notify(SfxBroadcaster& /*rBroadcaster*/, const SfxHint& rHint)
{
    if (rHint.GetId() == SfxHintId::Dying)
    {
        m_pDocument = nullptr;
    }
    else if (m_pDocument)
    {
        if (rHint.GetId() == SfxHintId::ScDataPilotModified)
        {
            auto pDataPilotHint = static_cast<const ScDataPilotModifiedHint*>(&rHint);
            if (pDataPilotHint->GetName() == m_sPivotTableName)
            {
                m_bNeedsUpdate = true;
                for (uno::Reference<util::XModifyListener> const & xListener : m_aValueListeners)
                {
                    css::chart::ChartDataChangeEvent aEvent(getXWeak(),
                                                            css::chart::ChartDataChangeType_ALL,
                                                            0, 0, 0, 0);
                    xListener->modified(aEvent);
                }
            }
        }
    }
}

sal_Bool SAL_CALL PivotTableDataProvider::createDataSourcePossible(const uno::Sequence<beans::PropertyValue>& /*aArguments*/)
{
    SolarMutexGuard aGuard;
    if (!m_pDocument)
        return false;

    if (m_sPivotTableName.isEmpty())
        return false;

    ScDPCollection* pDPCollection = m_pDocument->GetDPCollection();
    return bool(pDPCollection->GetByName(m_sPivotTableName));
}

uno::Reference<chart2::data::XDataSource> SAL_CALL
    PivotTableDataProvider::createDataSource(const uno::Sequence<beans::PropertyValue>& aArguments)
{
    SolarMutexGuard aGuard;

    if (!m_pDocument)
        throw uno::RuntimeException();

    bool bOrientCol = true;
    OUString aRangeRepresentation;

    for (beans::PropertyValue const & rProperty : aArguments)
    {
        if (rProperty.Name == "DataRowSource")
        {
            chart::ChartDataRowSource eSource = chart::ChartDataRowSource_COLUMNS;
            if (!(rProperty.Value >>= eSource))
            {
                sal_Int32 nSource(0);
                if (rProperty.Value >>= nSource)
                    eSource = chart::ChartDataRowSource(nSource);
            }
            bOrientCol = (eSource == chart::ChartDataRowSource_COLUMNS);
        }
        else if (rProperty.Name == "CellRangeRepresentation")
            rProperty.Value >>= aRangeRepresentation;
    }

    uno::Reference<chart2::data::XDataSource> xResult;

    if (aRangeRepresentation == lcl_identifierForCategories())
        xResult = createCategoriesDataSource(bOrientCol);
    else
        xResult = createValuesDataSource();

    return xResult;
}

uno::Reference<chart2::data::XLabeledDataSequence>
    PivotTableDataProvider::newLabeledDataSequence()
{
    uno::Reference<chart2::data::XLabeledDataSequence> xResult;
    if (!m_xContext.is())
        return xResult;
    xResult.set(chart2::data::LabeledDataSequence::create(m_xContext), uno::UNO_QUERY_THROW);
    return xResult;
}

uno::Reference<chart2::data::XDataSource>
PivotTableDataProvider::createCategoriesDataSource(bool bOrientationIsColumn)
{
    if (m_bNeedsUpdate)
        collectPivotTableData();

    uno::Reference<chart2::data::XDataSource> xDataSource;
    std::vector<uno::Reference<chart2::data::XLabeledDataSequence>> aLabeledSequences;

    std::vector<std::vector<ValueAndFormat>> const & rCategoriesVector = bOrientationIsColumn ? m_aCategoriesColumnOrientation
                                                                                              : m_aCategoriesRowOrientation;

    for (std::vector<ValueAndFormat> const & rCategories : rCategoriesVector)
    {
        uno::Reference<chart2::data::XLabeledDataSequence> xResult = newLabeledDataSequence();
        rtl::Reference<PivotTableDataSequence> pSequence(new PivotTableDataSequence(m_pDocument,
                                                   lcl_identifierForCategories(), std::vector(rCategories)));
        pSequence->setRole(u"categories"_ustr);
        xResult->setValues(uno::Reference<chart2::data::XDataSequence>(pSequence));

        aLabeledSequences.push_back(xResult);
    }

    xDataSource.set(new PivotTableDataSource(std::move(aLabeledSequences)));
    return xDataSource;
}

void PivotTableDataProvider::collectPivotTableData()
{
    ScDPCollection* pDPCollection = m_pDocument->GetDPCollection();
    ScDPObject* pDPObject = pDPCollection->GetByName(m_sPivotTableName);
    if (!pDPObject)
        return;

    m_aCategoriesColumnOrientation.clear();
    m_aCategoriesRowOrientation.clear();
    m_aLabels.clear();
    m_aDataRowVector.clear();
    m_aColumnFields.clear();
    m_aRowFields.clear();
    m_aPageFields.clear();
    m_aDataFields.clear();
    m_aFieldOutputDescriptionMap.clear();

    uno::Reference<sheet::XDataPilotResults> xDPResults(pDPObject->GetSource(), uno::UNO_QUERY);
    if (!xDPResults.is())
        return;
    const uno::Sequence<uno::Sequence<sheet::DataResult>> xDataResultsSequence = xDPResults->getResults();

    std::unordered_set<size_t> aValidRowIndex;

    size_t nRowIndex = 0;
    for (uno::Sequence<sheet::DataResult> const & xDataResults : xDataResultsSequence)
    {
        std::vector<ValueAndFormat> aRow;
        bool bRowEmpty = true;
        // First pass - collect a row of valid data and track if the row is empty
        for (sheet::DataResult const & rDataResult : xDataResults)
        {
            if (rDataResult.Flags & css::sheet::DataResultFlags::SUBTOTAL)
                continue;
            if (rDataResult.Flags == 0 || rDataResult.Flags & css::sheet::DataResultFlags::HASDATA)
            {
                aRow.emplace_back(rDataResult.Flags ? rDataResult.Value
                                                    : std::numeric_limits<double>::quiet_NaN(), 0);
                if (rDataResult.Flags != 0) // set as valid only if we have data
                {
                    bRowEmpty = false;
                    // We need to remember all valid (non-empty) row indices
                    aValidRowIndex.insert(nRowIndex);
                }
            }
        }
        // Second pass: add to collection only non-empty rows
        if (!bRowEmpty)
        {
            size_t nColumnIndex = 0;
            for (ValueAndFormat const & aValue : aRow)
            {
                if (nColumnIndex >= m_aDataRowVector.size())
                    m_aDataRowVector.resize(nColumnIndex + 1);
                m_aDataRowVector[nColumnIndex].push_back(aValue);
                nColumnIndex++;
            }
        }
        nRowIndex++;
    }

    uno::Reference<sheet::XDimensionsSupplier> xDimensionsSupplier(pDPObject->GetSource());
    uno::Reference<container::XIndexAccess> xDims = new ScNameToIndexAccess(xDimensionsSupplier->getDimensions());

    std::unordered_map<OUString, sal_Int32> aDataFieldNumberFormatMap;
    std::vector<OUString> aDataFieldNamesVectors;

    std::unordered_map<OUString, OUString> aDataFieldCaptionNames;

    sheet::DataPilotFieldOrientation eDataFieldOrientation = sheet::DataPilotFieldOrientation_HIDDEN;

    for (sal_Int32 nDim = 0; nDim < xDims->getCount(); nDim++)
    {
        uno::Reference<uno::XInterface> xDim(xDims->getByIndex(nDim), uno::UNO_QUERY);
        uno::Reference<beans::XPropertySet> xDimProp(xDim, uno::UNO_QUERY);
        uno::Reference<sheet::XHierarchiesSupplier> xDimSupp(xDim, uno::UNO_QUERY);

        if (!xDimProp.is() || !xDimSupp.is())
            continue;

        sheet::DataPilotFieldOrientation eDimOrient =
            ScUnoHelpFunctions::GetEnumProperty(xDimProp, SC_UNO_DP_ORIENTATION,
                                                sheet::DataPilotFieldOrientation_HIDDEN);

        if (eDimOrient == sheet::DataPilotFieldOrientation_HIDDEN)
            continue;

        uno::Reference<container::XIndexAccess> xHierarchies = new ScNameToIndexAccess(xDimSupp->getHierarchies());
        sal_Int32 nHierarchy = ScUnoHelpFunctions::GetLongProperty(xDimProp, SC_UNO_DP_USEDHIERARCHY);
        if (nHierarchy >= xHierarchies->getCount())
            nHierarchy = 0;

        uno::Reference<sheet::XLevelsSupplier> xLevelsSupplier(xHierarchies->getByIndex(nHierarchy),
                                                               uno::UNO_QUERY);

        if (!xLevelsSupplier.is())
            continue;

        uno::Reference<container::XIndexAccess> xLevels = new ScNameToIndexAccess(xLevelsSupplier->getLevels());

        for (tools::Long nLevel = 0; nLevel < xLevels->getCount(); nLevel++)
        {
            uno::Reference<uno::XInterface> xLevel(xLevels->getByIndex(nLevel), uno::UNO_QUERY);
            uno::Reference<container::XNamed> xLevelName(xLevel, uno::UNO_QUERY);
            uno::Reference<sheet::XDataPilotMemberResults> xLevelResult(xLevel, uno::UNO_QUERY );

            if (xLevelName.is() && xLevelResult.is())
            {
                bool bIsDataLayout = ScUnoHelpFunctions::GetBoolProperty(xDimProp, SC_UNO_DP_ISDATALAYOUT);
                sal_Int32 nDimPos = ScUnoHelpFunctions::GetLongProperty(xDimProp, SC_UNO_DP_POSITION);
                sal_Int32 nNumberFormat = ScUnoHelpFunctions::GetLongProperty(xDimProp, SC_UNO_DP_NUMBERFO);
                bool bHasHiddenMember = ScUnoHelpFunctions::GetBoolProperty(xDimProp, SC_UNO_DP_HAS_HIDDEN_MEMBER);

                switch (eDimOrient)
                {
                    case sheet::DataPilotFieldOrientation_COLUMN:
                    {
                        m_aColumnFields.emplace_back(xLevelName->getName(), nDim, nDimPos, bHasHiddenMember);

                        const uno::Sequence<sheet::MemberResult> aSequence = xLevelResult->getResults();
                        size_t i = 0;
                        OUString sCaption;
                        OUString sName;
                        for (sheet::MemberResult const & rMember : aSequence)
                        {
                            // Skip grandtotals and subtotals
                            if (rMember.Flags & sheet::MemberResultFlags::SUBTOTAL ||
                                rMember.Flags & sheet::MemberResultFlags::GRANDTOTAL)
                                    continue;
                            if (rMember.Flags & sheet::MemberResultFlags::HASMEMBER ||
                                rMember.Flags & sheet::MemberResultFlags::CONTINUE)
                            {
                                if (!(rMember.Flags & sheet::MemberResultFlags::CONTINUE))
                                {
                                    sCaption = rMember.Caption;
                                    sName = rMember.Name;
                                }

                                if (i >= m_aLabels.size())
                                    m_aLabels.resize(i + 1);

                                if (o3tl::make_unsigned(nDimPos) >= m_aLabels[i].size())
                                    m_aLabels[i].resize(nDimPos + 1);
                                m_aLabels[i][nDimPos] = ValueAndFormat(sCaption);

                                if (bIsDataLayout)
                                {
                                    // Remember data fields to determine the number format of data
                                    aDataFieldNamesVectors.push_back(sName);
                                    eDataFieldOrientation = sheet::DataPilotFieldOrientation_COLUMN;
                                    // Remember the caption name
                                    aDataFieldCaptionNames[rMember.Name] = rMember.Caption;
                                }
                                i++;
                            }
                        }
                    }
                    break;

                    case sheet::DataPilotFieldOrientation_ROW:
                    {
                        m_aRowFields.emplace_back(xLevelName->getName(), nDim, nDimPos, bHasHiddenMember);

                        const uno::Sequence<sheet::MemberResult> aSequence = xLevelResult->getResults();

                        size_t i = 0;
                        size_t nEachIndex = 0;
                        std::unique_ptr<ValueAndFormat> pItem;

                        for (sheet::MemberResult const & rMember : aSequence)
                        {
                            bool bFound = aValidRowIndex.find(nEachIndex) != aValidRowIndex.end();

                            nEachIndex++;

                            bool bHasContinueFlag = rMember.Flags & sheet::MemberResultFlags::CONTINUE;

                            if (rMember.Flags & sheet::MemberResultFlags::HASMEMBER || bHasContinueFlag)
                            {
                                if (!bHasContinueFlag)
                                {
                                    // Chart2 does not use number format for labels, so use the display string.
                                    pItem.reset(new ValueAndFormat(rMember.Caption));
                                }

                                if (bFound)
                                {
                                    assert(pItem && "bHasContinueFlag must be false on this or some preceding element");

                                    if (i >= m_aCategoriesRowOrientation.size())
                                        m_aCategoriesRowOrientation.resize(i + 1);

                                    if (o3tl::make_unsigned(nDimPos) >= m_aCategoriesColumnOrientation.size())
                                        m_aCategoriesColumnOrientation.resize(nDimPos + 1);
                                    m_aCategoriesColumnOrientation[nDimPos].push_back(*pItem);

                                    if (o3tl::make_unsigned(nDimPos) >= m_aCategoriesRowOrientation[i].size())
                                        m_aCategoriesRowOrientation[i].resize(nDimPos + 1);
                                    m_aCategoriesRowOrientation[i][nDimPos] = *pItem;

                                    if (bIsDataLayout)
                                    {
                                        // Remember data fields to determine the number format of data
                                        aDataFieldNamesVectors.push_back(rMember.Name);
                                        eDataFieldOrientation = sheet::DataPilotFieldOrientation_ROW;

                                        // Remember the caption name
                                        aDataFieldCaptionNames[rMember.Name] = rMember.Caption;
                                    }

                                    // Set to empty so the sub categories are set to empty when they continue
                                    pItem.reset(new ValueAndFormat);
                                    i++;
                                }
                            }
                        }
                    }
                    break;

                    case sheet::DataPilotFieldOrientation_PAGE:
                    {
                        m_aPageFields.emplace_back(xLevelName->getName(), nDim, nDimPos, bHasHiddenMember);

                        // Resolve filtering
                        OUString aFieldOutputDescription;
                        if (bHasHiddenMember)
                        {
                            std::vector<OUString> aMembers = lcl_getVisiblePageMembers(xLevel);

                            if (aMembers.size() == 1)
                                aFieldOutputDescription = aMembers[0];
                            else
                                aFieldOutputDescription = ScResId(SCSTR_MULTIPLE);
                        }
                        else
                        {
                            aFieldOutputDescription = ScResId(SCSTR_ALL);
                        }
                        m_aFieldOutputDescriptionMap[nDim] = aFieldOutputDescription;
                    }
                    break;

                    case sheet::DataPilotFieldOrientation_DATA:
                    {
                        aDataFieldNumberFormatMap[xLevelName->getName()] = nNumberFormat;
                        m_aDataFields.emplace_back(xLevelName->getName(), nDim, nDimPos, bHasHiddenMember);
                    }
                    break;

                    default:
                        break;
                }
            }
        }
    }

    // Transform the name of data fields
    for (chart2::data::PivotTableFieldEntry& rDataFields : m_aDataFields)
    {
        rDataFields.Name = aDataFieldCaptionNames[rDataFields.Name];
    }

    // Apply number format to the data
    if (eDataFieldOrientation == sheet::DataPilotFieldOrientation_ROW)
    {
        for (std::vector<ValueAndFormat> & rDataRow : m_aDataRowVector)
        {
            size_t i = 0;
            for (ValueAndFormat & rItem : rDataRow)
            {
                const OUString& sName = aDataFieldNamesVectors[i];
                sal_Int32 nNumberFormat = aDataFieldNumberFormatMap[sName];
                rItem.m_nNumberFormat = nNumberFormat;
                i++;
            }
        }
    }
    else if (eDataFieldOrientation == sheet::DataPilotFieldOrientation_COLUMN)
    {
        size_t i = 0;
        for (std::vector<ValueAndFormat> & rDataRow : m_aDataRowVector)
        {
            const OUString& sName = aDataFieldNamesVectors[i];
            sal_Int32 nNumberFormat = aDataFieldNumberFormatMap[sName];
            for (ValueAndFormat & rItem : rDataRow)
            {
                rItem.m_nNumberFormat = nNumberFormat;
            }
            i++;
        }
    }

    // Sort fields so it respects the order of how it is represented in the pivot table

    auto funcDimensionPositionSortCompare = [] (chart2::data::PivotTableFieldEntry const & entry1,
                                                chart2::data::PivotTableFieldEntry const & entry2)
    {
        return entry1.DimensionPositionIndex < entry2.DimensionPositionIndex;
    };

    std::sort(m_aColumnFields.begin(), m_aColumnFields.end(), funcDimensionPositionSortCompare);
    std::sort(m_aRowFields.begin(),    m_aRowFields.end(),    funcDimensionPositionSortCompare);
    std::sort(m_aPageFields.begin(),   m_aPageFields.end(),   funcDimensionPositionSortCompare);
    std::sort(m_aDataFields.begin(),   m_aDataFields.end(),   funcDimensionPositionSortCompare);

    // Mark that we updated the data
    m_bNeedsUpdate = false;
}

rtl::Reference<PivotTableDataSequence>
PivotTableDataProvider::assignValuesToDataSequence(size_t nIndex)
{
    if (nIndex >= m_aDataRowVector.size())
        return nullptr;

    OUString sDataID = lcl_identifierForData(nIndex);

    std::vector<ValueAndFormat> const & rRowOfData = m_aDataRowVector[nIndex];
    rtl::Reference<PivotTableDataSequence> pSequence(new PivotTableDataSequence(m_pDocument, sDataID, std::vector(rRowOfData)));
    pSequence->setRole(u"values-y"_ustr);
    return pSequence;
}

rtl::Reference<PivotTableDataSequence>
PivotTableDataProvider::assignLabelsToDataSequence(size_t nIndex)
{
    OUString sLabelID = lcl_identifierForLabel(nIndex);

    OUStringBuffer aLabel;
    bool bFirst = true;

    if (m_aLabels.empty())
    {
        aLabel = ScResId(STR_PIVOT_TOTAL);
    }
    else if (nIndex < m_aLabels.size())
    {
        for (ValueAndFormat const & rItem : m_aLabels[nIndex])
        {
            if (bFirst)
            {
                aLabel.append(rItem.m_aString);
                bFirst = false;
            }
            else
            {
                aLabel.append(" - " + rItem.m_aString);
            }
        }
    }

    std::vector<ValueAndFormat> aLabelVector { ValueAndFormat(aLabel.makeStringAndClear()) };

    rtl::Reference<PivotTableDataSequence> pSequence(new PivotTableDataSequence(m_pDocument,
                                               std::move(sLabelID), std::move(aLabelVector)));
    pSequence->setRole(u"values-y"_ustr);
    return pSequence;
}

rtl::Reference<PivotTableDataSequence>
    PivotTableDataProvider::assignFirstCategoriesToDataSequence()
{
    if (m_aCategoriesColumnOrientation.empty())
        return nullptr;

    std::vector<ValueAndFormat> const & rCategories = m_aCategoriesColumnOrientation.back();

    rtl::Reference<PivotTableDataSequence> pSequence(new PivotTableDataSequence(m_pDocument,
                                               lcl_identifierForCategories(), std::vector(rCategories)));
    pSequence->setRole(u"categories"_ustr);
    return pSequence;
}

uno::Reference<chart2::data::XDataSource>
    PivotTableDataProvider::createValuesDataSource()
{
    if (m_bNeedsUpdate)
        collectPivotTableData();

    uno::Reference<chart2::data::XDataSource> xDataSource;
    std::vector<uno::Reference<chart2::data::XLabeledDataSequence>> aLabeledSequences;

    // Fill first sequence of categories
    {
        uno::Reference<chart2::data::XLabeledDataSequence> xResult = newLabeledDataSequence();
        xResult->setValues(assignFirstCategoriesToDataSequence());
        aLabeledSequences.push_back(xResult);
    }

    // Fill values and labels
    {
        for (size_t i = 0; i < m_aDataRowVector.size(); ++i)
        {
            uno::Reference<chart2::data::XLabeledDataSequence> xResult = newLabeledDataSequence();
            xResult->setValues(assignValuesToDataSequence(i));
            xResult->setLabel(assignLabelsToDataSequence(i));
            aLabeledSequences.push_back(xResult);
        }
    }

    xDataSource.set(new PivotTableDataSource(std::move(aLabeledSequences)));
    return xDataSource;
}


uno::Sequence<beans::PropertyValue> SAL_CALL PivotTableDataProvider::detectArguments(
            const uno::Reference<chart2::data::XDataSource> & xDataSource)
{
    if (!m_pDocument ||!xDataSource.is())
        return uno::Sequence<beans::PropertyValue>();

    return comphelper::InitPropertySequence({
        { "CellRangeRepresentation", uno::Any(u"PivotChart"_ustr) },
        { "DataRowSource", uno::Any(chart::ChartDataRowSource_COLUMNS) },
        { "FirstCellAsLabel", uno::Any(false) },
        { "HasCategories", uno::Any(true) }
    });
}

sal_Bool SAL_CALL PivotTableDataProvider::createDataSequenceByRangeRepresentationPossible(const OUString& /*aRangeRepresentation*/)
{
    return false;
}

uno::Reference<chart2::data::XDataSequence> SAL_CALL
    PivotTableDataProvider::createDataSequenceByRangeRepresentation(const OUString& /*rRangeRepresentation*/)
{
    uno::Reference<chart2::data::XDataSequence> xDataSequence;
    return xDataSequence;
}

uno::Reference<chart2::data::XDataSequence> SAL_CALL
    PivotTableDataProvider::createDataSequenceByValueArray(const OUString& /*aRole*/,
                                                           const OUString& /*aRangeRepresentation*/,
                                                           const OUString& /*aRoleQualifier*/)
{
    return uno::Reference<chart2::data::XDataSequence>();
}

uno::Reference<sheet::XRangeSelection> SAL_CALL PivotTableDataProvider::getRangeSelection()
{
    uno::Reference<sheet::XRangeSelection> xResult;

    uno::Reference<frame::XModel> xModel(lcl_GetXModel(m_pDocument));
    if (xModel.is())
        xResult.set(xModel->getCurrentController(), uno::UNO_QUERY);

    return xResult;
}

// XPivotTableDataProvider ========================================================

const std::vector<chart2::data::PivotTableFieldEntry>& PivotTableDataProvider::getColumnFields() const
{
    return m_aColumnFields;
}

const std::vector<chart2::data::PivotTableFieldEntry>& PivotTableDataProvider::getRowFields() const
{
    return m_aRowFields;
}

const std::vector<chart2::data::PivotTableFieldEntry>& PivotTableDataProvider::getPageFields() const
{
    return m_aPageFields;
}

const std::vector<chart2::data::PivotTableFieldEntry>& PivotTableDataProvider::getDataFields() const
{
    return m_aDataFields;
}

const OUString & PivotTableDataProvider::getPivotTableName() const
{
    return m_sPivotTableName;
}

void PivotTableDataProvider::setPivotTableName(const OUString& sPivotTableName)
{
    ScDPCollection* pDPCollection = m_pDocument->GetDPCollection();
    ScDPObject* pDPObject = pDPCollection->GetByName(sPivotTableName);
    if (pDPObject)
        m_sPivotTableName = sPivotTableName;
}

bool PivotTableDataProvider::hasPivotTable() const
{
    if (m_sPivotTableName.isEmpty())
        return false;

    ScDPCollection* pDPCollection = m_pDocument->GetDPCollection();
    ScDPObject* pDPObject = pDPCollection->GetByName(m_sPivotTableName);

    if (pDPObject)
        return true;

    return false;
}

uno::Reference<chart2::data::XDataSequence>
    PivotTableDataProvider::createDataSequenceOfValuesByIndex(sal_Int32 nIndex)
{
    SolarMutexGuard aGuard;

    if (m_bNeedsUpdate)
        collectPivotTableData();

    return assignValuesToDataSequence(size_t(nIndex));
}

uno::Reference<css::chart2::data::XDataSequence>
    PivotTableDataProvider::createDataSequenceOfLabelsByIndex(sal_Int32 nIndex)
{
    SolarMutexGuard aGuard;

    if (m_bNeedsUpdate)
        collectPivotTableData();

    return assignLabelsToDataSequence(size_t(nIndex));
}

uno::Reference<css::chart2::data::XDataSequence>
    PivotTableDataProvider::createDataSequenceOfCategories()
{
    SolarMutexGuard aGuard;

    if (m_bNeedsUpdate)
        collectPivotTableData();

    return assignFirstCategoriesToDataSequence();
}

OUString PivotTableDataProvider::getFieldOutputDescription(sal_Int32 nDimensionIndex) const
{
    if (nDimensionIndex < 0)
        return OUString();
    return m_aFieldOutputDescriptionMap.at(size_t(nDimensionIndex));
}

// XModifyBroadcaster ========================================================

void SAL_CALL PivotTableDataProvider::addModifyListener(const uno::Reference< util::XModifyListener>& aListener)
{
    SolarMutexGuard aGuard;

    m_aValueListeners.emplace_back(aListener);
}

void SAL_CALL PivotTableDataProvider::removeModifyListener(const uno::Reference<util::XModifyListener>& aListener )
{
    SolarMutexGuard aGuard;

    sal_uInt16 nCount = m_aValueListeners.size();
    for (sal_uInt16 n = nCount; n--;)
    {
        uno::Reference<util::XModifyListener>& rObject = m_aValueListeners[n];
        if (rObject == aListener)
        {
            m_aValueListeners.erase(m_aValueListeners.begin() + n);
        }
    }
}

// DataProvider XPropertySet ========================================================

uno::Reference< beans::XPropertySetInfo> SAL_CALL
    PivotTableDataProvider::getPropertySetInfo()
{
    SolarMutexGuard aGuard;
    static uno::Reference<beans::XPropertySetInfo> aRef =
        new SfxItemPropertySetInfo( m_aPropSet.getPropertyMap() );
    return aRef;
}

void SAL_CALL PivotTableDataProvider::setPropertyValue(const OUString& rPropertyName, const uno::Any& rValue)
{
    if (rPropertyName != SC_UNONAME_INCLUDEHIDDENCELLS)
        throw beans::UnknownPropertyException(rPropertyName);

    if (!(rValue >>= m_bIncludeHiddenCells))
        throw lang::IllegalArgumentException();
}

uno::Any SAL_CALL PivotTableDataProvider::getPropertyValue(const OUString& rPropertyName)
{
    uno::Any aRet;
    if (rPropertyName == SC_UNONAME_INCLUDEHIDDENCELLS)
        aRet <<= m_bIncludeHiddenCells;
    else if (rPropertyName == SC_UNONAME_USE_INTERNAL_DATA_PROVIDER)
    {
        // This is a read-only property.
        aRet <<= m_pDocument->PastingDrawFromOtherDoc();
    }
    else
        throw beans::UnknownPropertyException(rPropertyName);
    return aRet;
}

void SAL_CALL PivotTableDataProvider::addPropertyChangeListener(
        const OUString& /*rPropertyName*/,
        const uno::Reference<beans::XPropertyChangeListener>& /*xListener*/)
{
    OSL_FAIL("Not yet implemented");
}

void SAL_CALL PivotTableDataProvider::removePropertyChangeListener(
        const OUString& /*rPropertyName*/,
        const uno::Reference<beans::XPropertyChangeListener>& /*rListener*/)
{
    OSL_FAIL("Not yet implemented");
}

void SAL_CALL PivotTableDataProvider::addVetoableChangeListener(
        const OUString& /*rPropertyName*/,
        const uno::Reference<beans::XVetoableChangeListener>& /*rListener*/)
{
    OSL_FAIL("Not yet implemented");
}

void SAL_CALL PivotTableDataProvider::removeVetoableChangeListener(
        const OUString& /*rPropertyName*/,
        const uno::Reference<beans::XVetoableChangeListener>& /*rListener*/ )
{
    OSL_FAIL("Not yet implemented");
}

} // end sc namespace

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