/* -*- 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 <ChartModel.hxx>
#include <ChartTypeManager.hxx>
#include <ChartTypeTemplate.hxx>
#include <servicenames.hxx>
#include <DataSource.hxx>
#include <DataSourceHelper.hxx>
#include <ChartModelHelper.hxx>
#include <DisposeHelper.hxx>
#include <ControllerLockGuard.hxx>
#include <InternalDataProvider.hxx>
#include <ObjectIdentifier.hxx>
#include "PageBackground.hxx"
#include <CloneHelper.hxx>
#include <NameContainer.hxx>
#include "UndoManager.hxx"
#include <ChartView.hxx>
#include <PopupRequest.hxx>
#include <ModifyListenerHelper.hxx>
#include <RangeHighlighter.hxx>
#include <Diagram.hxx>
#include <comphelper/dumpxmltostring.hxx>

#include <com/sun/star/chart/ChartDataRowSource.hpp>
#include <com/sun/star/chart2/data/XPivotTableDataProvider.hpp>

#include <comphelper/processfactory.hxx>
#include <comphelper/propertysequence.hxx>
#include <cppuhelper/supportsservice.hxx>

#include <svl/numformat.hxx>
#include <svl/numuno.hxx>
#include <com/sun/star/lang/DisposedException.hpp>
#include <com/sun/star/view/XSelectionSupplier.hpp>
#include <com/sun/star/embed/EmbedMapUnits.hpp>
#include <com/sun/star/embed/Aspects.hpp>
#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
#include <com/sun/star/datatransfer/XTransferable.hpp>
#include <com/sun/star/drawing/XShapes.hpp>
#include <com/sun/star/document/DocumentProperties.hpp>
#include <com/sun/star/util/CloseVetoException.hpp>
#include <com/sun/star/util/XModifyBroadcaster.hpp>

#include <sal/log.hxx>
#include <utility>
#include <comphelper/diagnose_ex.hxx>
#include <libxml/xmlwriter.h>

using ::com::sun::star::uno::Sequence;
using ::com::sun::star::uno::Reference;
using ::com::sun::star::uno::Any;
using ::osl::MutexGuard;

using namespace ::com::sun::star;
using namespace ::apphelper;

namespace
{
constexpr OUString lcl_aGDIMetaFileMIMEType(
    u"application/x-openoffice-gdimetafile;windows_formatname=\"GDIMetaFile\""_ustr);
constexpr OUString lcl_aGDIMetaFileMIMETypeHighContrast(
    u"application/x-openoffice-highcontrast-gdimetafile;windows_formatname=\"GDIMetaFile\""_ustr);

} // anonymous namespace

// ChartModel Constructor and Destructor

namespace chart
{

ChartModel::ChartModel(uno::Reference<uno::XComponentContext > xContext)
    : m_aLifeTimeManager( this, this )
    , m_bReadOnly( false )
    , m_bModified( false )
    , m_nInLoad(0)
    , m_bUpdateNotificationsPending(false)
    , mbTimeBased(false)
    , m_aControllers( m_aModelMutex )
    , m_nControllerLockCount(0)
    , m_xContext(std::move( xContext ))
    , m_aVisualAreaSize( ChartModelHelper::getDefaultPageSize() )
    , m_xPageBackground( new PageBackground )
    , m_xXMLNamespaceMap( new NameContainer() )
    , mnStart(0)
    , mnEnd(0)
{
    osl_atomic_increment(&m_refCount);
    {
        m_xOldModelAgg.set(
            m_xContext->getServiceManager()->createInstanceWithContext(
            CHART_CHARTAPIWRAPPER_SERVICE_NAME,
            m_xContext ), uno::UNO_QUERY_THROW );
        m_xOldModelAgg->setDelegator( *this );
    }

    {
        m_xPageBackground->addModifyListener( this );
        m_xChartTypeManager = new ::chart::ChartTypeManager( m_xContext );
    }
    osl_atomic_decrement(&m_refCount);
}

ChartModel::ChartModel( const ChartModel & rOther )
    : impl::ChartModel_Base(rOther)
    , m_aLifeTimeManager( this, this )
    , m_bReadOnly( rOther.m_bReadOnly )
    , m_bModified( rOther.m_bModified )
    , m_nInLoad(0)
    , m_bUpdateNotificationsPending(false)
    , mbTimeBased(rOther.mbTimeBased)
    , m_aResource( rOther.m_aResource )
    , m_aMediaDescriptor( rOther.m_aMediaDescriptor )
    , m_aControllers( m_aModelMutex )
    , m_nControllerLockCount(0)
    , m_xContext( rOther.m_xContext )
    // @note: the old model aggregate must not be shared with other models if it
    // is, you get mutex deadlocks
    //, m_xOldModelAgg( nullptr ) //rOther.m_xOldModelAgg )
    // m_xStorage( nullptr ) //rOther.m_xStorage )
    , m_aVisualAreaSize( rOther.m_aVisualAreaSize )
    , m_aGraphicObjectVector( rOther.m_aGraphicObjectVector )
    , m_xDataProvider( rOther.m_xDataProvider )
    , m_xInternalDataProvider( rOther.m_xInternalDataProvider )
    , mnStart(rOther.mnStart)
    , mnEnd(rOther.mnEnd)
{
    osl_atomic_increment(&m_refCount);
    {
        m_xOldModelAgg.set(
            m_xContext->getServiceManager()->createInstanceWithContext(
            CHART_CHARTAPIWRAPPER_SERVICE_NAME,
            m_xContext ), uno::UNO_QUERY_THROW );
        m_xOldModelAgg->setDelegator( *this );

        Reference< util::XModifyListener > xListener;
        rtl::Reference< Title > xNewTitle;
        if ( rOther.m_xTitle )
            xNewTitle = new Title(*rOther.m_xTitle);
        rtl::Reference< ::chart::Diagram > xNewDiagram;
        if (rOther.m_xDiagram.is())
            xNewDiagram = new ::chart::Diagram( *rOther.m_xDiagram );
        rtl::Reference< ::chart::PageBackground > xNewPageBackground = new PageBackground( *rOther.m_xPageBackground );

        {
            rtl::Reference< ::chart::ChartTypeManager > xChartTypeManager; // does not implement XCloneable
            rtl::Reference< ::chart::NameContainer > xXMLNamespaceMap = new NameContainer( *rOther.m_xXMLNamespaceMap );

            {
                MutexGuard aGuard( m_aModelMutex );
                xListener = this;
                m_xTitle = xNewTitle;
                m_xDiagram = xNewDiagram;
                m_xPageBackground = xNewPageBackground;
                m_xChartTypeManager = std::move(xChartTypeManager);
                m_xXMLNamespaceMap = std::move(xXMLNamespaceMap);
            }
        }

        ModifyListenerHelper::addListener( xNewTitle, xListener );
        if( xNewDiagram && xListener)
            xNewDiagram->addModifyListener( xListener );
        if( xNewPageBackground && xListener)
            xNewPageBackground->addModifyListener( xListener );
        xListener.clear();
    }
    osl_atomic_decrement(&m_refCount);
}

ChartModel::~ChartModel()
{
    if( m_xOldModelAgg.is())
        m_xOldModelAgg->setDelegator( nullptr );
}

void SAL_CALL ChartModel::initialize( const Sequence< Any >& /*rArguments*/ )
{
    //#i113722# avoid duplicate creation

    //maybe additional todo?:
    //support argument "EmbeddedObject"?
    //support argument "EmbeddedScriptSupport"?
    //support argument "DocumentRecoverySupport"?
}

ChartView* ChartModel::getChartView() const
{
    return mxChartView.get();
}

// private methods

OUString ChartModel::impl_g_getLocation()
{

    LifeTimeGuard aGuard(m_aLifeTimeManager);
    if(!aGuard.startApiCall())
        return OUString(); //behave passive if already disposed or closed or throw exception @todo?
    //mutex is acquired
    return m_aResource;
}

bool ChartModel::impl_isControllerConnected( const uno::Reference< frame::XController >& xController )
{
    try
    {
        std::vector< uno::Reference<uno::XInterface> > aSeq = m_aControllers.getElements();
        for( const auto & r : aSeq )
        {
            if( r == xController )
                return true;
        }
    }
    catch (const uno::Exception&)
    {
    }
    return false;
}

uno::Reference< frame::XController > ChartModel::impl_getCurrentController()
{
        //@todo? hold only weak references to controllers

    // get the last active controller of this model
    if( m_xCurrentController.is() )
        return m_xCurrentController;

    // get the first controller of this model
    if( m_aControllers.getLength() )
    {
        uno::Reference<uno::XInterface> xI = m_aControllers.getInterface(0);
        return uno::Reference<frame::XController>( xI, uno::UNO_QUERY );
    }

    //return nothing if no controllers are connected at all
    return uno::Reference< frame::XController > ();
}

void ChartModel::impl_notifyCloseListeners()
{
    std::unique_lock aGuard(m_aLifeTimeManager.m_aAccessMutex);
    if( m_aLifeTimeManager.m_aCloseListeners.getLength(aGuard) )
    {
        lang::EventObject aEvent( static_cast< lang::XComponent*>(this) );
        m_aLifeTimeManager.m_aCloseListeners.notifyEach(aGuard, &util::XCloseListener::notifyClosing, aEvent);
    }
}

void ChartModel::impl_adjustAdditionalShapesPositionAndSize( const awt::Size& aVisualAreaSize )
{
    uno::Reference< beans::XPropertySet > xProperties( static_cast< ::cppu::OWeakObject* >( this ), uno::UNO_QUERY );
    if ( !xProperties.is() )
        return;

    uno::Reference< drawing::XShapes > xShapes;
    xProperties->getPropertyValue( u"AdditionalShapes"_ustr ) >>= xShapes;
    if ( !xShapes.is() )
        return;

    sal_Int32 nCount = xShapes->getCount();
    for ( sal_Int32 i = 0; i < nCount; ++i )
    {
        Reference< drawing::XShape > xShape;
        if ( xShapes->getByIndex( i ) >>= xShape )
        {
            if ( xShape.is() )
            {
                awt::Point aPos( xShape->getPosition() );
                awt::Size aSize( xShape->getSize() );

                double fWidth = static_cast< double >( aVisualAreaSize.Width ) / m_aVisualAreaSize.Width;
                double fHeight = static_cast< double >( aVisualAreaSize.Height ) / m_aVisualAreaSize.Height;

                aPos.X = static_cast< tools::Long >( aPos.X * fWidth );
                aPos.Y = static_cast< tools::Long >( aPos.Y * fHeight );
                aSize.Width = static_cast< tools::Long >( aSize.Width * fWidth );
                aSize.Height = static_cast< tools::Long >( aSize.Height * fHeight );

                xShape->setPosition( aPos );
                xShape->setSize( aSize );
            }
        }
    }
}

// lang::XServiceInfo

OUString SAL_CALL ChartModel::getImplementationName()
{
    return CHART_MODEL_SERVICE_IMPLEMENTATION_NAME;
}

sal_Bool SAL_CALL ChartModel::supportsService( const OUString& rServiceName )
{
    return cppu::supportsService(this, rServiceName);
}

css::uno::Sequence< OUString > SAL_CALL ChartModel::getSupportedServiceNames()
{
    return {
        CHART_MODEL_SERVICE_NAME,
        u"com.sun.star.document.OfficeDocument"_ustr,
        u"com.sun.star.chart.ChartDocument"_ustr
    };
}

// frame::XModel (required interface)

sal_Bool SAL_CALL ChartModel::attachResource( const OUString& rURL
        , const uno::Sequence< beans::PropertyValue >& rMediaDescriptor )
{
    /*
    The method attachResource() is used by the frame loader implementations
    to inform the model about its URL and MediaDescriptor.
    */

    LifeTimeGuard aGuard(m_aLifeTimeManager);
    if(!aGuard.startApiCall())
        return false; //behave passive if already disposed or closed or throw exception @todo?
    //mutex is acquired

    if(!m_aResource.isEmpty())//we have a resource already //@todo? or is setting a new resource allowed?
        return false;
    m_aResource = rURL;
    m_aMediaDescriptor = rMediaDescriptor;

    //@todo ? check rURL ??
    //@todo ? evaluate m_aMediaDescriptor;
    //@todo ? ... ??? --> nothing, this method is only for setting information

    return true;
}

OUString SAL_CALL ChartModel::getURL()
{
    return impl_g_getLocation();
}

uno::Sequence< beans::PropertyValue > SAL_CALL ChartModel::getArgs()
{
    /*
    The method getArgs() returns a sequence of property values
    that report the resource description according to com.sun.star.document.MediaDescriptor,
    specified on loading or saving with storeAsURL.
    */

    LifeTimeGuard aGuard(m_aLifeTimeManager);
    if(!aGuard.startApiCall())
        return uno::Sequence< beans::PropertyValue >(); //behave passive if already disposed or closed or throw exception @todo?
    //mutex is acquired

    return m_aMediaDescriptor;
}

void SAL_CALL ChartModel::connectController( const uno::Reference< frame::XController >& xController )
{
    //@todo? this method is declared as oneway -> ...?

    LifeTimeGuard aGuard(m_aLifeTimeManager);
    if(!aGuard.startApiCall())
        return ; //behave passive if already disposed or closed
    //mutex is acquired

    //--add controller
    m_aControllers.addInterface(xController);
}

void SAL_CALL ChartModel::disconnectController( const uno::Reference< frame::XController >& xController )
{
    //@todo? this method is declared as oneway -> ...?

    LifeTimeGuard aGuard(m_aLifeTimeManager);
    if(!aGuard.startApiCall())
        return; //behave passive if already disposed or closed

    //--remove controller
    m_aControllers.removeInterface(xController);

    //case: current controller is disconnected:
    if( m_xCurrentController == xController )
        m_xCurrentController.clear();

    if (m_xRangeHighlighter)
    {
        m_xRangeHighlighter->dispose();
        m_xRangeHighlighter.clear();
    }
    DisposeHelper::DisposeAndClear(m_xPopupRequest);
}

void SAL_CALL ChartModel::lockControllers()
{
    /*
    suspends some notifications to the controllers which are used for display updates.

    The calls to lockControllers() and unlockControllers() may be nested
    and even overlapping, but they must be in pairs. While there is at least one lock
    remaining, some notifications for display updates are not broadcasted.
    */

    //@todo? this method is declared as oneway -> ...?

    LifeTimeGuard aGuard(m_aLifeTimeManager);
    if(!aGuard.startApiCall())
        return; //behave passive if already disposed or closed or throw exception @todo?
    ++m_nControllerLockCount;
}

void SAL_CALL ChartModel::unlockControllers()
{
    /*
    resumes the notifications which were suspended by lockControllers() .

    The calls to lockControllers() and unlockControllers() may be nested
    and even overlapping, but they must be in pairs. While there is at least one lock
    remaining, some notifications for display updates are not broadcasted.
    */

    //@todo? this method is declared as oneway -> ...?

    LifeTimeGuard aGuard(m_aLifeTimeManager);
    if(!aGuard.startApiCall())
        return; //behave passive if already disposed or closed or throw exception @todo?
    if( m_nControllerLockCount == 0 )
    {
        SAL_WARN("chart2",  "ChartModel: unlockControllers called with m_nControllerLockCount == 0" );
        return;
    }
    --m_nControllerLockCount;
    if( m_nControllerLockCount == 0 && m_bUpdateNotificationsPending  )
    {
        aGuard.clear();
        impl_notifyModifiedListeners();
    }
}

sal_Bool SAL_CALL ChartModel::hasControllersLocked()
{
    LifeTimeGuard aGuard(m_aLifeTimeManager);
    if(!aGuard.startApiCall())
        return false; //behave passive if already disposed or closed or throw exception @todo?
    return ( m_nControllerLockCount != 0 ) ;
}

uno::Reference< frame::XController > SAL_CALL ChartModel::getCurrentController()
{
    LifeTimeGuard aGuard(m_aLifeTimeManager);
    if(!aGuard.startApiCall())
        throw lang::DisposedException(
                u"getCurrentController was called on an already disposed or closed model"_ustr,
                static_cast< ::cppu::OWeakObject* >(this) );

    return impl_getCurrentController();
}

void SAL_CALL ChartModel::setCurrentController( const uno::Reference< frame::XController >& xController )
{
    LifeTimeGuard aGuard(m_aLifeTimeManager);
    if(!aGuard.startApiCall())
        throw lang::DisposedException(
                u"setCurrentController was called on an already disposed or closed model"_ustr,
                static_cast< ::cppu::OWeakObject* >(this) );

    //OSL_ENSURE( impl_isControllerConnected(xController), "setCurrentController is called with a Controller which is not connected" );
    if(!impl_isControllerConnected(xController))
        throw container::NoSuchElementException(
                u"setCurrentController is called with a Controller which is not connected"_ustr,
                static_cast< ::cppu::OWeakObject* >(this) );

    m_xCurrentController = xController;

    if (m_xRangeHighlighter)
    {
        m_xRangeHighlighter->dispose();
        m_xRangeHighlighter.clear();
    }
    DisposeHelper::DisposeAndClear(m_xPopupRequest);
}

uno::Reference< uno::XInterface > SAL_CALL ChartModel::getCurrentSelection()
{
    LifeTimeGuard aGuard(m_aLifeTimeManager);
    if(!aGuard.startApiCall())
        throw lang::DisposedException(
                u"getCurrentSelection was called on an already disposed or closed model"_ustr,
                static_cast< ::cppu::OWeakObject* >(this) );

    uno::Reference< uno::XInterface > xReturn;
    uno::Reference< frame::XController > xController = impl_getCurrentController();

    aGuard.clear();
    if( xController.is() )
    {
        uno::Reference< view::XSelectionSupplier >  xSelectionSupl( xController, uno::UNO_QUERY );
        if ( xSelectionSupl.is() )
        {
            uno::Any aSel = xSelectionSupl->getSelection();
            OUString aObjectCID;
            if( aSel >>= aObjectCID )
                xReturn.set( ObjectIdentifier::getObjectPropertySet( aObjectCID, this));
        }
    }
    return xReturn;
}

// lang::XComponent (base of XModel)
void SAL_CALL ChartModel::dispose()
{
    Reference< XInterface > xKeepAlive( *this );

    //This object should release all resources and references in the
    //easiest possible manner
    //This object must notify all registered listeners using the method
    //<member>XEventListener::disposing</member>

    //hold no mutex
    if( !m_aLifeTimeManager.dispose() )
        return;

    //--release all resources and references
    //// @todo

    if ( m_xDiagram.is() )
        m_xDiagram->removeModifyListener( this );

    if ( m_xDataProvider.is() )
    {
        Reference<util::XModifyBroadcaster> xModifyBroadcaster( m_xDataProvider, uno::UNO_QUERY );
        if ( xModifyBroadcaster.is() )
            xModifyBroadcaster->removeModifyListener( this );
    }

    m_xDataProvider.clear();
    m_xInternalDataProvider.clear();
    m_xNumberFormatsSupplier.clear();
    m_xOwnNumberFormatsSupplier.clear();
    m_xChartTypeManager.clear();
    m_xDiagram.clear();
    m_xTitle.clear();
    m_xPageBackground.clear();
    m_xXMLNamespaceMap.clear();

    m_xStorage.clear();
        // just clear, don't dispose - we're not the owner

    if ( m_pUndoManager.is() )
        m_pUndoManager->disposing();
    m_pUndoManager.clear();
        // that's important, since the UndoManager implementation delegates its ref counting to ourself.

    if( m_xOldModelAgg.is())  // #i120828#, to release cyclic reference to ChartModel object
        m_xOldModelAgg->setDelegator( nullptr );

    m_aControllers.disposeAndClear( lang::EventObject( static_cast< cppu::OWeakObject * >( this )));
    m_xCurrentController.clear();

    if (m_xRangeHighlighter)
    {
        m_xRangeHighlighter->dispose();
        m_xRangeHighlighter.clear();
    }
    DisposeHelper::DisposeAndClear(m_xPopupRequest);

    if( m_xOldModelAgg.is())
        m_xOldModelAgg->setDelegator( nullptr );
}

void SAL_CALL ChartModel::addEventListener( const uno::Reference< lang::XEventListener > & xListener )
{
    if( m_aLifeTimeManager.impl_isDisposedOrClosed() )
        return; //behave passive if already disposed or closed

    std::unique_lock aGuard(m_aLifeTimeManager.m_aAccessMutex);
    m_aLifeTimeManager.m_aEventListeners.addInterface( aGuard, xListener );
}

void SAL_CALL ChartModel::removeEventListener( const uno::Reference< lang::XEventListener > & xListener )
{
    if( m_aLifeTimeManager.impl_isDisposedOrClosed(false) )
        return; //behave passive if already disposed or closed

    std::unique_lock aGuard(m_aLifeTimeManager.m_aAccessMutex);
    m_aLifeTimeManager.m_aEventListeners.removeInterface( aGuard, xListener );
}

// util::XCloseBroadcaster (base of XCloseable)
void SAL_CALL ChartModel::addCloseListener( const uno::Reference<   util::XCloseListener > & xListener )
{
    m_aLifeTimeManager.g_addCloseListener( xListener );
}

void SAL_CALL ChartModel::removeCloseListener( const uno::Reference< util::XCloseListener > & xListener )
{
    if( m_aLifeTimeManager.impl_isDisposedOrClosed(false) )
        return; //behave passive if already disposed or closed

    std::unique_lock aGuard(m_aLifeTimeManager.m_aAccessMutex);
    m_aLifeTimeManager.m_aCloseListeners.removeInterface( aGuard, xListener );
}

// util::XCloseable
void SAL_CALL ChartModel::close( sal_Bool bDeliverOwnership )
{
    //hold no mutex

    if( !m_aLifeTimeManager.g_close_startTryClose( bDeliverOwnership ) )
        return;
    //no mutex is acquired

    // At the end of this method may we must dispose ourself ...
    // and may nobody from outside hold a reference to us ...
    // then it's a good idea to do that by ourself.
    uno::Reference< uno::XInterface > xSelfHold( static_cast< ::cppu::OWeakObject* >(this) );

    //the listeners have had no veto
    //check whether we self can close
    {
        util::CloseVetoException aVetoException(
                        u"the model itself could not be closed"_ustr,
                        static_cast< ::cppu::OWeakObject* >(this) );

        m_aLifeTimeManager.g_close_isNeedToCancelLongLastingCalls( bDeliverOwnership, aVetoException );
    }
    m_aLifeTimeManager.g_close_endTryClose_doClose();

    // BM @todo: is it ok to call the listeners here?
    impl_notifyCloseListeners();
}

// lang::XTypeProvider
uno::Sequence< uno::Type > SAL_CALL ChartModel::getTypes()
{
    uno::Reference< lang::XTypeProvider > xAggTypeProvider;
    if( (m_xOldModelAgg->queryAggregation( cppu::UnoType<decltype(xAggTypeProvider)>::get()) >>= xAggTypeProvider)
        && xAggTypeProvider.is())
    {
        return comphelper::concatSequences(
            impl::ChartModel_Base::getTypes(),
            xAggTypeProvider->getTypes());
    }

    return impl::ChartModel_Base::getTypes();
}

// document::XDocumentPropertiesSupplier
uno::Reference< document::XDocumentProperties > SAL_CALL
        ChartModel::getDocumentProperties()
{
    ::osl::MutexGuard aGuard( m_aModelMutex );
    if ( !m_xDocumentProperties.is() )
    {
        m_xDocumentProperties.set( document::DocumentProperties::create( ::comphelper::getProcessComponentContext() ) );
    }
    return m_xDocumentProperties;
}

// document::XDocumentPropertiesSupplier
Reference< document::XUndoManager > SAL_CALL ChartModel::getUndoManager(  )
{
    ::osl::MutexGuard aGuard( m_aModelMutex );
    if ( !m_pUndoManager.is() )
        m_pUndoManager.set( new UndoManager( *this, m_aModelMutex ) );
    return m_pUndoManager;
}

// chart2::XChartDocument

uno::Reference< chart2::XDiagram > SAL_CALL ChartModel::getFirstDiagram()
{
    MutexGuard aGuard( m_aModelMutex );
    return m_xDiagram;
}

void SAL_CALL ChartModel::setFirstDiagram( const uno::Reference< chart2::XDiagram >& xDiagram )
{
    rtl::Reference< ::chart::Diagram > xOldDiagram;
    Reference< util::XModifyListener > xListener;
    {
        MutexGuard aGuard( m_aModelMutex );
        if( xDiagram.get() == m_xDiagram.get() )
            return;
        xOldDiagram = m_xDiagram;
        assert(!xDiagram || dynamic_cast<::chart::Diagram*>(xDiagram.get()));
        m_xDiagram = dynamic_cast<::chart::Diagram*>(xDiagram.get());
        xListener = this;
    }
    //don't keep the mutex locked while calling out
    if( xOldDiagram && xListener )
        xOldDiagram->removeModifyListener( xListener );
    ModifyListenerHelper::addListener( xDiagram, xListener );
    setModified( true );
}

Reference< chart2::data::XDataSource > ChartModel::impl_createDefaultData()
{
    Reference< chart2::data::XDataSource > xDataSource;
    if( hasInternalDataProvider() )
    {
        //init internal dataprovider
        {
            beans::NamedValue aParam( u"CreateDefaultData"_ustr ,uno::Any(true) );
            uno::Sequence< uno::Any > aArgs{ uno::Any(aParam) };
            m_xInternalDataProvider->initialize(aArgs);
        }
        //create data
        uno::Sequence<beans::PropertyValue> aArgs( comphelper::InitPropertySequence({
            { "CellRangeRepresentation", uno::Any( u"all"_ustr ) },
            { "HasCategories", uno::Any( true ) },
            { "FirstCellAsLabel", uno::Any( true ) },
            { "DataRowSource", uno::Any( css::chart::ChartDataRowSource_COLUMNS ) }
            }));
        xDataSource = m_xInternalDataProvider->createDataSource( aArgs );
    }
    return xDataSource;
}

void SAL_CALL ChartModel::createInternalDataProvider( sal_Bool bCloneExistingData )
{
    // don't lock the mutex, because this call calls out to code that tries to
    // lock the solar mutex. On the other hand, a paint locks the solar mutex
    // and calls to the model lock the model's mutex => deadlock
    // @todo: lock a separate mutex in the InternalData class
    if( !hasInternalDataProvider() )
    {
        if( bCloneExistingData )
            m_xInternalDataProvider = ChartModelHelper::createInternalDataProvider( this, true );
        else
        {
            m_xInternalDataProvider = ChartModelHelper::createInternalDataProvider( nullptr, true );
            m_xInternalDataProvider->setChartModel(this);
        }
        m_xDataProvider.set( m_xInternalDataProvider );
    }
    setModified( true );
}

void ChartModel::removeDataProviders()
{
    if (m_xInternalDataProvider.is())
        m_xInternalDataProvider.clear();
    if (m_xDataProvider.is())
        m_xDataProvider.clear();
}

void ChartModel::dumpAsXml(xmlTextWriterPtr pWriter) const
{
    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("ChartModel"));
    (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);

    if (mxChartView.is())
    {
        mxChartView->dumpAsXml(pWriter);
    }

    (void)xmlTextWriterEndElement(pWriter);
}

sal_Bool SAL_CALL ChartModel::hasInternalDataProvider()
{
    return m_xDataProvider.is() && m_xInternalDataProvider.is();
}

uno::Reference< chart2::data::XDataProvider > SAL_CALL ChartModel::getDataProvider()
{
    MutexGuard aGuard( m_aModelMutex );
    return m_xDataProvider;
}

// ____ XDataReceiver ____

void SAL_CALL ChartModel::attachDataProvider( const uno::Reference< chart2::data::XDataProvider >& xDataProvider )
{
    {
        MutexGuard aGuard( m_aModelMutex );
        uno::Reference< beans::XPropertySet > xProp( xDataProvider, uno::UNO_QUERY );
        if( xProp.is() )
        {
            try
            {
                bool bIncludeHiddenCells = ChartModelHelper::isIncludeHiddenCells( this );
                xProp->setPropertyValue(u"IncludeHiddenCells"_ustr, uno::Any(bIncludeHiddenCells));
            }
            catch (const beans::UnknownPropertyException&)
            {
            }
        }

        uno::Reference<util::XModifyBroadcaster> xModifyBroadcaster(xDataProvider, uno::UNO_QUERY);
        if (xModifyBroadcaster.is())
        {
            xModifyBroadcaster->addModifyListener(this);
        }

        m_xDataProvider.set( xDataProvider );
        m_xInternalDataProvider.clear();

        //the numberformatter is kept independent of the data provider!
    }
    setModified( true );
}

void SAL_CALL ChartModel::attachNumberFormatsSupplier( const uno::Reference< util::XNumberFormatsSupplier >& xNewSupplier )
{
    {
        MutexGuard aGuard( m_aModelMutex );
        if( xNewSupplier == m_xNumberFormatsSupplier )
            return;
        if( xNewSupplier == uno::Reference<XNumberFormatsSupplier>(m_xOwnNumberFormatsSupplier) )
            return;
        if( m_xOwnNumberFormatsSupplier.is() && xNewSupplier.is() )
        {
            //@todo
            //merge missing numberformats from own to new formatter
        }
        else if( !xNewSupplier.is() )
        {
            if( m_xNumberFormatsSupplier.is() )
            {
                //@todo
                //merge missing numberformats from old numberformatter to own numberformatter
                //create own numberformatter if necessary
            }
        }

        m_xNumberFormatsSupplier.set( xNewSupplier );
        m_xOwnNumberFormatsSupplier.clear();
    }
    setModified( true );
}

void SAL_CALL ChartModel::setArguments( const Sequence< beans::PropertyValue >& aArguments )
{
    {
        MutexGuard aGuard( m_aModelMutex );
        if( !m_xDataProvider.is() )
            return;
        lockControllers();

        try
        {
            Reference< chart2::data::XDataSource > xDataSource( m_xDataProvider->createDataSource( aArguments ) );
            if( xDataSource.is() )
            {
                rtl::Reference< Diagram > xDia = getFirstChartDiagram();
                if( !xDia.is() )
                {
                    rtl::Reference< ::chart::ChartTypeTemplate > xTemplate( impl_createDefaultChartTypeTemplate() );
                    if( xTemplate.is())
                        setFirstDiagram( xTemplate->createDiagramByDataSource( xDataSource, aArguments ) );
                }
                else
                    xDia->setDiagramData( xDataSource, aArguments );
            }
        }
        catch (const lang::IllegalArgumentException&)
        {
            throw;
        }
        catch (const uno::Exception&)
        {
            DBG_UNHANDLED_EXCEPTION("chart2");
        }
        unlockControllers();
    }
    setModified( true );
}

Sequence< OUString > SAL_CALL ChartModel::getUsedRangeRepresentations()
{
    return DataSourceHelper::getUsedDataRanges( this );
}

Reference< chart2::data::XDataSource > SAL_CALL ChartModel::getUsedData()
{
    return DataSourceHelper::getUsedData( *this );
}

Reference< chart2::data::XRangeHighlighter > SAL_CALL ChartModel::getRangeHighlighter()
{
    if( ! m_xRangeHighlighter.is())
        m_xRangeHighlighter = new RangeHighlighter( this );
    return m_xRangeHighlighter;
}

Reference<awt::XRequestCallback> SAL_CALL ChartModel::getPopupRequest()
{
    if (!m_xPopupRequest.is())
        m_xPopupRequest.set(new PopupRequest);
    return m_xPopupRequest;
}

rtl::Reference< ::chart::ChartTypeTemplate > ChartModel::impl_createDefaultChartTypeTemplate()
{
    rtl::Reference< ::chart::ChartTypeTemplate > xTemplate;
    if( m_xChartTypeManager.is() )
        xTemplate = m_xChartTypeManager->createTemplate( u"com.sun.star.chart2.template.Column"_ustr );
    return xTemplate;
}

void SAL_CALL ChartModel::setChartTypeManager( const uno::Reference< chart2::XChartTypeManager >& xNewManager )
{
    {
        MutexGuard aGuard( m_aModelMutex );
        m_xChartTypeManager = dynamic_cast<::chart::ChartTypeManager*>(xNewManager.get());
        assert(!xNewManager || m_xChartTypeManager);
    }
    setModified( true );
}

uno::Reference< chart2::XChartTypeManager > SAL_CALL ChartModel::getChartTypeManager()
{
    MutexGuard aGuard( m_aModelMutex );
    return m_xChartTypeManager;
}

uno::Reference< beans::XPropertySet > SAL_CALL ChartModel::getPageBackground()
{
    MutexGuard aGuard( m_aModelMutex );
    return m_xPageBackground;
}

void SAL_CALL ChartModel::createDefaultChart()
{
    insertDefaultChart();
}

// ____ XTitled ____
uno::Reference< chart2::XTitle > SAL_CALL ChartModel::getTitleObject()
{
    MutexGuard aGuard( m_aModelMutex );
    return m_xTitle;
}

rtl::Reference< Title > ChartModel::getTitleObject2() const
{
    MutexGuard aGuard( m_aModelMutex );
    return m_xTitle;
}

void SAL_CALL ChartModel::setTitleObject( const uno::Reference< chart2::XTitle >& xNewTitle )
{
    rtl::Reference<Title> xTitle = dynamic_cast<Title*>(xNewTitle.get());
    assert(!xNewTitle || xTitle);
    setTitleObject(xTitle);
}

void ChartModel::setTitleObject( const rtl::Reference< Title >& xTitle )
{
    {
        MutexGuard aGuard( m_aModelMutex );
        if( m_xTitle.is() )
            ModifyListenerHelper::removeListener( m_xTitle, this );
        m_xTitle = xTitle;
        ModifyListenerHelper::addListener( m_xTitle, this );
    }
    setModified( true );
}

// ____ XInterface (for old API wrapper) ____
uno::Any SAL_CALL ChartModel::queryInterface( const uno::Type& aType )
{
    uno::Any aResult( impl::ChartModel_Base::queryInterface( aType ));

    if( ! aResult.hasValue())
    {
        // try old API wrapper
        try
        {
            if( m_xOldModelAgg.is())
                aResult = m_xOldModelAgg->queryAggregation( aType );
        }
        catch (const uno::Exception&)
        {
            DBG_UNHANDLED_EXCEPTION("chart2");
        }
    }

    return aResult;
}

// ____ XCloneable ____
Reference< util::XCloneable > SAL_CALL ChartModel::createClone()
{
    std::unique_lock aGuard(m_aLifeTimeManager.m_aAccessMutex);
    return Reference< util::XCloneable >( new ChartModel( *this ));
}

// ____ XVisualObject ____
void SAL_CALL ChartModel::setVisualAreaSize( ::sal_Int64 nAspect, const awt::Size& aSize )
{
    if( nAspect == embed::Aspects::MSOLE_CONTENT )
    {
        ControllerLockGuard aLockGuard( *this );
        bool bChanged =
            (m_aVisualAreaSize.Width != aSize.Width ||
             m_aVisualAreaSize.Height != aSize.Height);

        // #i12587# support for shapes in chart
        if ( bChanged )
        {
            impl_adjustAdditionalShapesPositionAndSize( aSize );
        }

        m_aVisualAreaSize = aSize;
        if( bChanged )
            setModified( true );
    }
    else
    {
        OSL_FAIL( "setVisualAreaSize: Aspect not implemented yet.");
    }
}

awt::Size SAL_CALL ChartModel::getVisualAreaSize( ::sal_Int64 nAspect )
{
    OSL_ENSURE( nAspect == embed::Aspects::MSOLE_CONTENT,
                "No aspects other than content are supported" );
    // other possible aspects are MSOLE_THUMBNAIL, MSOLE_ICON and MSOLE_DOCPRINT

    return m_aVisualAreaSize;
}

embed::VisualRepresentation SAL_CALL ChartModel::getPreferredVisualRepresentation( ::sal_Int64 nAspect )
{
    OSL_ENSURE( nAspect == embed::Aspects::MSOLE_CONTENT,
                "No aspects other than content are supported" );

    embed::VisualRepresentation aResult;

    try
    {
        Sequence< sal_Int8 > aMetafile;

        //get view from old api wrapper
        Reference< datatransfer::XTransferable > xTransferable(
            createInstance( CHART_VIEW_SERVICE_NAME ), uno::UNO_QUERY );
        if( xTransferable.is() )
        {
            datatransfer::DataFlavor aDataFlavor( lcl_aGDIMetaFileMIMEType,
                    u"GDIMetaFile"_ustr,
                    cppu::UnoType<uno::Sequence< sal_Int8 >>::get() );

            uno::Any aData( xTransferable->getTransferData( aDataFlavor ) );
            aData >>= aMetafile;
        }

        aResult.Flavor.MimeType = lcl_aGDIMetaFileMIMEType;
        aResult.Flavor.DataType = cppu::UnoType<decltype(aMetafile)>::get();

        aResult.Data <<= aMetafile;
    }
    catch (const uno::Exception&)
    {
        DBG_UNHANDLED_EXCEPTION("chart2");
    }

    return aResult;
}

::sal_Int32 SAL_CALL ChartModel::getMapUnit( ::sal_Int64 nAspect )
{
    OSL_ENSURE( nAspect == embed::Aspects::MSOLE_CONTENT,
                "No aspects other than content are supported" );
    return embed::EmbedMapUnits::ONE_100TH_MM;
}

// ____ datatransfer::XTransferable ____
uno::Any SAL_CALL ChartModel::getTransferData( const datatransfer::DataFlavor& aFlavor )
{
    uno::Any aResult;
    if( !isDataFlavorSupported( aFlavor ) )
        throw datatransfer::UnsupportedFlavorException(
            aFlavor.MimeType, static_cast< ::cppu::OWeakObject* >( this ));

    try
    {
        //get view from old api wrapper
        Reference< datatransfer::XTransferable > xTransferable(
            createInstance( CHART_VIEW_SERVICE_NAME ), uno::UNO_QUERY );
        if( xTransferable.is() &&
            xTransferable->isDataFlavorSupported( aFlavor ))
        {
            aResult = xTransferable->getTransferData( aFlavor );
        }
    }
    catch (const uno::Exception&)
    {
        DBG_UNHANDLED_EXCEPTION("chart2");
    }

    return aResult;
}

Sequence< datatransfer::DataFlavor > SAL_CALL ChartModel::getTransferDataFlavors()
{
    return { datatransfer::DataFlavor( lcl_aGDIMetaFileMIMETypeHighContrast,
        u"GDIMetaFile"_ustr,
        cppu::UnoType<uno::Sequence< sal_Int8 >>::get() ) };
}

sal_Bool SAL_CALL ChartModel::isDataFlavorSupported( const datatransfer::DataFlavor& aFlavor )
{
    return aFlavor.MimeType == lcl_aGDIMetaFileMIMETypeHighContrast;
}

namespace
{
enum eServiceType
{
    SERVICE_DASH_TABLE,
    SERVICE_GRADIENT_TABLE,
    SERVICE_HATCH_TABLE,
    SERVICE_BITMAP_TABLE,
    SERVICE_TRANSP_GRADIENT_TABLE,
    SERVICE_MARKER_TABLE,
    SERVICE_NAMESPACE_MAP
};

typedef std::map< OUString, enum eServiceType > tServiceNameMap;

tServiceNameMap & lcl_getStaticServiceNameMap()
{
    static tServiceNameMap aServiceNameMap{
        {"com.sun.star.drawing.DashTable",                    SERVICE_DASH_TABLE},
        {"com.sun.star.drawing.GradientTable",                SERVICE_GRADIENT_TABLE},
        {"com.sun.star.drawing.HatchTable",                   SERVICE_HATCH_TABLE},
        {"com.sun.star.drawing.BitmapTable",                  SERVICE_BITMAP_TABLE},
        {"com.sun.star.drawing.TransparencyGradientTable",    SERVICE_TRANSP_GRADIENT_TABLE},
        {"com.sun.star.drawing.MarkerTable",                  SERVICE_MARKER_TABLE},
        {"com.sun.star.xml.NamespaceMap",                     SERVICE_NAMESPACE_MAP}};
    return aServiceNameMap;
}
}
// ____ XMultiServiceFactory ____
Reference< uno::XInterface > SAL_CALL ChartModel::createInstance( const OUString& rServiceSpecifier )
{
    tServiceNameMap & rMap = lcl_getStaticServiceNameMap();

    tServiceNameMap::const_iterator aIt( rMap.find( rServiceSpecifier ));
    if( aIt != rMap.end())
    {
        switch( (*aIt).second )
        {
            case SERVICE_DASH_TABLE:
            case SERVICE_GRADIENT_TABLE:
            case SERVICE_HATCH_TABLE:
            case SERVICE_BITMAP_TABLE:
            case SERVICE_TRANSP_GRADIENT_TABLE:
            case SERVICE_MARKER_TABLE:
                {
                    if(!mxChartView.is())
                    {
                        mxChartView = new ChartView( m_xContext, *this);
                    }
                    return mxChartView->createInstance( rServiceSpecifier );
                }
                break;
            case SERVICE_NAMESPACE_MAP:
                return static_cast<cppu::OWeakObject*>(m_xXMLNamespaceMap.get());
        }
    }
    else if(rServiceSpecifier == CHART_VIEW_SERVICE_NAME)
    {
        if(!mxChartView.is())
        {
            mxChartView = new ChartView( m_xContext, *this);
        }

        return static_cast< ::cppu::OWeakObject* >( mxChartView.get() );
    }
    else
    {
        if( m_xOldModelAgg.is() )
        {
            Any aAny = m_xOldModelAgg->queryAggregation( cppu::UnoType<lang::XMultiServiceFactory>::get());
            uno::Reference< lang::XMultiServiceFactory > xOldModelFactory;
            if( (aAny >>= xOldModelFactory) && xOldModelFactory.is() )
            {
                return xOldModelFactory->createInstance( rServiceSpecifier );
            }
        }
    }
    return nullptr;
}

Reference< uno::XInterface > SAL_CALL ChartModel::createInstanceWithArguments(
            const OUString& rServiceSpecifier , const Sequence< Any >& Arguments )
{
    OSL_ENSURE( Arguments.hasElements(), "createInstanceWithArguments: Warning: Arguments are ignored" );
    return createInstance( rServiceSpecifier );
}

Sequence< OUString > SAL_CALL ChartModel::getAvailableServiceNames()
{
    uno::Sequence< OUString > aResult;

    if( m_xOldModelAgg.is())
    {
        Any aAny = m_xOldModelAgg->queryAggregation( cppu::UnoType<lang::XMultiServiceFactory>::get());
        uno::Reference< lang::XMultiServiceFactory > xOldModelFactory;
        if( (aAny >>= xOldModelFactory) && xOldModelFactory.is() )
        {
            return xOldModelFactory->getAvailableServiceNames();
        }
    }
    return aResult;
}

Reference< util::XNumberFormatsSupplier > const & ChartModel::getNumberFormatsSupplier()
{
    if( !m_xNumberFormatsSupplier.is() )
    {
        if( !m_xOwnNumberFormatsSupplier.is() )
        {
            m_apSvNumberFormatter.reset( new SvNumberFormatter( m_xContext, LANGUAGE_SYSTEM ) );
            m_xOwnNumberFormatsSupplier = new SvNumberFormatsSupplierObj( m_apSvNumberFormatter.get() );
            //pOwnNumberFormatter->ChangeStandardPrec( 15 ); todo?
        }
        m_xNumberFormatsSupplier = m_xOwnNumberFormatsSupplier;
    }
    return m_xNumberFormatsSupplier;
}

// ____ XUnoTunnel ___
::sal_Int64 SAL_CALL ChartModel::getSomething( const Sequence< ::sal_Int8 >& aIdentifier )
{
    if( comphelper::isUnoTunnelId<SvNumberFormatsSupplierObj>(aIdentifier) )
    {
        Reference< lang::XUnoTunnel > xTunnel( getNumberFormatsSupplier(), uno::UNO_QUERY );
        if( xTunnel.is() )
            return xTunnel->getSomething( aIdentifier );
    }
    return 0;
}

// ____ XNumberFormatsSupplier ____
uno::Reference< beans::XPropertySet > SAL_CALL ChartModel::getNumberFormatSettings()
{
    Reference< util::XNumberFormatsSupplier > xSupplier( getNumberFormatsSupplier() );
    if( xSupplier.is() )
        return xSupplier->getNumberFormatSettings();
    return uno::Reference< beans::XPropertySet >();
}

uno::Reference< util::XNumberFormats > SAL_CALL ChartModel::getNumberFormats()
{
    Reference< util::XNumberFormatsSupplier > xSupplier( getNumberFormatsSupplier() );
    if( xSupplier.is() )
        return xSupplier->getNumberFormats();
    return uno::Reference< util::XNumberFormats >();
}

// ____ XChild ____
Reference< uno::XInterface > SAL_CALL ChartModel::getParent()
{
    return Reference< uno::XInterface >(m_xParent,uno::UNO_QUERY);
}

void SAL_CALL ChartModel::setParent( const Reference< uno::XInterface >& Parent )
{
    if( Parent != m_xParent )
        m_xParent.set( Parent, uno::UNO_QUERY );
}

// ____ XDataSource ____
uno::Sequence< Reference< chart2::data::XLabeledDataSequence > > SAL_CALL ChartModel::getDataSequences()
{
    rtl::Reference< DataSource > xSource = DataSourceHelper::getUsedData( *this );
    if( xSource.is())
        return xSource->getDataSequences();

    return uno::Sequence< Reference< chart2::data::XLabeledDataSequence > >();
}

//XDumper
OUString SAL_CALL ChartModel::dump(OUString const & kind)
{
    if (kind.isEmpty()) {
        return comphelper::dumpXmlToString([this](auto writer) { return dumpAsXml(writer); });
    }

    // kind == "shapes":
    uno::Reference< qa::XDumper > xDumper(
            createInstance( CHART_VIEW_SERVICE_NAME ), uno::UNO_QUERY );
    if (xDumper.is())
        return xDumper->dump(kind);

    return OUString();
}

void ChartModel::setTimeBasedRange(sal_Int32 nStart, sal_Int32 nEnd)
{
    mnStart = nStart;
    mnEnd = nEnd;
    mbTimeBased = true;
}

void ChartModel::update()
{
    if(!mxChartView.is())
    {
        mxChartView = new ChartView( m_xContext, *this);
    }
    mxChartView->setViewDirty();
    mxChartView->update();
}

bool ChartModel::isDataFromSpreadsheet()
{
    return !isDataFromPivotTable() && !hasInternalDataProvider();
}

bool ChartModel::isDataFromPivotTable() const
{
    uno::Reference<chart2::data::XPivotTableDataProvider> xPivotTableDataProvider(m_xDataProvider, uno::UNO_QUERY);
    return xPivotTableDataProvider.is();
}

}  // namespace chart

extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
com_sun_star_comp_chart2_ChartModel_get_implementation(css::uno::XComponentContext *context,
        css::uno::Sequence<css::uno::Any> const &)
{
    return cppu::acquire(new ::chart::ChartModel(context));
}

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