/* -*- 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 <limits>
#include <memory>
#include <VDataSeries.hxx>
#include <DataSeries.hxx>
#include <DataSeriesProperties.hxx>
#include <ObjectIdentifier.hxx>
#include <CommonConverters.hxx>
#include <LabelPositionHelper.hxx>
#include <ChartType.hxx>
#include <ChartTypeHelper.hxx>
#include <RegressionCurveCalculator.hxx>
#include <RegressionCurveHelper.hxx>
#include <unonames.hxx>

#include <com/sun/star/chart/MissingValueTreatment.hpp>
#include <com/sun/star/chart2/DataPointLabel.hpp>
#include <com/sun/star/chart2/Symbol.hpp>
#include <com/sun/star/chart2/XRegressionCurveCalculator.hpp>
#include <com/sun/star/chart2/RelativePosition.hpp>
#include <com/sun/star/chart2/RelativeSize.hpp>

#include <o3tl/compare.hxx>
#include <osl/diagnose.h>
#include <tools/color.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/beans/XPropertyState.hpp>

namespace chart {

using namespace ::com::sun::star;
using namespace ::com::sun::star::chart2;
using ::com::sun::star::uno::Reference;
using namespace ::chart::DataSeriesProperties;

void VDataSequence::init( const uno::Reference< data::XDataSequence >& xModel )
{
    m_xModel = xModel;
    m_aValues = DataSequenceToDoubleSequence( xModel );
}

bool VDataSequence::is() const
{
    return m_xModel.is();
}
void VDataSequence::clear()
{
    m_xModel = nullptr;
    m_aValues.realloc(0);
}

double VDataSequence::getValue( sal_Int32 index ) const
{
    if( 0<=index && index<m_aValues.getLength() )
        return m_aValues[index];
    return std::numeric_limits<double>::quiet_NaN();
}

sal_Int32 VDataSequence::detectNumberFormatKey( sal_Int32 index ) const
{
    sal_Int32 nNumberFormatKey = -1;

    // -1 is allowed and means a key for the whole sequence
    if( -1<=index && index<m_aValues.getLength() && m_xModel.is())
    {
        nNumberFormatKey = m_xModel->getNumberFormatKeyByIndex( index );
    }

    return nNumberFormatKey;
}

sal_Int32 VDataSequence::getLength() const
{
    return m_aValues.getLength();
}

namespace
{
struct lcl_LessXOfPoint
{
    bool operator() ( const std::vector< double >& first,
                             const std::vector< double >& second )
    {
        if( !first.empty() && !second.empty() )
        {
            return o3tl::strong_order(first[0], second[0]) < 0;
        }
        return false;
    }
};

void lcl_clearIfNoValuesButTextIsContained( VDataSequence& rData, const uno::Reference<data::XDataSequence>& xDataSequence )
{
    //#i71686#, #i101968#, #i102428#
    sal_Int32 nCount = rData.m_aValues.getLength();
    for( sal_Int32 i = 0; i < nCount; ++i )
    {
        if( !std::isnan( rData.m_aValues[i] ) )
            return;
    }
    //no double value is contained
    //is there any text?
    uno::Sequence< OUString > aStrings( DataSequenceToStringSequence( xDataSequence ) );
    sal_Int32 nTextCount = aStrings.getLength();
    for( sal_Int32 j = 0; j < nTextCount; ++j )
    {
        if( !aStrings[j].isEmpty() )
        {
            rData.clear();
            return;
        }
    }
    //no content at all
}

void lcl_maybeReplaceNanWithZero( double& rfValue, sal_Int32 nMissingValueTreatment )
{
    if( nMissingValueTreatment == css::chart::MissingValueTreatment::USE_ZERO
        && (std::isnan(rfValue) || std::isinf(rfValue)) )
            rfValue = 0.0;
}

}

VDataSeries::VDataSeries( const rtl::Reference< DataSeries >& xDataSeries )
    : m_nPolygonIndex(0)
    , m_fLogicMinX(0.0)
    , m_fLogicMaxX(0.0)
    , m_fLogicZPos(0.0)
    , m_xDataSeries(xDataSeries)
    , m_nPointCount(0)
    , m_pValueSequenceForDataLabelNumberFormatDetection(&m_aValues_Y)
    , m_fXMeanValue(std::numeric_limits<double>::quiet_NaN())
    , m_fYMeanValue(std::numeric_limits<double>::quiet_NaN())
    , m_eStackingDirection(StackingDirection_NO_STACKING)
    , m_nAxisIndex(0)
    , m_bConnectBars(false)
    , m_bGroupBarsPerAxis(true)
    , m_nStartingAngle(90)
    , m_nGlobalSeriesIndex(0)
    , m_nCurrentAttributedPoint(-1)
    , m_nMissingValueTreatment(css::chart::MissingValueTreatment::LEAVE_GAP)
    , m_bAllowPercentValueInDataLabel(false)
    , mpOldSeries(nullptr)
    , mnPercent(0.0)
{
    m_xDataSeriesProps = m_xDataSeries;

    const std::vector< uno::Reference< chart2::data::XLabeledDataSequence > > & aDataSequences =
            m_xDataSeries->getDataSequences2();

    for(sal_Int32 nN = aDataSequences.size();nN--;)
    {
        uno::Reference<data::XDataSequence>  xDataSequence( aDataSequences[nN]->getValues());
        uno::Reference<beans::XPropertySet> xProp(xDataSequence, uno::UNO_QUERY );
        if( xProp.is())
        {
            try
            {
                uno::Any aARole = xProp->getPropertyValue(u"Role"_ustr);
                OUString aRole;
                aARole >>= aRole;

                if (aRole == "values-x")
                {
                    m_aValues_X.init( xDataSequence );
                    lcl_clearIfNoValuesButTextIsContained( m_aValues_X, xDataSequence );
                }
                else if (aRole =="values-y")
                    m_aValues_Y.init( xDataSequence );
                else if (aRole == "values-min")
                    m_aValues_Y_Min.init( xDataSequence );
                else if (aRole == "values-max")
                    m_aValues_Y_Max.init( xDataSequence );
                else if (aRole == "values-first")
                    m_aValues_Y_First.init( xDataSequence );
                else if (aRole == "values-last")
                    m_aValues_Y_Last.init( xDataSequence );
                else if (aRole == "values-size")
                    m_aValues_Bubble_Size.init( xDataSequence );
                else
                {
                    VDataSequence aSequence;
                    aSequence.init(xDataSequence);
                    m_PropertyMap.insert(std::make_pair(aRole, aSequence));
                }
            }
            catch( const uno::Exception& )
            {
                TOOLS_WARN_EXCEPTION("chart2", "" );
            }
        }
    }

    //determine the point count
    m_nPointCount = m_aValues_Y.getLength();
    {
        if( m_nPointCount < m_aValues_Bubble_Size.getLength() )
            m_nPointCount = m_aValues_Bubble_Size.getLength();
        if( m_nPointCount < m_aValues_Y_Min.getLength() )
            m_nPointCount = m_aValues_Y_Min.getLength();
        if( m_nPointCount < m_aValues_Y_Max.getLength() )
            m_nPointCount = m_aValues_Y_Max.getLength();
        if( m_nPointCount < m_aValues_Y_First.getLength() )
            m_nPointCount = m_aValues_Y_First.getLength();
        if( m_nPointCount < m_aValues_Y_Last.getLength() )
            m_nPointCount = m_aValues_Y_Last.getLength();
    }

    if( !xDataSeries.is())
        return;

    try
    {
        // "AttributedDataPoints"
        xDataSeries->getFastPropertyValue(PROP_DATASERIES_ATTRIBUTED_DATA_POINTS) >>= m_aAttributedDataPointIndexList;

        xDataSeries->getFastPropertyValue(PROP_DATASERIES_STACKING_DIRECTION) >>= m_eStackingDirection; // "StackingDirection"

        xDataSeries->getFastPropertyValue(PROP_DATASERIES_ATTACHED_AXIS_INDEX) >>= m_nAxisIndex; // "AttachedAxisIndex"
        if(m_nAxisIndex<0)
            m_nAxisIndex=0;
    }
    catch( const uno::Exception& )
    {
        TOOLS_WARN_EXCEPTION("chart2", "" );
    }
}

VDataSeries::~VDataSeries()
{
}

void VDataSeries::doSortByXValues()
{
    if( !(m_aValues_X.is() && m_aValues_X.m_aValues.hasElements()) )
        return;

    //prepare a vector for sorting
    std::vector< std::vector< double > > aTmp;//outer vector are points, inner vector are the different values of the point
    sal_Int32 nPointIndex = 0;
    for( nPointIndex=0; nPointIndex < m_nPointCount; nPointIndex++ )
    {
        aTmp.push_back(
                        { ((nPointIndex < m_aValues_X.m_aValues.getLength()) ? m_aValues_X.m_aValues[nPointIndex]
                                                                           : std::numeric_limits<double>::quiet_NaN()),
                          ((nPointIndex < m_aValues_Y.m_aValues.getLength()) ? m_aValues_Y.m_aValues[nPointIndex]
                                                                           : std::numeric_limits<double>::quiet_NaN())
                        }
                      );
    }

    //do sort
    std::stable_sort( aTmp.begin(), aTmp.end(), lcl_LessXOfPoint() );

    //fill the sorted points back to the members
    m_aValues_X.m_aValues.realloc( m_nPointCount );
    auto pDoublesX = m_aValues_X.m_aValues.getArray();
    m_aValues_Y.m_aValues.realloc( m_nPointCount );
    auto pDoublesY = m_aValues_Y.m_aValues.getArray();

    for( nPointIndex=0; nPointIndex < m_nPointCount; nPointIndex++ )
    {
        pDoublesX[nPointIndex]=aTmp[nPointIndex][0];
        pDoublesY[nPointIndex]=aTmp[nPointIndex][1];
    }
}

void VDataSeries::releaseShapes()
{
    m_xGroupShape.clear();
    m_xLabelsGroupShape.clear();
    m_xErrorXBarsGroupShape.clear();
    m_xErrorYBarsGroupShape.clear();
    m_xFrontSubGroupShape.clear();
    m_xBackSubGroupShape.clear();

    m_aPolyPolygonShape3D.clear();
    m_nPolygonIndex = 0;
}

const rtl::Reference<::chart::DataSeries>& VDataSeries::getModel() const
{
    return m_xDataSeries;
}

void VDataSeries::setCategoryXAxis()
{
    m_aValues_X.clear();
    m_bAllowPercentValueInDataLabel = true;
}

void VDataSeries::setXValues( const Reference< chart2::data::XDataSequence >& xValues )
{
    m_aValues_X.clear();
    m_aValues_X.init( xValues );
    m_bAllowPercentValueInDataLabel = true;
}

void VDataSeries::setXValuesIfNone( const Reference< chart2::data::XDataSequence >& xValues )
{
    if( m_aValues_X.is() )
        return;

    m_aValues_X.init( xValues );
    lcl_clearIfNoValuesButTextIsContained( m_aValues_X, xValues );
}

void VDataSeries::setGlobalSeriesIndex( sal_Int32 nGlobalSeriesIndex )
{
    m_nGlobalSeriesIndex = nGlobalSeriesIndex;
}

void VDataSeries::setParticle( const OUString& rSeriesParticle )
{
    m_aSeriesParticle = rSeriesParticle;

    //get CID
    m_aCID = ObjectIdentifier::createClassifiedIdentifierForParticle( m_aSeriesParticle );
    m_aPointCID_Stub = ObjectIdentifier::createSeriesSubObjectStub( OBJECTTYPE_DATA_POINT, m_aSeriesParticle );

    m_aLabelCID_Stub = ObjectIdentifier::createClassifiedIdentifierWithParent(
                        OBJECTTYPE_DATA_LABEL, u"", getLabelsCID() );
}
OUString VDataSeries::getErrorBarsCID(bool bYError) const
{
    OUString aChildParticle( ObjectIdentifier::getStringForType(
                                      bYError ? OBJECTTYPE_DATA_ERRORS_Y : OBJECTTYPE_DATA_ERRORS_X )
                             + "=" );

    return ObjectIdentifier::createClassifiedIdentifierForParticles(
            m_aSeriesParticle, aChildParticle );
}
OUString VDataSeries::getLabelsCID() const
{
    OUString aChildParticle( ObjectIdentifier::getStringForType( OBJECTTYPE_DATA_LABELS ) + "=" );

    return ObjectIdentifier::createClassifiedIdentifierForParticles(
            m_aSeriesParticle, aChildParticle );
}
OUString VDataSeries::getDataCurveCID( sal_Int32 nCurveIndex, bool bAverageLine ) const
{
    return ObjectIdentifier::createDataCurveCID( m_aSeriesParticle, nCurveIndex, bAverageLine );
}

OUString VDataSeries::getDataCurveEquationCID( sal_Int32 nCurveIndex ) const
{
    return ObjectIdentifier::createDataCurveEquationCID( m_aSeriesParticle, nCurveIndex );
}
void VDataSeries::setPageReferenceSize( const awt::Size & rPageRefSize )
{
    m_aReferenceSize = rPageRefSize;
}

void VDataSeries::setConnectBars( bool bConnectBars )
{
    m_bConnectBars = bConnectBars;
}

bool VDataSeries::getConnectBars() const
{
    return m_bConnectBars;
}

void VDataSeries::setGroupBarsPerAxis( bool bGroupBarsPerAxis )
{
    m_bGroupBarsPerAxis = bGroupBarsPerAxis;
}

bool VDataSeries::getGroupBarsPerAxis() const
{
    return m_bGroupBarsPerAxis;
}

void VDataSeries::setStartingAngle( sal_Int32 nStartingAngle )
{
    m_nStartingAngle = nStartingAngle;
}

sal_Int32 VDataSeries::getStartingAngle() const
{
    return m_nStartingAngle;
}

chart2::StackingDirection VDataSeries::getStackingDirection() const
{
    return m_eStackingDirection;
}

sal_Int32 VDataSeries::getAttachedAxisIndex() const
{
    return m_nAxisIndex;
}

void VDataSeries::setAttachedAxisIndex( sal_Int32 nAttachedAxisIndex )
{
    if( nAttachedAxisIndex < 0 )
        nAttachedAxisIndex = 0;
    m_nAxisIndex = nAttachedAxisIndex;
}

double VDataSeries::getXValue( sal_Int32 index ) const
{
    double fRet = std::numeric_limits<double>::quiet_NaN();
    if(m_aValues_X.is())
    {
        if( 0<=index && index<m_aValues_X.getLength() )
        {
            fRet = m_aValues_X.m_aValues[index];
            if(mpOldSeries && index < mpOldSeries->m_aValues_X.getLength())
            {
                double nOldVal = mpOldSeries->m_aValues_X.m_aValues[index];
                fRet = nOldVal + (fRet - nOldVal) * mnPercent;
            }
        }
    }
    else
    {
        // #i70133# always return correct X position - needed for short data series
        if( 0<=index /*&& index < m_nPointCount*/ )
            fRet = index+1;//first category (index 0) matches with real number 1.0
    }
    lcl_maybeReplaceNanWithZero( fRet, getMissingValueTreatment() );
    return fRet;
}

double VDataSeries::getYValue( sal_Int32 index ) const
{
    double fRet = std::numeric_limits<double>::quiet_NaN();
    if(m_aValues_Y.is())
    {
        if( 0<=index && index<m_aValues_Y.getLength() )
        {
            fRet = m_aValues_Y.m_aValues[index];
            if(mpOldSeries && index < mpOldSeries->m_aValues_Y.getLength())
            {
                double nOldVal = mpOldSeries->m_aValues_Y.m_aValues[index];
                fRet = nOldVal + (fRet - nOldVal) * mnPercent;
            }
        }
    }
    else
    {
        // #i70133# always return correct X position - needed for short data series
        if( 0<=index /*&& index < m_nPointCount*/ )
            fRet = index+1;//first category (index 0) matches with real number 1.0
    }
    lcl_maybeReplaceNanWithZero( fRet, getMissingValueTreatment() );
    return fRet;
}

void VDataSeries::getMinMaxXValue(double& fMin, double& fMax) const
{
    fMax = std::numeric_limits<double>::quiet_NaN();
    fMin = std::numeric_limits<double>::quiet_NaN();

    uno::Sequence< double > aValuesX = getAllX();

    if(!aValuesX.hasElements())
        return;

    sal_Int32 i = 0;
    while ( i < aValuesX.getLength() && std::isnan(aValuesX[i]) )
        i++;
    if ( i < aValuesX.getLength() )
        fMax = fMin = aValuesX[i++];

    for ( ; i < aValuesX.getLength(); i++)
    {
        const double aValue = aValuesX[i];
        if ( aValue > fMax)
        {
            fMax = aValue;
        }
        else if ( aValue < fMin)
        {
            fMin = aValue;
        }
    }
}
double VDataSeries::getY_Min( sal_Int32 index ) const
{
    return m_aValues_Y_Min.getValue( index );
}
double VDataSeries::getY_Max( sal_Int32 index ) const
{
    return m_aValues_Y_Max.getValue( index );
}
double VDataSeries::getY_First( sal_Int32 index ) const
{
    return m_aValues_Y_First.getValue( index );
}
double VDataSeries::getY_Last( sal_Int32 index ) const
{
    return m_aValues_Y_Last.getValue( index );
}
double VDataSeries::getBubble_Size( sal_Int32 index ) const
{
    double nNewVal = m_aValues_Bubble_Size.getValue( index );
    if(mpOldSeries && index < mpOldSeries->m_aValues_Bubble_Size.getLength())
    {
        double nOldVal = mpOldSeries->m_aValues_Bubble_Size.getValue( index );
        nNewVal = nOldVal + (nNewVal - nOldVal) * mnPercent;
    }

    return nNewVal;
}

bool VDataSeries::hasExplicitNumberFormat( sal_Int32 nPointIndex, bool bForPercentage ) const
{
    OUString aPropName = bForPercentage ? u"PercentageNumberFormat"_ustr : CHART_UNONAME_NUMFMT;
    bool bHasNumberFormat = false;
    bool bLinkToSource = true;
    uno::Reference< beans::XPropertySet > xPointProp( getPropertiesOfPoint( nPointIndex ));
    if( xPointProp.is() && (xPointProp->getPropertyValue(CHART_UNONAME_LINK_TO_SRC_NUMFMT) >>= bLinkToSource))
    {
        sal_Int32 nNumberFormat = -1;
        if( !bLinkToSource && (xPointProp->getPropertyValue(aPropName) >>= nNumberFormat))
            bHasNumberFormat = true;
    }
    return bHasNumberFormat;
}
sal_Int32 VDataSeries::getExplicitNumberFormat( sal_Int32 nPointIndex, bool bForPercentage ) const
{
    OUString aPropName = bForPercentage ? u"PercentageNumberFormat"_ustr : CHART_UNONAME_NUMFMT;
    sal_Int32 nNumberFormat = -1;
    uno::Reference< beans::XPropertySet > xPointProp( getPropertiesOfPoint( nPointIndex ));
    if( xPointProp.is() )
        xPointProp->getPropertyValue(aPropName) >>= nNumberFormat;
    return nNumberFormat;
}
void VDataSeries::setRoleOfSequenceForDataLabelNumberFormatDetection( std::u16string_view rRole )
{
    if (rRole == u"values-y")
        m_pValueSequenceForDataLabelNumberFormatDetection = &m_aValues_Y;
    else if (rRole == u"values-size")
        m_pValueSequenceForDataLabelNumberFormatDetection = &m_aValues_Bubble_Size;
    else if (rRole == u"values-min")
        m_pValueSequenceForDataLabelNumberFormatDetection = &m_aValues_Y_Min;
    else if (rRole == u"values-max")
        m_pValueSequenceForDataLabelNumberFormatDetection = &m_aValues_Y_Max;
    else if (rRole == u"values-first")
        m_pValueSequenceForDataLabelNumberFormatDetection = &m_aValues_Y_First;
    else if (rRole == u"values-last")
        m_pValueSequenceForDataLabelNumberFormatDetection = &m_aValues_Y_Last;
    else if (rRole == u"values-x")
        m_pValueSequenceForDataLabelNumberFormatDetection = &m_aValues_X;
}
sal_Int32 VDataSeries::detectNumberFormatKey( sal_Int32 index ) const
{
    sal_Int32 nRet = 0;
    if( m_pValueSequenceForDataLabelNumberFormatDetection )
        nRet = m_pValueSequenceForDataLabelNumberFormatDetection->detectNumberFormatKey( index );
    return nRet;
}

sal_Int32 VDataSeries::getLabelPlacement( sal_Int32 nPointIndex, const rtl::Reference< ChartType >& xChartType, bool bSwapXAndY ) const
{
    sal_Int32 nLabelPlacement=0;
    try
    {
        uno::Reference< beans::XPropertySet > xPointProps( getPropertiesOfPoint( nPointIndex ) );
        if( xPointProps.is() )
            xPointProps->getPropertyValue(u"LabelPlacement"_ustr) >>= nLabelPlacement;

        const uno::Sequence < sal_Int32 > aAvailablePlacements( ChartTypeHelper::getSupportedLabelPlacements(
                xChartType, bSwapXAndY, m_xDataSeries ) );

        for( sal_Int32 n : aAvailablePlacements )
            if( n == nLabelPlacement )
                return nLabelPlacement; //ok

        //otherwise use the first supported one
        if( aAvailablePlacements.hasElements() )
        {
            nLabelPlacement = aAvailablePlacements[0];
            if( xPointProps.is() )
                xPointProps->setPropertyValue(u"LabelPlacement"_ustr, uno::Any(nLabelPlacement));
            return nLabelPlacement;
        }

        OSL_FAIL("no label placement supported");
    }
    catch( const uno::Exception& )
    {
        TOOLS_WARN_EXCEPTION("chart2", "" );
    }
    return nLabelPlacement;
}

awt::Point VDataSeries::getLabelPosition( awt::Point aTextShapePos, sal_Int32 nPointIndex ) const
{
    awt::Point aPos(-1, -1);
    try
    {
        RelativePosition aCustomLabelPosition;
        uno::Reference< beans::XPropertySet > xPointProps(getPropertiesOfPoint(nPointIndex));
        if( xPointProps.is() && (xPointProps->getPropertyValue(u"CustomLabelPosition"_ustr) >>= aCustomLabelPosition))
        {
            aPos.X = static_cast<sal_Int32>(aCustomLabelPosition.Primary * m_aReferenceSize.Width) + aTextShapePos.X;
            aPos.Y = static_cast<sal_Int32>(aCustomLabelPosition.Secondary * m_aReferenceSize.Height) + aTextShapePos.Y;
        }
    }
    catch (const uno::Exception&)
    {
        TOOLS_WARN_EXCEPTION("chart2", "");
    }
    return aPos;
}

bool VDataSeries::isLabelCustomPos(sal_Int32 nPointIndex) const
{
    bool bCustom = false;
    try
    {
        if( isAttributedDataPoint(nPointIndex) )
        {
            uno::Reference< beans::XPropertySet > xPointProps(m_xDataSeries->getDataPointByIndex(nPointIndex));
            RelativePosition aCustomLabelPosition;
            if( xPointProps.is() && (xPointProps->getPropertyValue(u"CustomLabelPosition"_ustr) >>= aCustomLabelPosition) )
                bCustom = true;
        }
    }
    catch (const uno::Exception&)
    {
        TOOLS_WARN_EXCEPTION("chart2", "");
    }
    return bCustom;
}

awt::Size VDataSeries::getLabelCustomSize(sal_Int32 nPointIndex) const
{
    awt::Size aSize(-1, -1);
    try
    {
        RelativeSize aCustomLabelSize;
        const uno::Reference<beans::XPropertySet> xPointProps(getPropertiesOfPoint(nPointIndex));
        if (xPointProps.is() && (xPointProps->getPropertyValue(u"CustomLabelSize"_ustr) >>= aCustomLabelSize))
        {
            aSize.Width = static_cast<sal_Int32>(aCustomLabelSize.Primary * m_aReferenceSize.Width);
            aSize.Height = static_cast<sal_Int32>(aCustomLabelSize.Secondary * m_aReferenceSize.Height);
        }
    }
    catch (const uno::Exception&)
    {
        DBG_UNHANDLED_EXCEPTION("chart2");
    }
    return aSize;
}

double VDataSeries::getMinimumofAllDifferentYValues( sal_Int32 index ) const
{
    double fMin = std::numeric_limits<double>::infinity();

    if( !m_aValues_Y.is() &&
        (m_aValues_Y_Min.is() || m_aValues_Y_Max.is()
        || m_aValues_Y_First.is() || m_aValues_Y_Last.is() ) )
    {
        double fY_Min = getY_Min( index );
        double fY_Max = getY_Max( index );
        double fY_First = getY_First( index );
        double fY_Last = getY_Last( index );

        if(fMin>fY_First)
            fMin=fY_First;
        if(fMin>fY_Last)
            fMin=fY_Last;
        if(fMin>fY_Min)
            fMin=fY_Min;
        if(fMin>fY_Max)
            fMin=fY_Max;
    }
    else
    {
        double fY = getYValue( index );
        if(fMin>fY)
            fMin=fY;
    }

    if( std::isinf(fMin) )
        return std::numeric_limits<double>::quiet_NaN();

    return fMin;
}

double VDataSeries::getMaximumofAllDifferentYValues( sal_Int32 index ) const
{
    double fMax = -std::numeric_limits<double>::infinity();

    if( !m_aValues_Y.is() &&
        (m_aValues_Y_Min.is() || m_aValues_Y_Max.is()
        || m_aValues_Y_First.is() || m_aValues_Y_Last.is() ) )
    {
        double fY_Min = getY_Min( index );
        double fY_Max = getY_Max( index );
        double fY_First = getY_First( index );
        double fY_Last = getY_Last( index );

        if(fMax<fY_First)
            fMax=fY_First;
        if(fMax<fY_Last)
            fMax=fY_Last;
        if(fMax<fY_Min)
            fMax=fY_Min;
        if(fMax<fY_Max)
            fMax=fY_Max;
    }
    else
    {
        double fY = getYValue( index );
        if(fMax<fY)
            fMax=fY;
    }

    if( std::isinf(fMax) )
        return std::numeric_limits<double>::quiet_NaN();

    return fMax;
}

uno::Sequence< double > const & VDataSeries::getAllX() const
{
    if(!m_aValues_X.is() && !m_aValues_X.getLength() && m_nPointCount)
    {
        //init x values from category indexes
        //first category (index 0) matches with real number 1.0
        m_aValues_X.m_aValues.realloc( m_nPointCount );
        auto pDoubles = m_aValues_X.m_aValues.getArray();
        for(sal_Int32 nN=m_aValues_X.getLength();nN--;)
            pDoubles[nN] = nN+1;
    }
    return m_aValues_X.m_aValues;
}

uno::Sequence< double > const & VDataSeries::getAllY() const
{
    if(!m_aValues_Y.is() && !m_aValues_Y.getLength() && m_nPointCount)
    {
        //init y values from indexes
        //first y-value (index 0) matches with real number 1.0
        m_aValues_Y.m_aValues.realloc( m_nPointCount );
        auto pDoubles = m_aValues_Y.m_aValues.getArray();
        for(sal_Int32 nN=m_aValues_Y.getLength();nN--;)
            pDoubles[nN] = nN+1;
    }
    return m_aValues_Y.m_aValues;
}

double VDataSeries::getXMeanValue() const
{
    if( std::isnan( m_fXMeanValue ) )
    {
        rtl::Reference< RegressionCurveCalculator > xCalculator( RegressionCurveHelper::createRegressionCurveCalculatorByServiceName( u"com.sun.star.chart2.MeanValueRegressionCurve" ) );
        uno::Sequence< double > aXValuesDummy;
        xCalculator->recalculateRegression( aXValuesDummy, getAllX() );
        m_fXMeanValue = xCalculator->getCurveValue( 1.0 );
    }
    return m_fXMeanValue;
}

double VDataSeries::getYMeanValue() const
{
    if( std::isnan( m_fYMeanValue ) )
    {
        rtl::Reference< RegressionCurveCalculator > xCalculator(
            RegressionCurveHelper::createRegressionCurveCalculatorByServiceName(u"com.sun.star.chart2.MeanValueRegressionCurve"));
        uno::Sequence< double > aXValuesDummy;
        xCalculator->recalculateRegression( aXValuesDummy, getAllY() );
        m_fYMeanValue = xCalculator->getCurveValue( 1.0 );
    }
    return m_fYMeanValue;
}

static std::optional<Symbol> getSymbolPropertiesFromPropertySet( const uno::Reference< beans::XPropertySet >& xProp )
{
    Symbol aSymbolProps;
    try
    {
        if( xProp->getPropertyValue(u"Symbol"_ustr) >>= aSymbolProps )
        {
            //use main color to fill symbols
            xProp->getPropertyValue(u"Color"_ustr) >>= aSymbolProps.FillColor;
            // border of symbols always same as fill color
            aSymbolProps.BorderColor = aSymbolProps.FillColor;
        }
        else
            return std::nullopt;
    }
    catch(const uno::Exception &)
    {
        TOOLS_WARN_EXCEPTION("chart2", "" );
    }
    return aSymbolProps;
}

Symbol* VDataSeries::getSymbolProperties( sal_Int32 index ) const
{
    Symbol* pRet=nullptr;
    if( isAttributedDataPoint( index ) )
    {
        adaptPointCache( index );
        if (!m_oSymbolProperties_AttributedPoint)
            m_oSymbolProperties_AttributedPoint
                = getSymbolPropertiesFromPropertySet(getPropertiesOfPoint(index));
        pRet = &*m_oSymbolProperties_AttributedPoint;
        //if a single data point does not have symbols but the dataseries itself has symbols
        //we create an invisible symbol shape to enable selection of that point
        if( !pRet || pRet->Style == SymbolStyle_NONE )
        {
            if (!m_oSymbolProperties_Series)
                m_oSymbolProperties_Series
                    = getSymbolPropertiesFromPropertySet(getPropertiesOfSeries());
            if( m_oSymbolProperties_Series && m_oSymbolProperties_Series->Style != SymbolStyle_NONE )
            {
                if (!m_oSymbolProperties_InvisibleSymbolForSelection)
                {
                    m_oSymbolProperties_InvisibleSymbolForSelection.emplace();
                    m_oSymbolProperties_InvisibleSymbolForSelection->Style = SymbolStyle_STANDARD;
                    m_oSymbolProperties_InvisibleSymbolForSelection->StandardSymbol = 0;//square
                    m_oSymbolProperties_InvisibleSymbolForSelection->Size = css::awt::Size(0, 0);//tdf#126033
                    m_oSymbolProperties_InvisibleSymbolForSelection->BorderColor = 0xff000000;//invisible
                    m_oSymbolProperties_InvisibleSymbolForSelection->FillColor = 0xff000000;//invisible
                }
                pRet = &*m_oSymbolProperties_InvisibleSymbolForSelection;
            }
        }
    }
    else
    {
        if (!m_oSymbolProperties_Series)
            m_oSymbolProperties_Series
                = getSymbolPropertiesFromPropertySet(getPropertiesOfSeries());
        pRet = &*m_oSymbolProperties_Series;
    }

    if( pRet && pRet->Style == SymbolStyle_AUTO )
    {
        pRet->Style = SymbolStyle_STANDARD;

        sal_Int32 nIndex = m_nGlobalSeriesIndex;
        if(m_aValues_X.is())
            nIndex++;
        pRet->StandardSymbol = nIndex;
    }

    return pRet;
}

uno::Reference< beans::XPropertySet > VDataSeries::getXErrorBarProperties( sal_Int32 index ) const
{
    uno::Reference< beans::XPropertySet > xErrorBarProp;

    uno::Reference< beans::XPropertySet > xPointProp( getPropertiesOfPoint( index ));
    if( xPointProp.is() )
        xPointProp->getPropertyValue(CHART_UNONAME_ERRORBAR_X) >>= xErrorBarProp;
    return xErrorBarProp;
}

uno::Reference< beans::XPropertySet > VDataSeries::getYErrorBarProperties( sal_Int32 index ) const
{
    uno::Reference< beans::XPropertySet > xErrorBarProp;

    uno::Reference< beans::XPropertySet > xPointProp( getPropertiesOfPoint( index ));
    if( xPointProp.is() )
        xPointProp->getPropertyValue(CHART_UNONAME_ERRORBAR_Y) >>= xErrorBarProp;
    return xErrorBarProp;
}

bool VDataSeries::hasPointOwnColor( sal_Int32 index ) const
{
    if( !isAttributedDataPoint(index) )
        return false;

    try
    {
        uno::Reference< beans::XPropertyState > xPointState( getPropertiesOfPoint(index), uno::UNO_QUERY_THROW );
        return (xPointState->getPropertyState(u"Color"_ustr) != beans::PropertyState_DEFAULT_VALUE );
    }
    catch(const uno::Exception&)
    {
        TOOLS_WARN_EXCEPTION("chart2", "" );
    }
    return false;
}

bool VDataSeries::isAttributedDataPoint( sal_Int32 index ) const
{
    //returns true if the data point assigned by the given index has set its own properties
    if( index>=m_nPointCount || m_nPointCount==0)
        return false;
    for(sal_Int32 n : m_aAttributedDataPointIndexList)
    {
        if(index == n)
            return true;
    }
    return false;
}

bool VDataSeries::isVaryColorsByPoint() const
{
    bool bVaryColorsByPoint = false;
    if( m_xDataSeries )
        m_xDataSeries->getFastPropertyValue(PROP_DATASERIES_VARY_COLORS_BY_POINT) >>= bVaryColorsByPoint; // "VaryColorsByPoint"
    return bVaryColorsByPoint;
}

uno::Reference< beans::XPropertySet > VDataSeries::getPropertiesOfPoint( sal_Int32 index ) const
{
    if( isAttributedDataPoint( index ) )
        return m_xDataSeries->getDataPointByIndex(index);
    return getPropertiesOfSeries();
}

const uno::Reference<beans::XPropertySet> & VDataSeries::getPropertiesOfSeries() const
{
    return m_xDataSeriesProps;
}

static std::optional<DataPointLabel> getDataPointLabelFromPropertySet( const uno::Reference< beans::XPropertySet >& xProp )
{
    std::optional< DataPointLabel > apLabel( std::in_place );
    try
    {
        if( !(xProp->getPropertyValue(CHART_UNONAME_LABEL) >>= *apLabel) )
            apLabel.reset();
    }
    catch(const uno::Exception &)
    {
        TOOLS_WARN_EXCEPTION("chart2", "" );
    }
    return apLabel;
}

void VDataSeries::adaptPointCache( sal_Int32 nNewPointIndex ) const
{
    if( m_nCurrentAttributedPoint != nNewPointIndex )
    {
        m_oLabel_AttributedPoint.reset();
        m_oLabelPropNames_AttributedPoint.reset();
        m_oLabelPropValues_AttributedPoint.reset();
        m_oSymbolProperties_AttributedPoint.reset();
        m_nCurrentAttributedPoint = nNewPointIndex;
    }
}

DataPointLabel* VDataSeries::getDataPointLabel( sal_Int32 index ) const
{
    DataPointLabel* pRet = nullptr;
    if( isAttributedDataPoint( index ) )
    {
        adaptPointCache( index );
        if (!m_oLabel_AttributedPoint)
            m_oLabel_AttributedPoint
                = getDataPointLabelFromPropertySet(getPropertiesOfPoint(index));
        if (m_oLabel_AttributedPoint)
            pRet = &*m_oLabel_AttributedPoint;
    }
    else
    {
        if (!m_oLabel_Series)
            m_oLabel_Series
                = getDataPointLabelFromPropertySet(getPropertiesOfPoint(index));
        if (m_oLabel_Series)
            pRet = &*m_oLabel_Series;
    }
    if( !m_bAllowPercentValueInDataLabel )
    {
        if( pRet )
            pRet->ShowNumberInPercent = false;
    }
    return pRet;
}

DataPointLabel* VDataSeries::getDataPointLabelIfLabel( sal_Int32 index ) const
{
    DataPointLabel* pLabel = getDataPointLabel( index );
    if( !pLabel || (!pLabel->ShowNumber && !pLabel->ShowNumberInPercent
        && !pLabel->ShowCategoryName && !pLabel->ShowCustomLabel && !pLabel->ShowSeriesName ) )
        return nullptr;
    return pLabel;
}

bool VDataSeries::getTextLabelMultiPropertyLists( sal_Int32 index
    , tNameSequence*& pPropNames
    , tAnySequence*& pPropValues ) const
{
    pPropNames = nullptr; pPropValues = nullptr;
    uno::Reference< beans::XPropertySet > xTextProp;
    bool bDoDynamicFontResize = false;
    if( isAttributedDataPoint( index ) )
    {
        adaptPointCache( index );
        if (!m_oLabelPropValues_AttributedPoint)
        {
            // Cache these properties for this point.
            m_oLabelPropNames_AttributedPoint.emplace();
            m_oLabelPropValues_AttributedPoint.emplace();
            xTextProp.set( getPropertiesOfPoint( index ));
            PropertyMapper::getTextLabelMultiPropertyLists(
                xTextProp, *m_oLabelPropNames_AttributedPoint, *m_oLabelPropValues_AttributedPoint);
            bDoDynamicFontResize = true;
        }
        pPropNames = &*m_oLabelPropNames_AttributedPoint;
        pPropValues = &*m_oLabelPropValues_AttributedPoint;
    }
    else
    {
        if (!m_oLabelPropValues_Series)
        {
            // Cache these properties for the whole series.
            m_oLabelPropNames_Series.emplace();
            m_oLabelPropValues_Series.emplace();
            xTextProp.set( getPropertiesOfPoint( index ));
            PropertyMapper::getTextLabelMultiPropertyLists(
                xTextProp, *m_oLabelPropNames_Series, *m_oLabelPropValues_Series);
            bDoDynamicFontResize = true;
        }
        pPropNames = &*m_oLabelPropNames_Series;
        pPropValues = &*m_oLabelPropValues_Series;
    }

    if( bDoDynamicFontResize &&
        pPropNames && pPropValues &&
        xTextProp.is())
    {
        LabelPositionHelper::doDynamicFontResize( *pPropValues, *pPropNames, xTextProp, m_aReferenceSize );
    }

    return (pPropNames && pPropValues);
}

void VDataSeries::setMissingValueTreatment( sal_Int32 nMissingValueTreatment )
{
    m_nMissingValueTreatment = nMissingValueTreatment;
}

sal_Int32 VDataSeries::getMissingValueTreatment() const
{
    return m_nMissingValueTreatment;
}

VDataSeries::VDataSeries()
    : m_nPolygonIndex(0)
    , m_fLogicMinX(0)
    , m_fLogicMaxX(0)
    , m_fLogicZPos(0)
    , m_nPointCount(0)
    , m_pValueSequenceForDataLabelNumberFormatDetection(nullptr)
    , m_fXMeanValue(0)
    , m_fYMeanValue(0)
    , m_eStackingDirection(chart2::StackingDirection_NO_STACKING)
    , m_nAxisIndex(0)
    , m_bConnectBars(false)
    , m_bGroupBarsPerAxis(false)
    , m_nStartingAngle(0)
    , m_nGlobalSeriesIndex(0)
    , m_nCurrentAttributedPoint(0)
    , m_nMissingValueTreatment(0)
    , m_bAllowPercentValueInDataLabel(false)
    , mpOldSeries(nullptr)
    , mnPercent(0)
{
}

void VDataSeries::setOldTimeBased( VDataSeries* pOldSeries, double nPercent )
{
    mnPercent = nPercent;
    mpOldSeries = pOldSeries;
    mpOldSeries->mpOldSeries = nullptr;
}

VDataSeries* VDataSeries::createCopyForTimeBased() const
{
    VDataSeries* pNew = new VDataSeries();
    pNew->m_aValues_X = m_aValues_X;
    pNew->m_aValues_Y = m_aValues_Y;
    pNew->m_aValues_Z = m_aValues_Z;
    pNew->m_aValues_Y_Min = m_aValues_Y_Min;
    pNew->m_aValues_Y_Max = m_aValues_Y_Max;
    pNew->m_aValues_Y_First = m_aValues_Y_First;
    pNew->m_aValues_Y_Last = m_aValues_Y_Last;
    pNew->m_aValues_Bubble_Size = m_aValues_Bubble_Size;
    pNew->m_PropertyMap = m_PropertyMap;

    pNew->m_nPointCount = m_nPointCount;

    return pNew;
}

double VDataSeries::getValueByProperty( sal_Int32 nIndex, const OUString& rPropName ) const
{
    auto const itr = m_PropertyMap.find(rPropName);
    if (itr == m_PropertyMap.end())
        return std::numeric_limits<double>::quiet_NaN();

    const VDataSequence* pData = &itr->second;
    double fValue = pData->getValue(nIndex);
    if(mpOldSeries && mpOldSeries->hasPropertyMapping(rPropName))
    {
        double fOldValue = mpOldSeries->getValueByProperty( nIndex, rPropName );
        if(rPropName.endsWith("Color"))
        {
            //optimized interpolation for color values
            Color aColor(ColorTransparency, static_cast<sal_uInt32>(fValue));
            Color aOldColor(ColorTransparency, static_cast<sal_uInt32>(fOldValue));
            sal_uInt8 r = aOldColor.GetRed() + (aColor.GetRed() - aOldColor.GetRed()) * mnPercent;
            sal_uInt8 g = aOldColor.GetGreen() + (aColor.GetGreen() - aOldColor.GetGreen()) * mnPercent;
            sal_uInt8 b = aOldColor.GetBlue() + (aColor.GetBlue() - aOldColor.GetBlue()) * mnPercent;
            sal_uInt8 a = aOldColor.GetAlpha() + (aColor.GetAlpha() - aOldColor.GetAlpha()) * mnPercent;
            Color aRet(ColorAlpha, a, r, g, b);
            return sal_uInt32(aRet);
        }
        return fOldValue + (fValue - fOldValue) * mnPercent;
    }
    return fValue;
}

bool VDataSeries::hasPropertyMapping(const OUString& rPropName ) const
{
    return m_PropertyMap.contains(rPropName);
}

} //namespace chart

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