/*****************************************************
*  
*  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.loaderClasses
{
	import flash.display.DisplayObject;
	import flash.display.Loader;
	import flash.display.LoaderInfo;
	import flash.errors.IOError;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.SecurityErrorEvent;
	import flash.geom.Rectangle;
	import flash.net.URLRequest;
	import flash.system.ApplicationDomain;
	import flash.system.LoaderContext;
	import flash.system.SecurityDomain;
	
	import org.osmf.events.MediaError;
	import org.osmf.events.MediaErrorCodes;
	import org.osmf.events.MediaErrorEvent;
	import org.osmf.media.MediaElement;
	import org.osmf.media.URLResource;
	import org.osmf.traits.DisplayObjectTrait;
	import org.osmf.traits.LoadState;
	import org.osmf.traits.LoadTrait;
	import org.osmf.utils.*;
	
	CONFIG::LOGGING
	{
	import org.osmf.logging.Log;
	import org.osmf.logging.Logger;
	}

	[ExcludeClass]
	
	/**
	 * @private
	 **/
	public class LoaderUtils
	{
		/**
		 * Creates a DisplayObjectTrait for the content in the given
		 * flash.display.Loader.
		 **/
		public static function createDisplayObjectTrait(loader:Loader, mediaElement:MediaElement):DisplayObjectTrait
		{
			var displayObject:DisplayObject = null;
			var mediaWidth:Number = 0;
			var mediaHeight:Number = 0;
						
			var info:LoaderInfo = loader.contentLoaderInfo;  
			
			// The display object must be a loader in order to support crossdomain
			// SWF loading.
			
			// The content of a loaded SWF can be accessed (at the developer's discrection) 
			// by casting the display object back to a loader and accessing its content property.
			displayObject = loader;
			
			// Add a scroll rect, to allow the loaded content to
			// overdraw its bounds, while maintaining scale, and size
			// with the layout system.
			//
			displayObject.scrollRect = new Rectangle(0, 0, info.width, info.height);
			
			mediaWidth = info.width;
			mediaHeight = info.height;

			return new DisplayObjectTrait(displayObject, mediaWidth, mediaHeight);
		}
		
		/**
		 * Loads the given LoadTrait.
		 * 
		 * @param loadTrait The LoadTrait whose URL represents the image or SWF to load.
		 * @param updateLoadTraitFunction Function to invoke when the LoadTrait's state changes.
		 * @param useCurrentSecurityDomain Indicates whether the content should be loaded into
		 * the current security domain, or the default.  The former should be used for SWFs whose
		 * content needs to be accessed (and which needs to access player content).  The latter
		 * should be used for images, or for SWFs that can be isolated.
		 * @param checkPolicyFile Indicates whether the load operation should check for the
		 * presence of a policy file on the server.  Should be true for images (if pixel-level
		 * access is required, false for SWFs (since SWF security is handled in a different way).
		 * @param validateLoadedContentFunction Function to invoke in order to validate loaded
		 * content.  When this param is non-null, the content will be loaded into a separate
		 * ApplicationDomain so that class types are not merged.  This allows the loading
		 * application to inspect player and SWF classes separately.  If the function returns
		 * true, then the content will be loaded into the current security domain.  If false,
		 * then this will be treated as a load error.
		 **/
		public static function loadLoadTrait(loadTrait:LoadTrait, updateLoadTraitFunction:Function, useCurrentSecurityDomain:Boolean, checkPolicyFile:Boolean, validateLoadedContentFunction:Function=null):void
		{
			var loaderLoadTrait:LoaderLoadTrait = loadTrait as LoaderLoadTrait;

			var loader:Loader = new Loader();
			loaderLoadTrait.loader = loader;
			
			updateLoadTraitFunction(loadTrait, LoadState.LOADING);
			
			var context:LoaderContext = new LoaderContext();
			var urlReq:URLRequest = new URLRequest((loadTrait.resource as URLResource).url.toString());
			
			context.checkPolicyFile = checkPolicyFile;

			// Local files should never be loaded into the current security domain.
			if (	useCurrentSecurityDomain
				&&	urlReq.url.search(/^file:\//i) == -1
			   )
			{
				context.securityDomain = SecurityDomain.currentDomain;
			}
			
			if (validateLoadedContentFunction != null)
			{
				// Don't load into the default ApplicationDomain.  Instead,
				// we'll load into a child of the system ApplicationDomain
				// (so that class types) don't get merged.  If the validation
				// function returns true, then we'll execute a second load,
				// this time into the default ApplicationDomain (so that class
				// types are merged).
				context.applicationDomain = new ApplicationDomain();
			}
			
			CONFIG::LOGGING
			{
				if (context.securityDomain != null)
				{
					logger.debug("Loading SWF into current security domain: " + urlReq.url);
				}
				if (context.applicationDomain != null)
				{
					logger.debug("Loading SWF into separate application domain: " + urlReq.url);
				}
			}
			
			toggleLoaderListeners(loader, true);
			try
			{
				loader.load(urlReq, context);
			}
			catch (ioError:IOError)
			{
				onIOError(null, ioError.message);
			}
			catch (securityError:SecurityError)
			{
				onSecurityError(null, securityError.message);
			}

			function toggleLoaderListeners(loader:Loader, on:Boolean):void
			{
				if (on)
				{
					loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete);
					loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
					loader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
				}
				else
				{
					loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, onLoadComplete);
					loader.contentLoaderInfo.removeEventListener(IOErrorEvent.IO_ERROR, onIOError);
					loader.contentLoaderInfo.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
				}
			}
		
			function onLoadComplete(event:Event):void
			{
				toggleLoaderListeners(loader, false);
				
				if (validateLoadedContentFunction != null)
				{
					var validated:Boolean = validateLoadedContentFunction(loader.content);
					if (validated)
					{
						// Unload the loaded SWF, we don't need it anymore.
						loader.unloadAndStop();
						loader = null;
						
						loadLoadTrait(loadTrait, updateLoadTraitFunction, useCurrentSecurityDomain, false, null);
					}
					else
					{
						// Unload the loaded SWF, we don't need it anymore.
						loader.unloadAndStop();
						loader = null;
						
						updateLoadTraitFunction(loadTrait, LoadState.LOAD_ERROR);
						loadTrait.dispatchEvent
							( new MediaErrorEvent
								( MediaErrorEvent.MEDIA_ERROR
								, false
								, false
								, new MediaError
									( MediaErrorCodes.IO_ERROR
									)
								)
							);
					}
				}
				else
				{
					updateLoadTraitFunction(loadTrait, LoadState.READY);
				}
			}

			function onIOError(ioEvent:IOErrorEvent, ioEventDetail:String=null):void
			{	
				toggleLoaderListeners(loader, false);
				loader = null;
				
				updateLoadTraitFunction(loadTrait, LoadState.LOAD_ERROR);
				loadTrait.dispatchEvent
					( new MediaErrorEvent
						( MediaErrorEvent.MEDIA_ERROR
						, false
						, false
						, new MediaError
							( MediaErrorCodes.IO_ERROR
							, ioEvent ? ioEvent.text : ioEventDetail
							)
						)
					);
			}

			function onSecurityError(securityEvent:SecurityErrorEvent, securityEventDetail:String=null):void
			{
				CONFIG::LOGGING
				{
					logger.debug("Security error when loading image/SWF: " + (securityEvent ? securityEvent.text : securityEventDetail));
				}

				toggleLoaderListeners(loader, false);
				loader = null;
				
				updateLoadTraitFunction(loadTrait, LoadState.LOAD_ERROR);
				loadTrait.dispatchEvent
					( new MediaErrorEvent
						( MediaErrorEvent.MEDIA_ERROR
						, false
						, false
						, new MediaError
							( MediaErrorCodes.SECURITY_ERROR
							, securityEvent ? securityEvent.text : securityEventDetail
							)
						)
					);
			}
		}

		/**
		 * Unloads the given LoadTrait.
		 **/
		public static function unloadLoadTrait(loadTrait:LoadTrait, updateLoadTraitFunction:Function):void
		{
			var loaderLoadTrait:LoaderLoadTrait = loadTrait as LoaderLoadTrait;
			updateLoadTraitFunction(loadTrait, LoadState.UNLOADING);			
			loaderLoadTrait.loader.unloadAndStop();
			updateLoadTraitFunction(loadTrait, LoadState.UNINITIALIZED);
		}
		
		private static const SWF_MIME_TYPE:String = "application/x-shockwave-flash";
		
		CONFIG::LOGGING
		{
			private static const logger:Logger = Log.getLogger("org.osmf.elements.loaderClasses.LoaderUtils");
		}
	}
}