/**************************************************************
 * 
 * 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
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



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

// must be first
#include <canvas/debug.hxx>
#include <tools/diagnose_ex.h>
#include <canvas/verbosetrace.hxx>

#include <rtl/math.hxx>
#include <rtl/logfile.hxx>

#include <vcl/metaact.hxx>
#include <vcl/gdimtf.hxx>
#include <basegfx/numeric/ftools.hxx>

#include "drawshapesubsetting.hxx"
#include "drawshape.hxx"

#include <boost/bind.hpp>

#include <algorithm>
#include <functional>
#include <limits>

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


namespace slideshow
{
    namespace internal
    {

        //////////////////////////////////////////////////////////////////////
        //
        // Private methods
        //
        //////////////////////////////////////////////////////////////////////

        void DrawShapeSubsetting::ensureInitializedNodeTree() const
        {
            ENSURE_OR_THROW( mpMtf,
                              "DrawShapeSubsetting::ensureInitializedNodeTree(): Invalid mtf" );

            if( mbNodeTreeInitialized )
                return; // done, already initialized.

            // init doctree vector
            maActionClassVector.clear();
            maActionClassVector.reserve( mpMtf->GetActionCount() );

            // search metafile for text output
            MetaAction* pCurrAct;

            sal_Int32 nActionIndex(0);
            sal_Int32 nLastTextActionIndex(0);
            for( pCurrAct = mpMtf->FirstAction(); pCurrAct; pCurrAct = mpMtf->NextAction() )
            {
                // check for one of our special text doctree comments
                switch( pCurrAct->GetType() )
                {
                    case META_COMMENT_ACTION:
                    {
                        MetaCommentAction* pAct = static_cast<MetaCommentAction*>(pCurrAct);

                        // skip comment if not a special XTEXT comment
                        if( pAct->GetComment().CompareIgnoreCaseToAscii( "XTEXT", 5 ) == COMPARE_EQUAL )
                        {
                            // fill classification vector with NOOPs,
                            // then insert corresponding classes at
                            // the given index
                            maActionClassVector.resize( nActionIndex+1, CLASS_NOOP );

                            if( pAct->GetComment().CompareIgnoreCaseToAscii( "XTEXT_EOC" ) == COMPARE_EQUAL )
                            {
                                // special, because can happen
                                // in-between of portions - set
                                // character-end classificator at
                                // given index (relative to last text
                                // action).
                                const sal_Int32 nIndex( nLastTextActionIndex + pAct->GetValue() );

                                ENSURE_OR_THROW( static_cast< ::std::size_t >(nIndex) < maActionClassVector.size(),
                                                  "DrawShapeSubsetting::ensureInitializedNodeTree(): sentence index out of range" );

                                maActionClassVector[ nIndex ] = CLASS_CHARACTER_CELL_END;
                            }
                            else if( pAct->GetComment().CompareIgnoreCaseToAscii( "XTEXT_EOW" ) == COMPARE_EQUAL )
                            {
                                // special, because can happen
                                // in-between of portions - set
                                // word-end classificator at given
                                // index (relative to last text
                                // action).
                                const sal_Int32 nIndex( nLastTextActionIndex + pAct->GetValue() );

                                ENSURE_OR_THROW( static_cast< ::std::size_t >(nIndex) < maActionClassVector.size(),
                                                  "DrawShapeSubsetting::ensureInitializedNodeTree(): sentence index out of range" );

                                maActionClassVector[ nIndex ] = CLASS_WORD_END;
                            }
                            else if( pAct->GetComment().CompareIgnoreCaseToAscii( "XTEXT_EOS" ) == COMPARE_EQUAL )
                            {
                                // special, because can happen
                                // in-between of portions - set
                                // sentence-end classificator at given
                                // index (relative to last text
                                // action).
                                const sal_Int32 nIndex( nLastTextActionIndex + pAct->GetValue() );

                                ENSURE_OR_THROW( static_cast< ::std::size_t >(nIndex) < maActionClassVector.size(),
                                                  "DrawShapeSubsetting::ensureInitializedNodeTree(): sentence index out of range" );

                                maActionClassVector[ nIndex ] = CLASS_SENTENCE_END;
                            }
                            else if( pAct->GetComment().CompareIgnoreCaseToAscii( "XTEXT_EOL" ) == COMPARE_EQUAL )
                            {
                                maActionClassVector[ nActionIndex ] = CLASS_LINE_END;
                            }
                            else if( pAct->GetComment().CompareIgnoreCaseToAscii( "XTEXT_EOP" ) == COMPARE_EQUAL )
                            {
                                maActionClassVector[ nActionIndex ] = CLASS_PARAGRAPH_END;
                            }
                            else if( pAct->GetComment().CompareIgnoreCaseToAscii( "XTEXT_PAINTSHAPE_END" ) == COMPARE_EQUAL )
                            {
                                maActionClassVector[ nActionIndex ] = CLASS_SHAPE_END;
                            }
                            else if( pAct->GetComment().CompareIgnoreCaseToAscii( "XTEXT_PAINTSHAPE_BEGIN" ) == COMPARE_EQUAL )
                            {
                                maActionClassVector[ nActionIndex ] = CLASS_SHAPE_START;
                            }
                        }
                        ++nActionIndex;
                        break;
                    }
                    case META_TEXT_ACTION:
                    case META_TEXTARRAY_ACTION:
                    case META_STRETCHTEXT_ACTION:
                        nLastTextActionIndex = nActionIndex;
                        // fallthrough intended
                    default:
                        // comment action and all actions not
                        // explicitely handled here:
                        nActionIndex += getNextActionOffset(pCurrAct);
                        break;
                }
            }

            mbNodeTreeInitialized = true;
        }

        void DrawShapeSubsetting::updateSubsetBounds( const SubsetEntry& rSubsetEntry )
        {
            // TODO(F1): This removes too much from non-contiguous subsets
            mnMinSubsetActionIndex = ::std::min(
                mnMinSubsetActionIndex,
                rSubsetEntry.mnStartActionIndex );
            mnMaxSubsetActionIndex = ::std::max(
                mnMaxSubsetActionIndex,
                rSubsetEntry.mnEndActionIndex );
        }

        void DrawShapeSubsetting::updateSubsets()
        {
            maCurrentSubsets.clear();

            if( !maSubsetShapes.empty() )
            {
                if( maSubset.isEmpty() )
                {
                    // non-subsetted node, with some child subsets
                    // that subtract from it
                    maCurrentSubsets.push_back( DocTreeNode( 0,
                                                             mnMinSubsetActionIndex,
                                                             DocTreeNode::NODETYPE_INVALID ) );
                    maCurrentSubsets.push_back( DocTreeNode( mnMaxSubsetActionIndex,
                                                             maActionClassVector.size(),
                                                             DocTreeNode::NODETYPE_INVALID ) );
                }
                else
                {
                    // subsetted node, from which some further child
                    // subsets subtract content
                    maCurrentSubsets.push_back( DocTreeNode( maSubset.getStartIndex(),
                                                             mnMinSubsetActionIndex,
                                                             DocTreeNode::NODETYPE_INVALID ) );
                    maCurrentSubsets.push_back( DocTreeNode( mnMaxSubsetActionIndex,
                                                             maSubset.getEndIndex(),
                                                             DocTreeNode::NODETYPE_INVALID ) );
                }
            }
            else
            {
                // no further child subsets, simply add our subset (if any)
                if( !maSubset.isEmpty() )
                {
                    // subsetted node, without any subset children
                    maCurrentSubsets.push_back( maSubset );
                }
            }
        }

        //////////////////////////////////////////////////////////////////////
        //
        // Public methods
        //
        //////////////////////////////////////////////////////////////////////

        DrawShapeSubsetting::DrawShapeSubsetting() :
            maActionClassVector(),
            mpMtf(),
            maSubset(),
            maSubsetShapes(),
            mnMinSubsetActionIndex( SAL_MAX_INT32 ),
            mnMaxSubsetActionIndex(0),
            maCurrentSubsets(),
            mbNodeTreeInitialized( false )
        {
        }

        DrawShapeSubsetting::DrawShapeSubsetting( const GDIMetaFileSharedPtr& rMtf ) :
            maActionClassVector(),
            mpMtf( rMtf ),
            maSubset(),
            maSubsetShapes(),
            mnMinSubsetActionIndex( SAL_MAX_INT32 ),
            mnMaxSubsetActionIndex(0),
            maCurrentSubsets(),
            mbNodeTreeInitialized( false )
        {
            ENSURE_OR_THROW( mpMtf, 
                              "DrawShapeSubsetting::DrawShapeSubsetting(): Invalid metafile" );

            initCurrentSubsets();
        }

        DrawShapeSubsetting::DrawShapeSubsetting( const DocTreeNode&			rShapeSubset,
                                                  const GDIMetaFileSharedPtr&	rMtf ) :
            maActionClassVector(),
            mpMtf( rMtf ),
            maSubset( rShapeSubset ),
            maSubsetShapes(),
            mnMinSubsetActionIndex( SAL_MAX_INT32 ),
            mnMaxSubsetActionIndex(0),
            maCurrentSubsets(),
            mbNodeTreeInitialized( false )
        {
            ENSURE_OR_THROW( mpMtf, 
                              "DrawShapeSubsetting::DrawShapeSubsetting(): Invalid metafile" );

            initCurrentSubsets();
        }

        void DrawShapeSubsetting::reset()
        {
            maActionClassVector.clear();
            mpMtf.reset();
            maSubset.reset();
            maSubsetShapes.clear();
            mnMinSubsetActionIndex = SAL_MAX_INT32;
            mnMaxSubsetActionIndex = 0;
            maCurrentSubsets.clear();
            mbNodeTreeInitialized = false;
        }

        void DrawShapeSubsetting::reset( const ::boost::shared_ptr< GDIMetaFile >& rMtf )
        {
            reset();
            mpMtf = rMtf;

            initCurrentSubsets();
        }

        void DrawShapeSubsetting::reset( const DocTreeNode&                          rShapeSubset,
                                         const ::boost::shared_ptr< GDIMetaFile >&   rMtf )
        {
            reset();
            mpMtf = rMtf;
            maSubset = rShapeSubset;

            initCurrentSubsets();
        }

        void DrawShapeSubsetting::initCurrentSubsets()
        {
            // only add subset to vector, if it's not empty - that's
            // because the vector's content is later literally used
            // for e.g. painting.
            if( !maSubset.isEmpty() )
                maCurrentSubsets.push_back( maSubset );
        }

        DocTreeNode DrawShapeSubsetting::getSubsetNode() const
        {
            return maSubset;
        }

        bool DrawShapeSubsetting::hasSubsetShapes() const
        {
            return !maSubsetShapes.empty();
        }

        AttributableShapeSharedPtr DrawShapeSubsetting::getSubsetShape( const DocTreeNode& rTreeNode ) const
        {
            RTL_LOGFILE_CONTEXT( aLog, "::presentation::internal::DrawShapeSubsetting::getSubsetShape()" );

            // subset shape already created for this DocTreeNode?
            SubsetEntry aEntry;
            
            aEntry.mnStartActionIndex 	= rTreeNode.getStartIndex();
            aEntry.mnEndActionIndex 	= rTreeNode.getEndIndex();

            ShapeSet::const_iterator aIter;
            if( (aIter=maSubsetShapes.find( aEntry )) != maSubsetShapes.end() )
            {
                // already created, return found entry
                return aIter->mpShape;
            }

            return AttributableShapeSharedPtr();
        }

        void DrawShapeSubsetting::addSubsetShape( const AttributableShapeSharedPtr& rShape )
        {
            RTL_LOGFILE_CONTEXT( aLog, "::presentation::internal::DrawShapeSubsetting::addSubsetShape()" );

            // subset shape already created for this DocTreeNode?
            SubsetEntry aEntry;
            const DocTreeNode& rEffectiveSubset( rShape->getSubsetNode() );
            
            aEntry.mnStartActionIndex 	= rEffectiveSubset.getStartIndex();
            aEntry.mnEndActionIndex 	= rEffectiveSubset.getEndIndex();

            ShapeSet::const_iterator aIter;
            if( (aIter=maSubsetShapes.find( aEntry )) != maSubsetShapes.end() )
            {
                // already created, increment use count and return

                // safe cast, since set order does not depend on
                // mnSubsetQueriedCount
                const_cast<SubsetEntry&>(*aIter).mnSubsetQueriedCount++;
            }
            else
            {
                // not yet created, init entry
                aEntry.mnSubsetQueriedCount = 1;
                aEntry.mpShape = rShape;
                
                maSubsetShapes.insert( aEntry );
                
                // update cached subset borders
                updateSubsetBounds( aEntry );
                updateSubsets();
            }
        }

        bool DrawShapeSubsetting::revokeSubsetShape( const AttributableShapeSharedPtr& rShape )
        {
            RTL_LOGFILE_CONTEXT( aLog, "::presentation::internal::DrawShapeSubsetting::revokeSubsetShape()" );

            // lookup subset shape
            SubsetEntry aEntry;
            const DocTreeNode& rEffectiveSubset( rShape->getSubsetNode() );
            
            aEntry.mnStartActionIndex 	= rEffectiveSubset.getStartIndex();
            aEntry.mnEndActionIndex 	= rEffectiveSubset.getEndIndex();

            ShapeSet::iterator aIter;
            if( (aIter=maSubsetShapes.find( aEntry )) == maSubsetShapes.end() )
                return false; // not found, subset was never queried

            // last client of the subset revoking?
            if( aIter->mnSubsetQueriedCount > 1 )
            {
                // no, still clients out there. Just decrement use count
                // safe cast, since order does not depend on mnSubsetQueriedCount
                const_cast<SubsetEntry&>(*aIter).mnSubsetQueriedCount--;

                VERBOSE_TRACE( "Subset summary: shape 0x%X, %d open subsets, revoked subset has refcount %d",
                               this,
                               maSubsetShapes.size(),
                               aIter->mnSubsetQueriedCount );

                return false; // not the last client
            }

            VERBOSE_TRACE( "Subset summary: shape 0x%X, %d open subsets, cleared subset has range [%d,%d]",
                           this,
                           maSubsetShapes.size(),
                           aEntry.mnStartActionIndex,
                           aEntry.mnEndActionIndex );

            // yes, remove from set
            maSubsetShapes.erase( aIter );


            // update currently active subset for _our_ shape (the
            // part of this shape that is visible, i.e. not displayed
            // in subset shapes)
            // ======================================================

            // init bounds
            mnMinSubsetActionIndex = SAL_MAX_INT32;
            mnMaxSubsetActionIndex = 0;

            // TODO(P2): This is quite expensive, when 
            // after every subset effect end, we have to scan
            // the whole shape set

            // determine new subset range
            ::std::for_each( maSubsetShapes.begin(),
                             maSubsetShapes.end(),
                             ::boost::bind(&DrawShapeSubsetting::updateSubsetBounds,
                                           this,
                                           _1 ) );

            updateSubsets();

            return true;
        }

        namespace
        {
            /** Iterate over all action classification entries in the
                given range, pass each element range found to the
                given functor.

                This method extracts, for each of the different action
                classifications, the count and the ranges for each of
                them, and calls the provided functor with that
                information.

                @tpl FunctorT
                This is the functor's operator() calling signature,
                with eCurrElemClassification denoting the current
                classification type the functor is called for,
                nCurrElemCount the running total of elements visited
                for the given class (starting from 0), and
                rCurrElemBegin/rCurrElemEnd the range of the current
                element (i.e. the iterators from the start to the end
                of this element).
                <pre>
                bool operator()( IndexClassificator								 eCurrElemClassification
                				 sal_Int32										 nCurrElemCount,
								 const IndexClassificatorVector::const_iterator& rCurrElemBegin,
								 const IndexClassificatorVector::const_iterator& rCurrElemEnd );
                </pre>
                If the functor returns false, iteration over the
                shapes is immediately stopped.

                @param io_pFunctor
                This functor is called for every shape found.

                @param rBegin
                Start of range to iterate over

                @param rEnd
                End of range to iterate over

                @return the number of shapes found in the metafile
             */
            template< typename FunctorT > void iterateActionClassifications(
                FunctorT& 															 io_rFunctor,
                const DrawShapeSubsetting::IndexClassificatorVector::const_iterator& rBegin,
                const DrawShapeSubsetting::IndexClassificatorVector::const_iterator& rEnd )
            {
                sal_Int32 nCurrShapeCount( 0 );
                sal_Int32 nCurrParaCount( 0 );
                sal_Int32 nCurrLineCount( 0 );
                sal_Int32 nCurrSentenceCount( 0 );
                sal_Int32 nCurrWordCount( 0 );
                sal_Int32 nCurrCharCount( 0 );

                DrawShapeSubsetting::IndexClassificatorVector::const_iterator aLastShapeStart(rBegin);
                DrawShapeSubsetting::IndexClassificatorVector::const_iterator aLastParaStart(rBegin);
                DrawShapeSubsetting::IndexClassificatorVector::const_iterator aLastLineStart(rBegin);
                DrawShapeSubsetting::IndexClassificatorVector::const_iterator aLastSentenceStart(rBegin);
                DrawShapeSubsetting::IndexClassificatorVector::const_iterator aLastWordStart(rBegin);
                DrawShapeSubsetting::IndexClassificatorVector::const_iterator aLastCharStart(rBegin);

                DrawShapeSubsetting::IndexClassificatorVector::const_iterator aNext;
                DrawShapeSubsetting::IndexClassificatorVector::const_iterator aCurr( rBegin );
                while( aCurr != rEnd )
                {
                    // aNext will hold an iterator to the next element
                    // (or the past-the-end iterator, if aCurr
                    // references the last element). Used to pass a
                    // valid half-open range to the functors.
                    aNext = aCurr;
                    ++aNext;

                    switch( *aCurr )
                    {
                        default:
                            ENSURE_OR_THROW( false,
                                              "Unexpected type in iterateDocShapes()" );
                        case DrawShapeSubsetting::CLASS_NOOP:
                            // ignore NOOP actions
                            break;

                        case DrawShapeSubsetting::CLASS_SHAPE_START:
                            // regardless of ending action
                            // classifications before: a new shape
                            // always also starts contained elements
                            // anew
                            aLastShapeStart    =
                            aLastParaStart     =
                            aLastLineStart     =
                            aLastSentenceStart =
                            aLastWordStart     =
                            aLastCharStart     = aCurr;
                            break;

                        case DrawShapeSubsetting::CLASS_SHAPE_END:
                            if( !io_rFunctor( DrawShapeSubsetting::CLASS_SHAPE_END,
                                              nCurrShapeCount, 
                                              aLastShapeStart,
                                              aNext ) )
                            {
                                return;
                            }

                            ++nCurrShapeCount;
                            // FALLTHROUGH intended: shape end also
                            // ends lines
                        case DrawShapeSubsetting::CLASS_PARAGRAPH_END:
                            if( !io_rFunctor( DrawShapeSubsetting::CLASS_PARAGRAPH_END,
                                              nCurrParaCount, 
                                              aLastParaStart,
                                              aNext ) )
                            {
                                return;
                            }

                            ++nCurrParaCount;
                            aLastParaStart = aNext;
                            // FALLTHROUGH intended: para end also
                            // ends line
                        case DrawShapeSubsetting::CLASS_LINE_END:
                            if( !io_rFunctor( DrawShapeSubsetting::CLASS_LINE_END,
                                              nCurrLineCount, 
                                              aLastLineStart,
                                              aNext ) )
                            {
                                return;
                            }

                            ++nCurrLineCount;
                            aLastLineStart = aNext;

                            if( *aCurr == DrawShapeSubsetting::CLASS_LINE_END )
                            {
                                // DON'T fall through here, as a line
                                // does NOT end neither a sentence,
                                // nor a word. OTOH, all parent
                                // structures (paragraph and shape),
                                // which itself fall through to this
                                // code, DO end word, sentence and
                                // character cell.

                                // TODO(F1): Maybe a line should end a
                                // character cell, OTOH?
                                break;
                            }
                            // FALLTHROUGH intended
                        case DrawShapeSubsetting::CLASS_SENTENCE_END:
                            if( !io_rFunctor( DrawShapeSubsetting::CLASS_SENTENCE_END,
                                              nCurrSentenceCount, 
                                              aLastSentenceStart,
                                              aNext ) )
                            {
                                return;
                            }

                            ++nCurrSentenceCount;
                            aLastSentenceStart = aNext;
                            // FALLTHROUGH intended
                        case DrawShapeSubsetting::CLASS_WORD_END:
                            if( !io_rFunctor( DrawShapeSubsetting::CLASS_WORD_END,
                                              nCurrWordCount, 
                                              aLastWordStart,
                                              aNext ) )
                            {
                                return;
                            }

                            ++nCurrWordCount;
                            aLastWordStart = aNext;
                            // FALLTHROUGH intended
                        case DrawShapeSubsetting::CLASS_CHARACTER_CELL_END:
                            if( !io_rFunctor( DrawShapeSubsetting::CLASS_CHARACTER_CELL_END,
                                              nCurrCharCount, 
                                              aLastCharStart,
                                              aNext ) )
                            {
                                return;
                            }

                            ++nCurrCharCount;
                            aLastCharStart = aNext;
                            break;
                    }

                    aCurr = aNext;
                }
            }

            DrawShapeSubsetting::IndexClassificator mapDocTreeNode( DocTreeNode::NodeType eNodeType )
            {
                switch( eNodeType )
                {
                    case DocTreeNode::NODETYPE_INVALID:
                        // FALLTHROUGH intended
                    default:
                        OSL_ENSURE(false,
                                   "DrawShapeSubsetting::mapDocTreeNode(): unexpected node type");
                        return DrawShapeSubsetting::CLASS_NOOP;

                    case DocTreeNode::NODETYPE_LOGICAL_SHAPE:
                        // FALLTHROUGH intended
                    case DocTreeNode::NODETYPE_FORMATTING_SHAPE:
                        return DrawShapeSubsetting::CLASS_SHAPE_END;

                    case DocTreeNode::NODETYPE_FORMATTING_LINE:
                        return DrawShapeSubsetting::CLASS_LINE_END;

                    case DocTreeNode::NODETYPE_LOGICAL_PARAGRAPH:
                        return DrawShapeSubsetting::CLASS_PARAGRAPH_END;

                    case DocTreeNode::NODETYPE_LOGICAL_SENTENCE:
                        return DrawShapeSubsetting::CLASS_SENTENCE_END;

                    case DocTreeNode::NODETYPE_LOGICAL_WORD:
                        return DrawShapeSubsetting::CLASS_WORD_END;

                    case DocTreeNode::NODETYPE_LOGICAL_CHARACTER_CELL:
                        return DrawShapeSubsetting::CLASS_CHARACTER_CELL_END;
                };
            }

            /// Counts number of class occurences
            class CountClassFunctor
            {
            public:
                CountClassFunctor( DrawShapeSubsetting::IndexClassificator eClass ) : 
                    meClass( eClass ),
                    mnCurrCount(0)
                {
                }

                bool operator()( DrawShapeSubsetting::IndexClassificator								eCurrElemClassification,
                				 sal_Int32																/*nCurrElemCount*/,
								 const DrawShapeSubsetting::IndexClassificatorVector::const_iterator&	/*rCurrElemBegin*/,
								 const DrawShapeSubsetting::IndexClassificatorVector::const_iterator&	/*rCurrElemEnd*/ )
                {
                    if( eCurrElemClassification == meClass )
                        ++mnCurrCount;

                    return true; // never stop, count all occurences
                }

                sal_Int32 getCount() const
                {
                    return mnCurrCount;
                }

            private:
                DrawShapeSubsetting::IndexClassificator meClass;
                sal_Int32								mnCurrCount;
            };
        }

        sal_Int32 DrawShapeSubsetting::implGetNumberOfTreeNodes( const DrawShapeSubsetting::IndexClassificatorVector::const_iterator& rBegin,
                                                                 const DrawShapeSubsetting::IndexClassificatorVector::const_iterator& rEnd,
                                                                 DocTreeNode::NodeType 												  eNodeType ) const
        {
            const IndexClassificator eRequestedClass( 
                mapDocTreeNode( eNodeType ) );

            // create a counting functor for the requested class of
            // actions
            CountClassFunctor aFunctor( eRequestedClass );

            // count all occurences in the given range
            iterateActionClassifications( aFunctor, rBegin, rEnd );

            return aFunctor.getCount();
        }

        sal_Int32 DrawShapeSubsetting::getNumberOfTreeNodes( DocTreeNode::NodeType eNodeType ) const
        {
            ensureInitializedNodeTree();

            return implGetNumberOfTreeNodes( maActionClassVector.begin(),
                                             maActionClassVector.end(),
                                             eNodeType );
        }

        namespace
        {
            /** This functor finds the nth occurrence of a given
                action class.

                The operator() compares the given index value with the
                requested index, as given on the functor's
                constructor. Then, the operator() returns false,
                denoting that the requested action is found.
             */
            class FindNthElementFunctor
            {
            public:
                FindNthElementFunctor( sal_Int32 								nNodeIndex,
                                       DrawShapeSubsetting::IndexClassificator	eClass ) :
                    mnNodeIndex( nNodeIndex ),
                    meClass( eClass )
                {
                }

                bool operator()( DrawShapeSubsetting::IndexClassificator								eCurrElemClassification,
                				 sal_Int32																nCurrElemCount,
								 const DrawShapeSubsetting::IndexClassificatorVector::const_iterator&	rCurrElemBegin,
								 const DrawShapeSubsetting::IndexClassificatorVector::const_iterator&	rCurrElemEnd )
                {
                    if( eCurrElemClassification == meClass &&
                        nCurrElemCount == mnNodeIndex )
                    {
                        maLastBegin = rCurrElemBegin;
                        maLastEnd = rCurrElemEnd;

                        return false; // abort iteration, we've
                                      // already found what we've been
                                      // looking for
                    }

                    return true; // keep on truckin'
                }

                DrawShapeSubsetting::IndexClassificatorVector::const_iterator getBeginElement() const
                {
                    return maLastBegin;
                }

                DrawShapeSubsetting::IndexClassificatorVector::const_iterator getEndElement() const
                {
                    return maLastEnd;
                }

            private:
                sal_Int32														mnNodeIndex;
                DrawShapeSubsetting::IndexClassificatorVector::const_iterator	maLastBegin;
                DrawShapeSubsetting::IndexClassificatorVector::const_iterator	maLastEnd;
                DrawShapeSubsetting::IndexClassificator							meClass;
            };

            DocTreeNode makeTreeNode( const DrawShapeSubsetting::IndexClassificatorVector::const_iterator& rBegin,
                                      const DrawShapeSubsetting::IndexClassificatorVector::const_iterator& rStart,
                                      const DrawShapeSubsetting::IndexClassificatorVector::const_iterator& rEnd,
                                      DocTreeNode::NodeType												   eNodeType )
            {
                return DocTreeNode( ::std::distance(rBegin,
                                                    rStart),
                                    ::std::distance(rBegin,
                                                    rEnd),
                                    eNodeType );
            }
        }

        DocTreeNode DrawShapeSubsetting::implGetTreeNode( const IndexClassificatorVector::const_iterator&	rBegin,
                                                          const IndexClassificatorVector::const_iterator&	rEnd,
                                                          sal_Int32											nNodeIndex,
                                                          DocTreeNode::NodeType								eNodeType ) const
        {
            const IndexClassificator eRequestedClass( 
                mapDocTreeNode( eNodeType ) );

            // create a nth element functor for the requested class of
            // actions, and nNodeIndex as the target index
            FindNthElementFunctor aFunctor( nNodeIndex,
                                            eRequestedClass );

            // find given index in the given range
            iterateActionClassifications( aFunctor, rBegin, rEnd );

            return makeTreeNode( maActionClassVector.begin(),
                                 aFunctor.getBeginElement(),
                                 aFunctor.getEndElement(),
                                 eNodeType );
        }

        DocTreeNode DrawShapeSubsetting::getTreeNode( sal_Int32				nNodeIndex,
                                                      DocTreeNode::NodeType	eNodeType ) const
        {
            ensureInitializedNodeTree();

            return implGetTreeNode( maActionClassVector.begin(),
                                    maActionClassVector.end(),
                                    nNodeIndex,
                                    eNodeType );
        }

        sal_Int32 DrawShapeSubsetting::getNumberOfSubsetTreeNodes( const DocTreeNode&  		rParentNode,
                                                                   DocTreeNode::NodeType	eNodeType ) const
        {
            ensureInitializedNodeTree();

            // convert from vector indices to vector iterators
            const DrawShapeSubsetting::IndexClassificatorVector::const_iterator aBegin( maActionClassVector.begin() );
            const DrawShapeSubsetting::IndexClassificatorVector::const_iterator aParentBegin( aBegin + rParentNode.getStartIndex() );
            const DrawShapeSubsetting::IndexClassificatorVector::const_iterator aParentEnd( aBegin + rParentNode.getEndIndex() );

            return implGetNumberOfTreeNodes( aParentBegin,
                                             aParentEnd,
                                             eNodeType );
        }

        DocTreeNode DrawShapeSubsetting::getSubsetTreeNode( const DocTreeNode& 		rParentNode,
                                                            sal_Int32				nNodeIndex,
                                                            DocTreeNode::NodeType	eNodeType ) const
        {
            ensureInitializedNodeTree();

            // convert from vector indices to vector iterators
            const DrawShapeSubsetting::IndexClassificatorVector::const_iterator aBegin( maActionClassVector.begin() );
            const DrawShapeSubsetting::IndexClassificatorVector::const_iterator aParentBegin( aBegin + rParentNode.getStartIndex() );
            const DrawShapeSubsetting::IndexClassificatorVector::const_iterator aParentEnd( aBegin + rParentNode.getEndIndex() );

            return implGetTreeNode( aParentBegin,
                                    aParentEnd,
                                    nNodeIndex,
                                    eNodeType );
        }

        const VectorOfDocTreeNodes& DrawShapeSubsetting::getActiveSubsets() const
        {
            return maCurrentSubsets;
        }
        
    }
}
