/*****************************************************
*  
*  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.net
{
	import flash.utils.Dictionary;
	import flash.utils.Proxy;
	import flash.utils.flash_proxy;

	/**
	 * The NetClient class provides support for handling
	 * callbacks dynamically from an RTMP server that is streaming
	 * to a MediaElement that works with NetStream under the hood (such
	 * as VideoElement). 
	 * 
	 * <p>Use this class to listen for callbacks on the NetConnection
	 * and NetStream created by a NetLoader's load operation.</p>
	 * <p>Assign the value of the <code>client</code>
	 * property of the NetConnection or NetStream
	 * to an instance of the NetClient class.
	 * Then use the NetClient's <code>addHandler()</code>
	 * and <code>removeHandler()</code> methods to register and unregister handlers for
	 * the NetStream callbacks.</p>
	 * 
	 * @see NetLoader
	 * @see flash.net.NetConnection
	 * @see flash.net.NetStream
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 10
	 *  @playerversion AIR 1.5
	 *  @productversion OSMF 1.0
	 */	
	dynamic public class NetClient extends Proxy
	{
		/**
		 * Adds a handler for the specified callback name.
		 * 
		 * <p>If multiple handlers register for the same callback,
		 * the result of the callback is an array holding the results
		 * of each handler's invocation.
		 * </p>
		 * <p>
		 * This example sets up handler for the <code>onMetaData</code>
		 * callback.
		 * <listing>
		 * function onMetaData(value:Object):void
		 * {
		 * 	  trace("Got metadata.");
		 * }
		 * 
		 * var stream:NetStream;
		 * var client:NetClient = (stream.client as NetClient); //assign the stream to the NetClient
		 * client.addHandler("onMetaData", onMetaData); //add the handler
		 * </listing>
		 * </p>
		 * 
		 * @param name Name of callback to handle.
		 * @param handler Handler to add.
		 * @priority The priority level of the handler.  The higher the number, the higher the priority.
		 * All handlers with priority N are processed before handlers of priority N-1.  If two or more
		 * handlers are added with the same priority, they are processed in the order in which they
		 * were added.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */		
		public function addHandler(name:String, handler:Function, priority:int=0):void
		{
			var handlersForName:Array 
				= handlers.hasOwnProperty(name)
					? handlers[name]
					: (handlers[name] = []);
			
			if (handlersForName.indexOf(handler) == -1)
			{
				var inserted:Boolean = false;
				
				priority = Math.max(0, priority);
				
				// Higher priority handlers are at the front of the list.
				if (priority > 0)
				{
					for (var i:int = 0; i < handlersForName.length; i++)
					{
						var handlerWithPriority:Object = handlersForName[i];
						
						// Stop iterating when we're passed all handlers of
						// this priority.
						if (handlerWithPriority.priority < priority)
						{
							handlersForName.splice(i, 0, {handler:handler, priority:priority});
							inserted = true;
							break;
						}
					}
				}
				if (!inserted)
				{
					handlersForName.push({handler:handler, priority:priority});
				}
			}
		}
		
		/**
		 * Removes a handler method for the specified callback name.
		 * 
		 * @param name Name of callback for whose handler is being removed.
		 * @param handler Handler to remove.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */		
		public function removeHandler(name:String,handler:Function):void
		{
			if (handlers.hasOwnProperty(name))
			{
				var handlersForName:Array = handlers[name];
				for (var i:int = 0; i < handlersForName.length; i++)
				{
					var handlerWithPriority:Object = handlersForName[i];
					if (handlerWithPriority.handler == handler)
					{
						handlersForName.splice(i, 1);
						
						break;
					}
				}
			}
		}
		
		// Proxy Overrides
		//
		
		/**
		 * @private
		 */		
		override flash_proxy function callProperty(methodName:*, ... args):*
		{
			return invokeHandlers(methodName, args);
        }
        
        /**
		 * @private
		 */
		override flash_proxy function getProperty(name:*):* 
		{
			var result:*;			
			if (handlers.hasOwnProperty(name))
			{
				result 
					=  function():*
						{
							return invokeHandlers(arguments.callee.name, arguments);
						}
				
				result.name = name;
			}
			
			return result;
		}
		
        /**
		 * @private
		 */
		override flash_proxy function hasProperty(name:*):Boolean
		{
			return handlers.hasOwnProperty(name);
		}

		// Internals
		//
		
		/**
		 * @private
		 * 
		 * Holds an array of handlers per callback name. 
		 */		
		private var handlers:Dictionary = new Dictionary();
		
		/**
		 * @private
		 * 
		 * Utility method that invokes the handlers for the specified
		 * callback name.
		 *  
		 * @param name The callback name to invoke the handlers for.
		 * @param args The arguments to pass to the individual handlers on
		 * invoking them.
		 * @return <code>null</code> if no handlers have been added for the
		 * specified callback, or otherwise an array holding the result of
		 * each individual handler invokation. 
		 * 
		 */				
		private function invokeHandlers(name:String, args:Array):*
		{
			var result:Array;
			
        	if (handlers.hasOwnProperty(name))
			{
				result = [];
				var handlersForName:Array = handlers[name];
				for each (var handler:Object in handlersForName)
				{
					result.push(handler.handler.apply(null,args));
				}
			}
			
			return result;
		}
	}
}