 import spark.components.supportClasses.GroupBase;
 import spark.components.supportClasses.ScrollerLayout;
 import spark.components.supportClasses.SkinnableComponent;
+import spark.core.IGraphicElement;
 import spark.core.IViewport;
 import spark.core.NavigationUnit;
 import spark.effects.Animate;

    
    //--------------------------------------------------------------------------
    // 
    // Event Handlers
    //
    //--------------------------------------------------------------------------
    
    /**
     *  @private
     *  Helper function for checkScrollPositionsOnUpdateComplete.  
     */
    private function getMotionPathCurrentVelocity(mp:MotionPath,currentTime:Number,totalTime:Number):Number
    {
        // Determine the fraction of the effect that has already played.
        var fraction:Number = currentTime / totalTime;

        // Now we need to determine the effective velocity at the effect's current position.
        // Here we use a "poor man's" approximation that doesn't require us to know any of the
        // derivative functions associated with the motion path.  We sample the position at two
        // time values very close together and assume the velocity slope is a straight line 
        // between them.  The smaller the distance between the two time values, the closer the 
        // result will be to the "instantaneous" velocity.
        const TINY_DELTA_TIME:Number = 0.00001; 
        var value1:Number = Number(mp.getValue(fraction));
        var value2:Number = Number(mp.getValue(fraction + (TINY_DELTA_TIME / totalTime)));
        return (value2 - value1) / TINY_DELTA_TIME;
    }
    
    /**
     *  @private 
     */
    private function checkScrollPositionsOnUpdateComplete(event:FlexEvent):void
    {
        viewport.removeEventListener(FlexEvent.UPDATE_COMPLETE, 
            checkScrollPositionsOnUpdateComplete);
        
        var curVelocity:Point;
        
        // Determine the new maximum valid scroll positions
        var maxVSP:Number = viewport.contentHeight > viewport.height ? 
            viewport.contentHeight-viewport.height : 0; 
        var maxHSP:Number = viewport.contentWidth > viewport.width ? 
            viewport.contentWidth-viewport.width : 0;
        
        // Determine whether we possibly need to re-throw because of changed max positions.
        var didNotThrowFarEnough:Boolean = throwReachedMaximumScrollPosition &&
            (throwFinalVSP < maxVSP || throwFinalHSP < maxHSP);
        var threwTooFar:Boolean = (throwFinalVSP > maxVSP || throwFinalHSP > maxHSP);

        if (throwEffect && throwEffect.isPlaying && (didNotThrowFarEnough || threwTooFar))
        {
            // There's currently a throw animation playing, and it's throwing to a 
            // now-incorrect position.  We're going to stop the animation and start
            // a better one to continue the throw.
            
            var velX:Number = 0;
            var velY:Number = 0;

            // Get the current position of the existing throw animation
            var effectTime:Number = throwEffect.playheadTime;

            // It's possible for playheadTime to not be set if we're getting it
            // before the first animation timer call.
            if (isNaN(effectTime))
                effectTime = 0;

            var effectDuration:Number = throwEffect.duration;
            
            // Now get the current effective velocity for each motionpath in the animation. 
            for (var t:int = 0; t < throwEffect.motionPaths.length; t++)
            {
                var vel:Number = getMotionPathCurrentVelocity(throwEffect.motionPaths[t], effectTime, effectDuration);

                // The property can only either be horizontalScrollPosition or verticalScrollPosition
                if (throwEffect.motionPaths[t].property == "horizontalScrollPosition")
                    velX = vel;
                else
                    velY = vel;
            }

            // Stop the existing throw animation now that we've determined its current velocities.
            stoppedPreemptively = true;
            throwEffect.stop();

            // Now perform a new throw to get us to the right position.
            performThrow(-velX, -velY);
        }
        else if (!inTouchInteraction)
        {
            // No touch interaction is in effect, but the content may be sitting at
            // a scroll position that is now invalid.  Note that performThrow will
            // only actually perform a throw if the positions actually are invalid.
            performThrow(0, 0);
        }
    }
    
    /**
     *  @private 
     */
    private function viewport_propertyChangeHandler(event:PropertyChangeEvent):void
    {
        switch(event.property) 
        {
            case "contentWidth": 
            case "contentHeight": 
                invalidateSkin();
                if (getStyle("interactionMode") == InteractionMode.TOUCH)
                {
                    // If the content size changed, then the valid scroll position ranges 
                    // may have changed.  In this case, we need to schedule an updateComplete 
                    // handler to check and potentially correct the scroll positions. 
                    viewport.addEventListener(FlexEvent.UPDATE_COMPLETE, 
                        checkScrollPositionsOnUpdateComplete);
                }
                break;
        }
    }
    
    /**
     *  @private 
     *  Listens for any focusIn events from descendants 
     */ 
    override protected function focusInHandler(event:FocusEvent):void
    {
        super.focusInHandler(event);
        
        // When we gain focus, make sure the focused element is visible
        if (viewport && ensureFocusedElementIsVisible)
        {
            var elt:IVisualElement = focusManager.getFocus() as IVisualElement; 
            lastFocusedElement = elt;
            
            if (elt)
                scrollElementIntoView(elt);
        }
        
    }
    
    /**
     *  @private
     *  Scrolls the viewport so the element is visible in the viewable area  
     */ 
    private function scrollElementIntoView(element:IVisualElement):void
    {   
        var layout:LayoutBase = null;
        
        if (viewport is GroupBase)
            layout = GroupBase(viewport).layout;
        else if (viewport is SkinnableContainer)
            layout = SkinnableContainer(viewport).layout;
     
        if (layout)
        {
            // Scroll the element into view

                    else if (viewport.horizontalScrollPosition + width < eltBounds.right + focusThickness)
                        viewport.horizontalScrollPosition = eltBounds.right + focusThickness - width;
                }
                
                if (viewport is UIComponent)
                    UIComponent(viewport).validateNow();
            }
        }
    }
    
    //--------------------------------------------------------------------------
    // 
    // Event Handlers
    //
    //--------------------------------------------------------------------------
    
    /**
     *  @private
     *  Helper function for checkScrollPositionsOnUpdateComplete.  
     */
    private function getMotionPathCurrentVelocity(mp:MotionPath,currentTime:Number,totalTime:Number):Number
    {
        // Determine the fraction of the effect that has already played.
        var fraction:Number = currentTime / totalTime;

        // Now we need to determine the effective velocity at the effect's current position.
        // Here we use a "poor man's" approximation that doesn't require us to know any of the
        // derivative functions associated with the motion path.  We sample the position at two
        // time values very close together and assume the velocity slope is a straight line 
        // between them.  The smaller the distance between the two time values, the closer the 
        // result will be to the "instantaneous" velocity.
        const TINY_DELTA_TIME:Number = 0.00001; 
        var value1:Number = Number(mp.getValue(fraction));
        var value2:Number = Number(mp.getValue(fraction + (TINY_DELTA_TIME / totalTime)));
        return (value2 - value1) / TINY_DELTA_TIME;
    }
    
    /**
     *  @private 
     */
    private function checkScrollPositionsOnUpdateComplete(event:FlexEvent):void
    {
        viewport.removeEventListener(FlexEvent.UPDATE_COMPLETE, 
            checkScrollPositionsOnUpdateComplete);
        
        var curVelocity:Point;
        
        // Determine the new maximum valid scroll positions
        var maxVSP:Number = viewport.contentHeight > viewport.height ? 
            viewport.contentHeight-viewport.height : 0; 
        var maxHSP:Number = viewport.contentWidth > viewport.width ? 
            viewport.contentWidth-viewport.width : 0;
        
        // Determine whether we possibly need to re-throw because of changed max positions.
        var didNotThrowFarEnough:Boolean = throwReachedMaximumScrollPosition &&
            (throwFinalVSP < maxVSP || throwFinalHSP < maxHSP);
        var threwTooFar:Boolean = (throwFinalVSP > maxVSP || throwFinalHSP > maxHSP);

        if (throwEffect && throwEffect.isPlaying && (didNotThrowFarEnough || threwTooFar))
        {
            // There's currently a throw animation playing, and it's throwing to a 
            // now-incorrect position.  We're going to stop the animation and start
            // a better one to continue the throw.
            
            var velX:Number = 0;
            var velY:Number = 0;

            // Get the current position of the existing throw animation
            var effectTime:Number = throwEffect.playheadTime;

            // It's possible for playheadTime to not be set if we're getting it
            // before the first animation timer call.
            if (isNaN(effectTime))
                effectTime = 0;

            var effectDuration:Number = throwEffect.duration;
            
            // Now get the current effective velocity for each motionpath in the animation. 
            for (var t:int = 0; t < throwEffect.motionPaths.length; t++)
            {
                var vel:Number = getMotionPathCurrentVelocity(throwEffect.motionPaths[t], effectTime, effectDuration);

                // The property can only either be horizontalScrollPosition or verticalScrollPosition
                if (throwEffect.motionPaths[t].property == "horizontalScrollPosition")
                    velX = vel;
                else
                    velY = vel;
            }

            // Stop the existing throw animation now that we've determined its current velocities.
            stoppedPreemptively = true;
            throwEffect.stop();

            // Now perform a new throw to get us to the right position.
            performThrow(-velX, -velY);
        }
        else if (!inTouchInteraction)
        {
            // No touch interaction is in effect, but the content may be sitting at
            // a scroll position that is now invalid.  Note that performThrow will
            // only actually perform a throw if the positions actually are invalid.
            performThrow(0, 0);
        }
    }
    
    /**
     *  @private 
     */
    private function viewport_propertyChangeHandler(event:PropertyChangeEvent):void
    {
        switch(event.property) 
        {
            case "contentWidth": 
            case "contentHeight": 
                invalidateSkin();
                if (getStyle("interactionMode") == InteractionMode.TOUCH)
                {
                    // If the content size changed, then the valid scroll position ranges 
                    // may have changed.  In this case, we need to schedule an updateComplete 
                    // handler to check and potentially correct the scroll positions. 
                    viewport.addEventListener(FlexEvent.UPDATE_COMPLETE, 
                        checkScrollPositionsOnUpdateComplete);
                }
                break;
        }
    }
    
    /**
     *  @private 
     *  Listens for any focusIn events from descendants 
     */ 
    override protected function focusInHandler(event:FocusEvent):void
    {
        super.focusInHandler(event);
                
        // When we gain focus, make sure the focused element is visible
        if (viewport && ensureElementIsVisibleForSoftKeyboard)
        {
            var elt:IVisualElement = focusManager.getFocus() as IVisualElement; 
            lastFocusedElement = elt;
        }
    }
    
    /**
     *  @private
     */  
    override protected function focusOutHandler(event:FocusEvent):void
    {
        super.focusOutHandler(event);
        lastFocusedElement = null;
    }
    
    /**
