/*****************************************************
*  
*  Copyright 2009 Adobe Systems Incorporated.  All Rights Reserved.
*  
*****************************************************
*  The contents of this file are subject to the Mozilla Public License
*  Version 1.1 (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.mozilla.org/MPL/
*   
*  Software distributed under the License is distributed on an "AS IS"
*  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
*  License for the specific language governing rights and limitations
*  under the License.
*   
*  
*  The Initial Developer of the Original Code is Adobe Systems Incorporated.
*  Portions created by Adobe Systems Incorporated are Copyright (C) 2009 Adobe Systems 
*  Incorporated. All Rights Reserved. 
*  
*****************************************************/
package org.osmf.elements.compositeClasses
{
	import org.osmf.events.BufferEvent;
	import org.osmf.traits.BufferTrait;
	import org.osmf.traits.MediaTraitBase;
	import org.osmf.traits.MediaTraitType;
	
	/**
	 * Implementation of BufferTrait which can be a composite media trait.
	 * 
	 * For both parallel and serial media elements, a composite buffer trait
	 * keeps all buffer properties in sync for the composite element and its
	 * children.
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 10
	 *  @playerversion AIR 1.5
	 *  @productversion OSMF 1.0
	 */
	internal class CompositeBufferTrait extends BufferTrait implements IReusable
	{
		public function CompositeBufferTrait(traitAggregator:TraitAggregator, mode:String)
		{
			super();
			
			bufferTime					= 0;
			bufferTimeFromChildren		= true;
			settingBufferTime			= false;
			this.mode					= mode;
			this.traitAggregator = traitAggregator;
			traitAggregationHelper = new TraitAggregationHelper
				( traitType
				, traitAggregator
				, processAggregatedChild
				, processUnaggregatedChild
				);
		}
		
		/**
		 * @private
		 */
		override public function get bufferLength():Number
		{
			var length:Number;
			
			if (mode == CompositionMode.PARALLEL)
			{
				// In parallel,
				//
				// When none of the child trait is in the condition of bufferLength < bufferTime, 
				// the bufferLength of the trait is the average of the bufferLength values of its 
				// children. 
				// 
				// When at least one of the children is in the condition of bufferLength < bufferTime, 
				// the composite trait calculates its bufferLength as follows. For each child trait 
				// with bufferLength < bufferTime, the composite trait takes the actual bufferLength. 
				// For the rest of the children, the composite trait takes its bufferTime as its 
				// current bufferLength. Then the composite trait takes the average of the current 
				// bufferLength of the children (as described) as its bufferLength.
				var totalUnadjustedBufferLength:Number	= 0;
				var totalAdjustedBufferLength:Number	= 0;
				var needAdjustment:Boolean				= false;
				
				traitAggregator.forEachChildTrait
					(
					  function(mediaTrait:MediaTraitBase):void
					  {
					  	var bufferTrait:BufferTrait = BufferTrait(mediaTrait);
					  	if (bufferTrait.bufferLength < bufferTrait.bufferTime)
					  	{
					  		totalUnadjustedBufferLength	+= bufferTrait.bufferLength;
					  		totalAdjustedBufferLength	+= bufferTrait.bufferLength;
					  		needAdjustment = true;
					  	}
					  	else
					  	{
					  		totalUnadjustedBufferLength	+= bufferTrait.bufferLength;
					  		totalAdjustedBufferLength	+= bufferTrait.bufferTime;
					  	}
					  }
					, MediaTraitType.BUFFER
					);
					
				length = (needAdjustment? totalAdjustedBufferLength : totalUnadjustedBufferLength)
						/ traitAggregator.getNumTraits(MediaTraitType.BUFFER);
			}
			else
			{
				// In serial, the values of bufferLength of the composite trait is taken from the curent 
				// child trait.
				length =  (traitOfCurrentChild != null)? traitOfCurrentChild.bufferLength : 0;
			}
			
			return length;
		}
		
		override public function set bufferTime(value:Number):void
		{
			if (bufferTime == value)
			{
				return;
			}
			
			settingBufferTime = true;
			
			// Set new bufferTime to each child who supports BufferTrait 
			traitAggregator.forEachChildTrait
				(
				  function(mediaTrait:MediaTraitBase):void
				  {
				     BufferTrait(mediaTrait).bufferTime = value;
				  }
				, MediaTraitType.BUFFER
				);

			super.bufferTime = value;
			
			bufferTimeFromChildren = false;
			settingBufferTime = false;
		}
		
		/**
		 * @private
		 */
		public function attach():void
		{
			traitAggregationHelper.attach();
		}
		
		/**
		 * @private
		 */
		public function detach():void
		{
			traitAggregationHelper.detach();
		}

		private function processAggregatedChild(child:MediaTraitBase):void
		{
			onBufferingChanged();
			
			if (bufferTimeFromChildren)
			{
				onBufferTimeChanged();
			}
			else if (child is BufferTrait)
			{
				settingBufferTime = true;
				(child as BufferTrait).bufferTime = bufferTime;
				settingBufferTime = false;
			}

			child.addEventListener(BufferEvent.BUFFERING_CHANGE,	onBufferingChanged,		false, 0, true);
			child.addEventListener(BufferEvent.BUFFER_TIME_CHANGE,	onBufferTimeChanged,	false, 0, true);
		}

		private function processUnaggregatedChild(child:MediaTraitBase):void
		{
			onBufferingChanged();
			if (bufferTimeFromChildren)
			{
				onBufferTimeChanged();
			}

			child.removeEventListener(BufferEvent.BUFFERING_CHANGE,		onBufferingChanged);
			child.removeEventListener(BufferEvent.BUFFER_TIME_CHANGE,	onBufferTimeChanged);
		}
		
		private function onBufferingChanged(event:BufferEvent=null):void
		{
			var newBufferingState:Boolean = checkBuffering();
			if (newBufferingState != buffering)
			{
				setBuffering(newBufferingState);
			}
		}
		
		private function onBufferTimeChanged(event:BufferEvent=null):void
		{
			// The composite trait is in the middle of setting the bufferTime of each child.
			// So it may expect some bufferTimeChange events dispatched from the children. 
			// But under the circumstance, all these events should be ignored.
			if (settingBufferTime)
			{
				return;
			}
			
			var oldBufferTime:Number = bufferTime;
			var newBufferTime:Number = calculateBufferTime();
			bufferTimeFromChildren = true;
			if (oldBufferTime != newBufferTime)
			{
				super.bufferTime = newBufferTime;
			}
		}
		
		private function calculateBufferTime():Number
		{
			var time:Number = 0;
			
			if (mode == CompositionMode.PARALLEL)
			{
				// In parallel case, the bufferTime is the average of the bufferTime of the children.
				traitAggregator.forEachChildTrait
					(
					  function(mediaTrait:MediaTraitBase):void
					  {
					     time += BufferTrait(mediaTrait).bufferTime;
					  }
					, MediaTraitType.BUFFER
					);
				if (time > 0)
				{
					time = time / traitAggregator.getNumTraits(MediaTraitType.BUFFER);
				}
			}
			else
			{
				// In serial case, the bufferTime is taken from the current child.
				// If the current child does not support BufferTrait, return zero.
				time = traitOfCurrentChild != null ? traitOfCurrentChild.bufferTime : 0;
			}
			
			return time;
		}
		
		private function checkBuffering():Boolean
		{
			var isBuffering:Boolean = false;
			if (mode == CompositionMode.PARALLEL)
			{
				// In parallel case, buffering is true when at least one child is buffering.
				traitAggregator.forEachChildTrait
					(
					  function(mediaTrait:MediaTraitBase):void
					  {
					     isBuffering ||= BufferTrait(mediaTrait).buffering;
					  }
					, MediaTraitType.BUFFER
					);
			}
			else
			{
				// In serial case, buffering state is taken from that of the current child.
				// If the current child does not support BufferTrait, return false.
				isBuffering = traitOfCurrentChild != null? traitOfCurrentChild.buffering : false;
			}	
			
			return isBuffering;
		}
		
		private function get traitOfCurrentChild():BufferTrait
		{
			return   traitAggregator.listenedChild
				   ? traitAggregator.listenedChild.getTrait(MediaTraitType.BUFFER) as BufferTrait
				   : null;			
		}

		private var traitAggregator:TraitAggregator;
		private var traitAggregationHelper:TraitAggregationHelper;
		private var mode:String;		
		private var bufferTimeFromChildren:Boolean;
		private var settingBufferTime:Boolean;
	}
}