/*****************************************************
*  
*  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.containers
{
	import flash.display.DisplayObject;
	import flash.errors.IllegalOperationError;
	import flash.geom.Rectangle;
	import flash.utils.Dictionary;
	
	import org.osmf.events.ContainerChangeEvent;
	import org.osmf.layout.LayoutMetadata;
	import org.osmf.layout.LayoutRenderer;
	import org.osmf.layout.LayoutRendererBase;
	import org.osmf.layout.LayoutTargetEvent;
	import org.osmf.layout.LayoutTargetSprite;
	import org.osmf.layout.MediaElementLayoutTarget;
	import org.osmf.media.MediaElement;
	import org.osmf.utils.OSMFStrings;
	
	/**
	 * MediaContainer defines a Sprite-based IMediaContainer implementation.
	 * 
	 * @includeExample MediaContainerExample.as -noswf
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 10
	 *  @playerversion AIR 1.5
	 *  @productversion OSMF 1.0
	 */	
	public class MediaContainer extends LayoutTargetSprite implements IMediaContainer
	{
		/**
		 * Constructor.
		 *  
		 * @param layoutRenderer The layout renderer that will render
		 * the MediaElement instances that get added to this container. If no
		 * renderer is specified, a LayoutRenderer instance will be used.
		 * @param layoutMetadata The LayoutMetadata to use to layout this
		 * sprite.  Optional.
		 * 
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */		
		public function MediaContainer(layoutRenderer:LayoutRendererBase=null, layoutMetadata:LayoutMetadata=null)
		{
			super(layoutMetadata);
			
			_layoutRenderer = layoutRenderer || new LayoutRenderer();
			_layoutRenderer.container = this; 
		}
		
		/**
		 * Adds a MediaElement instance to the container.
		 * 
		 * @param element The MediaElement instance to add to the container.
		 * @returns The added MediaElement instance.
		 * @throws IllegalOperationError if the specified element is null,
		 * or already a child of the container.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */		
		public function addMediaElement(element:MediaElement):MediaElement
		{
			if (element == null)
			{
				throw new IllegalOperationError(OSMFStrings.getString(OSMFStrings.NULL_PARAM));
			}
			
			if (layoutTargets[element] == undefined)
			{
				// Media containers are under obligation to dispatch a container change event when
				// they add a media element:
				element.dispatchEvent
					( new ContainerChangeEvent
						( ContainerChangeEvent.CONTAINER_CHANGE
						, false, false
						, element.container, this
						)
					);
					
				var contentTarget:MediaElementLayoutTarget = MediaElementLayoutTarget.getInstance(element);
				
				layoutTargets[element] = contentTarget;
				_layoutRenderer.addTarget(contentTarget);
				
				element.addEventListener(ContainerChangeEvent.CONTAINER_CHANGE, onElementContainerChange);
			}
			else
			{
				throw new IllegalOperationError(OSMFStrings.getString(OSMFStrings.INVALID_PARAM));
			}
			
			return element;
		}
		
		/**
		 * Removes a MediaElement instance from the container.
		 * 
		 * @param element The element to remove from the container.
		 * @returns The removed MediaElement instance.
		 * @throws IllegalOperationError if the specified element isn't
		 * a child element, or is null.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */
		public function removeMediaElement(element:MediaElement):MediaElement
		{
			if (element == null)
			{
				throw new IllegalOperationError(OSMFStrings.getString(OSMFStrings.NULL_PARAM));
			}
			
			var result:MediaElement;
			var contentTarget:MediaElementLayoutTarget = layoutTargets[element];
			
			if (contentTarget)
			{
				element.removeEventListener(ContainerChangeEvent.CONTAINER_CHANGE, onElementContainerChange);
				_layoutRenderer.removeTarget(contentTarget);
				delete layoutTargets[element];
				result = element;
				
				// Media containers are under obligation to dispatch a container change event when
				// they remove a media element. See if we're still the element's container, though.
				// For if not, a change has already occured.
				if (element.container == this)
				{
					element.dispatchEvent
						( new ContainerChangeEvent
							( ContainerChangeEvent.CONTAINER_CHANGE
							, false, false
							, element.container, null
							)
						);
				}
			}
			else
			{
				throw new IllegalOperationError(OSMFStrings.getString(OSMFStrings.INVALID_PARAM));
			}
			
			return result;
		}
		
		/**
		 * Verifies if an element is a child of the container.
		 *  
		 * @param element Element to verify.
		 * @return True if the element if a child of the container.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */		
		public function containsMediaElement(element:MediaElement):Boolean
		{
			return layoutTargets[element] != undefined
		}
		
		/**
		 * The layout renderer that renders the MediaElement instances within
		 * this container.
		 **/
		public function get layoutRenderer():LayoutRendererBase
		{
			return _layoutRenderer;
		}
		
		/**
		 * Defines if the children of the container that display outside of its bounds 
		 * will be clipped or not.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */		
		public function get clipChildren():Boolean
		{
			return scrollRect != null;
		}
		
		public function set clipChildren(value:Boolean):void
		{
			if (value && scrollRect == null)
			{
				scrollRect = new Rectangle(0, 0, _layoutRenderer.measuredWidth, _layoutRenderer.measuredHeight);
			}
			else if (value == false && scrollRect)
			{
				scrollRect = null;
			} 
		}
		
		/**
		 * Defines the container's background color. By default, this value
		 * is set to NaN, which results in no background being drawn.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */		
		public function get backgroundColor():Number
		{
			return _backgroundColor;
		}

		public function set backgroundColor(value:Number):void
		{
			if (value != _backgroundColor)
			{
				_backgroundColor = value;
				drawBackground();
			}
		}
		
		/**
		 * Defines the container's background alpha. By default, this value
		 * is set to 1, which results in the background being fully opaque.
		 * 
		 * Note that a container will not have a background drawn unless its
		 * backgroundColor property is set.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */
		public function get backgroundAlpha():Number
		{
			return _backgroundAlpha;
		}

		public function set backgroundAlpha(value:Number):void
		{
			if (value != _backgroundAlpha)
			{
				_backgroundAlpha = value;
				drawBackground();
			}
		}
		
		// Overrides
		//
		
		/**
		 * @private
		 **/
		override public function layout(availableWidth:Number, availableHeight:Number, deep:Boolean = true):void
		{
			super.layout(availableWidth, availableHeight, deep);
			
			lastAvailableWidth = availableWidth;
			lastAvailableHeight = availableHeight;
			
			if (!isNaN(backgroundColor))
			{
				drawBackground();
			}
			
			if (scrollRect)
			{
				scrollRect = new Rectangle(0, 0, availableWidth, availableHeight);
			}
		}
				
		/**
		 * @private
		 */
		override public function validateNow():void
		{
			_layoutRenderer.validateNow();
		}
		
		// Overrides
		//
		
		/**
		 * @private
		 **/
		override public function addChild(child:DisplayObject):DisplayObject
		{
			throw new IllegalOperationError(OSMFStrings.getString(OSMFStrings.DIRECT_DISPLAY_LIST_MOD_ERROR));
		}
		
		/**
		 * @private
		 **/
		override public function addChildAt(child:DisplayObject, index:int):DisplayObject
		{
			throw new IllegalOperationError(OSMFStrings.getString(OSMFStrings.DIRECT_DISPLAY_LIST_MOD_ERROR));
		}
		
		/**
		 * @private
		 **/
		override public function removeChild(child:DisplayObject):DisplayObject
		{
			throw new IllegalOperationError(OSMFStrings.getString(OSMFStrings.DIRECT_DISPLAY_LIST_MOD_ERROR));
		}
		
		/**
		 * @private
		 **/
		override public function setChildIndex(child:DisplayObject, index:int):void
		{
			throw new IllegalOperationError(OSMFStrings.getString(OSMFStrings.DIRECT_DISPLAY_LIST_MOD_ERROR));
		}
		
		/**
		 * @private
		 **/
		override protected function onAddChildAt(event:LayoutTargetEvent):void
		{
			super.addChildAt(event.displayObject, event.index);
		}
		
		/**
		 * @private
		 **/
		override protected function onRemoveChild(event:LayoutTargetEvent):void
		{
			super.removeChild(event.displayObject);	
		}
		
		/**
		 * @private
		 **/
		override protected function onSetChildIndex(event:LayoutTargetEvent):void
		{
			super.setChildIndex(event.displayObject, event.index);	
		}
		
		// Internals
		//
		
		private function drawBackground():void
		{
			graphics.clear();
			
			if	(	!isNaN(_backgroundColor)
				&& 	_backgroundAlpha != 0
				&&	lastAvailableWidth
				&&	lastAvailableHeight
				)
			{
				graphics.beginFill(_backgroundColor,_backgroundAlpha);
				graphics.drawRect(0, 0, lastAvailableWidth, lastAvailableHeight);
				graphics.endFill();
			}
		}
		
		private function onElementContainerChange(event:ContainerChangeEvent):void
		{
			if (event.oldContainer == this)
			{
				removeMediaElement(event.target as MediaElement);
			}
		}
		
		/**
		 * @private
		 * 
		 * Dictionary of MediaElementLayoutTarget instances, index by the
		 * media elements that they wrap: 
		 */		
		private var layoutTargets:Dictionary = new Dictionary();
		
		private var _layoutRenderer:LayoutRendererBase;
		
		private var _backgroundColor:Number;
		private var _backgroundAlpha:Number;
		
		private var lastAvailableWidth:Number;
		private var lastAvailableHeight:Number;
	}
}