package classes.com.senocular.display {
	
	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;
	import flash.display.Shape;
	import flash.display.Sprite;
	import flash.display.Stage;
	import flash.events.Event;
	import flash.events.EventPhase;
	import flash.events.MouseEvent;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.geom.Transform;
	import flash.utils.Dictionary;
	
	// TODO: Documentation
	// TODO: Handle 0-size transformations
	
	/**
	 * Creates a transform tool that allows uaers to modify display objects on the screen
	 * 
	 * @usage
	 * <pre>
	 * var tool:TransformTool = new TransformTool();
	 * addChild(tool);
	 * tool.target = targetDisplayObject;
	 * </pre>
	 * 
	 * @version 0.9.11
	 * @author  Trevor McCauley
	 * @author  http://www.senocular.com
	 */
	public class TransformTool extends Sprite {
		
		// Variables
		private var toolInvertedMatrix:Matrix = new Matrix();
		private var innerRegistration:Point = new Point();
		private var registrationLog:Dictionary = new Dictionary(true);
		
		private var targetBounds:Rectangle = new Rectangle();
		
		private var mouseLoc:Point = new Point();
		private var mouseOffset:Point = new Point();
		private var innerMouseLoc:Point = new Point();
		private var interactionStart:Point = new Point();
		private var innerInteractionStart:Point = new Point();
		private var interactionStartAngle:Number = 0;
		private var interactionStartMatrix:Matrix = new Matrix();
		
		private var toolSprites:Sprite = new Sprite();
		private var lines:Sprite = new Sprite();
		private var moveControls:Sprite = new Sprite();
		private var registrationControls:Sprite = new Sprite();
		private var rotateControls:Sprite = new Sprite();
		private var scaleControls:Sprite = new Sprite();
		private var skewControls:Sprite = new Sprite();
		private var cursors:Sprite = new Sprite();
		private var customControls:Sprite = new Sprite();
		private var customCursors:Sprite = new Sprite();
		
		// With getter/setters
		private var _target:DisplayObject;
		private var _toolMatrix:Matrix = new Matrix();
		private var _globalMatrix:Matrix = new Matrix();
		
		private var _registration:Point = new Point();
		
		private var _livePreview:Boolean = true;
		private var _raiseNewTargets:Boolean = true;
		private var _moveNewTargets:Boolean = false;
		private var _moveEnabled:Boolean = true;
		private var _registrationEnabled:Boolean = true;
		private var _rotationEnabled:Boolean = true;
		private var _scaleEnabled:Boolean = true; 
		private var _skewEnabled:Boolean = true;
		private var _outlineEnabled:Boolean = true;
		private var _customControlsEnabled:Boolean = true;
		private var _customCursorsEnabled:Boolean = true;
		private var _cursorsEnabled:Boolean = true; 
		private var _rememberRegistration:Boolean = true;
		
		private var _constrainScale:Boolean = false;
		private var _constrainRotationAngle:Number = Math.PI/4; // default at 45 degrees
		private var _constrainRotation:Boolean = false;
		
		private var _moveUnderObjects:Boolean = true;
		private var _maintainControlForm:Boolean = true;
		private var _controlSize:Number = 8;
			
		private var _maxScaleX:Number = Infinity;
		private var _maxScaleY:Number = Infinity;
		
		private var _boundsTopLeft:Point = new Point();
		private var _boundsTop:Point = new Point();
		private var _boundsTopRight:Point = new Point();
		private var _boundsRight:Point = new Point();
		private var _boundsBottomRight:Point = new Point();
		private var _boundsBottom:Point = new Point();
		private var _boundsBottomLeft:Point = new Point();
		private var _boundsLeft:Point = new Point();
		private var _boundsCenter:Point = new Point();
		
		private var _currentControl:TransformToolControl;
		
		private var _moveControl:TransformToolControl;
		private var _registrationControl:TransformToolControl;
		private var _outlineControl:TransformToolControl;
		private var _scaleTopLeftControl:TransformToolControl;
		private var _scaleTopControl:TransformToolControl;
		private var _scaleTopRightControl:TransformToolControl;
		private var _scaleRightControl:TransformToolControl;
		private var _scaleBottomRightControl:TransformToolControl;
		private var _scaleBottomControl:TransformToolControl;
		private var _scaleBottomLeftControl:TransformToolControl;
		private var _scaleLeftControl:TransformToolControl;
		private var _rotationTopLeftControl:TransformToolControl;
		private var _rotationTopRightControl:TransformToolControl;
		private var _rotationBottomRightControl:TransformToolControl;
		private var _rotationBottomLeftControl:TransformToolControl;
		private var _skewTopControl:TransformToolControl;
		private var _skewRightControl:TransformToolControl;
		private var _skewBottomControl:TransformToolControl;
		private var _skewLeftControl:TransformToolControl;
			
		private var _moveCursor:TransformToolCursor;
		private var _registrationCursor:TransformToolCursor;
		private var _rotationCursor:TransformToolCursor;
		private var _scaleCursor:TransformToolCursor;
		private var _skewCursor:TransformToolCursor;
		
		// Event constants
		public static const NEW_TARGET:String = "newTarget";
		public static const TRANSFORM_TARGET:String = "transformTarget";
		public static const TRANSFORM_TOOL:String = "transformTool";
		public static const CONTROL_INIT:String = "controlInit";
		public static const CONTROL_TRANSFORM_TOOL:String = "controlTransformTool";
		public static const CONTROL_DOWN:String = "controlDown";
		public static const CONTROL_MOVE:String = "controlMove";
		public static const CONTROL_UP:String = "controlUp";
		public static const CONTROL_PREFERENCE:String = "controlPreference";
		
		// Skin constants
		public static const REGISTRATION:String = "registration";
		public static const SCALE_TOP_LEFT:String = "scaleTopLeft";
		public static const SCALE_TOP:String = "scaleTop";
		public static const SCALE_TOP_RIGHT:String = "scaleTopRight";
		public static const SCALE_RIGHT:String = "scaleRight";
		public static const SCALE_BOTTOM_RIGHT:String = "scaleBottomRight";
		public static const SCALE_BOTTOM:String = "scaleBottom";
		public static const SCALE_BOTTOM_LEFT:String = "scaleBottomLeft";
		public static const SCALE_LEFT:String = "scaleLeft";
		public static const ROTATION_TOP_LEFT:String = "rotationTopLeft";
		public static const ROTATION_TOP_RIGHT:String = "rotationTopRight";
		public static const ROTATION_BOTTOM_RIGHT:String = "rotationBottomRight";
		public static const ROTATION_BOTTOM_LEFT:String = "rotationBottomLeft";
		public static const SKEW_TOP:String = "skewTop";
		public static const SKEW_RIGHT:String = "skewRight";
		public static const SKEW_BOTTOM:String = "skewBottom";
		public static const SKEW_LEFT:String = "skewLeft";
		public static const CURSOR_REGISTRATION:String = "cursorRegistration";
		public static const CURSOR_MOVE:String = "cursorMove";
		public static const CURSOR_SCALE:String = "cursorScale";
		public static const CURSOR_ROTATION:String = "cursorRotate";
		public static const CURSOR_SKEW:String = "cursorSkew";
		
		// Properties
		
		/**
		 * The display object the transform tool affects
		 */
		public function get target():DisplayObject {
			return _target;
		}
		public function set target(d:DisplayObject):void {
			
			// null target, set target as null
			if (!d) {
				if (_target) {
					_target = null;
					updateControlsVisible();
					dispatchEvent(new Event(NEW_TARGET));
				}
				return;
			}else{
				
				// invalid target, do nothing
				if (d == _target || d == this || contains(d)
				|| (d is DisplayObjectContainer && (d as DisplayObjectContainer).contains(this))) {
					return;
				}
				
				// valid target, set and update
				_target = d;
				updateMatrix();
				setNewRegistation();
				updateControlsVisible();
				
				// raise to top of display list if applies
				if (_raiseNewTargets) {
					raiseTarget();
				}
			}
			
			// if not moving new targets, apply transforms
			if (!_moveNewTargets) {
				apply();
			}
			
			// send event; updates control points
			dispatchEvent(new Event(NEW_TARGET));
				
			// initiate move interaction if applies after controls updated
			if (_moveNewTargets && _moveEnabled && _moveControl) {
				_currentControl = _moveControl;
				_currentControl.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_DOWN));
			}
		}
		
		/**
		 * When true, new targets are placed at the top of their display list
		 * @see target
		 */
		public function get raiseNewTargets():Boolean {
			return _raiseNewTargets;
		}
		public function set raiseNewTargets(b:Boolean):void {
			_raiseNewTargets = b;
		}
		
		/**
		 * When true, new targets are immediately given a move interaction and can be dragged
		 * @see target
		 * @see moveEnabled
		 */
		public function get moveNewTargets():Boolean {
			return _moveNewTargets;
		}
		public function set moveNewTargets(b:Boolean):void {
			_moveNewTargets = b;
		}
		
		/**
		 * When true, the target instance scales with the tool as it is transformed.
		 * When false, transforms in the tool are only reflected when transforms are completed.
		 */
		public function get livePreview():Boolean {
			return _livePreview;
		}
		public function set livePreview(b:Boolean):void {
			_livePreview = b;
		}
		
		/**
		 * Controls the default Control sizes of controls used by the tool
		 */
		public function get controlSize():Number {
			return _controlSize;
		}
		public function set controlSize(n:Number):void {
			if (_controlSize != n) {
				_controlSize = n;
				dispatchEvent(new Event(CONTROL_PREFERENCE));
			}
		}
		
		/**
		 * When true, counters transformations applied to controls by their parent containers
		 */
		public function get maintainControlForm():Boolean {
			return _maintainControlForm;
		}
		public function set maintainControlForm(b:Boolean):void {
			if (_maintainControlForm != b) {
				_maintainControlForm = b;
				dispatchEvent(new Event(CONTROL_PREFERENCE));
			}
		}
		
		/**
		 * When true (default), the transform tool uses an invisible control using the shape of the current
		 * target to allow movement. This means any objects above the target but below the
		 * tool cannot be clicked on since this hidden control will be clicked on first
		 * (allowing you to move objects below others without selecting the objects on top).
		 * When false, the target itself is used for movement and any objects above the target
		 * become clickable preventing tool movement if the target itself is not clicked directly.
		 */
		public function get moveUnderObjects():Boolean {
			return _moveUnderObjects;
		}
		public function set moveUnderObjects(b:Boolean):void {
			if (_moveUnderObjects != b) {
				_moveUnderObjects = b;
				dispatchEvent(new Event(CONTROL_PREFERENCE));
			}
		}
		
		/**
		 * The transform matrix of the tool
		 * as it exists in its on coordinate space
		 * @see globalMatrix
		 */
		public function get toolMatrix():Matrix {
			return _toolMatrix.clone();
		}
		public function set toolMatrix(m:Matrix):void {
			updateMatrix(m, false);
			updateRegistration();
			dispatchEvent(new Event(TRANSFORM_TOOL));
		}
		
		/**
		 * The transform matrix of the tool
		 * as it appears in global space
		 * @see toolMatrix
		 */
		public function get globalMatrix():Matrix {
			var _globalMatrix:Matrix = _toolMatrix.clone();
			_globalMatrix.concat(transform.concatenatedMatrix);
			return _globalMatrix;
		}
		public function set globalMatrix(m:Matrix):void {
			updateMatrix(m);
			updateRegistration();
			dispatchEvent(new Event(TRANSFORM_TOOL));
		}
		
		/**
		 * The location of the registration point in the tool. Note: registration
		 * points are tool-specific.  If you change the registration point of a
		 * target, the new registration will only be reflected in the tool used
		 * to change that point.
		 * @see registrationEnabled
		 * @see rememberRegistration
		 */
		public function get registration():Point {
			return _registration.clone();
		}
		public function set registration(p:Point):void {
			_registration = p.clone();
			innerRegistration = toolInvertedMatrix.transformPoint(_registration);
			
			if (_rememberRegistration) {
				// log new registration point for the next
				// time this target is selected
				registrationLog[_target] = innerRegistration;
			}
			dispatchEvent(new Event(TRANSFORM_TOOL));
		}
		
		/**
		 * The current control being used in the tool if being manipulated.
		 * This value is null if the user is not transforming the tool.
		 */
		public function get currentControl():TransformToolControl {
			return _currentControl;
		}
		
		/**
		 * Allows or disallows users to move the tool
		 */
		public function get moveEnabled():Boolean {
			return _moveEnabled;
		}
		public function set moveEnabled(b:Boolean):void {
			if (_moveEnabled != b) {
				_moveEnabled = b;
				updateControlsEnabled();
			}
		}
		
		/**
		 * Allows or disallows users to see and move the registration point
		 * @see registration
		 * @see rememberRegistration
		 */
		public function get registrationEnabled():Boolean {
			return _registrationEnabled;
		}
		public function set registrationEnabled(b:Boolean):void {
			if (_registrationEnabled != b) {
				_registrationEnabled = b;
				updateControlsEnabled();
			}
		}
		
		/**
		 * Allows or disallows users to see and adjust rotation controls
		 */
		public function get rotationEnabled():Boolean {
			return _rotationEnabled;
		}
		public function set rotationEnabled(b:Boolean):void {
			if (_rotationEnabled != b) {
				_rotationEnabled = b;
				updateControlsEnabled();
			}
		}
		
		/**
		 * Allows or disallows users to see and adjust scale controls
		 */
		public function get scaleEnabled():Boolean {
			return _scaleEnabled;
		}
		public function set scaleEnabled(b:Boolean):void {
			if (_scaleEnabled != b) {
				_scaleEnabled = b;
				updateControlsEnabled();
			}
		}
		
		/**
		 * Allows or disallows users to see and adjust skew controls
		 */
		public function get skewEnabled():Boolean {
			return _skewEnabled;
		}
		public function set skewEnabled(b:Boolean):void {
			if (_skewEnabled != b) {
				_skewEnabled = b;
				updateControlsEnabled();
			}
		}
		
		/**
		 * Allows or disallows users to see tool boundry outlines
		 */
		public function get outlineEnabled():Boolean {
			return _outlineEnabled;
		}
		public function set outlineEnabled(b:Boolean):void {
			if (_outlineEnabled != b) {
				_outlineEnabled = b;
				updateControlsEnabled();
			}
		}
		
		/**
		 * Allows or disallows users to see native cursors
		 * @see addCursor
		 * @see removeCursor
		 * @see customCursorsEnabled
		 */
		public function get cursorsEnabled():Boolean {
			return _cursorsEnabled;
		}
		public function set cursorsEnabled(b:Boolean):void {
			if (_cursorsEnabled != b) {
				_cursorsEnabled = b;
				updateControlsEnabled();
			}
		}
		
		/**
		 * Allows or disallows users to see and use custom controls
		 * @see addControl
		 * @see removeControl
		 * @see customCursorsEnabled
		 */
		public function get customControlsEnabled():Boolean {
			return _customControlsEnabled;
		}
		public function set customControlsEnabled(b:Boolean):void {
			if (_customControlsEnabled != b) {
				_customControlsEnabled = b;
				updateControlsEnabled();
				dispatchEvent(new Event(CONTROL_PREFERENCE));
			}
		}
		
		/**
		 * Allows or disallows users to see custom cursors
		 * @see addCursor
		 * @see removeCursor
		 * @see cursorsEnabled
		 * @see customControlsEnabled
		 */
		public function get customCursorsEnabled():Boolean {
			return _customCursorsEnabled;
		}
		public function set customCursorsEnabled(b:Boolean):void {
			if (_customCursorsEnabled != b) {
				_customCursorsEnabled = b;
				updateControlsEnabled();
				dispatchEvent(new Event(CONTROL_PREFERENCE));
			}
		}
		
		/**
		 * Allows or disallows users to see custom cursors
		 * @see registration
		 */
		public function get rememberRegistration():Boolean {
			return _rememberRegistration;
		}
		public function set rememberRegistration(b:Boolean):void {
			_rememberRegistration = b;
			if (!_rememberRegistration) {
				registrationLog = new Dictionary(true);
			}
		}
		
		/**
		 * Allows constraining of scale transformations that scale along both X and Y.
		 * @see constrainRotation
		 */
		public function get constrainScale():Boolean {
			return _constrainScale;
		}
		public function set constrainScale(b:Boolean):void {
			if (_constrainScale != b) {
				_constrainScale = b;
				dispatchEvent(new Event(CONTROL_PREFERENCE));
			}
		}
		
		/**
		 * Allows constraining of rotation transformations by an angle
		 * @see constrainRotationAngle
		 * @see constrainScale
		 */
		public function get constrainRotation():Boolean {
			return _constrainRotation;
		}
		public function set constrainRotation(b:Boolean):void {
			if (_constrainRotation != b) {
				_constrainRotation = b;
				dispatchEvent(new Event(CONTROL_PREFERENCE));
			}
		}
		
		/**
		 * The angle at which rotation is constrainged when constrainRotation is true
		 * @see constrainRotation
		 */
		public function get constrainRotationAngle():Number {
			return _constrainRotationAngle * 180/Math.PI;
		}
		public function set constrainRotationAngle(n:Number):void {
			var angleInRadians:Number = n * Math.PI/180;
			if (_constrainRotationAngle != angleInRadians) {
				_constrainRotationAngle = angleInRadians;
				dispatchEvent(new Event(CONTROL_PREFERENCE));
			}
		}
		
		/**
		 * The maximum scaleX allowed to be applied to a target
		 */
		public function get maxScaleX():Number {
			return _maxScaleX;
		}
		public function set maxScaleX(n:Number):void {
			_maxScaleX = n;
		}
		
		/**
		 * The maximum scaleY allowed to be applied to a target
		 */
		public function get maxScaleY():Number {
			return _maxScaleY;
		}
		public function set maxScaleY(n:Number):void {
			_maxScaleY = n;
		}
		
		public function get boundsTopLeft():Point { return _boundsTopLeft.clone(); }
		public function get boundsTop():Point { return _boundsTop.clone(); }
		public function get boundsTopRight():Point { return _boundsTopRight.clone(); }
		public function get boundsRight():Point { return _boundsRight.clone(); }
		public function get boundsBottomRight():Point { return _boundsBottomRight.clone(); }
		public function get boundsBottom():Point { return _boundsBottom.clone(); }
		public function get boundsBottomLeft():Point { return _boundsBottomLeft.clone(); }
		public function get boundsLeft():Point { return _boundsLeft.clone(); }
		public function get boundsCenter():Point { return _boundsCenter.clone(); }
		public function get mouse():Point { return new Point(mouseX, mouseY); }
		
		public function get moveControl():TransformToolControl { return _moveControl; }
		public function get registrationControl():TransformToolControl { return _registrationControl; }
		public function get outlineControl():TransformToolControl { return _outlineControl; }
		public function get scaleTopLeftControl():TransformToolControl { return _scaleTopLeftControl; }
		public function get scaleTopControl():TransformToolControl { return _scaleTopControl; }
		public function get scaleTopRightControl():TransformToolControl { return _scaleTopRightControl; }
		public function get scaleRightControl():TransformToolControl { return _scaleRightControl; }
		public function get scaleBottomRightControl():TransformToolControl { return _scaleBottomRightControl; }
		public function get scaleBottomControl():TransformToolControl { return _scaleBottomControl; }
		public function get scaleBottomLeftControl():TransformToolControl { return _scaleBottomLeftControl; }
		public function get scaleLeftControl():TransformToolControl { return _scaleLeftControl; }
		public function get rotationTopLeftControl():TransformToolControl { return _rotationTopLeftControl; }
		public function get rotationTopRightControl():TransformToolControl { return _rotationTopRightControl; }
		public function get rotationBottomRightControl():TransformToolControl { return _rotationBottomRightControl; }
		public function get rotationBottomLeftControl():TransformToolControl { return _rotationBottomLeftControl; }
		public function get skewTopControl():TransformToolControl { return _skewTopControl; }
		public function get skewRightControl():TransformToolControl { return _skewRightControl; }
		public function get skewBottomControl():TransformToolControl { return _skewBottomControl; }
		public function get skewLeftControl():TransformToolControl { return _skewLeftControl; }
		
		public function get moveCursor():TransformToolCursor { return _moveCursor; }
		public function get registrationCursor():TransformToolCursor { return _registrationCursor; }
		public function get rotationCursor():TransformToolCursor { return _rotationCursor; }
		public function get scaleCursor():TransformToolCursor { return _scaleCursor; }
		public function get skewCursor():TransformToolCursor { return _skewCursor; }
		
		/**
		 * TransformTool Constructor.
		 * Creates new instances of the transform tool
		 */
		public function TransformTool() {
			createControls();
		}
		
		/**
		 * Provides a string representation of the transform instance
		 */
		override public function toString():String {
			return "[Transform Tool: target=" + String(_target) + "]" ;
		}
		
		// Setup
		private function createControls():void {
			
			// defining controls
			_moveControl = new TransformToolMoveShape("move", moveInteraction);
			_registrationControl = new TransformToolRegistrationControl(REGISTRATION, registrationInteraction, "registration");
			_rotationTopLeftControl = new TransformToolRotateControl(ROTATION_TOP_LEFT, rotationInteraction, "boundsTopLeft");
			_rotationTopRightControl = new TransformToolRotateControl(ROTATION_TOP_RIGHT, rotationInteraction, "boundsTopRight");
			_rotationBottomRightControl = new TransformToolRotateControl(ROTATION_BOTTOM_RIGHT, rotationInteraction, "boundsBottomRight");
			_rotationBottomLeftControl = new TransformToolRotateControl(ROTATION_BOTTOM_LEFT, rotationInteraction, "boundsBottomLeft");
			_scaleTopLeftControl = new TransformToolScaleControl(SCALE_TOP_LEFT, scaleBothInteraction, "boundsTopLeft");
			_scaleTopControl = new TransformToolScaleControl(SCALE_TOP, scaleYInteraction, "boundsTop");
			_scaleTopRightControl = new TransformToolScaleControl(SCALE_TOP_RIGHT, scaleBothInteraction, "boundsTopRight");
			_scaleRightControl = new TransformToolScaleControl(SCALE_RIGHT, scaleXInteraction, "boundsRight");
			_scaleBottomRightControl = new TransformToolScaleControl(SCALE_BOTTOM_RIGHT, scaleBothInteraction, "boundsBottomRight");
			_scaleBottomControl = new TransformToolScaleControl(SCALE_BOTTOM, scaleYInteraction, "boundsBottom");
			_scaleBottomLeftControl = new TransformToolScaleControl(SCALE_BOTTOM_LEFT, scaleBothInteraction, "boundsBottomLeft");
			_scaleLeftControl = new TransformToolScaleControl(SCALE_LEFT, scaleXInteraction, "boundsLeft");
			_skewTopControl = new TransformToolSkewBar(SKEW_TOP, skewXInteraction, "boundsTopRight", "boundsTopLeft", "boundsTopRight");
			_skewRightControl = new TransformToolSkewBar(SKEW_RIGHT, skewYInteraction, "boundsBottomRight", "boundsTopRight", "boundsBottomRight");
			_skewBottomControl = new TransformToolSkewBar(SKEW_BOTTOM, skewXInteraction, "boundsBottomLeft", "boundsBottomRight", "boundsBottomLeft");
			_skewLeftControl = new TransformToolSkewBar(SKEW_LEFT, skewYInteraction, "boundsTopLeft", "boundsBottomLeft", "boundsTopLeft");
			
			// defining cursors
			_moveCursor = new TransformToolMoveCursor();
			_moveCursor.addReference(_moveControl);
			
			_registrationCursor = new TransformToolRegistrationCursor();
			_registrationCursor.addReference(_registrationControl);
			
			_rotationCursor = new TransformToolRotateCursor();
			_rotationCursor.addReference(_rotationTopLeftControl);
			_rotationCursor.addReference(_rotationTopRightControl);
			_rotationCursor.addReference(_rotationBottomRightControl);
			_rotationCursor.addReference(_rotationBottomLeftControl);
			
			_scaleCursor = new TransformToolScaleCursor();
			_scaleCursor.addReference(_scaleTopLeftControl);
			_scaleCursor.addReference(_scaleTopControl);
			_scaleCursor.addReference(_scaleTopRightControl);
			_scaleCursor.addReference(_scaleRightControl);
			_scaleCursor.addReference(_scaleBottomRightControl);
			_scaleCursor.addReference(_scaleBottomControl);
			_scaleCursor.addReference(_scaleBottomLeftControl);
			_scaleCursor.addReference(_scaleLeftControl);
			
			_skewCursor = new TransformToolSkewCursor();
			_skewCursor.addReference(_skewTopControl);
			_skewCursor.addReference(_skewRightControl);
			_skewCursor.addReference(_skewBottomControl);
			_skewCursor.addReference(_skewLeftControl);
			
			// adding controls
			addToolControl(moveControls, _moveControl);
			addToolControl(registrationControls, _registrationControl);
			addToolControl(rotateControls, _rotationTopLeftControl);
			addToolControl(rotateControls, _rotationTopRightControl);
			addToolControl(rotateControls, _rotationBottomRightControl);
			addToolControl(rotateControls, _rotationBottomLeftControl);
			addToolControl(scaleControls, _scaleTopControl);
			addToolControl(scaleControls, _scaleRightControl);
			addToolControl(scaleControls, _scaleBottomControl);
			addToolControl(scaleControls, _scaleLeftControl);
			addToolControl(scaleControls, _scaleTopLeftControl);
			addToolControl(scaleControls, _scaleTopRightControl);
			addToolControl(scaleControls, _scaleBottomRightControl);
			addToolControl(scaleControls, _scaleBottomLeftControl);
			addToolControl(skewControls, _skewTopControl);
			addToolControl(skewControls, _skewRightControl);
			addToolControl(skewControls, _skewBottomControl);
			addToolControl(skewControls, _skewLeftControl);
			addToolControl(lines, new TransformToolOutline("outline"), false);
			
			// adding cursors
			addToolControl(cursors, _moveCursor, false);
			addToolControl(cursors, _registrationCursor, false);
			addToolControl(cursors, _rotationCursor, false);
			addToolControl(cursors, _scaleCursor, false);
			addToolControl(cursors, _skewCursor, false);
			
			
			updateControlsEnabled();
		}
		
		private function addToolControl(container:Sprite, control:TransformToolControl, interactive:Boolean = true):void {
			control.transformTool = this;
			if (interactive) {
				control.addEventListener(MouseEvent.MOUSE_DOWN, startInteractionHandler);	
			}
			container.addChild(control);
			control.dispatchEvent(new Event(CONTROL_INIT));
		}
		
		/**
		 * Allows you to add a custom control to the tool
		 * @see removeControl
		 * @see addCursor
		 * @see removeCursor
		 */
		public function addControl(control:TransformToolControl):void {
			addToolControl(customControls, control);
		}
		
		/**
		 * Allows you to remove a custom control to the tool
		 * @see addControl
		 * @see addCursor
		 * @see removeCursor
		 */
		public function removeControl(control:TransformToolControl):TransformToolControl {
			if (customControls.contains(control)) {
				customControls.removeChild(control);
				return control;
			}
			return null;
		}
		
		/**
		 * Allows you to add a custom cursor to the tool
		 * @see removeCursor
		 * @see addControl
		 * @see removeControl
		 */
		public function addCursor(cursor:TransformToolCursor):void {
			addToolControl(customCursors, cursor);
		}
		
		/**
		 * Allows you to remove a custom cursor to the tool
		 * @see addCursor
		 * @see addControl
		 * @see removeControl
		 */
		public function removeCursor(cursor:TransformToolCursor):TransformToolCursor {
			if (customCursors.contains(cursor)) {
				customCursors.removeChild(cursor);
				return cursor;
			}
			return null;
		}
		
		/**
		 * Allows you to change the appearance of default controls
		 * @see addControl
		 * @see removeControl
		 */
		public function setSkin(controlName:String, skin:DisplayObject):void {
			var control:TransformToolInternalControl = getControlByName(controlName);
			if (control) {
				control.skin = skin;
			}
		}
		
		/**
		 * Allows you to get the skin of an existing control.
		 * If one was not set, null is returned
		 * @see addControl
		 * @see removeControl
		 */
		public function getSkin(controlName:String):DisplayObject {
			var control:TransformToolInternalControl = getControlByName(controlName);
			return control.skin;
		}
		
		private function getControlByName(controlName:String):TransformToolInternalControl {
			var control:TransformToolInternalControl;
			var containers:Array = new Array(skewControls, registrationControls, cursors, rotateControls, scaleControls);
			var i:int = containers.length;
			while (i-- && control == null) {
				control = containers[i].getChildByName(controlName) as TransformToolInternalControl;
			}
			return control;
		}
		
		// Interaction Handlers
		private function startInteractionHandler(event:MouseEvent):void {
			_currentControl = event.currentTarget as TransformToolControl;
			if (_currentControl) {
				setupInteraction();
			}
		}
		
		private function setupInteraction():void {
			updateMatrix();
			apply();
			dispatchEvent(new Event(CONTROL_DOWN));
			
			// mouse offset to allow interaction from desired point
			mouseOffset = (_currentControl && _currentControl.referencePoint) ? _currentControl.referencePoint.subtract(new Point(mouseX, mouseY)) : new Point(0, 0);
			updateMouse();
			
			// set variables for interaction reference
			interactionStart = mouseLoc.clone();
			innerInteractionStart = innerMouseLoc.clone();
			interactionStartMatrix = _toolMatrix.clone();
			interactionStartAngle = distortAngle();
			
			if (stage) {
				// setup stage events to manage control interaction
				stage.addEventListener(MouseEvent.MOUSE_MOVE, interactionHandler);
				stage.addEventListener(MouseEvent.MOUSE_UP, endInteractionHandler, false);
				stage.addEventListener(MouseEvent.MOUSE_UP, endInteractionHandler, true);
			}
		}
		
		private function interactionHandler(event:MouseEvent):void {
			// define mouse position for interaction
			updateMouse();
			
			// use original toolMatrix for reference of interaction
			_toolMatrix = interactionStartMatrix.clone();
			
			// dispatch events that let controls do their thing
			dispatchEvent(new Event(CONTROL_MOVE));
			dispatchEvent(new Event(CONTROL_TRANSFORM_TOOL));
			
			if (_livePreview) {
				// update target if applicable
				apply();
			}
			
			// smooth sailing
			event.updateAfterEvent();
		}
		
		private function endInteractionHandler(event:MouseEvent):void {
			if (event.eventPhase == EventPhase.BUBBLING_PHASE || !(event.currentTarget is Stage)) {
				// ignore unrelated events received by stage
				return;
			}
			
			if (!_livePreview) {
				// update target if applicable
				apply();
			}
			
			// get stage reference from event in case
			// stage is no longer accessible from this instance
			var stageRef:Stage = event.currentTarget as Stage;
			stageRef.removeEventListener(MouseEvent.MOUSE_MOVE, interactionHandler);
			stageRef.removeEventListener(MouseEvent.MOUSE_UP, endInteractionHandler, false);
			stageRef.removeEventListener(MouseEvent.MOUSE_UP, endInteractionHandler, true);
			dispatchEvent(new Event(CONTROL_UP));
			_currentControl = null;
		}
		
		// Interaction Transformations
		/**
		 * Control Interaction.  Moves the tool
		 */
		public function moveInteraction():void {
			var moveLoc:Point = mouseLoc.subtract(interactionStart);
			_toolMatrix.tx += moveLoc.x;
			_toolMatrix.ty += moveLoc.y;
			updateRegistration();
			completeInteraction();
		}
		
		/**
		 * Control Interaction.  Moves the registration point
		 */
		public function registrationInteraction():void {
			// move registration point
			_registration.x = mouseLoc.x;
			_registration.y = mouseLoc.y;
			innerRegistration = toolInvertedMatrix.transformPoint(_registration);
			
			if (_rememberRegistration) {
				// log new registration point for the next
				// time this target is selected
				registrationLog[_target] = innerRegistration;
			}
			completeInteraction();
		}
		
		/**
		 * Control Interaction.  Rotates the tool
		 */
		public function rotationInteraction():void {
			// rotate in global transform
			var globalMatrix:Matrix = transform.concatenatedMatrix;
			var globalInvertedMatrix:Matrix = globalMatrix.clone();
			globalInvertedMatrix.invert();
			_toolMatrix.concat(globalMatrix);
			
			// get change in rotation
			var angle:Number = distortAngle() - interactionStartAngle;
			
			if (_constrainRotation) {
				// constrain rotation based on constrainRotationAngle
				if (angle > Math.PI) {
					angle -= Math.PI*2;
				}else if (angle < -Math.PI) {
					angle += Math.PI*2;
				}
				angle = Math.round(angle/_constrainRotationAngle)*_constrainRotationAngle;
			}
			
			// apply rotation to toolMatrix
			_toolMatrix.rotate(angle);
			
			_toolMatrix.concat(globalInvertedMatrix);
			completeInteraction(true);
		}
		
		/**
		 * Control Interaction.  Scales the tool along the X axis
		 */
		public function scaleXInteraction():void {
			
			// get distortion offset vertical movement
			var distortH:Point = distortOffset(new Point(innerMouseLoc.x, innerInteractionStart.y), innerInteractionStart.x - innerRegistration.x);
			
			// update the matrix for vertical scale
			_toolMatrix.a += distortH.x;
			_toolMatrix.b += distortH.y;
			completeInteraction(true);
		}
		
		/**
		 * Control Interaction.  Scales the tool along the Y axis
		 */
		public function scaleYInteraction():void {
			// get distortion offset vertical movement
			var distortV:Point = distortOffset(new Point(innerInteractionStart.x, innerMouseLoc.y), innerInteractionStart.y - innerRegistration.y);
			
			// update the matrix for vertical scale
			_toolMatrix.c += distortV.x;
			_toolMatrix.d += distortV.y;
			completeInteraction(true);
		}
		
		/**
		 * Control Interaction.  Scales the tool along both the X and Y axes
		 */
		public function scaleBothInteraction():void {
			// mouse reference, may change from innerMouseLoc if constraining
			var innerMouseRef:Point = innerMouseLoc.clone();
			
			if (_constrainScale) {
				
				// how much the mouse has moved from starting the interaction
				var moved:Point = innerMouseLoc.subtract(innerInteractionStart);
				
				// the relationship of the start location to the registration point
				var regOffset:Point = innerInteractionStart.subtract(innerRegistration);
				
				// find the ratios between movement and the registration offset
				var ratioH = regOffset.x ? moved.x/regOffset.x : 0;
				var ratioV = regOffset.y ? moved.y/regOffset.y : 0;
				
				// have the larger of the movement distances brought down
				// based on the lowest ratio to fit the registration offset
				if (ratioH > ratioV) {
					innerMouseRef.x = innerInteractionStart.x + regOffset.x * ratioV;
				}else{
					innerMouseRef.y = innerInteractionStart.y + regOffset.y * ratioH;
				}
			}
			
			// get distortion offsets for both vertical and horizontal movements
			var distortH:Point = distortOffset(new Point(innerMouseRef.x, innerInteractionStart.y), innerInteractionStart.x - innerRegistration.x);
			var distortV:Point = distortOffset(new Point(innerInteractionStart.x, innerMouseRef.y), innerInteractionStart.y - innerRegistration.y);
			
			// update the matrix for both scales
			_toolMatrix.a += distortH.x;
			_toolMatrix.b += distortH.y;
			_toolMatrix.c += distortV.x;
			_toolMatrix.d += distortV.y;
			completeInteraction(true);
		}
		
		/**
		 * Control Interaction.  Skews the tool along the X axis
		 */
		public function skewXInteraction():void {
			var distortH:Point = distortOffset(new Point(innerMouseLoc.x, innerInteractionStart.y), innerInteractionStart.y - innerRegistration.y);
			_toolMatrix.c += distortH.x;
			_toolMatrix.d += distortH.y;
			completeInteraction(true);
		}
		
		/**
		 * Control Interaction.  Skews the tool along the Y axis
		 */
		public function skewYInteraction():void {
			var distortV:Point = distortOffset(new Point(innerInteractionStart.x, innerMouseLoc.y), innerInteractionStart.x - innerRegistration.x);
			_toolMatrix.a += distortV.x;
			_toolMatrix.b += distortV.y;
			completeInteraction(true);
		}
		
		private function distortOffset(offset:Point, regDiff:Number):Point {
			// get changes in matrix combinations based on targetBounds
			var ratioH:Number = regDiff ? targetBounds.width/regDiff : 0;
			var ratioV:Number = regDiff ? targetBounds.height/regDiff : 0;
			offset = interactionStartMatrix.transformPoint(offset).subtract(interactionStart);
			offset.x *= targetBounds.width ? ratioH/targetBounds.width : 0;
			offset.y *= targetBounds.height ? ratioV/targetBounds.height : 0;
			return offset;
		}
		
		private function completeInteraction(offsetReg:Boolean = false):void {
			enforceLimits();
			if (offsetReg) {
				// offset of registration to have transformations based around
				// custom registration point
				var offset:Point = _registration.subtract(_toolMatrix.transformPoint(innerRegistration));
				_toolMatrix.tx += offset.x;
				_toolMatrix.ty += offset.y;
			}
			updateBounds();
		}
		
		// Information
		private function distortAngle():Number {
			// use global mouse and registration
			var globalMatrix:Matrix = transform.concatenatedMatrix;
			var gMouseLoc:Point = globalMatrix.transformPoint(mouseLoc);
			var gRegistration:Point = globalMatrix.transformPoint(_registration);
			
			// distance and angle of mouse from registration
			var offset:Point = gMouseLoc.subtract(gRegistration);
			return Math.atan2(offset.y, offset.x);
		}
		
		// Updates
		private function updateMouse():void {
			mouseLoc = new Point(mouseX, mouseY).add(mouseOffset);
			innerMouseLoc = toolInvertedMatrix.transformPoint(mouseLoc);
		}
		
		private function updateMatrix(useMatrix:Matrix = null, counterTransform:Boolean = true):void {
			if (_target) {
				_toolMatrix = useMatrix ? useMatrix.clone() : _target.transform.concatenatedMatrix.clone();
				if (counterTransform) {
					// counter transform of the parents of the tool
					var current:Matrix = transform.concatenatedMatrix;
					current.invert();
					_toolMatrix.concat(current);
				}
				enforceLimits();
				toolInvertedMatrix = _toolMatrix.clone();
				toolInvertedMatrix.invert();
				updateBounds();
			}
		}
		
		private function updateBounds():void {
			if (_target) {
				// update tool bounds based on target bounds
				targetBounds = _target.getBounds(_target);
				_boundsTopLeft = _toolMatrix.transformPoint(new Point(targetBounds.left, targetBounds.top));
				_boundsTopRight = _toolMatrix.transformPoint(new Point(targetBounds.right, targetBounds.top));
				_boundsBottomRight = _toolMatrix.transformPoint(new Point(targetBounds.right, targetBounds.bottom));
				_boundsBottomLeft = _toolMatrix.transformPoint(new Point(targetBounds.left, targetBounds.bottom));
				_boundsTop = Point.interpolate(_boundsTopLeft, _boundsTopRight, .5);
				_boundsRight = Point.interpolate(_boundsTopRight, _boundsBottomRight, .5);
				_boundsBottom = Point.interpolate(_boundsBottomRight, _boundsBottomLeft, .5);
				_boundsLeft = Point.interpolate(_boundsBottomLeft, _boundsTopLeft, .5);
				_boundsCenter = Point.interpolate(_boundsTopLeft, _boundsBottomRight, .5);
			}
		}
		
		private function updateControlsVisible():void {
			// show toolSprites only if there is a valid target
			var isChild:Boolean = contains(toolSprites);
			if (_target) {
				if (!isChild) {
					addChild(toolSprites);
				}				
			}else if (isChild) {
				removeChild(toolSprites);
			}
		}
		
		private function updateControlsEnabled():void {
			// highest arrangement
			updateControlContainer(customCursors, _customCursorsEnabled);
			updateControlContainer(cursors, _cursorsEnabled);
			updateControlContainer(customControls, _customControlsEnabled);
			updateControlContainer(registrationControls, _registrationEnabled);
			updateControlContainer(scaleControls, _scaleEnabled);
			updateControlContainer(skewControls, _skewEnabled);
			updateControlContainer(moveControls, _moveEnabled);
			updateControlContainer(rotateControls, _rotationEnabled);
			updateControlContainer(lines, _outlineEnabled);
			// lowest arrangement
		}
		
		private function updateControlContainer(container:Sprite, enabled:Boolean):void {
			var isChild:Boolean = toolSprites.contains(container);
			if (enabled) {
				// add child or sent to bottom if enabled
				if (isChild) {
					toolSprites.setChildIndex(container, 0);
				}else{
					toolSprites.addChildAt(container, 0);
				}
			}else if (isChild) {
				// removed if disabled
				toolSprites.removeChild(container);
			}
		}
		
		private function updateRegistration():void {
			_registration = _toolMatrix.transformPoint(innerRegistration);
		}
		
		private function enforceLimits():void {
			
			var currScale:Number;
			var angle:Number;
			var enforced:Boolean = false;
			
			// use global matrix
			var _globalMatrix:Matrix = _toolMatrix.clone();
			_globalMatrix.concat(transform.concatenatedMatrix);
			
			// check current scale in X
			currScale = Math.sqrt(_globalMatrix.a * _globalMatrix.a + _globalMatrix.b * _globalMatrix.b);
			if (currScale > _maxScaleX) {
				// set scaleX to no greater than _maxScaleX
				angle = Math.atan2(_globalMatrix.b, _globalMatrix.a);
				_globalMatrix.a = Math.cos(angle) * _maxScaleX;
				_globalMatrix.b = Math.sin(angle) * _maxScaleX;
				enforced = true;
			}
			
			// check current scale in Y
			currScale = Math.sqrt(_globalMatrix.c * _globalMatrix.c + _globalMatrix.d * _globalMatrix.d);
			if (currScale > _maxScaleY) {
				// set scaleY to no greater than _maxScaleY
				angle= Math.atan2(_globalMatrix.c, _globalMatrix.d);
				_globalMatrix.d = Math.cos(angle) * _maxScaleY;
				_globalMatrix.c = Math.sin(angle) * _maxScaleY;
				enforced = true;
			}
			
			
			// if scale was enforced, apply to _toolMatrix
			if (enforced) {
				_toolMatrix = _globalMatrix;
				var current:Matrix = transform.concatenatedMatrix;
				current.invert();
				_toolMatrix.concat(current);
			}
		}
		
		// Render
		private function setNewRegistation():void {
			if (_rememberRegistration && _target in registrationLog) {
				
				// retrieved saved reg point in log
				var savedReg:Point = registrationLog[_target];
				innerRegistration = registrationLog[_target];
			}else{
				
				// use internal own point
				innerRegistration = new Point(0, 0);
			}
			updateRegistration();
		}
		
		private function raiseTarget():void {
			// set target to last object in display list
			var index:int = _target.parent.numChildren - 1;
			_target.parent.setChildIndex(_target, index);
			
			// if this tool is in the same display list
			// raise it to the top above target
			if (_target.parent == parent) {
				parent.setChildIndex(this, index);
			}
		}
		
		/**
		 * Draws the transform tool over its target instance
		 */
		public function draw():void {
			// update the matrix and draw controls
			updateMatrix();
			dispatchEvent(new Event(TRANSFORM_TOOL));
		}
		
		/**
		 * Applies the current tool transformation to its target instance
		 */
		public function apply():void {
			if (_target) {
				
				// get matrix to apply to target
				var applyMatrix:Matrix = _toolMatrix.clone();
				applyMatrix.concat(transform.concatenatedMatrix);
				
				// if target has a parent, counter parent transformations
				if (_target.parent) {
					var invertMatrix:Matrix = target.parent.transform.concatenatedMatrix;
					invertMatrix.invert();
					applyMatrix.concat(invertMatrix);
				}
				
				// set target's matrix
				_target.transform.matrix = applyMatrix;
				
				dispatchEvent(new Event(TRANSFORM_TARGET));
			}
		}
	}
}


import flash.display.DisplayObject;
import flash.display.InteractiveObject;
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Matrix;
import flash.geom.Point;

import classes.com.senocular.display.TransformTool;
import classes.com.senocular.display.TransformToolControl;
import classes.com.senocular.display.TransformToolCursor;

// Controls
class TransformToolInternalControl extends TransformToolControl {
	
	public var interactionMethod:Function;
	public var referenceName:String;
	public var _skin:DisplayObject;
	
	public function set skin(skin:DisplayObject):void {
		if (_skin && contains(_skin)) {
			removeChild(_skin);
		}
		_skin = skin;
		if (_skin) {
			addChild(_skin);
		}
		draw();
	}
	
	public function get skin():DisplayObject {
		return _skin;
	}
	
	override public function get referencePoint():Point {
		if (referenceName in _transformTool) {
			return _transformTool[referenceName];
		}
		return null;
	}
		
	/*
	 * Constructor
	 */	
	public function TransformToolInternalControl(name:String, interactionMethod:Function = null, referenceName:String = null) {
		this.name = name;
		this.interactionMethod = interactionMethod;
		this.referenceName = referenceName;
		addEventListener(TransformTool.CONTROL_INIT, init);
	}
	
	protected function init(event:Event):void {
		_transformTool.addEventListener(TransformTool.NEW_TARGET, draw);
		_transformTool.addEventListener(TransformTool.TRANSFORM_TOOL, draw);
		_transformTool.addEventListener(TransformTool.CONTROL_TRANSFORM_TOOL, position);
		_transformTool.addEventListener(TransformTool.CONTROL_PREFERENCE, draw);
		_transformTool.addEventListener(TransformTool.CONTROL_MOVE, controlMove);
		draw();
	}
	
	public function draw(event:Event = null):void {
		if (_transformTool.maintainControlForm) {
			counterTransform();
		}
		position();
	}
	
	public function position(event:Event = null):void {
		var reference:Point = referencePoint;
		if (reference) {
			x = reference.x;
			y = reference.y;
		}
	}
	
	private function controlMove(event:Event):void {
		if (interactionMethod && _transformTool.currentControl == this) {
			interactionMethod();
		}
	}
}


class TransformToolMoveShape extends TransformToolInternalControl {
	
	private var lastTarget:DisplayObject;
	
	function TransformToolMoveShape(name:String, interactionMethod:Function) {
		super(name, interactionMethod);
	}
	
	override public function draw(event:Event = null):void {
		
		var currTarget:DisplayObject;
		var moveUnderObjects:Boolean = _transformTool.moveUnderObjects;
		
		// use hitArea if moving under objects
		// then movement would have the same depth as the tool
		if (moveUnderObjects) {
			hitArea = _transformTool.target as Sprite;
			currTarget = null;
			relatedObject = this;
			
		}else{
			
			// when not moving under objects
			// use the tool target to handle movement allowing
			// objects above it to be selectable
			hitArea = null;
			currTarget = _transformTool.target;
			relatedObject = _transformTool.target as InteractiveObject;
		}
		
		if (lastTarget != currTarget) {
			// set up/remove listeners for target being clicked
			if (lastTarget) {
				lastTarget.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDown, false);
			}
			if (currTarget) {
				currTarget.addEventListener(MouseEvent.MOUSE_DOWN, mouseDown, false, 0, true);
			}
			
			// register/unregister cursor with the object
			var cursor:TransformToolCursor = _transformTool.moveCursor;
			cursor.removeReference(lastTarget);
			cursor.addReference(currTarget);
			
			lastTarget = currTarget;
		}
	}
	
	private function mouseDown(event:MouseEvent):void {
		dispatchEvent(new MouseEvent(MouseEvent.MOUSE_DOWN));
	}
}


class TransformToolRegistrationControl extends TransformToolInternalControl {
		
	function TransformToolRegistrationControl(name:String, interactionMethod:Function, referenceName:String) {
		super(name, interactionMethod, referenceName);
	}

	override public function draw(event:Event = null):void {
		graphics.clear();
		if (!_skin) {
			graphics.lineStyle(1, 0);
			graphics.beginFill(0xFFFFFF);
			graphics.drawCircle(0, 0, _transformTool.controlSize/2);
			graphics.endFill();
		}
		super.draw();
	}
}


class TransformToolScaleControl extends TransformToolInternalControl {
	
	function TransformToolScaleControl(name:String, interactionMethod:Function, referenceName:String) {
		super(name, interactionMethod, referenceName);
	}

	override public function draw(event:Event = null):void {
		graphics.clear();
		if (!_skin) {
			graphics.lineStyle(2, 0xFFFFFF);
			graphics.beginFill(0);
			var size = _transformTool.controlSize;
			var size2:Number = size/2;
			graphics.drawRect(-size2, -size2, size, size);
			graphics.endFill();
		}
		super.draw();
	}
}


class TransformToolRotateControl extends TransformToolInternalControl {
	
	private var locationName:String;
	
	function TransformToolRotateControl(name:String, interactionMethod:Function, locationName:String) {
		super(name, interactionMethod);
		this.locationName = locationName;
	}

	override public function draw(event:Event = null):void {
		graphics.clear();
		if (!_skin) {
			graphics.beginFill(0xFF, 0);
			graphics.drawCircle(0, 0, _transformTool.controlSize*2);
			graphics.endFill();
		}
		super.draw();
	}
	
	override public function position(event:Event = null):void {
		if (locationName in _transformTool) {
			var location:Point = _transformTool[locationName];
			x = location.x;
			y = location.y;
		}
	}
}


class TransformToolSkewBar extends TransformToolInternalControl {
	
	private var locationStart:String;
	private var locationEnd:String;
	
	function TransformToolSkewBar(name:String, interactionMethod:Function, referenceName:String, locationStart:String, locationEnd:String) {
		super(name, interactionMethod, referenceName);
		this.locationStart = locationStart;
		this.locationEnd = locationEnd;
	}
	
	override public function draw(event:Event = null):void {
		graphics.clear();
		
		if (_skin) {
			super.draw(event);
			return;
		}
		
		// derive point locations for bar
		var locStart:Point = _transformTool[locationStart];
		var locEnd:Point = _transformTool[locationEnd];
		
		// counter transform
		var toolTrans:Matrix;
		var toolTransInverted:Matrix;
		var maintainControlForm:Boolean = _transformTool.maintainControlForm;
		if (maintainControlForm) {
			toolTrans = transform.concatenatedMatrix;
			toolTransInverted = toolTrans.clone();
			toolTransInverted.invert();
			
			locStart = toolTrans.transformPoint(locStart);
			locEnd = toolTrans.transformPoint(locEnd);
		}
		
		var size:Number = _transformTool.controlSize/2;
		var diff:Point = locEnd.subtract(locStart);
		var angle:Number = Math.atan2(diff.y, diff.x) - Math.PI/2;	
		var offset:Point = Point.polar(size, angle);
		
		var corner1:Point = locStart.add(offset);
		var corner2:Point = locEnd.add(offset);
		var corner3:Point = locEnd.subtract(offset);
		var corner4:Point = locStart.subtract(offset);
		if (maintainControlForm) {
			corner1 = toolTransInverted.transformPoint(corner1);
			corner2 = toolTransInverted.transformPoint(corner2);
			corner3 = toolTransInverted.transformPoint(corner3);
			corner4 = toolTransInverted.transformPoint(corner4);
		}
		
		// draw bar
		graphics.beginFill(0xFF0000, 0);
		graphics.moveTo(corner1.x, corner1.y);
		graphics.lineTo(corner2.x, corner2.y);
		graphics.lineTo(corner3.x, corner3.y);
		graphics.lineTo(corner4.x, corner4.y);
		graphics.lineTo(corner1.x, corner1.y);
		graphics.endFill();
	}

	override public function position(event:Event = null):void {
		if (_skin) {
			var locStart:Point = _transformTool[locationStart];
			var locEnd:Point = _transformTool[locationEnd];
			var location:Point = Point.interpolate(locStart, locEnd, .5);
			x = location.x;
			y = location.y;
		}else{
			x = 0;
			y = 0;
			draw(event);
		}
	}
}


class TransformToolOutline extends TransformToolInternalControl {
	
	function TransformToolOutline(name:String) {
		super(name);
	}

	override public function draw(event:Event = null):void {
		var topLeft:Point = _transformTool.boundsTopLeft;
		var topRight:Point = _transformTool.boundsTopRight;
		var bottomRight:Point = _transformTool.boundsBottomRight;
		var bottomLeft:Point = _transformTool.boundsBottomLeft;
		
		graphics.clear();
		graphics.lineStyle(0, 0);
		graphics.moveTo(topLeft.x, topLeft.y);
		graphics.lineTo(topRight.x, topRight.y);
		graphics.lineTo(bottomRight.x, bottomRight.y);
		graphics.lineTo(bottomLeft.x, bottomLeft.y);
		graphics.lineTo(topLeft.x, topLeft.y);
	}
	
	override public function position(event:Event = null):void {
		draw(event);
	}
}


// Cursors
class TransformToolInternalCursor extends TransformToolCursor {
	
	public var offset:Point = new Point();
	public var icon:Shape = new Shape();
	
	public function TransformToolInternalCursor() {
		addChild(icon);
		offset = _mouseOffset;
		addEventListener(TransformTool.CONTROL_INIT, init);
	}
		
	private function init(event:Event):void {
		_transformTool.addEventListener(TransformTool.NEW_TARGET, maintainTransform);
		_transformTool.addEventListener(TransformTool.CONTROL_PREFERENCE, maintainTransform);
		draw();
	}
	
	protected function maintainTransform(event:Event):void {
		offset = _mouseOffset;
		if (_transformTool.maintainControlForm) {
			transform.matrix = new Matrix();
			var concatMatrix:Matrix = transform.concatenatedMatrix;
			concatMatrix.invert();
			transform.matrix = concatMatrix;
			offset = concatMatrix.deltaTransformPoint(offset);
		}
		updateVisible(event);
	}
	
	protected function drawArc(originX:Number, originY:Number, radius:Number, angle1:Number, angle2:Number, useMove:Boolean = true):void {
		var diff:Number = angle2 - angle1;
		var divs:Number = 1 + Math.floor(Math.abs(diff)/(Math.PI/4));
		var span:Number = diff/(2*divs);
		var cosSpan:Number = Math.cos(span);
		var radiusc:Number = cosSpan ? radius/cosSpan : 0;
		var i:int;
		if (useMove) {
			icon.graphics.moveTo(originX + Math.cos(angle1)*radius, originY - Math.sin(angle1)*radius);
		}else{
			icon.graphics.lineTo(originX + Math.cos(angle1)*radius, originY - Math.sin(angle1)*radius);
		}
		for (i=0; i<divs; i++) {
			angle2 = angle1 + span;
			angle1 = angle2 + span;
			icon.graphics.curveTo(
				originX + Math.cos(angle2)*radiusc, originY - Math.sin(angle2)*radiusc,
				originX + Math.cos(angle1)*radius, originY - Math.sin(angle1)*radius
			);
		}
	}
	
	protected function getGlobalAngle(vector:Point):Number {
		var globalMatrix:Matrix = _transformTool.globalMatrix;
		vector = globalMatrix.deltaTransformPoint(vector);
		return Math.atan2(vector.y, vector.x) * (180/Math.PI);
	}
	
	override public function position(event:Event = null):void {
		if (parent) {
			x = parent.mouseX + offset.x;
			y = parent.mouseY + offset.y;
		}
	}
	
	public function draw():void {
		icon.graphics.beginFill(0);
		icon.graphics.lineStyle(1, 0xFFFFFF);
	}
}


class TransformToolRegistrationCursor extends TransformToolInternalCursor {
	
	public function TransformToolRegistrationCursor() {
	}
	
	override public function draw():void {
		super.draw();
		icon.graphics.drawCircle(0,0,2);
		icon.graphics.drawCircle(0,0,4);
		icon.graphics.endFill();
	}
}


class TransformToolMoveCursor extends TransformToolInternalCursor {
	
	public function TransformToolMoveCursor() {
	}
	
	override public function draw():void {
		super.draw();
		// up arrow
		icon.graphics.moveTo(1, 1);
		icon.graphics.lineTo(1, -2);
		icon.graphics.lineTo(-1, -2);
		icon.graphics.lineTo(2, -6);
		icon.graphics.lineTo(5, -2);
		icon.graphics.lineTo(3, -2);
		icon.graphics.lineTo(3, 1);
		// right arrow
		icon.graphics.lineTo(6, 1);
		icon.graphics.lineTo(6, -1);
		icon.graphics.lineTo(10, 2);
		icon.graphics.lineTo(6, 5);
		icon.graphics.lineTo(6, 3);
		icon.graphics.lineTo(3, 3);
		// down arrow
		icon.graphics.lineTo(3, 5);
		icon.graphics.lineTo(3, 6);
		icon.graphics.lineTo(5, 6);
		icon.graphics.lineTo(2, 10);
		icon.graphics.lineTo(-1, 6);
		icon.graphics.lineTo(1, 6);
		icon.graphics.lineTo(1, 5);
		// left arrow
		icon.graphics.lineTo(1, 3);
		icon.graphics.lineTo(-2, 3);
		icon.graphics.lineTo(-2, 5);
		icon.graphics.lineTo(-6, 2);
		icon.graphics.lineTo(-2, -1);
		icon.graphics.lineTo(-2, 1);
		icon.graphics.lineTo(1, 1);
		icon.graphics.endFill();
	}
}


class TransformToolScaleCursor extends TransformToolInternalCursor {
	
	public function TransformToolScaleCursor() {
	}
	
	override public function draw():void {
		super.draw();
		// right arrow
		icon.graphics.moveTo(4.5, -0.5);
		icon.graphics.lineTo(4.5, -2.5);
		icon.graphics.lineTo(8.5, 0.5);
		icon.graphics.lineTo(4.5, 3.5);
		icon.graphics.lineTo(4.5, 1.5);
		icon.graphics.lineTo(-0.5, 1.5);
		// left arrow
		icon.graphics.lineTo(-3.5, 1.5);
		icon.graphics.lineTo(-3.5, 3.5);
		icon.graphics.lineTo(-7.5, 0.5);
		icon.graphics.lineTo(-3.5, -2.5);
		icon.graphics.lineTo(-3.5, -0.5);
		icon.graphics.lineTo(4.5, -0.5);
		icon.graphics.endFill();
	}
	
	override public function updateVisible(event:Event = null):void {
		super.updateVisible(event);
		if (event) {
			var reference:TransformToolScaleControl = event.target as TransformToolScaleControl;
			if (reference) {
				switch(reference) {
					case _transformTool.scaleTopLeftControl:
					case _transformTool.scaleBottomRightControl:
						icon.rotation = (getGlobalAngle(new Point(0,100)) + getGlobalAngle(new Point(100,0)))/2;
						break;
					case _transformTool.scaleTopRightControl:
					case _transformTool.scaleBottomLeftControl:
						icon.rotation = (getGlobalAngle(new Point(0,-100)) + getGlobalAngle(new Point(100,0)))/2;
						break;
					case _transformTool.scaleTopControl:
					case _transformTool.scaleBottomControl:
						icon.rotation = getGlobalAngle(new Point(0,100));
						break;
					default:
						icon.rotation = getGlobalAngle(new Point(100,0));
				}
			}
		}
	}
}


class TransformToolRotateCursor extends TransformToolInternalCursor {
	
	public function TransformToolRotateCursor() {
	}
	
	override public function draw():void {
		super.draw();
		// curve
		var angle1:Number = Math.PI;
		var angle2:Number = -Math.PI/2;
		drawArc(0,0,4, angle1, angle2);
		drawArc(0,0,6, angle2, angle1, false);
		// arrow
		icon.graphics.lineTo(-8, 0);
		icon.graphics.lineTo(-5, 4);
		icon.graphics.lineTo(-2, 0);
		icon.graphics.lineTo(-4, 0);
		icon.graphics.endFill();
	}
}


class TransformToolSkewCursor extends TransformToolInternalCursor {
	
	public function TransformToolSkewCursor() {
	}
	
	override public function draw():void {
		super.draw();
		// right arrow
		icon.graphics.moveTo(-6, -1);
		icon.graphics.lineTo(6, -1);
		icon.graphics.lineTo(6, -4);
		icon.graphics.lineTo(10, 1);
		icon.graphics.lineTo(-6, 1);
		icon.graphics.lineTo(-6, -1);
		icon.graphics.endFill();
		
		super.draw();
		// left arrow
		icon.graphics.moveTo(10, 5);
		icon.graphics.lineTo(-2, 5);
		icon.graphics.lineTo(-2, 8);
		icon.graphics.lineTo(-6, 3);
		icon.graphics.lineTo(10, 3);
		icon.graphics.lineTo(10, 5);
		icon.graphics.endFill();
	}
	
	override public function updateVisible(event:Event = null):void {
		super.updateVisible(event);
		if (event) {
			var reference:TransformToolSkewBar = event.target as TransformToolSkewBar;
			if (reference) {
				switch(reference) {
					case _transformTool.skewLeftControl:
					case _transformTool.skewRightControl:
						icon.rotation = getGlobalAngle(new Point(0,100));
						break;
					default:
						icon.rotation = getGlobalAngle(new Point(100,0));
				}
			}
		}
	}
}