/*****************************************************
*  
*  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
{
	import __AS3__.vec.Vector;
	
	import flash.events.TimerEvent;
	import flash.utils.Timer;
	
	import org.osmf.elements.proxyClasses.DurationSeekTrait;
	import org.osmf.elements.proxyClasses.DurationTimeTrait;
	import org.osmf.events.PlayEvent;
	import org.osmf.events.SeekEvent;
	import org.osmf.events.TimeEvent;
	import org.osmf.media.MediaElement;
	import org.osmf.traits.MediaTraitType;
	import org.osmf.traits.PlayState;
	import org.osmf.traits.PlayTrait;

	/**
	 * DurationElement is a media element that wraps a MediaElement to give it
	 * temporal capabilities.
	 * It allows a non-temporal MediaElement to be treated as a temporal MediaElement.
	 * <p>The DurationElement class is especially useful for creating delays
	 * in the presentation of a media composition.
	 * For example, the following code presents a sequence of videos,
	 * separated from each other by five-second delays.</p>
	 * <listing>
	 * var sequence:SerialElement = new SerialElement();
	 * 
	 * sequence.addChild(new VideoElement(new URLResource("http://www.example.com/video1.flv")));
	 * sequence.addChild(new DurationElement(5));
	 * sequence.addChild(new VideoElement(new URLResource("http://www.example.com/ad.flv")));
	 * sequence.addChild(new DurationElement(5));
	 * sequence.addChild(new VideoElement(new URLResource("http://www.example.com/video2.flv")));
	 * 
	 * // Assign the SerialElement to the MediaPlayer.
	 * player.media = sequence;
	 * </listing>
	 * <p>The following example presents a sequence of rotating banners.
	 * The delays separating the appearances of the banners are 
	 * created with DurationElements.
	 * In addition, the images themselves are wrapped in DurationElements
	 * to enable them to support a duration.</p>
	 * <listing>
	 * // The first banner does not appear for five seconds.
	 * // Each banner is shown for 20 seconds.
	 * // There is a 15-second delay between images.
	 * 
	 * var bannerSequence:SerialElement = new SerialElement();
	 * 
	 * bannerSequence.addChild(new DurationElement(5));
	 * bannerSequence.addChild(new DurationElement(20,new ImageElement(new URLResource("http://www.example.com/banner1.jpg")));
	 * bannerSequence.addChild(new DurationElement(15));
	 * bannerSequence.addChild(new DurationElement(20,new ImageElement(new URLResource("http://www.example.com/banner2.jpg")));
	 * bannerSequence.addChild(new DurationElement(15));
	 * bannerSequence.addChild(new DurationElement(20,new ImageElement(new URLResource("http://www.example.com/banner3.jpg")));
	 * </listing>
	 * 
	 * The DurationElement will not work with elements that already have a TimeTrait, such
	 * as VideoElement.  To specify a start and end time for a VideoElement. use
	 * StreamingURLResource's <code>clipStartTime</code> and <code>clipEndTime</code> properties.
	 * 
	 * @includeExample DurationElementExample.as -noswf
	 * 
	 * @see org.osmf.elements.ProxyElement
	 * @see org.osmf.elements.SerialElement
	 * @see org.osmf.net.StreamingURLResource
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 10
	 *  @playerversion AIR 1.5
	 *  @productversion OSMF 1.0
	 */	
	public class DurationElement extends ProxyElement
	{
		/**
	 	 * Constructor.
	 	 * 
	 	 * @param duration Duration of the DurationElement's TimeTrait, in seconds.
	 	 * @param mediaElement Optional element to be wrapped by this DurationElement.
	 	 *  
	 	 *  @langversion 3.0
	 	 *  @playerversion Flash 10
	 	 *  @playerversion AIR 1.5
	 	 *  @productversion OSMF 1.0
	 	 */		
		public function DurationElement(duration:Number, mediaElement:MediaElement=null)
		{
			_duration = duration;
			
			// Prepare the position timer.
			playheadTimer = new Timer(DEFAULT_PLAYHEAD_UPDATE_INTERVAL);
			playheadTimer.addEventListener(TimerEvent.TIMER, onPlayheadTimer, false, 0, true);
			
			super(mediaElement != null ? mediaElement : new MediaElement());
		}
		
		/**
		 * @private
		 * 
	 	 * Sets up the element's TimeTrait, SeekTrait, and PlayTrait.
	 	 * The proxy's traits will override the same traits in the wrapped element.
	 	 * <p>This gives the application access to the trait properties in the wrapped
	 	 * element that did not exist before it was wrapped.</p>
	 	 * <p>For example, the DurationElement in the following line wraps an ImageElement.
	 	 * The <code>duration</code> property of the DurationElement's TimeTrait allows
	 	 * the application to specify the duration that the image is displayed, in this case 20 seconds.</p>
	 	 * <listing>
	 	 * bannerSequence.addChild(new DurationElement(20,new ImageElement(new ImageLoader(),
	 	 * 	new URLResource("http://www.example.com/banner1.jpg")));	
	 	 * </listing>
	 	 */	
		override protected function setupTraits():void
		{
			super.setupTraits();
			
			timeTrait = new DurationTimeTrait(_duration);
			
			// Increase the priority for our onComplete handler, so that we can get the
			// event first and present a consistent view to clients.
			timeTrait.addEventListener(TimeEvent.COMPLETE, onComplete, false, int.MAX_VALUE);
			addTrait(MediaTraitType.TIME, timeTrait);

			seekTrait = new DurationSeekTrait(timeTrait);
			addTrait(MediaTraitType.SEEK, seekTrait);
			
			// Reduce priority of our listener so that all other listeners will
			// receive the seeking=true event before we dispatch the seeking=false
			// event. 
			seekTrait.addEventListener(SeekEvent.SEEKING_CHANGE, onSeekingChange, false, -1);
			
			playTrait = new PlayTrait();
			playTrait.addEventListener(PlayEvent.PLAY_STATE_CHANGE, onPlayStateChange);
			addTrait(MediaTraitType.PLAY, playTrait);
			
			// Block all other traits, we'll unblock them once we're playing.
			blockedTraits = ALL_OTHER_TRAITS;
		}
		
		// Internals
		//

		private function onPlayheadTimer(event:TimerEvent):void
		{
			if (currentTime >= _duration)
			{
				playheadTimer.stop();
				playTrait.stop();

				currentTime = _duration;
			}
			else
			{
				// Increment our currentTime on each Timer tick.
				currentTime = (flash.utils.getTimer() - absoluteStartTime)/1000;
			}
		}
		
		private function onPlayStateChange(event:PlayEvent):void
		{
			if (event.playState == PlayState.PLAYING)
			{
				if (mediaAtEnd)
				{
					mediaAtEnd = false;
					currentTime = 0;
				} 
				
				// When play starts, we reset our absoluteStartTime based on
				// what our currentTime is.
				absoluteStartTime = flash.utils.getTimer() - currentTime*1000;
				playheadTimer.start();
			}
			else
			{
				playheadTimer.stop();
				
			}
			
			// When the element is neither playing nor paused, block exposure
			// of all non-overridden traits.
			if (event.playState != PlayState.STOPPED && currentTime < _duration)
			{
				blockedTraits = NO_TRAITS;
			}
			else
			{
				blockedTraits = ALL_OTHER_TRAITS;
			}

		}
		
		private function onSeekingChange(event:SeekEvent):void
		{
			mediaAtEnd = false;
			
			if (event.seeking)
			{
				// Adjust the currentTime and absoluteStartTime by the seek amount.
				var diff:Number = event.time - currentTime;
				currentTime = event.time;
				absoluteStartTime -= diff*1000;
			}
			else
			{
				// When the the user seeks outside the range of this element, block
				// exposure of all non-overridden traits.  When they seek into this
				// element, expose them again.  Note that we don't include currentTime
				// of zero as an unblocked case unless the media is playing, since
				// zero is usually the seek time for auto-rewound media.
				if (	currentTime < _duration
					&&	(currentTime > 0 || playTrait.playState == PlayState.PLAYING)
				   )
				{
					blockedTraits = NO_TRAITS;
				}
				else
				{
					blockedTraits = ALL_OTHER_TRAITS;
				}
			}
		}

		private function onComplete(event:TimeEvent):void
		{
			playheadTimer.stop();
			playTrait.stop();
			mediaAtEnd = true;
			
			// When playback completes, block exposure of all non-overridden traits.
			blockedTraits = ALL_OTHER_TRAITS;
		}
				
		private function get currentTime():Number
		{
			return _currentTime;
		}
		
		private function set currentTime(value:Number):void
		{
			_currentTime = value;
			timeTrait.currentTime = value;
		}
		
		private static const DEFAULT_PLAYHEAD_UPDATE_INTERVAL:Number = 250;
		private static const NO_TRAITS:Vector.<String> = new Vector.<String>();
		private static const ALL_OTHER_TRAITS:Vector.<String> = new Vector.<String>();
		{
			// Everything but LOAD, SEEK, PLAY, and TIME.
			ALL_OTHER_TRAITS.push(MediaTraitType.AUDIO);
			ALL_OTHER_TRAITS.push(MediaTraitType.BUFFER);
			ALL_OTHER_TRAITS.push(MediaTraitType.DISPLAY_OBJECT);
			ALL_OTHER_TRAITS.push(MediaTraitType.DRM);
			ALL_OTHER_TRAITS.push(MediaTraitType.DVR);
			ALL_OTHER_TRAITS.push(MediaTraitType.DYNAMIC_STREAM);
		}
		
		private var _currentTime:Number = 0; // seconds
		private var _duration:Number = 0;	// seconds
		private var absoluteStartTime:Number = 0; // milliseconds
		private var playheadTimer:Timer;
		private var mediaAtEnd:Boolean = false;
		
		private var timeTrait:DurationTimeTrait;
		private var seekTrait:DurationSeekTrait;
		private var playTrait:PlayTrait;
	}
}