////////////////////////////////////////////////////////////////////////////////
//
//  ADOBE SYSTEMS INCORPORATED
//  Copyright 2005-2007 Adobe Systems Incorporated
//  All Rights Reserved.
//
//  NOTICE: Adobe permits you to use, modify, and distribute this file
//  in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////

package mx.effects.effectClasses
{
import flash.events.TimerEvent;
import flash.system.ApplicationDomain;
import flash.utils.Timer;

import mx.core.FlexVersion;
import mx.core.UIComponent;
import mx.core.mx_internal;
import mx.effects.EffectInstance;
import mx.effects.IEffectInstance;
import mx.effects.Parallel;

use namespace mx_internal;

/**
 *  The ParallelInstance class implements the instance class
 *  for the Parallel effect.
 *  Flex creates an instance of this class when it plays a Parallel effect;
 *  you do not create one yourself.
 *
 *  @see mx.effects.Parallel
 *  
 *  @langversion 3.0
 *  @playerversion Flash 9
 *  @playerversion AIR 1.1
 *  @productversion Flex 3
 */  
public class ParallelInstance extends CompositeEffectInstance
{
    include "../../core/Version.as";

	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------

	/**
	 *  Constructor.
	 *
	 *  @param target This argument is ignored for Parallel effects.
	 *  It is included only for consistency with other types of effects.
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public function ParallelInstance(target:Object)
	{
		super(target);
	}
	
	//--------------------------------------------------------------------------
	//
	//  Variables
	//
	//--------------------------------------------------------------------------
	
	/**
	 *  @private
	 *  Holds the child effect instances that have already completed.
	 */
	private var doneEffectQueue:Array /* of EffectInstance */;
	
	/**
	 *  @private
	 *  Holds the child effect instances that are waiting to be replayed.
	 */
	private var replayEffectQueue:Array /* of EffectInstance */;

	/**
	 *  @private
	 */	
	private var isReversed:Boolean = false;	
			
	/**
	 *  @private
	 */	
	private var timer:Timer;
	
	//--------------------------------------------------------------------------
	//
	//  Overridden properties
	//
	//--------------------------------------------------------------------------

	//----------------------------------
	//  durationWithoutRepeat
	//----------------------------------

	/**
	 *  @private
	 */
    override mx_internal function get durationWithoutRepeat():Number
    {
        var _duration:Number = 0;
        
        // Get the largest actualDuration of all of our children
        var n:int = childSets.length;
        for (var i:int = 0; i < n; i++)
        {
            var instances:Array = childSets[i];
            _duration = Math.max(instances[0].actualDuration, _duration);
        }
        
        return _duration;
    }
	

    //----------------------------------
    //  playheadTime
    //----------------------------------

    /**
     * @inheritDoc
     * 
     * In a Parallel effect, setting <code>playheadTime</code> 
     * will cause all child effects to go to that same
     * <code>playheadTime</code>. This may cause child effects to 
     * lessen their <code>startDelay</code> (if they are currently 
     * waiting to be played), start playing (if the 
     * <code>playheadTime</code> is greater than their 
     * <code>startDelay</code>), or end (if the <code>playheadTime</code> 
     * is greater than their <code>startDelay</code> plus
     * their <code>duration</code>).
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    override public function set playheadTime(value:Number):void
    {        
        // This will seek in the Parallel's instance
        super.playheadTime = value;

        var compositeDur:Number = Parallel(effect).compositeDuration;
        var firstCycleDur:Number = compositeDur + startDelay + repeatDelay;
        var laterCycleDur:Number = compositeDur + repeatDelay;
        // totalDur is only sensible/used when repeatCount != 0
        var totalDur:Number = firstCycleDur + laterCycleDur * (repeatCount - 1);
        var childPlayheadTime:Number;
        if (value <= firstCycleDur) {
            childPlayheadTime = Math.min(value - startDelay, compositeDur);
            playCount = 1;
        }
        else
        {
            if (value >= totalDur && repeatCount != 0)
            {
                childPlayheadTime = compositeDur;
                playCount = repeatCount;
            }
            else
            {
                var valueAfterFirstCycle:Number = value - firstCycleDur;
                childPlayheadTime = Math.min(childPlayheadTime, compositeDur);
                childPlayheadTime = valueAfterFirstCycle % laterCycleDur;
                playCount = 1 + valueAfterFirstCycle / laterCycleDur;
            }
        }

        // Tell all of our children to set their playheadTime
        for (var i:int = 0; i < childSets.length; i++)
        {
            var instances:Array = childSets[i];            
            var m:int = instances.length;
            for (var j:int = 0; j < m; j++)
                instances[j].playheadTime = playReversed ?
                    Math.max(0, (childPlayheadTime - 
                                 (durationWithoutRepeat - instances[j].actualDuration))) :
                    childPlayheadTime;
        }

        // Add any effects waiting on the replayQueue, which holds the
        // effects waiting to be played at the right time when reversing
        if (playReversed && replayEffectQueue != null && replayEffectQueue.length > 0)
        {
            var position:Number = durationWithoutRepeat - playheadTime;
            var numDone:int = replayEffectQueue.length;	        
            for (i = numDone - 1; i >= 0; i--)
            {
                var childEffect:EffectInstance = replayEffectQueue[i];
                
                // If the position is less than the child duration, then
                // we must be either in or past the child effect, so we 
                // should start that effect
                if (position <= childEffect.actualDuration)
                {
                    // Move the effect from the done queue back onto the active one
                    if (activeEffectQueue == null)
                        activeEffectQueue = [];
                    activeEffectQueue.push(childEffect);
                    replayEffectQueue.splice(i,1);
                    
                    childEffect.playReversed = playReversed;
                    childEffect.startEffect();
                    // Note that we've already set the playheadTime on these
                    // queued effects in the previous loop on childSets
                }
            }
        }
        
    }

	//--------------------------------------------------------------------------
	//
	//  Overridden methods
	//
	//--------------------------------------------------------------------------

    private static var resizeEffectType:Class;
    private static var resizeEffectLoaded:Boolean = false;
	/**
	 *  @private
	 */
	override public function addChildSet(childSet:Array):void
	{
		super.addChildSet(childSet);
		
		// Make sure that spark Resize and mx Rotate are in the beginning of the 
        // array because they need to be before any Move effects
        // TODO: There is logic at the Animation level to handle processing 
        // width/height values before any others for any animation frame. We
        // maybe be able to remove this Resize-reordering logic altogether.
		if (childSets.length > 1 && childSet.length > 0)
		{
			var compChild:CompositeEffectInstance = childSet[0] as CompositeEffectInstance;
			
            // Resize effects must run before Move effects for autoCenterTransform to 
            // work correctly
            if (!resizeEffectLoaded)
            {
                resizeEffectLoaded = true;
                if (ApplicationDomain.currentDomain.hasDefinition("spark.effects.supportClasses.ResizeInstance"))
                    resizeEffectType = Class(ApplicationDomain.currentDomain.
                        getDefinition("spark.effects.supportClasses.ResizeInstance"));
            }
            if (resizeEffectType && (childSet[0] is resizeEffectType ||
                (compChild != null && compChild.hasResizeInstance())))
            {
                // Remove item we just added to the end and place it at the beginning,
                // but after any other Resize effects
                
                var resizeChildSet:Array = childSets.pop();
                var insertionIndex:int = 0;
                for (var i:int = 0; i < childSets.length; ++i)
                {
                    var currentChildSet:Array = childSets[i];
                    var currentChildSetComposite:CompositeEffectInstance = 
                        currentChildSet[0] as CompositeEffectInstance;
                    if (!(currentChildSet[0] is resizeEffectType) &&
                        (!currentChildSetComposite || !currentChildSetComposite.hasResizeInstance()))
                    {
                        break;
                    }
                    ++insertionIndex;
                }
                childSets.splice(insertionIndex, 0, resizeChildSet);
            }
			// Check if the child is a Rotate and also check if it is a composite effect that has a Rotate
            else if (childSet[0] is RotateInstance || (compChild != null && compChild.hasRotateInstance()))
			{
                // Remove item we just added to the end and place it at the beginning
                childSets.unshift(childSets.pop());
			}
		}
	}
	
	/**
	 *  @private
	 */
	override public function play():void
	{
		doneEffectQueue = [];
		activeEffectQueue = [];
		replayEffectQueue = [];
		
		// Create a timer tween used to keep track of the playheadTime
		
		var checkQueueLength:Boolean = false;
		
		// Dispatch an effectStart event from the target.
		super.play();

		var n:int;
		var i:int;
		
		n = childSets.length;
		for (i = 0; i < n; i++)
		{
			var instances:Array = childSets[i];
			
			var m:int = instances.length;
			for (var j:int = 0; j < m && activeEffectQueue != null; j++)
			{
				var childEffect:EffectInstance = instances[j];
				
				// Check if the effect should play right away
				// or should be put in the replay queue.
				if (playReversed &&
					childEffect.actualDuration < durationWithoutRepeat)
				{
					replayEffectQueue.push(childEffect);
					startTimer();
				}
				else
				{
					childEffect.playReversed = playReversed;
					activeEffectQueue.push(childEffect);
				}
				
				// Block all layout, responses from web services,
				// and other background processing until the effect
				// finishes executing.
				if (childEffect.suspendBackgroundProcessing)
					UIComponent.suspendBackgroundProcessing();		

			}		
		}
		
		if (activeEffectQueue.length > 0)
		{
			// Start all of the effects in the active queue.
			// Need to make a copy of the queue first since some effects
			// (like Action effects) are immediate and will be removed
			// from the activeEffectQueue before returning from startEffect().
			var queueCopy:Array = activeEffectQueue.slice(0);
			
			for (i = 0; i < queueCopy.length; i++)
			{
				queueCopy[i].startEffect();
			}
		}
	}
	
	/**
	 *  @private
	 */
	override public function pause():void
	{	
		super.pause();
		
		// Pause every currently playing effect instance.
        if (activeEffectQueue)
        {
    		var n:int = activeEffectQueue.length;
    		for (var i:int = 0; i < n; i++)
    		{
    			activeEffectQueue[i].pause();
    		}
        }
	}

	/**
	 *  @private
	 */
	override public function stop():void
	{
		stopTimer();
		
		if (activeEffectQueue)
		{
			var queueCopy:Array = activeEffectQueue.concat();
			activeEffectQueue = null;
			var n:int = queueCopy.length;
			for (var i:int = 0; i < n; i++)
			{
				if (queueCopy[i])
					queueCopy[i].stop();
			}
		}
		
		super.stop();
	}

	/**
	 *  @private
	 */
	override public function resume():void
	{
		super.resume();
	
		// Resume every currently playing effect instance.
        if (activeEffectQueue)
        {
    		var n:int = activeEffectQueue.length;
    		for (var i:int = 0; i < n; i++)
    		{
    			activeEffectQueue[i].resume();
    		}
        }
	}
		
	/**
	 *  @private
	 */
	override public function reverse():void
	{
		super.reverse();
		
		var n:int;
		var i:int;
		
		if (isReversed)
		{
			// We don't care about anything in the done queue.
			// Just reverse all the active ones.
			
			n = activeEffectQueue.length;
			for (i = 0; i < n; i++)
			{
				activeEffectQueue[i].reverse();
			} 
			
			stopTimer();
		}
		else
		{
			replayEffectQueue = doneEffectQueue.splice(0);
		
			// Reverse all of the active ones.
			// Plus, setup a timer to check if we reach any done ones.
			n = activeEffectQueue.length;
			for (i = 0; i < n; i++)
			{
				activeEffectQueue[i].reverse();
			} 
			
			startTimer();
		}
		
		isReversed = !isReversed;
	}
	
	/**
	 *  Interrupts any effects that are currently playing, skips over
	 *  any effects that haven't started playing, and jumps immediately
	 *  to the end of the composite effect.
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	override public function end():void
	{
		endEffectCalled = true;
		stopTimer();
		
		if (activeEffectQueue)
		{
			var queueCopy:Array = activeEffectQueue.concat();
			activeEffectQueue = null;
			var n:int = queueCopy.length;
			for (var i:int = 0; i < n; i++)
			{
				if (queueCopy[i])
					queueCopy[i].end();
			}
		}
		
		super.end();
	}
	
	/**
	 *  Each time a child effect of SequenceInstance or ParallelInstance
	 *  finishes, Flex calls the <code>onEffectEnd()</code> method.
	 *  For SequenceInstance, it plays the next effect.
	 *  In ParallelInstance, it keeps track of all the 
	 *  effects until all of them have finished playing. 
	 *  If you create a subclass of CompositeEffect, you must implement this method.
         *
         * @param childEffect A child effect that has finished. 
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	override protected function onEffectEnd(childEffect:IEffectInstance):void
	{
		// Each child effect notifies us when it is finished.  Remove
		// the notifying child from childSets, so that the end()
		// method doesn't call it.  When the last child notifies
		// us that it's finished, notify our listener that we're finished.

		// Resume the background processing that was suspended earlier
		if (Object(childEffect).suspendBackgroundProcessing)
			UIComponent.resumeBackgroundProcessing();

		// See endEffect, above.
		
		if (endEffectCalled || activeEffectQueue == null)
			return;
			
		var n:int = activeEffectQueue.length;	
		for (var i:int = 0; i < n; i++)
		{
			if (childEffect == activeEffectQueue[i])
			{
				doneEffectQueue.push(childEffect);
				activeEffectQueue.splice(i, 1);
				break;
			}
		}	
		
		if (n == 1)
		{
			// Note: This event must be dispatched *before* checking for
			// actualCachePolicy below so that the listener has a chance to set
			// it to "on".
			finishRepeat();
		}
	}
		
	//--------------------------------------------------------------------------
	//
	//  Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 *  @private
	 */	
	private function startTimer():void
	{
		if (!timer)
		{
			timer = new Timer(10);
			timer.addEventListener(TimerEvent.TIMER, timerHandler);
		}
		timer.start();
	}
	
	/**
	 *  @private
	 */	
	private function stopTimer():void
	{
		if (timer)
			timer.reset();
	}
	
	/**
	 *  @private
	 *  Used internally to figure out if we should be playing an effect
	 *  from the replay queue.
	 */	
	private function timerHandler(event:TimerEvent):void
	{
		// Assume we are playing in reverse
		// invert the playheadTime
		var position:Number = durationWithoutRepeat - playheadTime;
		var numDone:int = replayEffectQueue.length;	
		
		if (numDone == 0)
		{
			stopTimer();
			return;
		}
		
		for (var i:int = numDone - 1; i >= 0; i--)
		{
			var childEffect:EffectInstance = replayEffectQueue[i];
			
			if (position <= childEffect.actualDuration)
			{
				// Move the effect from the done queue back onto the active one
                if (activeEffectQueue == null)
                    activeEffectQueue = [];
				activeEffectQueue.push(childEffect);
				replayEffectQueue.splice(i,1);
				
				childEffect.playReversed =playReversed;
				childEffect.startEffect();
				 
			}
		}
		
	}

}

}
