// Copyright 2007. Adobe Systems Incorporated. All Rights Reserved.
package fl.managers {
	
	import fl.core.UIComponent;
	import flash.display.Sprite;
	import flash.text.TextFormat;
	import flash.utils.Dictionary;
	import flash.utils.getDefinitionByName;
	import flash.utils.getQualifiedClassName;
	import flash.utils.getQualifiedSuperclassName;
	
	/**
	 * The StyleManager class provides static methods that can be used to get and 
	 * set styles for a component instance, an entire component type, or all user 
	 * interface components in a Flash document. Styles are defined as values that 
     * affect the display of a component, including padding, text formats, and skins.
	 *
	 * @includeExample examples/StyleManagerExample.as -noswf
	 *
	 * @langversion 3.0
     * @playerversion Flash 9.0.28.0
	 *  
	 *  @playerversion AIR 1.0

	 *  @productversion Flash CS3
	 */
	public class StyleManager {
        /**
         * @private
         *
         * @langversion 3.0
         * @playerversion Flash 9.0.28.0
         */
		private static var _instance:StyleManager;

		// Allows lookups of all classes that use a specific style:
        /**
         * @private
         *
         * @langversion 3.0
         * @playerversion Flash 9.0.28.0
         */
		private var styleToClassesHash:Object;

		// Allows lookups of all instances of a specific class:
        /**
         * @private
         *
         * @langversion 3.0
         * @playerversion Flash 9.0.28.0
         */
		private var classToInstancesDict:Dictionary;

        // Allows lookup of current styles for a specific class:
        /**
         * @private
         *
         * @langversion 3.0
         * @playerversion Flash 9.0.28.0
         */
		private var classToStylesDict:Dictionary;

		// Allows lookup of default styles for a specific class:

        /**
         * @private
         *
         * @langversion 3.0
         * @playerversion Flash 9.0.28.0
         */
        private var classToDefaultStylesDict:Dictionary;

        /**
         * @private
         *
         * @langversion 3.0
         * @playerversion Flash 9.0.28.0
         */
		private var globalStyles:Object;
		
		/**
         * Creates a new StyleManager object.
         *
         * @langversion 3.0
         * @playerversion Flash 9.0.28.0
		 *  
		 *  @playerversion AIR 1.0

		 *  @productversion Flash CS3
		 */
		public function StyleManager() {
			styleToClassesHash = {};
			classToInstancesDict = new Dictionary(true);
			classToStylesDict = new Dictionary(true);
			classToDefaultStylesDict = new Dictionary(true)
			globalStyles = UIComponent.getStyleDefinition();
		}
		
		/**
         * @private
         *
         * @langversion 3.0
         * @playerversion Flash 9.0.28.0
		 */
		private static function getInstance() {
			if (_instance == null) { _instance = new StyleManager(); }
			return _instance;
		}
		
		/**
		 * Registers a component instance with the style manager. After a component instance is
		 * instantiated, it can register with the style manager to be notified of changes 
		 * in style. Component instances can register to receive notice of style changes that are
		 * component-based or global in nature.
         *
		 * @param instance The component instance to be registered for style
         * management.
         *
         * @internal Do you guys have a code snippet/test case/sample you could give us for this? (pdehaan(at)adobe.com)
         * Adobe: [LM] Although this method is public, it is all handled internally by UIComponent.  Each component registers itself when it instantiates.
         * @internal Should this then be (at)private in the docs?
         *
         * @langversion 3.0
         * @playerversion Flash 9.0.28.0
		 *  
		 *  @playerversion AIR 1.0

		 *  @productversion Flash CS3
		 */
		public static function registerInstance(instance:UIComponent):void {
			var inst:StyleManager = getInstance();
			var classDef:Class = getClassDef(instance);
			
			if (classDef == null) {	return;	}
			
			// check if an instance of this class has been registered before:
			if (inst.classToInstancesDict[classDef] == null) {
				
				inst.classToInstancesDict[classDef] = new Dictionary(true);
				// set up the style to class hash. This lets us look up which classes use which styles quickly.
				var target:Class = classDef;				
				var defaultStyles:Object;
				// Walk the inheritance chain looking for a default styles object.
				while (defaultStyles == null) {
					// Trick the strict compiler.
					if (target["getStyleDefinition"] != null) {
						defaultStyles = target["getStyleDefinition"]();
						break;
					}
					try {
						target = instance.loaderInfo.applicationDomain.getDefinition(getQualifiedSuperclassName(target)) as Class;
					} catch(err:Error) {
						try {
							target = getDefinitionByName(getQualifiedSuperclassName(target)) as Class;
						} catch (e:Error) {
							defaultStyles = UIComponent.getStyleDefinition();
							break;
						}
					}
				}
				
				var styleToClasses:Object = inst.styleToClassesHash;
				for (var n:String in defaultStyles) {
					
					if (styleToClasses[n] == null) {
						styleToClasses[n] = new Dictionary(true);
					}
					// add this class as a subscriber to this style:
					styleToClasses[n][classDef] = true;
				}
				// add this class's default styles:
				inst.classToDefaultStylesDict[classDef] = defaultStyles;
				if (inst.classToStylesDict[classDef] == null) {
					// set up the override styles table:
					inst.classToStylesDict[classDef] = {};
				}
			}
			inst.classToInstancesDict[classDef][instance] = true;
			setSharedStyles(instance);
		}
		
		/**
		 * @private
		 * 
		 * Sets an inherited style on a component.
		 *
         * @param instance The component object on which to set the inherited style.
         *
         * @langversion 3.0
         * @playerversion Flash 9.0.28.0
		 */
		private static function setSharedStyles(instance:UIComponent):void {
			var inst:StyleManager = getInstance();
			var classDef:Class = getClassDef(instance);
			var styles:Object = inst.classToDefaultStylesDict[classDef];
			for (var n:String in styles) {
				instance.setSharedStyle(n,getSharedStyle(instance,n));
			}
		}
		
        /**
         * @private
         *
         * @langversion 3.0
         * @playerversion Flash 9.0.28.0
		 */
		private static function getSharedStyle(instance:UIComponent,name:String):Object {
			var classDef:Class = getClassDef(instance);
			var inst:StyleManager = getInstance();
			// first check component styles:
			var style:Object = inst.classToStylesDict[classDef][name];
			if (style != null) { return style; }
			// then check global styles:
			style = inst.globalStyles[name];
			
			if (style != null) { return style; }
			// finally return the default component style:
			return inst.classToDefaultStylesDict[classDef][name];
		}
		
		/**
		 * Gets a style that exists on a specific component.
		 *
		 * @param component The name of the component instance on which to find the
		 *        requested style.
		 *
		 * @param name The name of the style to be retrieved.
		 *
		 * @return The requested style from the specified component. This function returns <code>null</code> 
         * if the specified style is not found.
         *
         * @see #clearComponentStyle()
         * @see #getStyle()
         * @see #setComponentStyle()
         *
         * @langversion 3.0
         * @playerversion Flash 9.0.28.0
		 *  
		 *  @playerversion AIR 1.0

		 *  @productversion Flash CS3
		 */
		public static function getComponentStyle(component:Object,name:String):Object {
			var classDef:Class = getClassDef(component);
			var styleHash:Object = getInstance().classToStylesDict[classDef];
			return (styleHash == null) ? null : styleHash[name];
		}
		
		/**
		 * Removes a style from the specified component.
		 *
		 * @param component The name of the component from which the style is to be removed.
		 *
         * @param name The name of the style to be removed.
         *
         * @see #clearStyle()
         * @see #getComponentStyle()
         * @see #setComponentStyle()
         * 
         * @langversion 3.0
         * @playerversion Flash 9.0.28.0
		 *  
		 *  @playerversion AIR 1.0

		 *  @productversion Flash CS3
		 */
		public static function clearComponentStyle(component:Object,name:String):void {
			var classDef:Class = getClassDef(component);
			var styleHash:Object = getInstance().classToStylesDict[classDef];
			if (styleHash != null && styleHash[name] != null) {
				delete(styleHash[name]);
				invalidateComponentStyle(classDef,name);
			}
		}
		
		/**
		 * Sets a style on all instances of a component type, for example, on all instances of a 
		 * Button component, or on all instances of a ComboBox component. 
		 *
		 * @param component The type of component, for example, Button or ComboBox.  This parameter also accepts
		 * a component instance or class that can be used to identify all instances of a component type.
		 *
		 * @param name The name of the style to be set.
		 *
		 * @param style The style object that describes the style that is to be set.
         *
         * @see #clearComponentStyle()
         * @see #getComponentStyle()
         * @see #setStyle()
         *
         * @langversion 3.0
         * @playerversion Flash 9.0.28.0
		 *  
		 *  @playerversion AIR 1.0

		 *  @productversion Flash CS3
		 */
		public static function setComponentStyle(component:Object,name:String,style:Object):void {
			var classDef:Class = getClassDef(component);
			var styleHash:Object = getInstance().classToStylesDict[classDef];
			if (styleHash == null) {
				styleHash = getInstance().classToStylesDict[classDef] = {};
			}
			if (styleHash == style) { return; }
			styleHash[name] = style;
			invalidateComponentStyle(classDef,name);
		}
		
        /**
         * @private (protected)
         *
         * @langversion 3.0
         * @playerversion Flash 9.0.28.0
		 */
		private static function getClassDef(component:Object):Class {
			if (component is Class) { 
				return (component as Class);
			}
			try {
				return getDefinitionByName(getQualifiedClassName(component)) as Class;
			} catch (e:Error) {
				if (component is UIComponent) {
					try {
						return component.loaderInfo.applicationDomain.getDefinition(getQualifiedClassName(component)) as Class;
					} catch (e:Error) {}
				}
			}
			return null;
		}
		
        /**
         * @private (protected)
         *
         * @langversion 3.0
         * @playerversion Flash 9.0.28.0
		 */
		private static function invalidateStyle(name:String):void {
			var classes:Dictionary = getInstance().styleToClassesHash[name];
			if (classes == null) { return; }
			for (var classRef:Object in classes) {
				invalidateComponentStyle(Class(classRef),name);
			}
		}
		
        /**
         * @private (protected)
         *
         * @langversion 3.0
         * @playerversion Flash 9.0.28.0
		 */
		private static function invalidateComponentStyle(componentClass:Class,name:String):void {
			var instances:Dictionary = getInstance().classToInstancesDict[componentClass];
			if (instances == null) { return; }
			
			for (var obj:Object in instances) {
				var instance:UIComponent = obj as UIComponent;
				if (instance == null) { continue; }
				instance.setSharedStyle(name,getSharedStyle(instance,name));
			}
		}
		
		/**
		 * Sets a global style for all user interface components in a document.
		 *
		 * @param name A String value that names the style to be set.
		 *
		 * @param style The style object to be set. The value of this property depends on the 
		 * style that the user sets. For example, if the style is set to "textFormat", the style 
		 * property should be set to a TextFormat object. A mismatch between the style name and
		 * the value of the style property may cause the component to behave incorrectly.
         *
         * @see #clearStyle()
         * @see #getStyle()
         * @see #setComponentStyle()
         *
         * @langversion 3.0
         * @playerversion Flash 9.0.28.0
		 *  
		 *  @playerversion AIR 1.0

		 *  @productversion Flash CS3
		 */
		public static function setStyle(name:String, style:Object):void {
			var styles:Object = getInstance().globalStyles;
			if (styles[name] === style && !(style is TextFormat)) { return; }
			styles[name] = style;
			invalidateStyle(name);
		}
		
		/**
		 * Removes a global style from all user interface components in a document.
		 *
         * @param name The name of the global style to be removed.
         *
         * @see #clearComponentStyle()
         * @see #getStyle()
         * @see #setStyle()
         *
         * @langversion 3.0
         * @playerversion Flash 9.0.28.0
		 *  
		 *  @playerversion AIR 1.0

		 *  @productversion Flash CS3
		 */
		public static function clearStyle(name:String):void {
			setStyle(name,null);
		}
		
		/**
		 * Gets a global style by name.
		 *
		 * @param name The name of the style to be retrieved.
		 *
		 * @return The value of the global style that was retrieved.
         *
		 * @internal "that was removed" - doesn't sound right. Do you guys have a code snippet/test 
		 *         case/sample you could give us for this? (rberry(at)adobe.com)
         * Adobe: [LM] Correct - description was wrong.  Code sample would be simple: {var textFormat:TextFormat = StyleManager.getStyle("textFormat") as TextFormat;}
         *
         * @see #clearStyle()
         * @see #getComponentStyle()
         * @see #setStyle()
         *
         * @langversion 3.0
         * @playerversion Flash 9.0.28.0
		 *  
		 *  @playerversion AIR 1.0

		 *  @productversion Flash CS3
		 */
		public static function getStyle(name:String):Object {
			return getInstance().globalStyles[name];
		}
		
	}
	
}
