/* -*- 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 <drawingml/chart/seriesconverter.hxx>

#include <com/sun/star/chart/DataLabelPlacement.hpp>
#include <com/sun/star/chart2/RelativePosition.hpp>
#include <com/sun/star/chart2/RelativeSize.hpp>
#include <com/sun/star/chart/ErrorBarStyle.hpp>
#include <com/sun/star/chart2/DataPointLabel.hpp>
#include <com/sun/star/drawing/Hatch.hpp>
#include <com/sun/star/chart2/XChartDocument.hpp>
#include <com/sun/star/chart2/XDataPointCustomLabelField.hpp>
#include <com/sun/star/chart2/DataPointCustomLabelField.hpp>
#include <com/sun/star/chart2/DataPointCustomLabelFieldType.hpp>
#include <com/sun/star/chart2/XDataSeries.hpp>
#include <com/sun/star/chart2/XRegressionCurve.hpp>
#include <com/sun/star/chart2/XRegressionCurveContainer.hpp>
#include <com/sun/star/chart2/data/XDataSink.hpp>
#include <com/sun/star/chart2/data/LabeledDataSequence.hpp>
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <com/sun/star/drawing/FillStyle.hpp>

#include <comphelper/sequence.hxx>
#include <osl/diagnose.h>
#include <drawingml/chart/datasourceconverter.hxx>
#include <drawingml/chart/seriesmodel.hxx>
#include <drawingml/chart/titleconverter.hxx>
#include <drawingml/chart/typegroupconverter.hxx>
#include <drawingml/chart/typegroupmodel.hxx>
#include <drawingml/fillproperties.hxx>
#include <oox/core/xmlfilterbase.hxx>
#include <oox/helper/modelobjecthelper.hxx>
#include <oox/token/properties.hxx>
#include <oox/token/tokens.hxx>
#include <drawingml/lineproperties.hxx>
#include <drawingml/textparagraph.hxx>
#include <drawingml/textrun.hxx>
#include <drawingml/textfield.hxx>
#include <drawingml/textbody.hxx>
#include <drawingml/hatchmap.hxx>
#include <oox/helper/graphichelper.hxx>
#include <docmodel/color/ComplexColor.hxx>
#include <docmodel/uno/UnoComplexColor.hxx>

namespace oox::drawingml::chart {

using namespace com::sun::star;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::chart2;
using namespace ::com::sun::star::chart2::data;
using namespace ::com::sun::star::uno;

namespace {

Reference< XLabeledDataSequence > lclCreateLabeledDataSequence(
        const ConverterRoot& rParent,
        DataSourceModel* pValues, const OUString& rRole,
        TextModel* pTitle = nullptr )
{
    // create data sequence for values
    Reference< XDataSequence > xValueSeq;
    if( pValues )
    {
        DataSourceConverter aSourceConv( rParent, *pValues );
        xValueSeq = aSourceConv.createDataSequence( rRole );
    }

    // create data sequence for title
    Reference< XDataSequence > xTitleSeq;
    if( pTitle )
    {
        TextConverter aTextConv( rParent, *pTitle );
        xTitleSeq = aTextConv.createDataSequence( u"label"_ustr );
    }

    // create the labeled data sequence, if values or title are present
    Reference< XLabeledDataSequence > xLabeledSeq;
    if( xValueSeq.is() || xTitleSeq.is() )
    {
        xLabeledSeq = LabeledDataSequence::create(rParent.getComponentContext());
        if( xLabeledSeq.is() )
        {
            xLabeledSeq->setValues( xValueSeq );
            xLabeledSeq->setLabel( xTitleSeq );
        }
    }
    return xLabeledSeq;
}

void convertTextProperty(PropertySet& rPropSet, ObjectFormatter& rFormatter,
        DataLabelModelBase::TextBodyRef xTextProps)
{
    rFormatter.convertTextFormatting( rPropSet, xTextProps, OBJECTTYPE_DATALABEL );
    ObjectFormatter::convertTextRotation( rPropSet, xTextProps, false );
    ObjectFormatter::convertTextWrap( rPropSet, xTextProps );
}

void lclConvertLabelFormatting( PropertySet& rPropSet, ObjectFormatter& rFormatter,
                                DataLabelModelBase& rDataLabel, const TypeGroupConverter& rTypeGroup,
                                bool bDataSeriesLabel, bool bCustomLabelField, bool bHasInternalData, bool bMSO2007Doc )
{
    const TypeGroupInfo& rTypeInfo = rTypeGroup.getTypeInfo();

    /*  Excel 2007 does not change the series setting for a single data point,
        if none of some specific elements occur. But only one existing element
        in a data point will reset most other of these elements from the series
        (e.g.: series has <c:showVal>, data point has <c:showCatName>, this
        will reset <c:showVal> for this point, unless <c:showVal> is repeated
        in the data point). The elements <c:layout>, <c:numberFormat>,
        <c:spPr>, <c:tx>, and <c:txPr> are not affected at all. */
    bool bHasAnyElement = true;
    if (bMSO2007Doc)
    {
        bHasAnyElement = rDataLabel.moaSeparator.has_value() || rDataLabel.monLabelPos.has_value() ||
            rDataLabel.mobShowCatName.has_value() || rDataLabel.mobShowLegendKey.has_value() ||
            rDataLabel.mobShowPercent.has_value() || rDataLabel.mobShowSerName.has_value() ||
            rDataLabel.mobShowVal.has_value();
    }

    bool bShowValue   = !rDataLabel.mbDeleted && rDataLabel.mobShowVal.value_or( !bMSO2007Doc );
    bool bShowPercent = !rDataLabel.mbDeleted && rDataLabel.mobShowPercent.value_or( !bMSO2007Doc ) && (rTypeInfo.meTypeCategory == TYPECATEGORY_PIE);
    bool bShowCateg   = !rDataLabel.mbDeleted && rDataLabel.mobShowCatName.value_or( !bMSO2007Doc );
    bool bShowSerName = !rDataLabel.mbDeleted && rDataLabel.mobShowSerName.value_or( !bMSO2007Doc );
    bool bShowSymbol  = !rDataLabel.mbDeleted && rDataLabel.mobShowLegendKey.value_or( !bMSO2007Doc );

    // tdf#132174, tdf#136650: the inner data table has no own cell number format.
    if( bHasInternalData && bShowValue && !bShowPercent )
        rDataLabel.maNumberFormat.mbSourceLinked = false;

    // type of attached label
    if( bHasAnyElement || rDataLabel.mbDeleted )
    {
        DataPointLabel aPointLabel( bShowValue, bShowPercent, bShowCateg, bShowSymbol, bCustomLabelField, bShowSerName );
        rPropSet.setProperty( PROP_Label, aPointLabel );
    }

    if( rDataLabel.mbDeleted )
        return;

    // data label number format (percentage format wins over value format)
    rFormatter.convertNumberFormat( rPropSet, rDataLabel.maNumberFormat, false, bShowPercent );

    // data label text formatting (frame formatting not supported by Chart2)
    if( bDataSeriesLabel || (rDataLabel.mxTextProp.is() && !rDataLabel.mxTextProp->getParagraphs().empty()) )
        convertTextProperty(rPropSet, rFormatter, rDataLabel.mxTextProp);

    // data label separator (do not overwrite series separator, if no explicit point separator is present)
    // Set the data label separator to "new line" if the value is shown as percentage with a category name,
    // just like in MS-Office. In any other case the default separator will be a semicolon.
    if( bShowPercent && !bShowValue && ( bDataSeriesLabel || rDataLabel.moaSeparator.has_value() ) )
        rPropSet.setProperty( PROP_LabelSeparator, rDataLabel.moaSeparator.value_or( "\n" ) );
    else if( bDataSeriesLabel || rDataLabel.moaSeparator.has_value() )
        rPropSet.setProperty( PROP_LabelSeparator, rDataLabel.moaSeparator.value_or( "; " ) );

    // data label placement (do not overwrite series placement, if no explicit point placement is present)
    if( !(bDataSeriesLabel || rDataLabel.monLabelPos.has_value()) )
        return;

    namespace csscd = css::chart::DataLabelPlacement;
    sal_Int32 nPlacement = -1;
    switch( rDataLabel.monLabelPos.value_or( XML_TOKEN_INVALID ) )
    {
        case XML_outEnd:    nPlacement = csscd::OUTSIDE;        break;
        case XML_inEnd:     nPlacement = csscd::INSIDE;         break;
        case XML_ctr:       nPlacement = csscd::CENTER;         break;
        case XML_inBase:    nPlacement = csscd::NEAR_ORIGIN;    break;
        case XML_t:         nPlacement = csscd::TOP;            break;
        case XML_b:         nPlacement = csscd::BOTTOM;         break;
        case XML_l:         nPlacement = csscd::LEFT;           break;
        case XML_r:         nPlacement = csscd::RIGHT;          break;
        case XML_bestFit:   nPlacement = csscd::AVOID_OVERLAP;  break;
    }

    if( !bDataSeriesLabel && nPlacement == -1 )
        return;

    if( nPlacement == -1 )
        nPlacement = rTypeInfo.mnDefLabelPos;

    rPropSet.setProperty( PROP_LabelPlacement, nPlacement );
}

void importBorderProperties( PropertySet& rPropSet, Shape& rShape, const GraphicHelper& rGraphicHelper )
{
    LineProperties& rLineProperties = rShape.getLineProperties();
    // no fill has the same effect as no border so skip it
    if (rLineProperties.maLineFill.moFillType.has_value() && rLineProperties.maLineFill.moFillType.value() == XML_noFill)
        return;

    if (rLineProperties.moLineWidth.has_value())
    {
        sal_Int32 nWidth = convertEmuToHmm(rLineProperties.moLineWidth.value());
        rPropSet.setProperty(PROP_LabelBorderWidth, uno::Any(nWidth));
        rPropSet.setProperty(PROP_LabelBorderStyle, uno::Any(drawing::LineStyle_SOLID));
    }
    const Color& aOOXColor = rLineProperties.maLineFill.maFillColor;
    ::Color nColor = aOOXColor.getColor(rGraphicHelper);
    rPropSet.setProperty(PROP_LabelBorderColor, uno::Any(nColor));

    model::ComplexColor aComplexColor = aOOXColor.getComplexColor();
    auto xComplexColor = model::color::createXComplexColor(aComplexColor);
    rPropSet.setProperty(PROP_LabelBorderComplexColor, uno::Any(xComplexColor));
}

void lcl_ImportLeaderLineProperties(PropertySet& rPropSet, Shape& rShape, const GraphicHelper& rGraphicHelper)
{
    LineProperties& rLineProperties = rShape.getLineProperties();
    // no fill has the same effect as no line so skip it
    if (rLineProperties.maLineFill.moFillType.has_value() && rLineProperties.maLineFill.moFillType.value() == XML_noFill)
        return;

    if (rLineProperties.moLineWidth.has_value())
    {
        sal_Int32 nWidth = convertEmuToHmm(rLineProperties.moLineWidth.value());
        rPropSet.setProperty(PROP_LineWidth, uno::Any(nWidth));
    }

    if (rLineProperties.maLineFill.moFillType.has_value() && rLineProperties.maLineFill.moFillType.value() == XML_solidFill)
    {
        const Color& aOOXColor = rLineProperties.maLineFill.maFillColor;
        ::Color nColor = aOOXColor.getColor(rGraphicHelper);
        rPropSet.setProperty(PROP_LineColor, uno::Any(nColor));

        model::ComplexColor aComplexColor = aOOXColor.getComplexColor();
        auto xComplexColor = model::color::createXComplexColor(aComplexColor);
        rPropSet.setProperty(PROP_LineComplexColor, uno::Any(xComplexColor));
    }
}

void importFillProperties( PropertySet& rPropSet, Shape& rShape, const GraphicHelper& rGraphicHelper, ModelObjectHelper& rModelObjHelper )
{
    FillProperties& rFillProperties = rShape.getFillProperties();

    if (rFillProperties.moFillType.has_value() && rFillProperties.moFillType.value() == XML_solidFill)
    {
        rPropSet.setProperty(PROP_LabelFillStyle, drawing::FillStyle_SOLID);

        const Color& aOOXColor = rFillProperties.maFillColor;
        ::Color nColor = aOOXColor.getColor(rGraphicHelper);
        rPropSet.setProperty(PROP_LabelFillColor, uno::Any(nColor));

        model::ComplexColor aComplexColor = aOOXColor.getComplexColor();
        auto xComplexColor = model::color::createXComplexColor(aComplexColor);
        rPropSet.setProperty(PROP_LabelFillComplexColor, uno::Any(xComplexColor));
    }
    else if(rFillProperties.moFillType.has_value() && rFillProperties.moFillType.value() == XML_pattFill)
    {
        rPropSet.setProperty(PROP_LabelFillStyle, drawing::FillStyle_HATCH);
        rPropSet.setProperty(PROP_LabelFillBackground, true);

        Color aHatchColor( rFillProperties.maPatternProps.maPattFgColor );
        drawing::Hatch aHatch = createHatch(rFillProperties.maPatternProps.moPattPreset.value(), aHatchColor.getColor(rGraphicHelper, 0));

        OUString sHatchName = rModelObjHelper.insertFillHatch(aHatch);
        rPropSet.setProperty(PROP_LabelFillHatchName, sHatchName);

        const Color& aOOXColor = rFillProperties.maPatternProps.maPattBgColor;
        ::Color nColor = aOOXColor.getColor(rGraphicHelper);
        rPropSet.setProperty(PROP_LabelFillColor, uno::Any(nColor));

        model::ComplexColor aComplexColor = aOOXColor.getComplexColor();
        auto xComplexColor = model::color::createXComplexColor(aComplexColor);
        rPropSet.setProperty(PROP_LabelFillComplexColor, uno::Any(xComplexColor));
    }
}

DataPointCustomLabelFieldType lcl_ConvertFieldNameToFieldEnum( std::u16string_view rField )
{
    if (rField == u"VALUE")
        return DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_VALUE;
    else if (rField == u"SERIESNAME")
        return DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_SERIESNAME;
    else if (rField == u"CATEGORYNAME")
        return DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_CATEGORYNAME;
    else if (rField == u"CELLREF")
        return DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_CELLREF;
    else if (rField == u"CELLRANGE")
        return DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_CELLRANGE;
    else if (rField == u"PERCENTAGE")
        return DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_PERCENTAGE;
    else
        return DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_TEXT;
}

} // namespace

DataLabelConverter::DataLabelConverter( const ConverterRoot& rParent, DataLabelModel& rModel ) :
    ConverterBase< DataLabelModel >( rParent, rModel )
{
}

DataLabelConverter::~DataLabelConverter()
{
}

void DataLabelConverter::convertFromModel( const Reference< XDataSeries >& rxDataSeries, const TypeGroupConverter& rTypeGroup )
{
    if (!rxDataSeries.is())
        return;

    try
    {
        bool bMSO2007Doc = getFilter().isMSO2007Document();
        bool bHasInternalData = getChartDocument()->hasInternalDataProvider();
        bool bCustomLabelField = mrModel.mxText && mrModel.mxText->mxTextBody && !mrModel.mxText->mxTextBody->getParagraphs().empty();
        PropertySet aPropSet( rxDataSeries->getDataPointByIndex( mrModel.mnIndex ) );

        lclConvertLabelFormatting( aPropSet, getFormatter(), mrModel, rTypeGroup, false, bCustomLabelField, bHasInternalData, bMSO2007Doc );

        const TypeGroupInfo& rTypeInfo = rTypeGroup.getTypeInfo();
        bool bIsPie = rTypeInfo.meTypeCategory == TYPECATEGORY_PIE;

        if( mrModel.mxLayout && !mrModel.mxLayout->mbAutoLayout )
        {
            RelativePosition aPos(mrModel.mxLayout->mfX, mrModel.mxLayout->mfY, css::drawing::Alignment_TOP_LEFT);
            aPropSet.setProperty(PROP_CustomLabelPosition, aPos);
            const RelativeSize aSize(mrModel.mxLayout->mfW, mrModel.mxLayout->mfH);
            aPropSet.setProperty(PROP_CustomLabelSize, aSize);
            sal_Int32 nPlacement = -1;
            if (bIsPie && aPropSet.getProperty(nPlacement, PROP_LabelPlacement)
                && nPlacement == css::chart::DataLabelPlacement::AVOID_OVERLAP)
                aPropSet.setProperty(PROP_LabelPlacement, css::chart::DataLabelPlacement::CUSTOM);
        }

        if (mrModel.mxShapeProp)
        {
            importBorderProperties(aPropSet, *mrModel.mxShapeProp, getFilter().getGraphicHelper());
            uno::Reference<lang::XMultiServiceFactory> xFactory(getChartDocument(), uno::UNO_QUERY);
            ModelObjectHelper& rHelper = getFilter().getModelObjectHelperForModel(xFactory);
            importFillProperties(aPropSet, *mrModel.mxShapeProp, getFilter().getGraphicHelper(),
                                 rHelper);
        }
        if( bCustomLabelField )
        {
            css::uno::Reference< XComponentContext > xContext = getComponentContext();

            auto& rParagraphs = mrModel.mxText->mxTextBody->getParagraphs();

            int nSequenceSize = 0;
            for( auto& pParagraph : rParagraphs )
                nSequenceSize += pParagraph->getRuns().size();

            int nParagraphs = rParagraphs.size();
            if( nParagraphs > 1 )
                nSequenceSize += nParagraphs - 1;

            std::optional< OUString > oaLabelText;
            std::optional< OUString > oaCellRange;
            if (mrModel.mobShowDataLabelsRange.value_or(false))
            {
                const DataSourceModel* pLabelSource = mrModel.mrParent.mpLabelsSource;
                if (pLabelSource && pLabelSource->mxDataSeq.is())
                {
                    oaCellRange = pLabelSource->mxDataSeq->maFormula;
                    const auto& rLabelMap = pLabelSource->mxDataSeq->maData;
                    const auto aKV = rLabelMap.find(mrModel.mnIndex);
                    if (aKV != rLabelMap.end())
                    {
                        oaLabelText.emplace();
                        aKV->second >>= *oaLabelText;
                    }
                }
            }

            uno::Sequence< css::uno::Reference< XDataPointCustomLabelField > > aSequence( nSequenceSize );
            auto aSequenceRange = asNonConstRange(aSequence);

            int nPos = 0;

            for( auto& pParagraph : rParagraphs )
            {
                for( auto& pRun : pParagraph->getRuns() )
                {
                    css::uno::Reference< XDataPointCustomLabelField > xCustomLabel = DataPointCustomLabelField::create( xContext );

                    // Store properties
                    oox::PropertySet aPropertySet( xCustomLabel );
                    convertTextProperty( aPropertySet, getFormatter(), mrModel.mxText->mxTextBody );
                    pRun->getTextCharacterProperties().pushToPropSet( aPropertySet, getFilter() );

                    if (TextField* pField = dynamic_cast<TextField*>(pRun.get()))
                    {
                        DataPointCustomLabelFieldType eType = lcl_ConvertFieldNameToFieldEnum( pField->getType() );

                        if (eType == DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_CELLRANGE && oaCellRange.has_value())
                        {
                            xCustomLabel->setCellRange( oaCellRange.value() );
                            xCustomLabel->setString( oaLabelText.value_or("") );
                            xCustomLabel->setDataLabelsRange( true );
                        }
                        else
                            xCustomLabel->setString( pField->getText() );

                        xCustomLabel->setFieldType( eType );
                        xCustomLabel->setGuid( pField->getUuid() );
                    }
                    else
                    {
                        xCustomLabel->setString( pRun->getText() );
                        xCustomLabel->setFieldType( DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_TEXT );
                    }
                    aSequenceRange[ nPos++ ] = std::move(xCustomLabel);
                }

                if( nParagraphs > 1 && nPos < nSequenceSize )
                {
                    css::uno::Reference< XDataPointCustomLabelField > xCustomLabel = DataPointCustomLabelField::create( xContext );
                    xCustomLabel->setFieldType( DataPointCustomLabelFieldType::DataPointCustomLabelFieldType_NEWLINE );
                    xCustomLabel->setString(u"\n"_ustr);
                    aSequenceRange[ nPos++ ] = std::move(xCustomLabel);
                }
            }

            aPropSet.setProperty( PROP_CustomLabelFields, Any( aSequence ) );
            convertTextProperty(aPropSet, getFormatter(), mrModel.mxText->mxTextBody);
        }
    }
    catch( Exception& )
    {
    }
}

DataLabelsConverter::DataLabelsConverter( const ConverterRoot& rParent, DataLabelsModel& rModel ) :
    ConverterBase< DataLabelsModel >( rParent, rModel )
{
}

DataLabelsConverter::~DataLabelsConverter()
{
}

namespace
{
/// Inherit <c:dLbl> text props (if not set) from <c:dLbls> text props (if set).
void InheritFromDataLabelsTextProps(const DataLabelsModel& rLabels, const DataLabelModel& rLabel)
{
    // See if <c:dLbls> contains text properties to inherit.
    if (!rLabels.mxTextProp.is() || rLabels.mxTextProp->getParagraphs().empty())
    {
        return;
    }

    const std::shared_ptr<TextParagraph>& rLabelsParagraph = rLabels.mxTextProp->getParagraphs()[0];

    // See if <c:dLbl> lacks text properties.
    if (rLabel.mxTextProp.is())
    {
        return;
    }

    if (!rLabel.mxText || !rLabel.mxText->mxTextBody
        || rLabel.mxText->mxTextBody->getParagraphs().empty())
    {
        return;
    }

    const std::shared_ptr<TextParagraph>& rLabelParagraph
        = rLabel.mxText->mxTextBody->getParagraphs()[0];

    // Inherit rLabel.mxText's char props from rLabels.mxTextProp's char props.
    TextCharacterProperties aCharProps;
    aCharProps.assignUsed(rLabelsParagraph->getProperties().getTextCharacterProperties());
    aCharProps.assignUsed(rLabelParagraph->getProperties().getTextCharacterProperties());
    rLabelParagraph->getProperties().getTextCharacterProperties().assignUsed(aCharProps);
}
}

void DataLabelsConverter::convertFromModel( const Reference< XDataSeries >& rxDataSeries, const TypeGroupConverter& rTypeGroup )
{
    PropertySet aPropSet( rxDataSeries );
    if( !mrModel.mbDeleted )
    {
        bool bMSO2007Doc = getFilter().isMSO2007Document();
        bool bHasInternalData = getChartDocument()->hasInternalDataProvider();

        lclConvertLabelFormatting( aPropSet, getFormatter(), mrModel, rTypeGroup, true, false, bHasInternalData, bMSO2007Doc );

        if (mrModel.mxShapeProp)
        {
            // Import baseline border properties for these data labels.
            importBorderProperties(aPropSet, *mrModel.mxShapeProp, getFilter().getGraphicHelper());
            uno::Reference<lang::XMultiServiceFactory> xFactory(getChartDocument(), uno::UNO_QUERY);
            ModelObjectHelper& rHelper = getFilter().getModelObjectHelperForModel(xFactory);
            importFillProperties(aPropSet, *mrModel.mxShapeProp, getFilter().getGraphicHelper(),
                                 rHelper);
        }
    }
    // import leaderline of data labels
    if( !mrModel.mbShowLeaderLines )
        aPropSet.setProperty( PROP_ShowCustomLeaderLines, false );

    if (mrModel.mxLeaderLines)
    {
        // Import leaderline properties (SolidFill color, and width)
        lcl_ImportLeaderLineProperties(aPropSet, *mrModel.mxLeaderLines,
                                       getFilter().getGraphicHelper());
    }

    // data point label settings
    for (auto const& pointLabel : mrModel.maPointLabels)
    {
        if (pointLabel->maNumberFormat.maFormatCode.isEmpty())
            pointLabel->maNumberFormat = mrModel.maNumberFormat;
        InheritFromDataLabelsTextProps(mrModel, *pointLabel);

        DataLabelConverter aLabelConv(*this, *pointLabel);
        aLabelConv.convertFromModel( rxDataSeries, rTypeGroup );
    }
}

ErrorBarConverter::ErrorBarConverter( const ConverterRoot& rParent, ErrorBarModel& rModel ) :
    ConverterBase< ErrorBarModel >( rParent, rModel )
{
}

ErrorBarConverter::~ErrorBarConverter()
{
}

void ErrorBarConverter::convertFromModel( const Reference< XDataSeries >& rxDataSeries )
{
    bool bShowPos = (mrModel.mnTypeId == XML_plus) || (mrModel.mnTypeId == XML_both);
    bool bShowNeg = (mrModel.mnTypeId == XML_minus) || (mrModel.mnTypeId == XML_both);
    if( !(bShowPos || bShowNeg) )
        return;

    try
    {
        Reference< XPropertySet > xErrorBar( createInstance( u"com.sun.star.chart2.ErrorBar"_ustr ), UNO_QUERY_THROW );
        PropertySet aBarProp( xErrorBar );

        // plus/minus bars
        aBarProp.setProperty( PROP_ShowPositiveError, bShowPos );
        aBarProp.setProperty( PROP_ShowNegativeError, bShowNeg );

        // type of displayed error
        namespace cssc = css::chart;
        switch( mrModel.mnValueType )
        {
            case XML_cust:
            {
                // #i87806# manual error bars
                aBarProp.setProperty( PROP_ErrorBarStyle, cssc::ErrorBarStyle::FROM_DATA );
                // attach data sequences to error bar
                Reference< XDataSink > xDataSink( xErrorBar, UNO_QUERY );
                if( xDataSink.is() )
                {
                    // create vector of all value sequences
                    ::std::vector< Reference< XLabeledDataSequence > > aLabeledSeqVec;
                    // add positive values
                    if( bShowPos )
                    {
                        Reference< XLabeledDataSequence > xValueSeq = createLabeledDataSequence( ErrorBarModel::PLUS );
                        if( xValueSeq.is() )
                            aLabeledSeqVec.push_back( xValueSeq );
                    }
                    // add negative values
                    if( bShowNeg )
                    {
                        Reference< XLabeledDataSequence > xValueSeq = createLabeledDataSequence( ErrorBarModel::MINUS );
                        if( xValueSeq.is() )
                            aLabeledSeqVec.push_back( xValueSeq );
                    }
                    // attach labeled data sequences to series
                    if( aLabeledSeqVec.empty() )
                        xErrorBar.clear();
                    else
                        xDataSink->setData( comphelper::containerToSequence( aLabeledSeqVec ) );
                }
            }
            break;
            case XML_fixedVal:
                aBarProp.setProperty( PROP_ErrorBarStyle, cssc::ErrorBarStyle::ABSOLUTE );
                aBarProp.setProperty( PROP_PositiveError, mrModel.mfValue );
                aBarProp.setProperty( PROP_NegativeError, mrModel.mfValue );
            break;
            case XML_percentage:
                aBarProp.setProperty( PROP_ErrorBarStyle, cssc::ErrorBarStyle::RELATIVE );
                aBarProp.setProperty( PROP_PositiveError, mrModel.mfValue );
                aBarProp.setProperty( PROP_NegativeError, mrModel.mfValue );
            break;
            case XML_stdDev:
                aBarProp.setProperty( PROP_ErrorBarStyle, cssc::ErrorBarStyle::STANDARD_DEVIATION );
                aBarProp.setProperty( PROP_Weight, mrModel.mfValue );
            break;
            case XML_stdErr:
                aBarProp.setProperty( PROP_ErrorBarStyle, cssc::ErrorBarStyle::STANDARD_ERROR );
            break;
            default:
                OSL_FAIL( "ErrorBarConverter::convertFromModel - unknown error bar type" );
                xErrorBar.clear();
        }

        // error bar formatting
        getFormatter().convertFrameFormatting( aBarProp, mrModel.mxShapeProp, OBJECTTYPE_ERRORBAR );

        if( xErrorBar.is() )
        {
            PropertySet aSeriesProp( rxDataSeries );
            switch( mrModel.mnDirection )
            {
                case XML_x: aSeriesProp.setProperty( PROP_ErrorBarX, xErrorBar );   break;
                case XML_y: aSeriesProp.setProperty( PROP_ErrorBarY, xErrorBar );   break;
                default:    OSL_FAIL( "ErrorBarConverter::convertFromModel - invalid error bar direction" );
            }
        }
    }
    catch( Exception& )
    {
        OSL_FAIL( "ErrorBarConverter::convertFromModel - error while creating error bars" );
    }
}

Reference< XLabeledDataSequence > ErrorBarConverter::createLabeledDataSequence( ErrorBarModel::SourceType eSourceType )
{
    OUString aRole;
    switch( eSourceType )
    {
        case ErrorBarModel::PLUS:
            switch( mrModel.mnDirection )
            {
                case XML_x: aRole = "error-bars-x-positive"; break;
                case XML_y: aRole = "error-bars-y-positive"; break;
            }
        break;
        case ErrorBarModel::MINUS:
            switch( mrModel.mnDirection )
            {
                case XML_x: aRole = "error-bars-x-negative"; break;
                case XML_y: aRole = "error-bars-y-negative"; break;
            }
        break;
    }
    OSL_ENSURE( !aRole.isEmpty(), "ErrorBarConverter::createLabeledDataSequence - invalid error bar direction" );
    return lclCreateLabeledDataSequence( *this, mrModel.maSources.get( eSourceType ).get(), aRole );
}

TrendlineLabelConverter::TrendlineLabelConverter( const ConverterRoot& rParent, TrendlineLabelModel& rModel ) :
    ConverterBase< TrendlineLabelModel >( rParent, rModel )
{
}

TrendlineLabelConverter::~TrendlineLabelConverter()
{
}

void TrendlineLabelConverter::convertFromModel( PropertySet& rPropSet )
{
    // formatting
    getFormatter().convertFormatting( rPropSet, mrModel.mxShapeProp, mrModel.mxTextProp, OBJECTTYPE_TRENDLINELABEL );
}

TrendlineConverter::TrendlineConverter( const ConverterRoot& rParent, TrendlineModel& rModel ) :
    ConverterBase< TrendlineModel >( rParent, rModel )
{
}

TrendlineConverter::~TrendlineConverter()
{
}

void TrendlineConverter::convertFromModel( const Reference< XDataSeries >& rxDataSeries )
{
    try
    {
        // trend line type
        OUString aServiceName;
        switch( mrModel.mnTypeId )
        {
            case XML_exp:
                aServiceName = "com.sun.star.chart2.ExponentialRegressionCurve";
            break;
            case XML_linear:
                aServiceName = "com.sun.star.chart2.LinearRegressionCurve";
            break;
            case XML_log:
                aServiceName = "com.sun.star.chart2.LogarithmicRegressionCurve";
            break;
            case XML_movingAvg:
                aServiceName = "com.sun.star.chart2.MovingAverageRegressionCurve";
            break;
            case XML_poly:
                aServiceName = "com.sun.star.chart2.PolynomialRegressionCurve";
            break;
            case XML_power:
                aServiceName = "com.sun.star.chart2.PotentialRegressionCurve";
            break;
            default:
                OSL_FAIL( "TrendlineConverter::convertFromModel - unknown trendline type" );
        }
        if( !aServiceName.isEmpty() )
        {
            Reference< XRegressionCurve > xRegCurve( createInstance( aServiceName ), UNO_QUERY_THROW );
            PropertySet aPropSet( xRegCurve );

            // Name
            aPropSet.setProperty( PROP_CurveName, mrModel.maName );
            aPropSet.setProperty( PROP_PolynomialDegree, mrModel.mnOrder );
            aPropSet.setProperty( PROP_MovingAveragePeriod, mrModel.mnPeriod );

            // Intercept
            bool hasIntercept = mrModel.mfIntercept.has_value();
            aPropSet.setProperty( PROP_ForceIntercept, hasIntercept);
            if (hasIntercept)
                aPropSet.setProperty( PROP_InterceptValue,  mrModel.mfIntercept.value());

            // Extrapolation
            if (mrModel.mfForward.has_value())
                aPropSet.setProperty( PROP_ExtrapolateForward, mrModel.mfForward.value() );
            if (mrModel.mfBackward.has_value())
                aPropSet.setProperty( PROP_ExtrapolateBackward, mrModel.mfBackward.value() );

            // trendline formatting
            getFormatter().convertFrameFormatting( aPropSet, mrModel.mxShapeProp, OBJECTTYPE_TRENDLINE );

            // #i83100# show equation and correlation coefficient
            PropertySet aLabelProp( xRegCurve->getEquationProperties() );
            aLabelProp.setProperty( PROP_ShowEquation, mrModel.mbDispEquation );
            aLabelProp.setProperty( PROP_ShowCorrelationCoefficient, mrModel.mbDispRSquared );

            // #i83100# formatting of the equation text box
            if( mrModel.mbDispEquation || mrModel.mbDispRSquared )
            {
                TrendlineLabelConverter aLabelConv( *this, mrModel.mxLabel.getOrCreate() );
                aLabelConv.convertFromModel( aLabelProp );
            }

            // unsupported: #i5085# manual trendline size
            // unsupported: #i34093# manual crossing point

            Reference< XRegressionCurveContainer > xRegCurveCont( rxDataSeries, UNO_QUERY_THROW );
            xRegCurveCont->addRegressionCurve( xRegCurve );
        }
    }
    catch( Exception& )
    {
        OSL_FAIL( "TrendlineConverter::convertFromModel - error while creating trendline" );
    }
}

DataPointConverter::DataPointConverter( const ConverterRoot& rParent, DataPointModel& rModel ) :
    ConverterBase< DataPointModel >( rParent, rModel )
{
}

DataPointConverter::~DataPointConverter()
{
}

void DataPointConverter::convertFromModel( const Reference< XDataSeries >& rxDataSeries,
        const TypeGroupConverter& rTypeGroup, const SeriesModel& rSeries )
{
    bool bMSO2007Doc = getFilter().isMSO2007Document();
    try
    {
        PropertySet aPropSet( rxDataSeries->getDataPointByIndex( mrModel.mnIndex ) );

        // data point marker
        if( ( mrModel.monMarkerSymbol.has_value() && mrModel.monMarkerSymbol.value() != rSeries.mnMarkerSymbol ) ||
            ( mrModel.monMarkerSize.has_value() && mrModel.monMarkerSize.value() != rSeries.mnMarkerSize ) )
            rTypeGroup.convertMarker( aPropSet, mrModel.monMarkerSymbol.value_or( rSeries.mnMarkerSymbol ),
                    mrModel.monMarkerSize.value_or( rSeries.mnMarkerSize ), mrModel.mxMarkerProp );

        // data point pie explosion
        if( mrModel.monExplosion.has_value() && mrModel.monExplosion.value() != rSeries.mnExplosion )
            rTypeGroup.convertPieExplosion( aPropSet, mrModel.monExplosion.value() );

        // data point invert negative
        if( mrModel.mbInvertNeg != rSeries.mbInvertNeg ) {
            aPropSet.setProperty( PROP_InvertNegative, mrModel.mbInvertNeg);
        }

        // point formatting
        if( mrModel.mxShapeProp.is() )
        {
            if( rTypeGroup.getTypeInfo().mbPictureOptions )
                getFormatter().convertFrameFormatting( aPropSet, mrModel.mxShapeProp, mrModel.mxPicOptions.getOrCreate(bMSO2007Doc), rTypeGroup.getSeriesObjectType(), rSeries.mnIndex );
            else
                getFormatter().convertFrameFormatting( aPropSet, mrModel.mxShapeProp, rTypeGroup.getSeriesObjectType(), rSeries.mnIndex );
        }
        else if (rSeries.mxShapeProp.is())
        {
            getFormatter().convertFrameFormatting( aPropSet, rSeries.mxShapeProp, rTypeGroup.getSeriesObjectType(), rSeries.mnIndex );
        }
    }
    catch( Exception& )
    {
    }
}

SeriesConverter::SeriesConverter( const ConverterRoot& rParent, SeriesModel& rModel ) :
    ConverterBase< SeriesModel >( rParent, rModel )
{
}

SeriesConverter::~SeriesConverter()
{
}

Reference< XLabeledDataSequence > SeriesConverter::createCategorySequence( const OUString& rRole )
{
    return createLabeledDataSequence(DataSourceType::CATEGORIES, rRole, false);
}

Reference< XLabeledDataSequence > SeriesConverter::createValueSequence( const OUString& rRole )
{
    return createLabeledDataSequence( DataSourceType::VALUES, rRole, true );
}

Reference< XDataSeries > SeriesConverter::createDataSeries( const TypeGroupConverter& rTypeGroup, bool bVaryColorsByPoint )
{
    const TypeGroupInfo& rTypeInfo = rTypeGroup.getTypeInfo();

    // create the data series object
    Reference< XDataSeries > xDataSeries( createInstance( u"com.sun.star.chart2.DataSeries"_ustr ), UNO_QUERY );
    PropertySet aSeriesProp( xDataSeries );

    // attach data and title sequences to series
    sal_Int32 nDataPointCount = 0;
    Reference< XDataSink > xDataSink( xDataSeries, UNO_QUERY );
    if( xDataSink.is() )
    {
        // create vector of all value sequences
        ::std::vector< Reference< XLabeledDataSequence > > aLabeledSeqVec;
        // add Y values
        Reference< XLabeledDataSequence > xYValueSeq = createValueSequence( u"values-y"_ustr );
        if( xYValueSeq.is() )
        {
            aLabeledSeqVec.push_back( xYValueSeq );
            Reference< XDataSequence > xValues = xYValueSeq->getValues();
            if( xValues.is() )
                nDataPointCount = xValues->getData().getLength();

            if (!nDataPointCount)
                // No values present.  Don't create a data series.
                return Reference<XDataSeries>();
        }
        // add X values of scatter and bubble charts
        if( !rTypeInfo.mbCategoryAxis )
        {
            Reference< XLabeledDataSequence > xXValueSeq = createCategorySequence( u"values-x"_ustr );
            if( xXValueSeq.is() )
                aLabeledSeqVec.push_back( xXValueSeq );
            // add size values of bubble charts
            if( rTypeInfo.meTypeId == TYPEID_BUBBLE )
            {
                Reference< XLabeledDataSequence > xSizeValueSeq =
                    createLabeledDataSequence( DataSourceType::POINTS, u"values-size"_ustr, true );
                if( xSizeValueSeq.is() )
                    aLabeledSeqVec.push_back( xSizeValueSeq );
            }
        }
        // attach labeled data sequences to series
        if( !aLabeledSeqVec.empty() )
            xDataSink->setData( comphelper::containerToSequence( aLabeledSeqVec ) );
    }

    // error bars
    for (auto const& errorBar : mrModel.maErrorBars)
    {
        ErrorBarConverter aErrorBarConv(*this, *errorBar);
        aErrorBarConv.convertFromModel( xDataSeries );
    }

    // trendlines
    for (auto const& trendLine : mrModel.maTrendlines)
    {
        TrendlineConverter aTrendlineConv(*this, *trendLine);
        aTrendlineConv.convertFromModel( xDataSeries );
    }

    // data point markers
    rTypeGroup.convertMarker( aSeriesProp, mrModel.mnMarkerSymbol, mrModel.mnMarkerSize, mrModel.mxMarkerProp );
#if OOX_CHART_SMOOTHED_PER_SERIES
    // #i66858# smoothed series lines
    rTypeGroup.convertLineSmooth( aSeriesProp, mrModel.mbSmooth );
#endif
    // 3D bar style (not possible to set at chart type -> set at all series)
    rTypeGroup.convertBarGeometry( aSeriesProp, mrModel.monShape.value_or( rTypeGroup.getModel().mnShape ) );
    // pie explosion (restricted to [0%,100%] in Chart2)
    rTypeGroup.convertPieExplosion( aSeriesProp, mrModel.mnExplosion );
    // invert if negative
    aSeriesProp.setProperty(PROP_InvertNegative, mrModel.mbInvertNeg);

    // series formatting
    ObjectFormatter& rFormatter = getFormatter();
    ObjectType eObjType = rTypeGroup.getSeriesObjectType();
    bool bMSO2007Doc = getFilter().isMSO2007Document();
    if( rTypeInfo.mbPictureOptions )
        rFormatter.convertFrameFormatting( aSeriesProp, mrModel.mxShapeProp, mrModel.mxPicOptions.getOrCreate(bMSO2007Doc), eObjType, mrModel.mnIndex );
    else
        rFormatter.convertFrameFormatting( aSeriesProp, mrModel.mxShapeProp, eObjType, mrModel.mnIndex );

    // set the (unused) property default value used by the Chart2 templates (true for pie/doughnut charts)
    bool bIsPie = rTypeInfo.meTypeCategory == TYPECATEGORY_PIE;
    aSeriesProp.setProperty( PROP_VaryColorsByPoint, bVaryColorsByPoint );

    // own area formatting for every data point (TODO: varying line color not supported)
    // #i91271# always set area formatting for every point in pie/doughnut charts to override their automatic point formatting
    if( bIsPie || (bVaryColorsByPoint && rTypeGroup.isSeriesFrameFormat() && ObjectFormatter::isAutomaticFill( mrModel.mxShapeProp )) )
    {
        /*  Set the series point number as color cycle size at the object
            formatter to get correct start-shade/end-tint. TODO: in doughnut
            charts, the sizes of the series may vary, need to use the maximum
            point count of all series. */
        sal_Int32 nOldMax = rFormatter.getMaxSeriesIndex();
        if( bVaryColorsByPoint )
            rFormatter.setMaxSeriesIndex( nDataPointCount - 1 );
        for( sal_Int32 nIndex = 0; nIndex < nDataPointCount; ++nIndex )
        {
            try
            {
                PropertySet aPointProp( xDataSeries->getDataPointByIndex( nIndex ) );
                rFormatter.convertAutomaticFill( aPointProp, eObjType, bVaryColorsByPoint ? nIndex : mrModel.mnIndex );
            }
            catch( Exception& )
            {
            }
        }
        rFormatter.setMaxSeriesIndex( nOldMax );
    }

    // data point settings
    for (auto const& point : mrModel.maPoints)
    {
        DataPointConverter aPointConv(*this, *point);
        aPointConv.convertFromModel( xDataSeries, rTypeGroup, mrModel );
    }

    /*  Series data label settings. If and only if the series does not contain
        a c:dLbls element, then the c:dLbls element of the parent chart type is
        used (data label settings of the parent chart type are *not* merged
        into own existing data label settings). */
    ModelRef< DataLabelsModel > xLabels = mrModel.mxLabels.is() ? mrModel.mxLabels : rTypeGroup.getModel().mxLabels;
    if( xLabels.is() )
    {
        if( xLabels->maNumberFormat.maFormatCode.isEmpty() )
        {
            // Use number format code from Value series
            DataSourceModel* pValues = mrModel.maSources.get( DataSourceType::VALUES ).get();
            if( pValues )
                xLabels->maNumberFormat.maFormatCode = pValues->mxDataSeq->maFormatCode;
        }
        DataLabelsConverter aLabelsConv( *this, *xLabels );
        aLabelsConv.convertFromModel( xDataSeries, rTypeGroup );
    }

    return xDataSeries;
}

// private --------------------------------------------------------------------

Reference< XLabeledDataSequence > SeriesConverter::createLabeledDataSequence(
        enum DataSourceType eSourceType, const OUString& rRole, bool bUseTextLabel )
{
    DataSourceModel* pValues = mrModel.maSources.get( eSourceType ).get();
    TextModel* pTitle = bUseTextLabel ? mrModel.mxText.get() : nullptr;
    return lclCreateLabeledDataSequence( *this, pValues, rRole, pTitle );
}

} // namespace oox

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