/*****************************************************
*  
*  Copyright 2009 Akamai Technologies, Inc.  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 Akamai Technologies, Inc.
*  Portions created by Akamai Technologies, Inc. are Copyright (C) 2009 Akamai 
*  Technologies, Inc. All Rights Reserved.
* 
*  Contributor: Adobe Systems Inc.
*  
*****************************************************/
package org.osmf.net
{
	import flash.events.EventDispatcher;
	import flash.events.NetStatusEvent;
	import flash.events.TimerEvent;
	import flash.net.NetStream;
	import flash.utils.Timer;
	
	CONFIG::LOGGING
	{
	import org.osmf.logging.Logger;
	import org.osmf.logging.Log;
	}

	/**
	 * The NetStreamMetricsBase class serves as a base class for a provider of
	 * run-time metrics to the NetStreamSwitchManager and its set of switching
	 * rules.  It calculates running averages for metrics that apply to all
	 * delivery methods.
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 10
	 *  @playerversion AIR 1.5
	 *  @productversion OSMF 1.0
	 */
	public class NetStreamMetricsBase extends EventDispatcher
	{		
		/**
		 * Constructor.
		 * 
		 * Note that for correct operation of this class, the caller must set the
		 * resource which the monitored stream is playing.
		 * 
		 * @param netStream The NetStream instance the class will monitor.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */
		public function NetStreamMetricsBase(netStream:NetStream)
		{
			super();

			_netStream = netStream;
			
			_droppedFPS = 0;
			_lastFrameDropCounter = 0;
			_lastFrameDropValue = 0;
			_maxFPS = 0;
			_averageDroppedFPSArray = new Array();

			_timer = new Timer(DEFAULT_UPDATE_INTERVAL);
			_timer.addEventListener(TimerEvent.TIMER, onTimerEvent);

			netStream.addEventListener(NetStatusEvent.NET_STATUS, onNetStatusEvent);
		}

		/**
		 * Returns the DynamicStreamingResource which the class is referencing.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */
		public function get resource():DynamicStreamingResource
		{
			return _resource;
		}
		
		public function set resource(value:DynamicStreamingResource):void 
		{
			_resource = value;
			_maxAllowedIndex = value != null ? value.streamItems.length - 1 : 0;
		}

		/**
		 * The NetStream object supplied to the constructor.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */
		public function get netStream():NetStream
		{
			return _netStream;
		}
		
		/**
		 * The current stream index.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */
		public function get currentIndex():int
		{
			return _currentIndex;
		}
		
		public function set currentIndex(value:int):void 
		{
			_currentIndex = value;
		}
		
		/**
		 * The maximum allowed index value.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */
		public function get maxAllowedIndex():int 
		{
			return _maxAllowedIndex;
		}
		
		public function set maxAllowedIndex(value:int):void
		{
			_maxAllowedIndex = value;
		}
		
		/**
		 * The update interval (in milliseconds) at which metrics are recalculated.  If
		 * set to zero, then metrics will not be recalculated.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */
		public function get updateInterval():Number 
		{
			return _timer.delay;
		}
		
		public function set updateInterval(value:Number):void 
		{
			_timer.delay = value;
			
			if (value <= 0)
			{
				_timer.stop();
			} 
		}
				
		/**
		 * The maximum achieved frame rate for this NetStream. 
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */
		public function get maxFPS():Number
		{
			return _maxFPS;
		}
		
		/**
		 * The frame drop rate calculated over the last interval.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */
		public function get droppedFPS():Number
		{
			return _droppedFPS;
		}
		
		/**
		 * The average frame-drop rate calculated over the life of the NetStream.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */
		public function get averageDroppedFPS():Number
		{
			return _averageDroppedFPS;
		}
		
		// Protected
		//
		
		/**
		 * Method invoked when the metrics should be recalculated.  If
		 * updateInterval is set, this method will be invoked whenever the
		 * updateInterval is reached.
		 **/
		protected function calculateMetrics():void
		{
			try 
			{
				// Estimate max (true) framerate.
				_maxFPS = netStream.currentFPS > _maxFPS ? netStream.currentFPS : _maxFPS;
				
				// Frame drop rate, per second, calculated over last second.
				if (_timer.currentCount - _lastFrameDropCounter > 1000 / _timer.delay) 
				{
					_droppedFPS = (netStream.info.droppedFrames - _lastFrameDropValue) / ((_timer.currentCount - _lastFrameDropCounter) * _timer.delay/1000);
					_lastFrameDropCounter = _timer.currentCount;
					_lastFrameDropValue = netStream.info.droppedFrames;
				}
				_averageDroppedFPSArray.unshift(_droppedFPS);
				if (_averageDroppedFPSArray.length > DEFAULT_AVG_FRAMERATE_SAMPLE_SIZE) 
				{
					_averageDroppedFPSArray.pop();
				}
				var totalDroppedFrameRate:Number = 0;
				for (var f:uint=0;f < _averageDroppedFPSArray.length;f++) 
				{
					totalDroppedFrameRate += _averageDroppedFPSArray[f];
				}
				
				_averageDroppedFPS = _averageDroppedFPSArray.length < DEFAULT_AVG_FRAMERATE_SAMPLE_SIZE ? 0 : totalDroppedFrameRate / _averageDroppedFPSArray.length;
				
			}
			catch (error:Error) 
			{
				CONFIG::LOGGING
				{
					logger.debug(".calculateMetrics() - " + error);
				}
				throw(error);
			}
		}
		
		// Internals
		//

		private function onNetStatusEvent(event:NetStatusEvent):void 
		{
			switch (event.info.code) 
			{
				case NetStreamCodes.NETSTREAM_PLAY_START:
					if (!_timer.running && updateInterval > 0) 
					{
						_timer.start();
					}
					break;
				case NetStreamCodes.NETSTREAM_PLAY_STOP:
					_timer.stop();
					break;
			}
		}
		
		private function onTimerEvent(event:TimerEvent):void 
		{
			if (isNaN(netStream.time))
			{
				_timer.stop();
			}
			else
			{
				calculateMetrics();
			}
		}
		
		private var _netStream:NetStream;
		private var _resource:DynamicStreamingResource;
		private var _currentIndex:int;
		private var _maxAllowedIndex:int;

		private var _timer:Timer;
		private var _averageDroppedFPSArray:Array;
		private var _averageDroppedFPS:Number;
		private var _droppedFPS:Number;
		private var _lastFrameDropValue:Number;
		private var _lastFrameDropCounter:Number;
		private var _maxFPS:Number;
		
		private static const DEFAULT_UPDATE_INTERVAL:Number = 100;
		private static const DEFAULT_AVG_FRAMERATE_SAMPLE_SIZE:Number = 50;
		
		CONFIG::LOGGING
		{
			private static const logger:Logger = Log.getLogger("org.osmf.net.NetStreamMetricsBase");
		}
	}
}
