/*****************************************************
*  
*  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.media.pluginClasses
{
	import flash.display.Loader;
	
	import org.osmf.events.MediaError;
	import org.osmf.events.MediaErrorCodes;
	import org.osmf.events.MediaErrorEvent;
	import org.osmf.media.MediaFactory;
	import org.osmf.media.MediaFactoryItem;
	import org.osmf.media.PluginInfo;
	import org.osmf.traits.LoadState;
	import org.osmf.traits.LoadTrait;
	import org.osmf.traits.LoaderBase;
	import org.osmf.utils.Version;
	
	CONFIG::LOGGING
	{
	import org.osmf.logging.Log;
	import org.osmf.logging.Logger;
	}

	/**
	 * The PluginLoader class extends LoaderBase to provide
	 * loading support for plugins.
	 * It is the base class
	 * for creating static and dynamic plugin loaders.
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 10
	 *  @playerversion AIR 1.5
	 *  @productversion OSMF 1.0
	 */
	internal class PluginLoader extends LoaderBase
	{
		/**
		 * Constructor.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */
		public function PluginLoader(mediaFactory:MediaFactory, minimumSupportedFrameworkVersion:String)
		{			
			this.mediaFactory = mediaFactory;
			this.minimumSupportedFrameworkVersion = minimumSupportedFrameworkVersion;
		}
		
		/**
		 * Unloads the given PluginInfo.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */ 
		protected function unloadFromPluginInfo(pluginInfo:PluginInfo):void
		{
			if (pluginInfo != null)
			{
				for (var i:int = 0; i < pluginInfo.numMediaFactoryItems; i++)
				{
					var item:MediaFactoryItem = pluginInfo.getMediaFactoryItemAt(i);
					
					var actualItem:MediaFactoryItem = mediaFactory.getItemById(item.id);
					if (actualItem != null)
					{
						mediaFactory.removeItem(actualItem);
					}
				}
			}
		}
		
		/**
		 * Loads the plugin into the LoadTrait.
		 * On success sets the LoadState of the LoadTrait to LOADING, 
		 * on failure to LOAD_ERROR.
		 * @param pluginInfo PluginInfo instance to use for this load operation.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */
		protected function loadFromPluginInfo(loadTrait:LoadTrait, pluginInfo:PluginInfo, loader:Loader = null):void
		{
			var invalidImplementation:Boolean = false;
			
			if (pluginInfo != null)
			{
				if (isPluginCompatible(pluginInfo))	
				{
					try
					{
						// Make sure the plugin metadata has the expected default params
						// (such as MediaFactory).
						var passedMediaFactory:MediaFactory = loadTrait.resource.getMetadataValue(PluginInfo.PLUGIN_MEDIAFACTORY_NAMESPACE) as MediaFactory;
						if (passedMediaFactory == null)
						{
							loadTrait.resource.addMetadataValue(PluginInfo.PLUGIN_MEDIAFACTORY_NAMESPACE, mediaFactory);
						}
						
						pluginInfo.initializePlugin(loadTrait.resource);
					
						for (var i:int = 0; i < pluginInfo.numMediaFactoryItems; i++)
						{
							// Range error usually comes from this method call.  But
							// we generate an error if the returned value is null.
							var item:MediaFactoryItem = pluginInfo.getMediaFactoryItemAt(i);
							if (item == null)
							{
								throw new RangeError();
							}
							
							mediaFactory.addItem(item);							
						}
						
						var pluginLoadTrait:PluginLoadTrait = loadTrait as PluginLoadTrait;
						pluginLoadTrait.pluginInfo = pluginInfo;
						pluginLoadTrait.loader = loader;
						updateLoadTrait(pluginLoadTrait, LoadState.READY);
					}
					catch (error:RangeError)
					{
						// Range error when retrieving media infos.
						invalidImplementation = true;
					}
				}
				else
				{
					// Version not supported by plugin.
					updateLoadTrait(loadTrait, LoadState.LOAD_ERROR);
					loadTrait.dispatchEvent
						( new MediaErrorEvent
							( MediaErrorEvent.MEDIA_ERROR
							, false
							, false
							, new MediaError(MediaErrorCodes.PLUGIN_VERSION_INVALID)
							)
						);
				}
			}
			else
			{
				// No PluginInfo on root.
				invalidImplementation = true;
			}
			
			if (invalidImplementation)
			{
				updateLoadTrait(loadTrait, LoadState.LOAD_ERROR);
				loadTrait.dispatchEvent
					( new MediaErrorEvent
						( MediaErrorEvent.MEDIA_ERROR
						, false
						, false
						, new MediaError(MediaErrorCodes.PLUGIN_IMPLEMENTATION_INVALID)
						)
					);
			}
		}
		
		protected function isPluginCompatible(pluginInfo:Object):Boolean
		{
			var isCompatible:Boolean = false;
			
			var version:String = 	pluginInfo.hasOwnProperty(FRAMEWORK_VERSION_PROPERTY_NAME)
								 ?	pluginInfo[FRAMEWORK_VERSION_PROPERTY_NAME]
								 : null;
			var isSupported:Boolean = isPluginVersionSupported(version)
			if (isSupported)
			{
				var versionSupportedFunction:Function
					= pluginInfo.hasOwnProperty(IS_FRAMEWORK_VERSION_SUPPORTED_PROPERTY_NAME)
					? pluginInfo[IS_FRAMEWORK_VERSION_SUPPORTED_PROPERTY_NAME] as Function
					: null
				
				if (versionSupportedFunction != null)
				{
					try
					{
						isCompatible = versionSupportedFunction(Version.version);
					}
					catch (error:Error)
					{
						// Swallow -- if the function is missing or incorrectly
						// specified, then it's clearly not compatible.
					}
				}
				
				CONFIG::LOGGING
				{
					if (!isCompatible)
					{
						logger.debug("Player version '" + Version.version + "' not supported by loaded plugin");
					}
				}
			}
			
			CONFIG::LOGGING
			{
				if (!isSupported)
				{
					logger.debug("Plugin version '" + version + "' not supported by loading player (whose version is " + Version.version + ")");
				}
			}
			
			return isCompatible;
		}

		private function isPluginVersionSupported(pluginVersion:String):Boolean
		{
			if (pluginVersion == null || pluginVersion.length == 0)
			{
				return false;
			}
			
			var minVersion:Object = VersionUtils.parseVersionString(minimumSupportedFrameworkVersion);
			var pVersion:Object = VersionUtils.parseVersionString(pluginVersion);
			
			// A player can load a plugin provided that the plugin's version is
			// at least as great as the minimum supported framework version.
			return 		pVersion.major > minVersion.major
					||	(	pVersion.major == minVersion.major
						&&	pVersion.minor >= minVersion.minor
						);
		}
		
		private var minimumSupportedFrameworkVersion:String;
		private var mediaFactory:MediaFactory;
		
		private static const FRAMEWORK_VERSION_PROPERTY_NAME:String = "frameworkVersion";
		private static const IS_FRAMEWORK_VERSION_SUPPORTED_PROPERTY_NAME:String = "isFrameworkVersionSupported";
		
		CONFIG::LOGGING
		{
			private static const logger:Logger = Log.getLogger("org.osmf.elements.loaderClasses.PluginLoader");
		}
	}
}