/**
 * VERSION: 0.94
 * DATE: 9/22/2009
 * AS3
 * UPDATES AND DOCUMENTATION AT: http://blog.greensock.com/
 **/
package com.greensock {
	import flash.display.*;
	import flash.geom.*;
	import flash.utils.*;
/**
 *  TweenProxy essentially "stands in" for a DisplayObject, adding several tweenable properties as well as the
 * 	ability to set a custom registration point around which all transformations (like rotation, scale, and skew) 
 * 	occur. In addition to all the standard DisplayObject properties, TweenProxy adds: <br />
 * 	<ul>
 * 		<li> registration : Point </li>
 * 		<li> registrationX : Number </li>
 * 		<li> registrationY : Number </li>
 * 		<li> localRegistration : Point </li>
 * 		<li> localRegistrationX : Number </li>
 * 		<li> localRegistrationY : Number </li>
 * 		<li> skewX : Number </li>
 * 		<li> skewY : Number </li>
 * 		<li> skewX2 : Number </li>
 * 		<li> skewY2 : Number </li>
 * 		<li> scale : Number </li>
 * 	</ul> <br />
 * 
 * 	Tween the skewX and/or skewY for normal skews (which visually adjust scale to compensate), or skewX2 and/or skewY2 in order to 
 * 	skew without visually adjusting scale. Either way, the actual scaleX/scaleY/scaleZ values are not altered as far as the proxy 
 * 	is concerned.<br /><br />
 * 	
 * 	The <code>registration</code> point is based on the DisplayObject's parent's coordinate space whereas the <code>localRegistration</code> corresponds 
 * 	to the DisplayObject's inner coordinates, so it's very simple to define the registration point whichever way you prefer.<br /><br />
 * 	
 * 	Once you create a TweenProxy, it is best to always control your DisplayObject's properties through the 
 * 	TweenProxy so that the values don't become out of sync. You can set ANY DisplayObject property through the TweenProxy, 
 * 	and you can call DisplayObject methods as well. If you directly change the properties of the target (without going through the proxy), 
 * 	you'll need to call the	calibrate() method on the proxy. It's usually best to create only ONE proxy for each target, but if 
 * 	you create more than one, they will communicate with each other to keep the transformations and registration position in sync
 * 	(unless you set ignoreSiblingUpdates to true).<br /><br />
 * 	
 * 	
 * <b>EXAMPLE:</b><br /><br />
 * 
 * 	To set a custom registration piont of x:100, y:100, and tween the skew of a MovieClip named "my_mc" 30 degrees 
 * 	on the x-axis and scale to half-size over the course of 3 seconds using an Elastic ease, do:<br /><br /><code>
 * 	
 * 	import com.greensock.TweenProxy; <br />
 * 	import com.greensock.easing.Elastic; <br />
 * 	import flash.geom.Point; <br /><br />
 * 	
 * 	var myProxy:TweenProxy = TweenProxy.create(my_mc);<br />
 * 	myProxy.registration = new Point(100, 100);<br />
 * 	TweenLite.to(myProxy, 3, {skewX:30, scale:0.5, ease:Elastic.easeOut});<br /><br /></code>
 * 
 * <b>Copyright 2009, GreenSock. All rights reserved.</b> This work is subject to the terms in <a href="http://www.greensock.com/terms_of_use.html">http://www.greensock.com/terms_of_use.html</a> or for corporate Club GreenSock members, the software agreement that was issued with the corporate membership.
 * 
 * @author Jack Doyle, jack@greensock.com
 */	 
	dynamic public class TweenProxy extends Proxy {
		/** @private **/
		public static const VERSION:Number = 0.94;
		/** @private **/
		private static const _DEG2RAD:Number = Math.PI / 180; //precompute for speed
		/** @private **/
		private static const _RAD2DEG:Number = 180 / Math.PI; //precompute for speed
		/** @private **/
		private static var _dict:Dictionary = new Dictionary(false);
		/** @private **/
		private static var _addedProps:String = " tint tintPercent scale skewX skewY skewX2 skewY2 target registration registrationX registrationY localRegistration localRegistrationX localRegistrationY "; //used in hasProperty
		/** @private **/
		private var _target:DisplayObject;
		/** @private **/
		private var _angle:Number;
		/** @private **/
		private var _scaleX:Number;
		/** @private **/
		private var _scaleY:Number;
		/** @private populated with all TweenProxy instances with the same _target (basically a faster way to access _dict[_target])**/
		private var _proxies:Array;
		/** @private according to the local coordinates of _target (not _target.parent)**/
		private var _localRegistration:Point; 
		/** @private according to _target.parent coordinates**/
		private var _registration:Point; 
		/** @private If the localRegistration point is at 0, 0, this is true. We just use it to speed up processing in getters/setters.**/
		private var _regAt0:Boolean;
		
		/** @private **/
		public var ignoreSiblingUpdates:Boolean = false;
		/** @private potentially checked by TweenLite in the future. **/
		public var isTweenProxy:Boolean = true;
		
		public function TweenProxy(target:DisplayObject, ignoreSiblingUpdates:Boolean=false) {
			_target = target;
			if (_dict[_target] == undefined) {
				_dict[_target] = [];
			}
			_proxies = _dict[_target];
			_proxies.push(this);
			_localRegistration = new Point(0, 0);
			this.ignoreSiblingUpdates = ignoreSiblingUpdates;
			calibrate();
		}
		
		public static function create(target:DisplayObject, allowRecycle:Boolean=true):TweenProxy {
			if (_dict[target] != null && allowRecycle) {
				return _dict[target][0];
			} else {
				return new TweenProxy(target);
			}
		}
		
		public function getCenter():Point {
			var remove:Boolean = false;
			if (_target.parent == null) {
				remove = true;
				var s:Sprite = new Sprite();
				s.addChild(_target);
			}
			var b:Rectangle = _target.getBounds(_target.parent);
			var p:Point = new Point(b.x + (b.width / 2), b.y + (b.height / 2));
			if (remove) {
				_target.parent.removeChild(_target);
			}
			return p;
		}
		
		public function get target():DisplayObject {
			return _target;
		}
		
		public function calibrate():void {
			_scaleX = _target.scaleX;
			_scaleY = _target.scaleY;
			_angle = _target.rotation * _DEG2RAD;
			calibrateRegistration();
		}
		
		public function destroy():void {
			var a:Array = _dict[_target], i:int;
			for (i = a.length - 1; i > -1; i--) {
				if (a[i] == this) {
					a.splice(i, 1);
				}
			}
			if (a.length == 0) {
				delete _dict[_target];
			}
			_target = null;
			_localRegistration = null;
			_registration = null;
			_proxies = null;
		}
		
		
//---- PROXY FUNCTIONS ------------------------------------------------------------------------------------------
				
		/** @private **/
		flash_proxy override function callProperty(name:*, ...args:Array):* {
			return _target[name].apply(null, args);
		}
		
		/** @private **/
		flash_proxy override function getProperty(prop:*):* {
			return _target[prop];
		}
		
		/** @private **/
		flash_proxy override function setProperty(prop:*, value:*):void {
			_target[prop] = value;
		}
		
		/** @private **/
		flash_proxy override function hasProperty(name:*):Boolean {
			if (_target.hasOwnProperty(name)) {
				return true;
			} else if (_addedProps.indexOf(" " + name + " ") != -1) {
				return true;
			} else {
				return false;
			}
		}
		

//---- GENERAL REGISTRATION -----------------------------------------------------------------------
		
		/** @private **/
		public function moveRegX(n:Number):void {
			_registration.x += n;
		}
		
		/** @private **/
		public function moveRegY(n:Number):void {
			_registration.y += n;
		}
		
		/** @private **/
		private function reposition():void {
			var p:Point = _target.parent.globalToLocal(_target.localToGlobal(_localRegistration));
			_target.x += _registration.x - p.x;
			_target.y += _registration.y - p.y;
		}
		
		/** @private **/
		private function updateSiblingProxies():void {
			for (var i:int = _proxies.length - 1; i > -1; i--) {
				if (_proxies[i] != this) {
					_proxies[i].onSiblingUpdate(_scaleX, _scaleY, _angle);
				}
			}
		}
		
		/** @private **/
		private function calibrateLocal():void {
			_localRegistration = _target.globalToLocal(_target.parent.localToGlobal(_registration));
			_regAt0 = (_localRegistration.x == 0 && _localRegistration.y == 0);
		}
		
		/** @private **/
		private function calibrateRegistration():void {
			_registration = _target.parent.globalToLocal(_target.localToGlobal(_localRegistration));
			_regAt0 = (_localRegistration.x == 0 && _localRegistration.y == 0);
		}
		
		/** @private **/
		public function onSiblingUpdate(scaleX:Number, scaleY:Number, angle:Number):void {
			_scaleX = scaleX;
			_scaleY = scaleY;
			_angle = angle;
			if (this.ignoreSiblingUpdates) {
				calibrateLocal();
			} else {
				calibrateRegistration();
			}
		}
		
		
//---- LOCAL REGISTRATION ---------------------------------------------------------------------------
		
		public function get localRegistration():Point {
			return _localRegistration;
		}
		public function set localRegistration(p:Point):void {
			_localRegistration = p;
			calibrateRegistration();
		}
		
		public function get localRegistrationX():Number {
			return _localRegistration.x;
		}
		public function set localRegistrationX(n:Number):void {
			_localRegistration.x = n;
			calibrateRegistration();
		}
		
		public function get localRegistrationY():Number {
			return _localRegistration.y;
		}
		public function set localRegistrationY(n:Number):void {
			_localRegistration.y = n;
			calibrateRegistration();
		}
		
		
//---- REGISTRATION (OUTER) ----------------------------------------------------------------------
		
		public function get registration():Point {
			return _registration
		}
		public function set registration(p:Point):void {
			_registration = p;
			calibrateLocal();
		}
		
		public function get registrationX():Number {
			return _registration.x;
		}
		public function set registrationX(n:Number):void {
			_registration.x = n;
			calibrateLocal();
		}
		
		public function get registrationY():Number {
			return _registration.y;
		}
		public function set registrationY(n:Number):void {
			_registration.y = n;
			calibrateLocal();
		}
		
		
//---- X/Y MOVEMENT ---------------------------------------------------------------------------------
		
		public function get x():Number {
			return _registration.x;
		}
		public function set x(n:Number):void {
			var tx:Number = (n - _registration.x);
			_target.x += tx;
			for (var i:int = _proxies.length - 1; i > -1; i--) {
				if (_proxies[i] == this || !_proxies[i].ignoreSiblingUpdates) {
					_proxies[i].moveRegX(tx);
				}
			}
		}
		
		public function get y():Number {
			return _registration.y;
		}
		public function set y(n:Number):void {
			var ty:Number = (n - _registration.y);
			_target.y += ty;
			for (var i:int = _proxies.length - 1; i > -1; i--) {
				if (_proxies[i] == this || !_proxies[i].ignoreSiblingUpdates) {
					_proxies[i].moveRegY(ty);
				}
			}
		}
		
	
//---- ROTATION ----------------------------------------------------------------------------
		
		public function get rotation():Number {
			return _angle * _RAD2DEG;
		}
		public function set rotation(n:Number):void {
			var radians:Number = n * _DEG2RAD;
			var m:Matrix = _target.transform.matrix;
			m.rotate(radians - _angle);
			_target.transform.matrix = m;
			_angle = radians;
			reposition();
			if (_proxies.length > 1) { //if there are other proxies controlling the same _target, make sure their _registration variable is updated
				updateSiblingProxies();
			}
		}
		
		
//---- SKEW -------------------------------------------------------------------------------
		
		public function get skewX():Number {
			var m:Matrix = _target.transform.matrix;
			return (Math.atan2(-m.c, m.d) - _angle) * _RAD2DEG;
		}
		public function set skewX(n:Number):void {
			var radians:Number = n * _DEG2RAD
			var m:Matrix = _target.transform.matrix;
			var sy:Number = (_scaleY < 0) ? -_scaleY : _scaleY;
			m.c = -sy * Math.sin(radians + _angle);
			m.d =  sy * Math.cos(radians + _angle);
			_target.transform.matrix = m;
			if (!_regAt0) {
				reposition();
			}
			if (_proxies.length > 1) { //if there are other proxies controlling the same _target, make sure their _registration variable is updated
				updateSiblingProxies();
			}
			
		}
		public function get skewY():Number {
			var m:Matrix = _target.transform.matrix;
			return (Math.atan2(m.b, m.a) - _angle) * _RAD2DEG;
		}
		public function set skewY(n:Number):void {
			var radians:Number = n * _DEG2RAD;
			var m:Matrix = _target.transform.matrix;
			var sx:Number = (_scaleX < 0) ? -_scaleX : _scaleX;
			m.a = sx * Math.cos(radians + _angle);
			m.b = sx * Math.sin(radians + _angle);
			_target.transform.matrix = m;
			if (!_regAt0) { 
				reposition();
			}
			if (_proxies.length > 1) { //if there are other proxies controlling the same _target, make sure their _registration variable is updated
				updateSiblingProxies();
			}
		}
		
		
//---- SKEW2 ----------------------------------------------------------------------------------
		
		public function get skewX2():Number {
			return this.skewX2Radians * _RAD2DEG;
		}
		public function set skewX2(n:Number):void {
			this.skewX2Radians = n * _DEG2RAD
		}
		public function get skewY2():Number {
			return this.skewY2Radians * _RAD2DEG;
		}
		public function set skewY2(n:Number):void {
			this.skewY2Radians = n * _DEG2RAD;
		}
		public function get skewX2Radians():Number {
			return -Math.atan(_target.transform.matrix.c);
		}
		public function set skewX2Radians(n:Number):void {
			var m:Matrix = _target.transform.matrix;
			m.c = Math.tan(-n);
			_target.transform.matrix = m;
			if (!_regAt0) { 
				reposition();
			}
			if (_proxies.length > 1) { //if there are other proxies controlling the same _target, make sure their _registration variable is updated
				updateSiblingProxies();
			}
		}
		public function get skewY2Radians():Number {
			return Math.atan(_target.transform.matrix.b);
		}
		public function set skewY2Radians(n:Number):void {
			var m:Matrix = _target.transform.matrix;
			m.b = Math.tan(n);
			_target.transform.matrix = m;
			if (!_regAt0) {
				reposition();
			}
			if (_proxies.length > 1) { //if there are other proxies controlling the same _target, make sure their _registration variable is updated
				updateSiblingProxies();
			}
		}
		
		
//---- SCALE --------------------------------------------------------------------------------------
		
		public function get scaleX():Number {
			return _scaleX;
		}
		public function set scaleX(n:Number):void {
			if (n == 0) {
				n = 0.0001;
			}
			var m:Matrix = _target.transform.matrix;
			m.rotate(-_angle);
			m.scale(n / _scaleX, 1);
			m.rotate(_angle);
			_target.transform.matrix = m;
			_scaleX = n;
			reposition();
			if (_proxies.length > 1) { //if there are other proxies controlling the same _target, make sure their _registration variable is updated
				updateSiblingProxies();
			}
		}
		public function get scaleY():Number {
			return _scaleY;
		}
		public function set scaleY(n:Number):void {
			if (n == 0) {
				n = 0.0001;
			}
			var m:Matrix = _target.transform.matrix;
			m.rotate(-_angle);
			m.scale(1, n / _scaleY);
			m.rotate(_angle);
			_target.transform.matrix = m;
			_scaleY = n;
			reposition();
			if (_proxies.length > 1) { //if there are other proxies controlling the same _target, make sure their _registration variable is updated
				updateSiblingProxies();
			}
		}
		
		public function get scale():Number {
			return (_scaleX + _scaleY) / 2;
		}
		public function set scale(n:Number):void {
			if (n == 0) {
				n = 0.0001;
			}
			var m:Matrix = _target.transform.matrix;
			m.rotate(-_angle);
			m.scale(n / _scaleX, n / _scaleY);
			m.rotate(_angle);
			_target.transform.matrix = m;
			_scaleX = _scaleY = n;
			reposition();
			if (_proxies.length > 1) { //if there are other proxies controlling the same _target, make sure their _registration variable is updated
				updateSiblingProxies();
			}
		}
		
				
//---- OTHER PROPERTIES ---------------------------------------------------------------------------------
	
		public function get alpha():Number {
			return _target.alpha;
		}
		public function set alpha(n:Number):void {
			_target.alpha = n;
		}
		public function get width():Number {
			return _target.width;
		}
		public function set width(n:Number):void {
			_target.width = n;
			if (!_regAt0) { 
				reposition();
			}
			if (_proxies.length > 1) { //if there are other proxies controlling the same _target, make sure their _registration variable is updated
				updateSiblingProxies();
			}
		}
		public function get height():Number {
			return _target.height;
		}
		public function set height(n:Number):void {
			_target.height = n;
			if (!_regAt0) { 
				reposition();
			}
			if (_proxies.length > 1) { //if there are other proxies controlling the same _target, make sure their _registration variable is updated
				updateSiblingProxies();
			}
		}
		
	}
}