/*****************************************************
*  
*  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 flash.errors.IllegalOperationError;
	import flash.events.EventDispatcher;
	
	import org.osmf.events.LoadEvent;
	import org.osmf.events.MediaElementEvent;
	import org.osmf.events.MediaError;
	import org.osmf.events.MediaErrorCodes;
	import org.osmf.events.MediaErrorEvent;
	import org.osmf.media.MediaElement;
	import org.osmf.traits.LoadState;
	import org.osmf.traits.LoadTrait;
	import org.osmf.traits.MediaTraitType;
	
	/**
	 * Dispatched when the requested MediaElement has been found, or when it
	 * has been determined that no such MediaElement exists.
	 * 
	 * @eventType org.osmf.composition.events.TraitLoaderEvent.TRAIT_FOUND
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 10
	 *  @playerversion AIR 1.5
	 *  @productversion OSMF 1.0
	 */
	[Event(name="traitFound",type="org.osmf.composition.events.TraitLoaderEvent")]

	/**
	 * Utility class for doing conditional loads of MediaElements.
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 10
	 *  @playerversion AIR 1.5
	 *  @productversion OSMF 1.0
	 */
	internal class TraitLoader extends EventDispatcher
	{
		/**
		 * Iterates over a list of MediaElements looking for a given trait, and
		 * returns the first MediaElement in the list which either has the trait,
		 * or which acquires the trait as a result of being loaded.  (As such,
		 * has the side effect of loading all MediaElements in the list up until
		 * it finds one with the requested trait.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */
		public function findOrLoadMediaElementWithTrait(mediaElements:Array, traitType:String):void
		{
			var noSuchTrait:Boolean = true;
			
			for each (var mediaElement:MediaElement in mediaElements)
			{
				var loadTrait:LoadTrait = mediaElement.getTrait(MediaTraitType.LOAD) as LoadTrait;
				
				if (	mediaElement.hasTrait(traitType)
					&&	(	loadTrait == null
						 || loadTrait.loadState == LoadState.READY
						)
				   )
				{
					// If the next MediaElement has the requested trait (and is loaded),
					// then we're done.
					//
					
					noSuchTrait = false;
					
					dispatchFindOrLoadEvent(mediaElement);
					break;
				}
				else if (loadTrait != null &&
						 loadTrait.loadState != LoadState.READY)
				{
					// If the next MediaElement doesn't have the trait, but has
					// the LoadTrait and is not yet loaded, then we should
					// load it in case the trait gets added as a result of the
					// load operation.
					//
					
					// We're not sure yet if there's a trait.
					noSuchTrait = false;

					// We wait for the load to complete.
					loadTrait.addEventListener(LoadEvent.LOAD_STATE_CHANGE, onLoadStateChange);
					
					// It's possible the trait will be removed prior to the completion
					// of the load.  In that case, we'll need to wait until a new
					// trait gets added and listen to that.
					mediaElement.addEventListener(MediaElementEvent.TRAIT_REMOVE, onTraitRemove);
					
					// Execute the load operation.
					executeLoad(loadTrait, mediaElement);
					
					// Stop iterating, we need to wait until the load completes.
					break;
					
					function onLoadStateChange(event:LoadEvent):void
					{
						var loadTrait:LoadTrait = event.target as LoadTrait;
						if (loadTrait.loadState == LoadState.READY)
						{
							mediaElement.removeEventListener(MediaElementEvent.TRAIT_REMOVE, onTraitRemove);
							loadTrait.removeEventListener(LoadEvent.LOAD_STATE_CHANGE, onLoadStateChange);
							
							if (mediaElement.hasTrait(traitType))
							{
								dispatchFindOrLoadEvent(mediaElement);
							}
							else
							{
								// Recursively call this method, after stripping
								// off all MediaElements up to and including
								// the one we're iterating over (i.e. the one
								// we just loaded).
								findOrLoadMediaElementWithTrait
									( mediaElements.slice(mediaElements.indexOf(mediaElement)+1)
									, traitType
									);
							}
						}
					}
					
					function onTraitRemove(event:MediaElementEvent):void
					{
						if (event.traitType == MediaTraitType.LOAD)
						{
							// Our trait got removed mid-stream, we need to wait
							// until we get the new LoadTrait.
							loadTrait.removeEventListener(LoadEvent.LOAD_STATE_CHANGE, onLoadStateChange);
						
							mediaElement.addEventListener(MediaElementEvent.TRAIT_ADD, onTraitAdd);
						}
					}
					
					function onTraitAdd(event:MediaElementEvent):void
					{
						if (event.traitType == MediaTraitType.LOAD)
						{
							// Our trait was re-added, now we should load it and
							// wait for completion.
							mediaElement.removeEventListener(MediaElementEvent.TRAIT_ADD, onTraitAdd);
							
							loadTrait = mediaElement.getTrait(MediaTraitType.LOAD) as LoadTrait;
							loadTrait.addEventListener(LoadEvent.LOAD_STATE_CHANGE, onLoadStateChange);
							executeLoad(loadTrait, mediaElement);
						}
					}
				}
			}
			
			if (noSuchTrait)
			{
				dispatchFindOrLoadEvent(null);
			}
		}
		
		private function executeLoad(loadTrait:LoadTrait, mediaElement:MediaElement):void
		{
			// If it's already loading, then we only need to wait for
			// the event.
			if (loadTrait.loadState != LoadState.LOADING)
			{
				try
				{
					loadTrait.load();
				}
				catch (error:IllegalOperationError)
				{
					// Translate this to a MediaError.
					mediaElement.dispatchEvent
						( new MediaErrorEvent
							( MediaErrorEvent.MEDIA_ERROR
							, false
							, false
							, new MediaError(MediaErrorCodes.MEDIA_LOAD_FAILED, error.message)
							)
						);
						
					dispatchFindOrLoadEvent(null);
				}
			}
		}
		
		private function dispatchFindOrLoadEvent(mediaElement:MediaElement):void
		{
			dispatchEvent(new TraitLoaderEvent(mediaElement));
		}
	}
}