/*****************************************************
*  
*  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.errors.IllegalOperationError;
	import flash.external.ExternalInterface;
	import flash.utils.Dictionary;
	
	import org.osmf.elements.htmlClasses.HTMLAudioTrait;
	import org.osmf.elements.htmlClasses.HTMLLoadTrait;
	import org.osmf.elements.htmlClasses.HTMLPlayTrait;
	import org.osmf.elements.htmlClasses.HTMLTimeTrait;
	import org.osmf.events.LoadEvent;
	import org.osmf.events.MediaError;
	import org.osmf.events.MediaErrorCodes;
	import org.osmf.events.MediaErrorEvent;
	import org.osmf.media.MediaElement;
	import org.osmf.media.MediaResourceBase;
	import org.osmf.media.URLResource;
	import org.osmf.traits.LoadState;
	import org.osmf.traits.MediaTraitBase;
	import org.osmf.traits.MediaTraitType;
	import org.osmf.utils.OSMFStrings;

	/**
	 * HTMLElement is a media element that represents a piece of media external
	 * to the Flash SWF, and within an HTML region.  It serves as a bridge between
	 * the OSMF APIs for controlling media, and a corresponding (external)
	 * JavaScript implementation.
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 10
	 *  @playerversion AIR 1.5
	 *  @productversion OSMF 1.0
	 */	
	public class HTMLElement extends MediaElement
	{
		// Public API
		//
		
		/**
		 * @private
		 */		
		public function set scriptPath(value:String):void
		{
			_scriptPath = value;
		}
		
		/**
		 * @private
		 */
		public function getPropertyCallback(property:String):*
		{
			var result:*;
			var playable:HTMLPlayTrait = switchableTraits[MediaTraitType.PLAY] as HTMLPlayTrait;
			var temporal:HTMLTimeTrait = switchableTraits[MediaTraitType.TIME] as HTMLTimeTrait;
			var audible:HTMLAudioTrait = switchableTraits[MediaTraitType.AUDIO] as HTMLAudioTrait;
			
			// All property names start with a capital, for they translate
			// to 'getXxxx' in JavaScript.
			switch (property)
			{
				// MediaElement core:
				case "Resource":
					if (resource is URLResource)
					{
						result = URLResource(resource).url;
					}
					break;
					
				// LoadTrait:
				case "LoadState":
					result = loadTrait.loadState;
					break;
					
				// PlayTrait:
				case "Playable":
					result = hasTrait(MediaTraitType.PLAY);
					break;
				case "PlayState":
					result = playable ? playable.playState : null; 
					break;
				case "CanPause":
					result = playable ? playable.canPause : false;
					break;
				
				// TimeTrait:
				case "Temporal":
					result = hasTrait(MediaTraitType.TIME);
					break;
				case "Duration":
					result = temporal ? temporal.duration : NaN;
					break;
				case "CurrentTime":
					result = temporal ? temporal.currentTime : NaN;
					break;
					
				// AudioTrait:
				case "Volume":
					result = audible ? audible.volume : NaN;
					break;
				case "Muted":
					result = audible ? audible.muted : false;
					break;
				case "Pan":
					result = audible ? audible.pan : NaN;
					break;
			}
			
			return result;
		}
		
		/**
		 * @private
		 */
		public function setPropertyCallback(property:String, value:*):void
		{
			settingAProperty = true;
			
			var playable:HTMLPlayTrait = switchableTraits[MediaTraitType.PLAY] as HTMLPlayTrait;
			var temporal:HTMLTimeTrait = switchableTraits[MediaTraitType.TIME] as HTMLTimeTrait;
			var audible:HTMLAudioTrait = switchableTraits[MediaTraitType.AUDIO] as HTMLAudioTrait;
				
			// All property names start with a capital, for they translate
			// to 'setXxxx' in JavaScript.
			switch (property)
			{
				// Load Trait
				case "LoadState":
					var newLoadState:String = value;
					if (loadTrait)
					{
						loadTrait.loadState = newLoadState;
						if (newLoadState == LoadState.LOAD_ERROR)
						{
							dispatchEvent
								( new MediaErrorEvent
									( MediaErrorEvent.MEDIA_ERROR
									, false
									, false
									, new MediaError(MediaErrorCodes.MEDIA_LOAD_FAILED)
									)
								);
						}
					}
					break;
					
				// Play Trait:
				case "Playable":
					setTraitEnabled(MediaTraitType.PLAY, value as Boolean);
					break;
				case "CanPause":
					if (playable)
					{
						playable.canPause = value;
					}
					break;
				case "PlayState":
					if (playable)
					{
						playable.playState = value;
					}
				
				// Time Trait:
				case "Temporal":
					setTraitEnabled(MediaTraitType.TIME, value as Boolean);
					break;
				case "Duration":
					if (temporal)
					{
						temporal.duration = value as Number;
					}
					break;
				case "CurrentTime":
					if (temporal)
					{
						temporal.currentTime = value as Number;
					}
					break;
					
				// AudioTrait
				case "Audible":
					setTraitEnabled(MediaTraitType.AUDIO, value as Boolean);
					break;
				case "Volume":
					if (audible)
					{
						audible.volume = value as Number;
					}
					break;
				case "Muted":
					if (audible)
					{
						audible.muted = value as Boolean;
					}
					break;
				case "Pan":
					if (audible)
					{
						audible.pan = value as Number;
					}
					break;
				// If the property is unknown, throw an exception:
				default:
					throw new IllegalOperationError
						( "Property '"
						+ property
						+ "' assigned from JavaScript is not supported on a MediaElement."
						);
					break;
			}
			
			settingAProperty = false;
		} 
		
		/**
		 * @private
		 */
		internal var settingAProperty:Boolean;
		
		/**
		 * @private
		 */
		public function invokeJavaScriptMethod(methodName:String, ...arguments):*
		{
			requireScriptPath;
			
			arguments.unshift(_scriptPath + "__" + methodName + "__");
			var result:* = ExternalInterface.call.apply(this, arguments);
					
			return result;
		}
		
		/**
		 * @private
		 */		
		private function setTraitEnabled(type:String, enabled:Boolean):void
		{
			if (switchableTraitTypes.indexOf(type) == -1)
			{
				throw new IllegalOperationError(OSMFStrings.getString(OSMFStrings.INVALID_PARAM));
			}
			
			var trait:MediaTraitBase = switchableTraits[type];
			if (trait == null && enabled == true)
			{
				// Instantiate the correct trait implementation:
				switch(type)
				{
					case MediaTraitType.PLAY:
						trait = new HTMLPlayTrait(this);
						break;
					case MediaTraitType.TIME:
						trait = new HTMLTimeTrait(this);
						break;
					case MediaTraitType.AUDIO:
						trait = new HTMLAudioTrait(this);
						break;
				}
				switchableTraits[type] = trait;
				updateTraits();
			}
			else if (trait != null && enabled == false)
			{
				// Remove the current trait implementation:
				delete switchableTraits[type];
				updateTraits();
			}
		}
		
		// Overrides
		//
		
		/**
		 * @private
		 */
		override public function set resource(value:MediaResourceBase):void
		{
			if (resource != value)
			{
				super.resource = value;
				
				// LoadTrait cannot have its resource reset anymore. As a
				// result, we need to consruct a new trait on the elements
				// resource being set:
				
				if (loadTrait)
				{
					loadTrait.unload();
					loadTrait.removeEventListener
						( LoadEvent.LOAD_STATE_CHANGE
						, onLoadStateChange
						);
				}
				
				loadTrait = new HTMLLoadTrait(this);
				
				{
					addTrait(MediaTraitType.LOAD, loadTrait);
					
					loadTrait.addEventListener
						( LoadEvent.LOAD_STATE_CHANGE
						, onLoadStateChange
						, false, int.MAX_VALUE
						);
				}
			}
		}
		
		// Private
		//
	
		private function onLoadStateChange(event:LoadEvent):void
		{
			updateTraits();
		}
	
		private function updateTraits():void
		{
			var type:String;
			
			if (loadTrait && loadTrait.loadState == LoadState.READY)
			{
				// Make sure that the constructed trait objects are
				// being reflected on being loaded:
				for (var typeObject:Object in switchableTraits)
				{
					type = String(typeObject);
					var trait:MediaTraitBase = switchableTraits[type]; 
					if (hasTrait(type) == false)
					{
						addTrait(type, trait);
					}
				}
			}
			else
			{
				// Don't expose any traits if not loaded (except for the 
				// LoadTrait):
				for each (type in traitTypes)
				{
					if (type != MediaTraitType.LOAD)
					{
						removeTrait(type);
					}
				}
			}
		}
	
		private function get requireScriptPath():*
		{
			if (_scriptPath == null)
			{
				throw new IllegalOperationError(OSMFStrings.getString(OSMFStrings.NULL_SCRIPT_PATH));	
			}
			
			return undefined;
		}
	
		private var loadTrait:HTMLLoadTrait;
		
		private var switchableTraits:Dictionary = new Dictionary();
		
		private var _scriptPath:String;
		
		/* static */
		
		private static const switchableTraitTypes:Vector.<String> = new Vector.<String>(3);
			switchableTraitTypes[0] = MediaTraitType.PLAY;
			switchableTraitTypes[1] = MediaTraitType.TIME;
			switchableTraitTypes[2] = MediaTraitType.AUDIO;
			
	}
}