////////////////////////////////////////////////////////////////////////////////
//
//  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.
//
////////////////////////////////////////////////////////////////////////////////

package mx.collections
{

import org.apache.royale.events.EventDispatcher;

//import mx.collections.errors.ChildItemPendingError;
//import mx.collections.errors.ItemPendingError;
import mx.core.mx_internal;
import mx.events.CollectionEvent;
import mx.events.CollectionEventKind;
import mx.events.FlexEvent;
import mx.utils.ObjectUtil;
import mx.utils.UIDUtil;

use namespace mx_internal;

/**
 *  The HierarchicalCollectionViewCursor class defines a 
 *  cursor for a hierarchical view of a standard collection. 
 *  The collection that this cursor walks across need not be hierarchical - it may be flat. 
 *  This class delegates to the IHierarchicalData for information regarding the tree 
 *  structure of the data it walks across. 
 *  
 *  @see HierarchicalCollectionView
 *  
 *  @langversion 3.0
 *  @playerversion Flash 9
 *  @playerversion AIR 1.1
 *  @productversion Flex 3
 */
public class HierarchicalCollectionViewCursor extends EventDispatcher
                                    implements IHierarchicalCollectionViewCursor
{
//    include "../core/Version.as";
        
    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------

    /**
     *  Constructor.
     *
     *  @param collection The HierarchicalCollectionView instance referenced by this cursor.
     *
     *  @param model The source data collection.
     *
     *  @param hierarchicalData The data used to create the HierarchicalCollectionView instance.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function HierarchicalCollectionViewCursor(
                            collection:HierarchicalCollectionView,
                            model:ICollectionView,
                            hierarchicalData:IHierarchicalData)
    {
        super();
        
        //fields
        this.collection = collection;
        this.hierarchicalData = hierarchicalData;
        this.model = model;
        
        //events
        collection.addEventListener(CollectionEvent.COLLECTION_CHANGE, collectionChangeHandler, false/*, 0, true*/);

        //init
        modelCursor = model.createCursor();
        
        //check to see if the model has more than one top level items
        more = model.length > 1;
    }

    /**
     *  Finalizes the cursor, to clean up resources.
     *  Required because weak references are not available in JS.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Royale 0.9.8
     */
    public function finalizeThis():void
    {
        if (modelCursor) modelCursor.finalizeThis();
        if (collection) collection.removeEventListener(CollectionEvent.COLLECTION_CHANGE, collectionChangeHandler);
    }
    
    //--------------------------------------------------------------------------
    //
    // Variables
    //
    //--------------------------------------------------------------------------
    
    /**
     *  @private
     */
    private var hierarchicalData:IHierarchicalData;
    
    /**
     *  @private
     *  The bookmark representing the position of the current node in its siblings collection
     */
    private var currentChildBookmark:CursorBookmark = CursorBookmark.FIRST;

    /**
     *  @private
     *  Its effective offset into the "array".
     */
    private var currentIndex:int = 0;
    
    /**
     *  @private
     *  The depth of the current node.
     */
    private var _currentDepth:int = 1; 
    
    /**
     *  @private
     *  The current set of childNodes we are walking.
     */
    private var childNodes:ICollectionView;
    
    /**
     *  @private
     *  The current set of parentNodes that we have walked from
     */
    private var parentNodes:Array = [];
    
    /**
     *  @private
     *  A stack of the currentChildBookmark in all parents of the currentNode.
     */
    private var parentBookmarkStack:Array = [];

    /**
     *  @private
     *  The collection that stores the user data
     */
    private var model:ICollectionView;
    
    /**
     *  @private
     *  The collection wrapper of the model
     */
    private var collection:HierarchicalCollectionView;
    
    /**
     *  @private
     *  Flag indicating model has more data
     */ 
    private var more:Boolean;
    
    /**
     *  @private
     *  Cursor from the model
     */ 
    private var modelCursor:IViewCursor;

    //--------------------------------------------------------------------------
    //
    //  Properties
    //
    //--------------------------------------------------------------------------

    //----------------------------------
    // index
    //----------------------------------
    /**
     * @private
     */
    public function get index():int
    {
        return currentIndex;
    }
    
    //----------------------------------
    //  bookmark
    //----------------------------------
    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get bookmark():CursorBookmark
    {
        return new CursorBookmark(currentIndex.toString());
    }

    //---------------------------------- 
    //  current
    //----------------------------------
    
    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get current():Object
    {
        if (parentBookmarkStack.length == 0)
        {
            return modelCursor.current;
        }
        else
        {
            var childCursor:IViewCursor = childNodes.createCursor();
            //try
            //{
                
                childCursor.seek(currentChildBookmark);
                return childCursor.current;
                /*
            }
            catch (e:ItemPendingError)
            {
                return null;
            }*/
        }
        
        return null;
    }


    //---------------------------------
    // currentDepth
    //---------------------------------
    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get currentDepth():int
    {
        return _currentDepth;
    }


    //----------------------------------
    //  beforeFirst
    //----------------------------------
    /**
     *  @copy mx.collections.IViewCursor#beforeFirst
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get beforeFirst():Boolean
    {
        return currentIndex < 0 && current == null;
    }
    
    //----------------------------------
    //  afterLast
    //----------------------------------
    /**
     *  @copy mx.collections.IViewCursor#afterLast
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get afterLast():Boolean
    {
        return currentIndex >= collection.length && current == null;
    } 
    
    //----------------------------------
    //  view
    //----------------------------------
    /**
     *  @copy mx.collections.IViewCursor#view
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get view():ICollectionView
    {
        return model;
    }

    //--------------------------------------------------------------------------
    //
    //  Methods
    //
    //--------------------------------------------------------------------------
    
    /**
     *  <p>Note that for this class, the view does not need to be sorted in order to
     *  call this method. Also, if the item cannot be found, the cursor location is
     *  left on the last queried object.</p>
     *
     *  @inheritDoc
     * 
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function findAny(valuesToMatch:Object):Boolean
    {
        seek(CursorBookmark.FIRST);
        
        do
        {
            if (ObjectUtil.valuesAreSubsetOfObject(valuesToMatch, hierarchicalData.getData(current)))
                return true;
        }
        while(moveNext());

        return false;
    }

    /**
     *  <p>Note that for this class, the view does not need to be sorted in order to
     *  call this method. Also, if the item cannot be found, the cursor location is
     *  left on the last queried object.</p>
     *
     *  @inheritDoc
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function findFirst(values:Object):Boolean
    {
        return findAny(values);
    }

    /**
     *  <p>Note that for this class, the view does not need to be sorted in order to
     *  call this method. Also, if the item cannot be found, the cursor location is
     *  left on the last queried object.</p>
     *
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function findLast(valuesToMatch:Object):Boolean
    {
        seek(CursorBookmark.LAST);
        
        do
        {
            if (ObjectUtil.valuesAreSubsetOfObject(valuesToMatch, hierarchicalData.getData(current)))
                return true;
        }
        while(movePrevious());

        return false;
    }


    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function moveNext():Boolean 
    {
        var currentNode:Object = current;
        //if there is no currentNode then we can't move forward and must be off the ends
        if (currentNode == null) 
            return false; 
        
        var childCursor:IViewCursor;
        var uid:String = UIDUtil.getUID(currentNode);

        // If current node is a branch and is open, the first child is our next item so return it
        if (collection.openNodes[uid] &&
            hierarchicalData.canHaveChildren(currentNode) && 
            hierarchicalData.hasChildren(currentNode))
        {
                var previousChildNodes:ICollectionView = childNodes;
                childNodes = collection.getChildren(currentNode);
                if (childNodes.length > 0)
                {
                    parentBookmarkStack.push(currentChildBookmark);
                    parentNodes.push(currentNode);
                    currentChildBookmark = CursorBookmark.FIRST;
                    childCursor = childNodes.createCursor();
                    //try
                    //{
                        currentNode = childCursor.current;
                        // update parent map
                        updateParentMap(currentNode);
                    //}
                    //catch (e:ItemPendingError)
                    //{
                    //    currentNode = null;
                    //    throw new ChildItemPendingError(e.message);
                    //}
                    currentIndex++;
                    _currentDepth++;
                    return true;
                }
                else
                {
                    childNodes = previousChildNodes;
                }
        }

        // Otherwise until we find the next child (could be on any level)
        while (true)
        {
            // If we hit the end of this list, pop up a level.
            if(childNodes != null)
            {
                childCursor = childNodes.createCursor();
                //try
                //{
                    childCursor.seek(currentChildBookmark);
                    childCursor.moveNext();
                //}
                //catch(e:ItemPendingError)
                //{
                //    childCursor.seek(CursorBookmark.FIRST);
                //}
             }
             
             var grandParent:Object;
            
            if (childNodes != null && 
                childNodes.length > 0 && 
                (childCursor.bookmark == CursorBookmark.LAST || childCursor.afterLast))
            {
                //check for the end of the tree here.
                if (parentBookmarkStack.length < 1 && !more)  
                {
                    currentNode = null;
                    currentIndex++;
                    _currentDepth = 1;
                    return false;
                }
                else 
                {  
                    //pop up to parent
                    currentNode = parentNodes.pop(); 
                    //get parents siblings 
                    grandParent = parentNodes[parentNodes.length-1];
                    //we could probably assume that a non-null grandparent has descendants 
                    //but the analogy only goes so far... 
                    if (grandParent != null && 
                        hierarchicalData.canHaveChildren(grandParent) &&
                        hierarchicalData.hasChildren(grandParent))
                    {
                        childNodes = collection.getChildren(grandParent);
                    }
                    else
                    {
                        childNodes = null;
                    }
                    //get new current nodes index
                    currentChildBookmark = parentBookmarkStack.pop();
                    //pop the level up one
                    _currentDepth--;
                }
            }
            else
            {
                //if no childnodes then we're probably at the top level
                if (parentBookmarkStack.length == 0)
                {
                    //check for more top level siblings
                    //and if we're here the depth should be 1
                    _currentDepth = 1;
                    more = modelCursor.moveNext();
                    if (more) 
                    {
                        currentNode = modelCursor.current;
                        break;
                    } 
                    else 
                    {
                        //at the end of the tree
                        _currentDepth = 1;
                        currentIndex++;  //this should push us to afterLast
                        currentNode = null;
                        return false;
                    }
                }
                else 
                {
                    //get the next child node
                    //try
                    //{
                        childCursor = childNodes.createCursor();
                        childCursor.seek(currentChildBookmark);
                        
                        childCursor.moveNext();
                        currentChildBookmark = childCursor.bookmark;
                        
                        currentNode = childCursor.current;
                        break;
                    //}
                        /*
                    catch(e:ItemPendingError)
                    {
                        //pop up to parent
                        currentNode = parentNodes.pop(); 
                        //get parents siblings 
                        grandParent = parentNodes[parentNodes.length-1];
                        //we could probably assume that a non-null grandparent has descendants 
                        //but the analogy only goes so far... 
                        if (grandParent != null && 
                            hierarchicalData.canHaveChildren(grandParent) &&
                            hierarchicalData.hasChildren(grandParent))
                        {
                            childNodes = collection.getChildren(grandParent);
                        }
                        else
                        {
                            childNodes = null;
                        }
                        //get new current nodes index
                        currentChildBookmark = parentBookmarkStack.pop();
                        //pop the level up one
                        _currentDepth--;
                        
                        if (parentBookmarkStack.length == 0)
                        {
                            //check for more top level siblings
                            //and if we're here the depth should be 1
                            _currentDepth = 1;
                            more = modelCursor.moveNext();
                            if (more) 
                            {
                                currentNode = modelCursor.current;
                                throw new ChildItemPendingError(e.message);
                            } 
                            else 
                            {
                                //at the end of the tree
                                _currentDepth = 1;
                                currentIndex++;  //this should push us to afterLast
                                currentNode = null;
                                return false;
                            }
                        }
                        break;
                    }
                        */
                }
            } 
        }
        
        updateParentMap(currentNode);

        currentIndex++;
        return true;
    }
    
    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function movePrevious():Boolean
    {
        var currentNode:Object = current;
        // If there are no items, there's no current node, so return false.
        if (currentNode == null)
            return false;
        
        var childCursor:IViewCursor;
        
        //if not at top level
        if (parentNodes && parentNodes.length > 0)
        {
            if (currentChildBookmark == CursorBookmark.FIRST)
            {
                //at the first node in this branch so move to parent
                currentNode = parentNodes.pop();
                currentChildBookmark = parentBookmarkStack.pop();
                var grandParent:Object = parentNodes[parentNodes.length-1];
                //we could probably assume that a non-null grandparent has descendants 
                //but the analogy only goes so far... 
                if (grandParent != null && 
                    hierarchicalData.canHaveChildren(grandParent) &&
                    hierarchicalData.hasChildren(grandParent))
                {
                    childNodes = collection.getChildren(grandParent);
                }
                else
                {
                    childNodes = null;  
                }
                _currentDepth--;
                currentIndex--;
                return true;
            }
            else 
            {
                // get previous child sibling
                //try 
                //{
                    childCursor = childNodes.createCursor();
                    
                    childCursor.seek(currentChildBookmark);
                    childCursor.movePrevious();
                    currentChildBookmark = childCursor.bookmark;
                    currentNode = childCursor.current;
                    
                    //try
                    //{
                        // this is needed because the cursor bookmark for
                        // the zero position and CursorBookmark.FIRST is not 
                        // same. this condition is encountered while doing
                        // a movePrevious()
                        childCursor.movePrevious();
                        if (childCursor.bookmark == CursorBookmark.FIRST)
                            currentChildBookmark = CursorBookmark.FIRST;
                        /*
                    }
                    catch (e:ItemPendingError)
                    {
                    }
                }
                catch(e:ItemPendingError)
                {
                    //lets try to recover
                    return false;
                }
                        */
            }   
        }
        //handle top level siblings
        else 
        {
            more = modelCursor.movePrevious();
            if (more)
            {
                //move back one node and then loop through children
                currentNode = modelCursor.current;
            }
            //if past the begining of the tree return false
            else 
            {
                //currentIndex--;  //should be before first
                currentIndex = -1;
                currentNode = null;
                return false;
            }
        }
        while (true)
        {
            // If there are children, walk backwards on the children
            // and consider youself after your children.
            if (collection.openNodes[UIDUtil.getUID(currentNode)] &&
                hierarchicalData.canHaveChildren(currentNode) &&
                hierarchicalData.hasChildren(currentNode))
            {
                var previousChildNodes:ICollectionView = childNodes;
                childNodes = collection.getChildren(currentNode);
                if (childNodes.length > 0)
                {
                    parentBookmarkStack.push(currentChildBookmark);
                    parentNodes.push(currentNode);
                    // if the child collection has only one item then set the
                    // bookmark to first
                    if (childNodes.length == 1)
                        currentChildBookmark = CursorBookmark.FIRST;
                    else
                        currentChildBookmark = CursorBookmark.LAST;
                    
                    childCursor = childNodes.createCursor();
                    //try
                    //{
                        childCursor.seek(currentChildBookmark);
                        currentNode = childCursor.current;
                    /*}
                    catch (e:ItemPendingError)
                    {
                        try
                        {
                            childCursor.seek(CursorBookmark.FIRST);
                            while (!childCursor.afterLast)
                            {
                                currentNode = childCursor.current;
                                childCursor.moveNext();
                            }
                        }
                        catch(e1:ItemPendingError)
                        {
                        }
                        
                        throw new ChildItemPendingError(e.message);
                    }*/
                    _currentDepth++;
                }
                else
                {
                    childNodes = previousChildNodes;
                    break;
                }
            }   
            else
            {
                //No more open branches so we'll bail
                break;
            }
        }

        updateParentMap(currentNode);

        currentIndex--;
        return true;
    }

    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function seek(bookmark:CursorBookmark, offset:int=0,
                         prefetch:int = 0):void
    {
        var value:int;
        
        if (bookmark == CursorBookmark.FIRST)
        {
            value = 0;
        }
        else if (bookmark == CursorBookmark.CURRENT)
        {
            value = currentIndex;
        }
        else if (bookmark == CursorBookmark.LAST)
        {
            value = collection.length - 1;
        }
        else
        {
            value = int(bookmark.value);
        }
        
        value = Math.max(Math.min(value + offset, collection.length), 0);
        var dc:int = Math.abs(currentIndex - value);
        var de:int = Math.abs(collection.length - value);
        var movedown:Boolean = true;
        // if we're closer to the current than the beginning
        if (dc < value)
        {
            // if we're closer to the end than the current position
            if (de < dc)
            {
                moveToLast();

                if (de == 0)
                {       
                    // if de = 0; we need to be "off the end"
                    moveNext();
                    value = 0;
                }
                else
                {
                    value = de - 1;
                }
                movedown = false;
            }
            else
            {
                movedown = currentIndex < value;
                value = dc;
                // if current is off the end, reset
                if (currentIndex == collection.length)
                {
                    value--;
                    moveToLast();
                }
            }
        }
        else // we're closer to the beginning than the current
        {
            // if we're closer to the end than the beginning
            if (de < value)
            {
                moveToLast();
                if (de == 0)
                {       
                    // if de = 0; we need to be "off the end"
                    moveNext();
                    value = 0;
                }
                else
                {
                    value = de - 1;
                }
                movedown = false;
            }
            else
            {
                moveToFirst();
            }
        }

        if (movedown)
        {
            while (value && value > 0) 
            {
                moveNext();
                value--;
            }
        }
        else
        {
            while (value && value > 0)  
            {
                movePrevious();
                value--;
            }
        }    
    }
    
    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function insert(item:Object):void
    {
        var parent:Object = collection.getParentItem(current);
        collection.addChildAt(parent, item, currentIndex);
    }
    
    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function remove():Object
    {
        var obj:Object = current;
        var parent:Object = collection.getParentItem(current);
        collection.removeChild(parent, current);
        return obj;
    }
    
    /**
     *  @private
     *  Determines if a node is visible on the screen
     */
    private function isItemVisible(node:Object):Boolean
    { 
        var parentNode:Object = collection.getParentItem(node);
        while (parentNode != null)
        {
            if (collection.openNodes[UIDUtil.getUID(parentNode)] == null)
                return false;
            
            parentNode = collection.getParentItem(parentNode);
        }
        return true;
    }
    
    /**
     * @private
     * Update the parent map.
     */ 
    private function updateParentMap(currentNode:Object):void
    {
        if (currentNode != null)
        {
            var uid:String = UIDUtil.getUID(currentNode);
            collection.addParentMapping(uid, parentNodes[parentNodes.length - 1], false);
        }
    }

    /**
     *  @private
     *  Creates a stack of parent nodes by walking upwards
     */
    private function getParentStack(node:Object):Array
    {
        var nodeParents:Array = [];
        
        // Make a list of parents of the node.
        var obj:Object = collection.getParentItem(node);
        while (obj != null)
        {
            nodeParents.unshift(obj);
            obj = collection.getParentItem(obj);
        }
        return nodeParents;
    }

    /**
     *  @private
     *  When something happens to the tree, find out if it happened
     *  to children that occur before the current child in the tree walk.
     */
    private function isNodeBefore(node:Object, currentNode:Object):Boolean
    {
        if (currentNode == null)
            return false;

        var i:int;
        var tmpChildNodes:ICollectionView;
        var sameParent:Object;

        var nodeParents:Array = getParentStack(node);
        var curParents:Array = getParentStack(currentNode);
        
        var cursor:IViewCursor;
        var child:Object;

        // Starting from the root, compare parents
        // (assumes the root must be the same).
        while (nodeParents.length != 0 && curParents.length != 0)
        {
            var nodeParent:Object = nodeParents.shift();
            var curParent:Object = curParents.shift();
            
            // If not the same parentm then which ever one is first
            // in the child list is before the other.
            if (nodeParent != curParent)
            {
                // The last parent must be common.
                sameParent = collection.getParentItem(nodeParent);
                
                // Get the child list.
                if (sameParent != null && 
                    hierarchicalData.canHaveChildren(sameParent) &&
                    hierarchicalData.hasChildren(sameParent))
                {
                    tmpChildNodes = collection.getChildren(sameParent);
                }
                else
                {
                    tmpChildNodes = model; 
                }
                // Walk it until you hit one or the other.
                {
                    cursor = tmpChildNodes.createCursor();
                    //try
                    //{
                        cursor.seek(CursorBookmark.FIRST, i);
                        child = cursor.current;
                    //}
                    //catch (e:ItemPendingError)
                    //{
                        // item pending - this may never happen
                    //    return false;
                    //}
                    
                    if (child == curParent)
                        return false;

                    if (child == nodeParent)
                        return true;
                }
            }
        }

        if (nodeParents.length)
            node = nodeParents.shift();
        if (curParents.length)
            currentNode = curParents.shift();

        // If we get here, they have the same parentage or one or both
        // had a root parent. Who's first?
        tmpChildNodes = model; 
        cursor = tmpChildNodes.createCursor();
        while (!cursor.afterLast)
        {
            child = cursor.current;

            if (child == currentNode)
                return false;

            if (child == node)
                return true;
            
            //try
            //{
                cursor.moveNext();
            //}
            //catch (e:ItemPendingError)
            //{
                // item pending
            //    return false;
            //}
        }
        return false;
    }
    
    /**
     *  @private
     */
    private function moveToFirst():void
    {
        childNodes = null;
        
        //first move to the begining of the top level collection
        // let it throw an IPE, the classes using this cursor will handle it
        modelCursor.seek(CursorBookmark.FIRST, 0);
        
        more = model.length > 1;
        currentChildBookmark = CursorBookmark.FIRST;
        parentNodes = [];
        parentBookmarkStack = [];
        currentIndex = 0;
        _currentDepth = 1;
    }
    
    /**
     *  @private
     */
    public function moveToLast():void
    {
        childNodes = null;
        parentBookmarkStack = [];
        _currentDepth = 1;
        parentNodes = [];

        //first move to the end of the top level collection
        // let it throw an IPE, the classes using this cursor will handle it
        modelCursor.seek(CursorBookmark.LAST, 0);
        
        //if its a branch and open then get children for the last item
        var currentNode:Object = modelCursor.current;
        //if current node is open get its children
        while (collection.openNodes[UIDUtil.getUID(currentNode)] &&
               hierarchicalData.canHaveChildren(currentNode) &&
               hierarchicalData.hasChildren(currentNode))
        {
            var previousChildNodes:ICollectionView = childNodes;
            childNodes = collection.getChildren(currentNode);
            if (childNodes != null && childNodes.length > 0)
            {
                var childCursor:IViewCursor = childNodes.createCursor();
                //try
                //{
                    childCursor.seek(CursorBookmark.LAST);
                //}
                //catch (e:ItemPendingError)
                //{
                    // just break because if the last child item is pending
                    // return its parent
                //    break;
                //}
                parentNodes.push(currentNode);
                parentBookmarkStack.push(currentChildBookmark);                
                currentNode = childCursor.current;
                currentChildBookmark = CursorBookmark.LAST;
                //try
                //{
                    // this is needed because the cursor bookmark for
                    // the zero position and CursorBookmark.FIRST is not 
                    // same. this condition is encountered while doing
                    // a movePrevious()
                    childCursor.movePrevious();
                    if (childCursor.bookmark == CursorBookmark.FIRST)
                        currentChildBookmark = CursorBookmark.FIRST;
                //}
                //catch (e:ItemPendingError)
                //{
                //}
                _currentDepth++;
            }
            else 
            {
                childNodes = previousChildNodes;
                break;
            }
        }
        currentIndex = collection.length - 1;
    }
    
    //--------------------------------------------------------------------------
    //
    //  Event handlers
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    public function collectionChangeHandler(event:CollectionEvent):void
    {
        var i:int;
        var n:int;
        var changingNode:Object;
        var parentOfChangingNode:Object;
        var parentOfCurrentNode:Object;
        var parentStack:Array;
        var isBefore:Boolean = false;
        var parentOfChangingNodeIndex:int;
        var isChangingNodeParentAncestorOfSelectedNode:Boolean;
        var bookmarkInChangingCollection:CursorBookmark;
		var changingNodeCollectionBookmarkIndex:int;
        var changingNodeAndSiblings:ICollectionView;
        var changingCollectionCursor:IViewCursor;

        if (event.kind == CollectionEventKind.ADD)
        {
            n = event.items.length;
            if (event.location <= currentIndex)
            {
                currentIndex += n;
                isBefore = true;
            }

            parentStack = getParentStack(current);
            parentOfCurrentNode = parentStack[parentStack.length - 1];

            for (i = 0; i < n; i++)
            {
                changingNode = event.items[i];
                if (isBefore)
                {
                    // if the added node is before the current
                    // and they share parents then we have to
                    // adjust the currentChildIndex or
                    // the stack of child indexes.
                    parentOfChangingNode = collection.getParentItem(changingNode);
                    changingNodeAndSiblings = collection.getChildren(parentOfChangingNode); 

                    if (parentOfChangingNode == parentOfCurrentNode)
                    {
                        if (changingNodeAndSiblings != null)
                        {
                            changingCollectionCursor = changingNodeAndSiblings.createCursor();
                            //try
                            //{
                                changingCollectionCursor.seek(currentChildBookmark);
                                changingCollectionCursor.moveNext();
                                currentChildBookmark = changingCollectionCursor.bookmark;
                            //}
                            //catch (e:ItemPendingError)
                            //{
                            //}
                        }
                    }
                    else {
                        parentOfChangingNodeIndex = parentStack.indexOf(parentOfChangingNode);
                        isChangingNodeParentAncestorOfSelectedNode = parentOfChangingNodeIndex != -1;
                        if (isChangingNodeParentAncestorOfSelectedNode)
                        {
                            if (changingNodeAndSiblings != null)
                            {
                                changingNodeCollectionBookmarkIndex = parentOfChangingNodeIndex + 1;
                                changingCollectionCursor = changingNodeAndSiblings.createCursor();
                                bookmarkInChangingCollection = parentBookmarkStack[changingNodeCollectionBookmarkIndex];
                                //try
                                //{
                                    changingCollectionCursor.seek(bookmarkInChangingCollection);
                                    changingCollectionCursor.moveNext();
                                //}
                                //catch (e:ItemPendingError)
                                //{
                                //}
                                parentBookmarkStack[changingNodeCollectionBookmarkIndex] = changingCollectionCursor.bookmark;
                            }
                        }
                    }
                }
            }
            
        }
        else if (event.kind == CollectionEventKind.REMOVE)
        {
            n = event.items.length;
            if (event.location <= currentIndex)
            {
                var lastIndexAffectedByDeletion:int = event.location + n;
                var isCurrentIndexAmongRemovedNodes:Boolean = lastIndexAffectedByDeletion >= currentIndex;
                var currentItemNotFoundAmongItsSiblings:Boolean = isCurrentIndexAmongRemovedNodes ? false : (!afterLast && !beforeFirst && current == null);

                if (isCurrentIndexAmongRemovedNodes || currentItemNotFoundAmongItsSiblings)
                {
                    // the list classes expect that we
                    // leave the cursor on whatever falls
                    // into that slot
                    var indexToReturnTo:int = isCurrentIndexAmongRemovedNodes ? event.location : currentIndex - n;
                    moveToFirst();
                    seek(CursorBookmark.FIRST, indexToReturnTo);

                    return;
                }

                currentIndex -= n;
                isBefore = true;
            }

            parentStack = getParentStack(current);
            parentOfCurrentNode = parentStack[parentStack.length - 1];

            for (i = 0; i < n; i++)
            {
                changingNode = event.items[i];
                if (isBefore)
                {
                    // if the removed node is before the current
                    // and they share parents then we have to
                    // adjust the currentChildIndex or
                    // the stack of child indexes.
                    parentOfChangingNode = collection.getParentItem(changingNode);
                    changingNodeAndSiblings = collection.getChildren(parentOfChangingNode);

                    if (parentOfChangingNode == parentOfCurrentNode)
                    {
                        if(currentChildBookmark == CursorBookmark.LAST)
                            break;

                        if (changingNodeAndSiblings != null)
                        {
                            changingCollectionCursor = changingNodeAndSiblings.createCursor();
                            //try
                            //{
                                changingCollectionCursor.seek(currentChildBookmark);
                                changingCollectionCursor.movePrevious();
                                currentChildBookmark = changingCollectionCursor.bookmark;
                            //}
                            //catch (e:ItemPendingError)
                            //{
                            //}
                        }
                    }
                    else {
                        parentOfChangingNodeIndex = parentStack.indexOf(parentOfChangingNode);
                        isChangingNodeParentAncestorOfSelectedNode = parentOfChangingNodeIndex != -1;
                        if (isChangingNodeParentAncestorOfSelectedNode)
                        {
                            if (changingNodeAndSiblings != null)
                            {
                                changingNodeCollectionBookmarkIndex = parentOfChangingNodeIndex + 1;
                                changingCollectionCursor = changingNodeAndSiblings.createCursor();
                                bookmarkInChangingCollection = parentBookmarkStack[changingNodeCollectionBookmarkIndex];

                                if(bookmarkInChangingCollection == CursorBookmark.LAST)
                                    break;

                                //try
                                //{
                                    changingCollectionCursor.seek(bookmarkInChangingCollection);
                                    changingCollectionCursor.movePrevious();
                                //}
                                //catch (e:ItemPendingError)
                                //{
                                //}
                                parentBookmarkStack[changingNodeCollectionBookmarkIndex] = changingCollectionCursor.bookmark;
                            }
                        }
                    }
                }
            }
        }
        else if (event.kind == CollectionEventKind.RESET)
        {
            // update the source collection and the cursor
            model = collection.treeData;
            modelCursor = model.createCursor();
            // dispatch CURSOR_UPDATE event
            //collection.dispatchEvent(new FlexEvent(FlexEvent.CURSOR_UPDATE));
        }
        else if (event.kind == CollectionEventKind.REFRESH)
        {
            // find the correct index of the item
            if (!(beforeFirst || afterLast))
            {
                findFirst(current);
            }
        }
    }
}
}
