/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_chart2.hxx"

#include "ObjectHierarchy.hxx"
#include "ObjectIdentifier.hxx"
#include "ChartModelHelper.hxx"
#include "DiagramHelper.hxx"
#include "RegressionCurveHelper.hxx"
#include "AxisHelper.hxx"
#include "chartview/ExplicitValueProvider.hxx"
#include "macros.hxx"
#include "LineProperties.hxx"
#include "ChartTypeHelper.hxx"
#include "DataSeriesHelper.hxx"
#include "LegendHelper.hxx"
#include "chartview/DrawModelWrapper.hxx"

#include <map>
#include <algorithm>

#include <com/sun/star/chart2/XTitled.hpp>
#include <com/sun/star/chart2/XCoordinateSystemContainer.hpp>
#include <com/sun/star/chart2/XChartTypeContainer.hpp>
#include <com/sun/star/chart2/XDataSeriesContainer.hpp>
#include <com/sun/star/chart/ErrorBarStyle.hpp>

#include <com/sun/star/container/XIndexAccess.hpp>
#include <com/sun/star/awt/Key.hpp>
#include <com/sun/star/awt/KeyModifier.hpp>

using namespace ::com::sun::star;
using namespace ::com::sun::star::chart2;

using ::com::sun::star::uno::Reference;
using ::com::sun::star::uno::Sequence;
using ::rtl::OUString;

namespace
{

struct lcl_ObjectToOID : public ::std::unary_function< Reference< uno::XInterface >, ::chart::ObjectIdentifier >
{
    explicit lcl_ObjectToOID( const Reference< chart2::XChartDocument > & xChartDoc ) :
            m_xModel( xChartDoc, uno::UNO_QUERY )
    {}

    ::chart::ObjectIdentifier operator() ( const Reference< uno::XInterface > & xObj )
    {
        return ::chart::ObjectIdentifier( ::chart::ObjectIdentifier::createClassifiedIdentifierForObject( xObj, m_xModel ) );
    }

private:
    Reference< frame::XModel > m_xModel;
};

void lcl_getChildOIDs(
    ::chart::ObjectHierarchy::tChildContainer& rOutChildren,
    const Reference< container::XIndexAccess >& xShapes )
{
    if( xShapes.is())
    {
        sal_Int32 nCount = xShapes->getCount();
        for( sal_Int32 i=0; i<nCount; ++i)
        {
            Reference< beans::XPropertySet > xShapeProp( xShapes->getByIndex( i ), uno::UNO_QUERY );
            if( xShapeProp.is())
            {
                Reference< beans::XPropertySetInfo > xInfo( xShapeProp->getPropertySetInfo());
                OUString aName;
                if( xInfo.is() &&
                    xInfo->hasPropertyByName( C2U("Name")) &&
                    (xShapeProp->getPropertyValue( C2U("Name")) >>= aName ) &&
                    aName.getLength() > 0 &&
                    ::chart::ObjectIdentifier::isCID( aName ))
                {
                    rOutChildren.push_back( ::chart::ObjectIdentifier( aName ) );
                }
                Reference< container::XIndexAccess > xNewShapes( xShapeProp, uno::UNO_QUERY );
                if( xNewShapes.is())
                    lcl_getChildOIDs( rOutChildren, xNewShapes );
            }
        }
    }
}

void lcl_addAxisTitle( const Reference< XAxis >& xAxis, ::chart::ObjectHierarchy::tChildContainer& rContainer, const Reference< frame::XModel >& xChartModel )
{
    Reference< XTitled > xAxisTitled( xAxis, uno::UNO_QUERY );
    if( xAxisTitled.is())
    {
        Reference< XTitle > xAxisTitle( xAxisTitled->getTitleObject());
        if( xAxisTitle.is())
            rContainer.push_back(
                ::chart::ObjectIdentifier( ::chart::ObjectIdentifier::createClassifiedIdentifierForObject( xAxisTitle, xChartModel ) ) );
    }
}

} // anonymous namespace

namespace chart
{

namespace impl
{

class ImplObjectHierarchy
{
public:
    explicit ImplObjectHierarchy(
        const Reference< XChartDocument >& xChartDocument,
        ExplicitValueProvider* pExplicitValueProvider,
        bool bFlattenDiagram, bool bOrderingForElementSelector );

    bool                              hasChildren( const ObjectHierarchy::tOID& rParent );
    ObjectHierarchy::tChildContainer  getChildren( const ObjectHierarchy::tOID& rParent );
    ObjectHierarchy::tChildContainer  getSiblings( const ObjectHierarchy::tOID& rNode );

    ObjectHierarchy::tOID             getParent( const ObjectHierarchy::tOID& rOID );

private:
    void createTree( const Reference< XChartDocument > & xChartDocument );
    void createAxesTree(
        ObjectHierarchy::tChildContainer & rContainer,
        const Reference< XChartDocument > & xChartDoc,
        const Reference< XDiagram > & xDiagram  );
    void createDiagramTree(
        ObjectHierarchy::tChildContainer& rContainer,
        const Reference< XChartDocument >& xChartDoc,
        const Reference< XDiagram >& xDiagram );
    void createDataSeriesTree(
        ObjectHierarchy::tChildContainer & rOutDiagramSubContainer,
        const Reference< XDiagram > & xDiagram );
    void createWallAndFloor(
        ObjectHierarchy::tChildContainer & rContainer,
        const Reference< XDiagram > & xDiagram );
    void createLegendTree(
        ObjectHierarchy::tChildContainer & rContainer,
        const Reference< XChartDocument > & xChartDoc,
        const Reference< XDiagram > & xDiagram  );
    void createAdditionalShapesTree( ObjectHierarchy::tChildContainer& rContainer );

    ObjectHierarchy::tOID getParentImpl(
        const ObjectHierarchy::tOID& rParentOID,
        const ObjectHierarchy::tOID& rOID );

    typedef ::std::map< ObjectHierarchy::tOID, ObjectHierarchy::tChildContainer >
        tChildMap;
    tChildMap m_aChildMap;
    ExplicitValueProvider* m_pExplicitValueProvider;
    bool m_bFlattenDiagram;
    bool m_bOrderingForElementSelector;
};

ImplObjectHierarchy::ImplObjectHierarchy(
    const Reference< XChartDocument >& xChartDocument,
    ExplicitValueProvider* pExplicitValueProvider,
    bool bFlattenDiagram,
    bool bOrderingForElementSelector ) :
        m_pExplicitValueProvider( pExplicitValueProvider ),
        m_bFlattenDiagram( bFlattenDiagram ),
        m_bOrderingForElementSelector( bOrderingForElementSelector )
{
    createTree( xChartDocument );
    // don't remember this helper to avoid access after lifetime
    m_pExplicitValueProvider = 0;
}

void ImplObjectHierarchy::createTree( const Reference< XChartDocument >& xChartDocument )
{
    m_aChildMap = tChildMap();//clear tree

    if( !xChartDocument.is() )
        return;

    //@todo: change ObjectIdentifier to take an XChartDocument rather than XModel
    Reference< frame::XModel > xModel( xChartDocument, uno::UNO_QUERY );
    Reference< XDiagram > xDiagram( ChartModelHelper::findDiagram( xChartDocument ) );
    ObjectHierarchy::tOID aDiaOID;
    if( xDiagram.is() )
        aDiaOID = ObjectIdentifier( ObjectIdentifier::createClassifiedIdentifierForObject( xDiagram, xModel ) );
    ObjectHierarchy::tChildContainer aTopLevelContainer;

    // First Level

    // Chart Area
    if( m_bOrderingForElementSelector )
    {
        aTopLevelContainer.push_back( ObjectIdentifier( ObjectIdentifier::createClassifiedIdentifier( OBJECTTYPE_PAGE, OUString() ) ) );
        if( xDiagram.is() )
        {
            aTopLevelContainer.push_back( aDiaOID );
            createWallAndFloor( aTopLevelContainer, xDiagram );
            createLegendTree( aTopLevelContainer, xChartDocument, xDiagram  );
        }
    }

    // Main Title
    Reference< XTitled > xDocTitled( xChartDocument, uno::UNO_QUERY );
    if( xDocTitled.is())
    {
        Reference< XTitle > xMainTitle( xDocTitled->getTitleObject());
        if( xMainTitle.is())
            aTopLevelContainer.push_back(
                ObjectIdentifier( ObjectIdentifier::createClassifiedIdentifierForObject( xMainTitle, xModel ) ) );
    }

    if( xDiagram.is())
    {
        // Sub Title.  Note: This is interpreted of being top level
        Reference< XTitled > xDiaTitled( xDiagram, uno::UNO_QUERY );
        if( xDiaTitled.is())
        {
            Reference< XTitle > xSubTitle( xDiaTitled->getTitleObject());
            if( xSubTitle.is())
                aTopLevelContainer.push_back(
                    ObjectIdentifier( ObjectIdentifier::createClassifiedIdentifierForObject( xSubTitle, xModel ) ) );
        }

        if( !m_bOrderingForElementSelector )
        {
            // Axis Titles. Note: These are interpreted of being top level
            Sequence< Reference< XAxis > > aAxes( AxisHelper::getAllAxesOfDiagram( xDiagram ) );
            for( sal_Int32 i=0; i<aAxes.getLength(); ++i )
                lcl_addAxisTitle( aAxes[i], aTopLevelContainer, xModel );
        
            // Diagram
            aTopLevelContainer.push_back( aDiaOID );
        }

        if( m_bFlattenDiagram )
            createDiagramTree( aTopLevelContainer, xChartDocument, xDiagram );
        else
        {
            ObjectHierarchy::tChildContainer aSubContainer;
            createDiagramTree( aSubContainer, xChartDocument, xDiagram );
            if( !aSubContainer.empty() )
                m_aChildMap[ aDiaOID ] = aSubContainer;
        }

        if( !m_bOrderingForElementSelector )
            createLegendTree( aTopLevelContainer, xChartDocument, xDiagram  );
    }

    // #i12587# support for shapes in chart
    if ( !m_bOrderingForElementSelector )
    {
        createAdditionalShapesTree( aTopLevelContainer );
    }

    // Chart Area
    if( !m_bOrderingForElementSelector )
        aTopLevelContainer.push_back(
            ObjectIdentifier( ObjectIdentifier::createClassifiedIdentifier( OBJECTTYPE_PAGE, OUString() ) ) );

    if( ! aTopLevelContainer.empty())
        m_aChildMap[ ObjectHierarchy::getRootNodeOID() ] = aTopLevelContainer;
}

void ImplObjectHierarchy::createLegendTree(
    ObjectHierarchy::tChildContainer & rContainer,
    const Reference< XChartDocument > & xChartDoc,
    const Reference< XDiagram > & xDiagram  )
{
    if( xDiagram.is() && LegendHelper::hasLegend( xDiagram ) )
    {
        ObjectHierarchy::tOID aLegendOID( ObjectIdentifier( ObjectIdentifier::createClassifiedIdentifierForObject( xDiagram->getLegend(), Reference< frame::XModel >( xChartDoc, uno::UNO_QUERY ) ) ) );
        rContainer.push_back( aLegendOID );

        // iterate over child shapes of legend and search for matching CIDs
        if( m_pExplicitValueProvider )
        {
            Reference< container::XIndexAccess > xLegendShapeContainer(
                m_pExplicitValueProvider->getShapeForCID( aLegendOID.getObjectCID() ), uno::UNO_QUERY );
            ObjectHierarchy::tChildContainer aLegendEntryOIDs;
            lcl_getChildOIDs( aLegendEntryOIDs, xLegendShapeContainer );

            m_aChildMap[ aLegendOID ] = aLegendEntryOIDs;
        }
    }
}

void ImplObjectHierarchy::createAxesTree(
    ObjectHierarchy::tChildContainer & rContainer,
    const Reference< XChartDocument > & xChartDoc,
    const Reference< XDiagram > & xDiagram  )
{
    Reference< XCoordinateSystemContainer > xCooSysCnt( xDiagram, uno::UNO_QUERY_THROW );
    sal_Int32 nDimensionCount = DiagramHelper::getDimension( xDiagram );
    uno::Reference< chart2::XChartType > xChartType( DiagramHelper::getChartTypeByIndex( xDiagram, 0 ) );
    bool bSupportsAxesGrids = ChartTypeHelper::isSupportingMainAxis( xChartType, nDimensionCount, 0 );
    if( bSupportsAxesGrids )
    {
        Sequence< Reference< XAxis > > aAxes( AxisHelper::getAllAxesOfDiagram( xDiagram, /* bOnlyVisible = */ true ) );
        if( !m_bOrderingForElementSelector )
            ::std::transform( aAxes.getConstArray(), aAxes.getConstArray() + aAxes.getLength(),
                          ::std::back_inserter( rContainer ),
                          lcl_ObjectToOID( xChartDoc ));

        // get all axes, also invisible ones
        aAxes = AxisHelper::getAllAxesOfDiagram( xDiagram, /* bOnlyVisible = */ false );
        // Grids
        Reference< frame::XModel > xChartModel( xChartDoc, uno::UNO_QUERY );
        for( sal_Int32 nA=0; nA<aAxes.getLength(); ++nA )
        {
            Reference< XAxis > xAxis( aAxes[nA] );
            if(!xAxis.is())
                continue;

            sal_Int32 nCooSysIndex = 0;
            sal_Int32 nDimensionIndex = 0;
            sal_Int32 nAxisIndex = 0;
            AxisHelper::getIndicesForAxis( xAxis, xDiagram, nCooSysIndex, nDimensionIndex, nAxisIndex );
            if( nAxisIndex>0 && !ChartTypeHelper::isSupportingSecondaryAxis( xChartType, nDimensionCount, nDimensionIndex ) )
                continue;
            
            if( m_bOrderingForElementSelector )
            {
                // axis
                if( AxisHelper::isAxisVisible( xAxis ) )
                    rContainer.push_back(
                        ObjectIdentifier( ObjectIdentifier::createClassifiedIdentifierForObject( xAxis, xChartModel ) ) );

                // axis title
                lcl_addAxisTitle( aAxes[nA], rContainer, xChartModel );
            }

            Reference< beans::XPropertySet > xGridProperties( xAxis->getGridProperties() );
            if( AxisHelper::isGridVisible( xGridProperties ) )
            {
                //main grid
                rContainer.push_back(
                    ObjectIdentifier( ObjectIdentifier( ObjectIdentifier::createClassifiedIdentifierForGrid( xAxis, xChartModel ) ) ) );
            }

            Sequence< Reference< beans::XPropertySet > > aSubGrids( xAxis->getSubGridProperties() );;
            sal_Int32 nSubGrid = 0;
            for( nSubGrid = 0; nSubGrid < aSubGrids.getLength(); ++nSubGrid )
            {
                Reference< beans::XPropertySet > xSubGridProperties( aSubGrids[nSubGrid] );
                if( AxisHelper::isGridVisible( xSubGridProperties ) )
                {
                    //sub grid
                    rContainer.push_back(
                        ObjectIdentifier( ObjectIdentifier( ObjectIdentifier::createClassifiedIdentifierForGrid( xAxis, xChartModel, nSubGrid ) ) ) );
                }
            }
        }
    }
}

void ImplObjectHierarchy::createWallAndFloor(
    ObjectHierarchy::tChildContainer & rContainer,
    const Reference< XDiagram > & xDiagram )
{
    sal_Int32 nDimensionCount = DiagramHelper::getDimension( xDiagram );
    bool bIsThreeD = ( nDimensionCount == 3 );
    bool bHasWall = DiagramHelper::isSupportingFloorAndWall( xDiagram );
    if( bHasWall && bIsThreeD )
    {
        rContainer.push_back(
            ObjectIdentifier( ObjectIdentifier::createClassifiedIdentifier( OBJECTTYPE_DIAGRAM_WALL, rtl::OUString() ) ) );

        Reference< beans::XPropertySet > xFloor( xDiagram->getFloor());
        if( xFloor.is())
            rContainer.push_back(
                ObjectIdentifier( ObjectIdentifier::createClassifiedIdentifier( OBJECTTYPE_DIAGRAM_FLOOR, rtl::OUString() ) ) );
    }

}

void ImplObjectHierarchy::createDiagramTree(
    ObjectHierarchy::tChildContainer & rContainer,
    const Reference< XChartDocument > & xChartDoc,
    const Reference< XDiagram > & xDiagram )
{
    if( !m_bOrderingForElementSelector )
    {
        createDataSeriesTree( rContainer, xDiagram );
        createAxesTree( rContainer, xChartDoc, xDiagram  );
        createWallAndFloor( rContainer, xDiagram );
    }
    else
    {
        createAxesTree( rContainer, xChartDoc, xDiagram  );
        createDataSeriesTree( rContainer, xDiagram );
    }
}

void ImplObjectHierarchy::createDataSeriesTree(
    ObjectHierarchy::tChildContainer & rOutDiagramSubContainer,
    const Reference< XDiagram > & xDiagram )
{
    Reference< XCoordinateSystemContainer > xCooSysCnt( xDiagram, uno::UNO_QUERY_THROW );

    try
    {
        sal_Int32 nDiagramIndex = 0;
        sal_Int32 nDimensionCount = DiagramHelper::getDimension( xDiagram );
        Sequence< Reference< XCoordinateSystem > > aCooSysSeq(
            xCooSysCnt->getCoordinateSystems());
        for( sal_Int32 nCooSysIdx=0; nCooSysIdx<aCooSysSeq.getLength(); ++nCooSysIdx )
        {
            Reference< XChartTypeContainer > xCTCnt( aCooSysSeq[nCooSysIdx], uno::UNO_QUERY_THROW );
            Sequence< Reference< XChartType > > aChartTypeSeq( xCTCnt->getChartTypes());
            for( sal_Int32 nCTIdx=0; nCTIdx<aChartTypeSeq.getLength(); ++nCTIdx )
            {
                Reference< XChartType > xChartType( aChartTypeSeq[nCTIdx] );
                Reference< XDataSeriesContainer > xDSCnt( xChartType, uno::UNO_QUERY_THROW );
                Sequence< Reference< XDataSeries > > aSeriesSeq( xDSCnt->getDataSeries() );
                const sal_Int32 nNumberOfSeries =
                    ChartTypeHelper::getNumberOfDisplayedSeries( xChartType, aSeriesSeq.getLength());

                for( sal_Int32 nSeriesIdx=0; nSeriesIdx<nNumberOfSeries; ++nSeriesIdx )
                {
                    OUString aSeriesParticle(
                        ObjectIdentifier::createParticleForSeries(
                            nDiagramIndex, nCooSysIdx, nCTIdx, nSeriesIdx ));
                    ObjectHierarchy::tOID aSeriesOID(
                        ObjectIdentifier( ObjectIdentifier::createClassifiedIdentifierForParticle( aSeriesParticle ) ) );
                    rOutDiagramSubContainer.push_back( aSeriesOID );

                    ObjectHierarchy::tChildContainer aSeriesSubContainer;

                    Reference< chart2::XDataSeries > xSeries( aSeriesSeq[nSeriesIdx], uno::UNO_QUERY );

                    // data lablels
                    if( DataSeriesHelper::hasDataLabelsAtSeries( xSeries ) )
                    {
                        rtl::OUString aChildParticle( ObjectIdentifier::getStringForType( OBJECTTYPE_DATA_LABELS ) );
                        aChildParticle+=(C2U("="));
                        aSeriesSubContainer.push_back(
                                    ObjectIdentifier( ObjectIdentifier::createClassifiedIdentifierForParticles( aSeriesParticle, aChildParticle ) ) );
                    }

                    // Statistics
                    if( ChartTypeHelper::isSupportingStatisticProperties( xChartType, nDimensionCount ) )
                    {
                        Reference< chart2::XRegressionCurveContainer > xCurveCnt( xSeries, uno::UNO_QUERY );
                        if( xCurveCnt.is())
                        {
                            Sequence< Reference< chart2::XRegressionCurve > > aCurves( xCurveCnt->getRegressionCurves());
                            for( sal_Int32 nCurveIdx=0; nCurveIdx<aCurves.getLength(); ++nCurveIdx )
                            {
                                bool bIsAverageLine = RegressionCurveHelper::isMeanValueLine( aCurves[nCurveIdx] );
                                aSeriesSubContainer.push_back(
                                    ObjectIdentifier( ObjectIdentifier::createDataCurveCID( aSeriesParticle, nCurveIdx, bIsAverageLine ) ) );
                                if( RegressionCurveHelper::hasEquation( aCurves[nCurveIdx] ) )
                                {
                                    aSeriesSubContainer.push_back(
                                        ObjectIdentifier( ObjectIdentifier::createDataCurveEquationCID( aSeriesParticle, nCurveIdx ) ) );
                                }
                            }
                            Reference< beans::XPropertySet > xSeriesProp( xSeries, uno::UNO_QUERY );
                            Reference< beans::XPropertySet > xErrorBarProp;
                            if( xSeriesProp.is() &&
                                (xSeriesProp->getPropertyValue( C2U("ErrorBarY")) >>= xErrorBarProp) &&
                                xErrorBarProp.is())
                            {
                                sal_Int32 nStyle = ::com::sun::star::chart::ErrorBarStyle::NONE;
                                if( ( xErrorBarProp->getPropertyValue( C2U("ErrorBarStyle")) >>= nStyle ) &&
                                    ( nStyle != ::com::sun::star::chart::ErrorBarStyle::NONE ) )
                                {
                                    aSeriesSubContainer.push_back(
                                        ObjectIdentifier( ObjectIdentifier::createClassifiedIdentifierWithParent(
                                            OBJECTTYPE_DATA_ERRORS, OUString(), aSeriesParticle ) ) );
                                }
                            }
                        }
                    }

                    // Data Points
                    // iterate over child shapes of legend and search for matching CIDs
                    if( m_pExplicitValueProvider )
                    {
                        Reference< container::XIndexAccess > xSeriesShapeContainer(
                            m_pExplicitValueProvider->getShapeForCID( aSeriesOID.getObjectCID() ), uno::UNO_QUERY );
                        lcl_getChildOIDs( aSeriesSubContainer, xSeriesShapeContainer );
                    }

                    if( ! aSeriesSubContainer.empty())
                        m_aChildMap[ aSeriesOID ] = aSeriesSubContainer;
                }
            }
        }
    }
    catch( uno::Exception & ex )
    {
        ASSERT_EXCEPTION( ex );
    }
}

void ImplObjectHierarchy::createAdditionalShapesTree( ObjectHierarchy::tChildContainer& rContainer )
{
    try
    {
        if ( m_pExplicitValueProvider )
        {
            Reference< drawing::XDrawPage > xDrawPage( m_pExplicitValueProvider->getDrawModelWrapper()->getMainDrawPage() );
            Reference< drawing::XShapes > xDrawPageShapes( xDrawPage, uno::UNO_QUERY_THROW );
            Reference< drawing::XShapes > xChartRoot( DrawModelWrapper::getChartRootShape( xDrawPage ) );
            sal_Int32 nCount = xDrawPageShapes->getCount();
            for ( sal_Int32 i = 0; i < nCount; ++i )
            {
                Reference< drawing::XShape > xShape;
                if ( xDrawPageShapes->getByIndex( i ) >>= xShape )
                {
                    if ( xShape.is() && xShape != xChartRoot )
                    {
                        rContainer.push_back( ObjectIdentifier( xShape ) );
                    }
                }
            }
        }
    }
    catch ( uno::Exception& ex )
    {
        ASSERT_EXCEPTION( ex );
    }
}

bool ImplObjectHierarchy::hasChildren( const ObjectHierarchy::tOID& rParent )
{
    if ( rParent.isValid() )
    {
        tChildMap::const_iterator aIt( m_aChildMap.find( rParent ));
        if( aIt != m_aChildMap.end())
            return ! (aIt->second.empty());
    }
    return false;
}

ObjectHierarchy::tChildContainer ImplObjectHierarchy::getChildren( const ObjectHierarchy::tOID& rParent )
{
    if ( rParent.isValid() )
    {
        tChildMap::const_iterator aIt( m_aChildMap.find( rParent ));
        if( aIt != m_aChildMap.end())
            return aIt->second;
    }
    return ObjectHierarchy::tChildContainer();
}

ObjectHierarchy::tChildContainer ImplObjectHierarchy::getSiblings( const ObjectHierarchy::tOID& rNode )
{
    if ( rNode.isValid() && !ObjectHierarchy::isRootNode( rNode ) )
    {
        for( tChildMap::const_iterator aIt( m_aChildMap.begin());
             aIt != m_aChildMap.end(); ++aIt )
        {
            ObjectHierarchy::tChildContainer::const_iterator aElemIt(
                ::std::find( aIt->second.begin(), aIt->second.end(), rNode ));
            if( aElemIt != aIt->second.end())
                return aIt->second;
        }
    }
    return ObjectHierarchy::tChildContainer();
}

ObjectHierarchy::tOID ImplObjectHierarchy::getParentImpl(
    const ObjectHierarchy::tOID & rParentOID,
    const ObjectHierarchy::tOID & rOID )
{
    // search children
    ObjectHierarchy::tChildContainer aChildren( getChildren( rParentOID ));
    ObjectHierarchy::tChildContainer::const_iterator aIt(
        ::std::find( aChildren.begin(), aChildren.end(), rOID ));
    // recursion end
    if( aIt != aChildren.end())
        return rParentOID;

    for( aIt = aChildren.begin(); aIt != aChildren.end(); ++aIt )
    {
        // recursion
        ObjectHierarchy::tOID aTempParent( getParentImpl( *aIt, rOID ));
        if ( aTempParent.isValid() )
        {
            // exit on success
            return aTempParent;
        }
    }

    // exit on fail
    return ObjectHierarchy::tOID();
}

ObjectHierarchy::tOID ImplObjectHierarchy::getParent(
    const ObjectHierarchy::tOID & rOID )
{
    return getParentImpl( ObjectHierarchy::getRootNodeOID(), rOID );
}

} // namespace impl


ObjectHierarchy::ObjectHierarchy(
    const Reference< XChartDocument > & xChartDocument,
    ExplicitValueProvider * pExplicitValueProvider /* = 0 */,
    bool bFlattenDiagram /* = false */,
    bool bOrderingForElementSelector /* = false */) :
        m_apImpl( new impl::ImplObjectHierarchy( xChartDocument, pExplicitValueProvider, bFlattenDiagram, bOrderingForElementSelector ))
{}

ObjectHierarchy::~ObjectHierarchy()
{}

ObjectHierarchy::tOID ObjectHierarchy::getRootNodeOID()
{
    return ObjectIdentifier( C2U( "ROOT" ) );
}

bool ObjectHierarchy::isRootNode( const ObjectHierarchy::tOID& rOID )
{
    return ( rOID == ObjectHierarchy::getRootNodeOID() );
}

ObjectHierarchy::tChildContainer ObjectHierarchy::getTopLevelChildren() const
{
    return m_apImpl->getChildren( ObjectHierarchy::getRootNodeOID());
}

bool ObjectHierarchy::hasChildren( const tOID& rParent ) const
{
    return m_apImpl->hasChildren( rParent );
}

ObjectHierarchy::tChildContainer ObjectHierarchy::getChildren(
    const ObjectHierarchy::tOID& rParent ) const
{
    if ( rParent.isValid() )
        return m_apImpl->getChildren( rParent );

    return ObjectHierarchy::tChildContainer();
}

ObjectHierarchy::tChildContainer ObjectHierarchy::getSiblings(
    const ObjectHierarchy::tOID& rNode ) const
{
    if ( rNode.isValid() && !isRootNode( rNode ) )
        return m_apImpl->getSiblings( rNode );

    return ObjectHierarchy::tChildContainer();
}

ObjectHierarchy::tOID ObjectHierarchy::getParent(
    const ObjectHierarchy::tOID& rNode ) const
{
    return m_apImpl->getParent( rNode );
}

sal_Int32 ObjectHierarchy::getIndexInParent(
    const ObjectHierarchy::tOID& rNode ) const
{
    tOID aParentOID( m_apImpl->getParent( rNode ));
    tChildContainer aChildren( m_apImpl->getChildren( aParentOID ) );
    tChildContainer::const_iterator aIt( aChildren.begin() );
    for( sal_Int32 nIndex = 0; aIt != aChildren.end(); ++nIndex, ++aIt )
    {
        if ( *aIt == rNode )
            return nIndex;
    }
    return -1;
}

// ================================================================================

ObjectKeyNavigation::ObjectKeyNavigation(
    const ObjectHierarchy::tOID & rCurrentOID,
    const Reference< chart2::XChartDocument > & xChartDocument,
    ExplicitValueProvider * pExplicitValueProvider /* = 0 */ ) :
        m_aCurrentOID( rCurrentOID ),
        m_xChartDocument( xChartDocument ),
        m_pExplicitValueProvider( pExplicitValueProvider ),
        m_bStepDownInDiagram( true )
{
    if ( !m_aCurrentOID.isValid() )
    {
        setCurrentSelection( ObjectHierarchy::getRootNodeOID() );
    }
}

bool ObjectKeyNavigation::handleKeyEvent(
    const awt::KeyEvent & rEvent )
{
    bool bResult = false;

    switch( rEvent.KeyCode )
    {
        case awt::Key::TAB:
            if( rEvent.Modifiers & awt::KeyModifier::SHIFT )
                bResult = previous();
            else
                bResult = next();
            break;
        case awt::Key::HOME:
            bResult = first();
            break;
        case awt::Key::END:
            bResult = last();
            break;
        case awt::Key::F3:
            if( rEvent.Modifiers & awt::KeyModifier::SHIFT )
                bResult = up();
            else
                bResult = down();
            break;
        case awt::Key::ESCAPE:
            setCurrentSelection( ObjectIdentifier() );
            bResult = true;
            break;
        default:
            bResult = false;
            break;
    }
    return bResult;
}

void ObjectKeyNavigation::setCurrentSelection( const ObjectHierarchy::tOID& rOID )
{
    m_aCurrentOID = rOID;
}

ObjectHierarchy::tOID ObjectKeyNavigation::getCurrentSelection() const
{
    return m_aCurrentOID;
}

bool ObjectKeyNavigation::first()
{
    ObjectHierarchy aHierarchy( m_xChartDocument, m_pExplicitValueProvider, m_bStepDownInDiagram );
    ObjectHierarchy::tChildContainer aSiblings( aHierarchy.getSiblings( getCurrentSelection() ) );
    bool bResult = !aSiblings.empty();
    if( bResult )
        setCurrentSelection( aSiblings.front());
    else
        bResult = veryFirst();
    return bResult;
}

bool ObjectKeyNavigation::last()
{
    ObjectHierarchy aHierarchy( m_xChartDocument, m_pExplicitValueProvider, m_bStepDownInDiagram );
    ObjectHierarchy::tChildContainer aSiblings( aHierarchy.getSiblings( getCurrentSelection() ) );
    bool bResult = !aSiblings.empty();
    if( bResult )
        setCurrentSelection( aSiblings.back());
    else
        bResult = veryLast();
    return bResult;
}

bool ObjectKeyNavigation::next()
{
    ObjectHierarchy aHierarchy( m_xChartDocument, m_pExplicitValueProvider, m_bStepDownInDiagram );
    ObjectHierarchy::tChildContainer aSiblings( aHierarchy.getSiblings( getCurrentSelection() ) );
    bool bResult = !aSiblings.empty();
    if( bResult )
    {
        ObjectHierarchy::tChildContainer::const_iterator aIt(
            ::std::find( aSiblings.begin(), aSiblings.end(), getCurrentSelection()));
        OSL_ASSERT( aIt != aSiblings.end());
        if( ++aIt == aSiblings.end())
            aIt = aSiblings.begin();
        setCurrentSelection( *aIt );
    }
    else
        bResult = veryFirst();

    return bResult;
}

bool ObjectKeyNavigation::previous()
{
    ObjectHierarchy aHierarchy( m_xChartDocument, m_pExplicitValueProvider, m_bStepDownInDiagram );
    ObjectHierarchy::tChildContainer aSiblings( aHierarchy.getSiblings( getCurrentSelection()));
    bool bResult = !aSiblings.empty();
    if( bResult )
    {
        ObjectHierarchy::tChildContainer::const_iterator aIt(
            ::std::find( aSiblings.begin(), aSiblings.end(), getCurrentSelection()));
        OSL_ASSERT( aIt != aSiblings.end());
        if( aIt == aSiblings.begin())
            aIt = aSiblings.end();
        --aIt;
        setCurrentSelection( *aIt );
    }
    else
        bResult = veryLast();
    return bResult;
}

bool ObjectKeyNavigation::up()
{
    ObjectHierarchy aHierarchy( m_xChartDocument, m_pExplicitValueProvider, m_bStepDownInDiagram );
    bool bResult = !ObjectHierarchy::isRootNode( getCurrentSelection());
    if( bResult )
        setCurrentSelection( aHierarchy.getParent( getCurrentSelection()));
    return bResult;
}

bool ObjectKeyNavigation::down()
{
    ObjectHierarchy aHierarchy( m_xChartDocument, m_pExplicitValueProvider, m_bStepDownInDiagram );
    bool bResult = aHierarchy.hasChildren( getCurrentSelection());
    if( bResult )
    {
        ObjectHierarchy::tChildContainer aChildren = aHierarchy.getChildren( getCurrentSelection());
        OSL_ASSERT( !aChildren.empty());
        setCurrentSelection( aChildren.front());
    }
    return bResult;
}

bool ObjectKeyNavigation::veryFirst()
{
    ObjectHierarchy aHierarchy( m_xChartDocument, m_pExplicitValueProvider, m_bStepDownInDiagram );
    ObjectHierarchy::tChildContainer aChildren( aHierarchy.getTopLevelChildren());
    bool bResult = !aChildren.empty();
    if( bResult )
        setCurrentSelection( aChildren.front());
    return bResult;
}

bool ObjectKeyNavigation::veryLast()
{
    ObjectHierarchy aHierarchy( m_xChartDocument, m_pExplicitValueProvider, m_bStepDownInDiagram );
    ObjectHierarchy::tChildContainer aChildren( aHierarchy.getTopLevelChildren());
    bool bResult = !aChildren.empty();
    if( bResult )
        setCurrentSelection( aChildren.back());
    return bResult;
}

} //  namespace chart

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