package classes.away3d.animators
{
	import classes.away3d.arcane;
	import classes.away3d.animators.utils.*;
	import classes.away3d.core.base.*;
	import classes.away3d.core.geom.*;
	
	import flash.geom.*;
	
	use namespace arcane;
	
	public class PathAnimator extends Animator
	{
		private var _path:Path;
		private var _commands:Vector.<PathCommand>;
		private var _cCommand:PathCommand;
		private var _nCommand:PathCommand;
		private var _position:Vector3D = new Vector3D();
		private var _tangent:Vector3D = new Vector3D();
		private var _rotation:Vector3D = new Vector3D();
		private var _worldAxis:Vector3D = new Vector3D(0,1,0);
		
        protected override function updateTarget():void
        {
        }
        
        protected override function getDefaultFps():Number
		{
			return 1;
		}
		
		protected override function updateProgress(val:Number):void
		{
			super.updateProgress(val);
			
			if (_currentFrame == _commands.length) {
        		_cCommand = _nCommand = _commands[_currentFrame-1];
        	} else {
	        	_cCommand = _commands[_currentFrame];
	        	
	        	if (_currentFrame == _commands.length - 1) {
	        		if (loop)
	        			_nCommand = _commands[0];
	        		else
	        			_nCommand = _commands[_currentFrame];
	        	} else {
	        		_nCommand = _commands[_currentFrame+1];
	        	}
        	}
			
			var start:Vector3D = _cCommand.pStart;
			var control:Vector3D = _cCommand.pControl;
			var end:Vector3D = _cCommand.pEnd;
			
			//calculate position
			var dt:Number = 2 * _invFraction;
			_position.x = start.x + _fraction * (dt * (control.x - start.x) + _fraction * (end.x - start.x));
			_position.y = start.y + _fraction * (dt * (control.y - start.y) + _fraction * (end.y - start.y));
			_position.z = start.z + _fraction * (dt * (control.z - start.z) + _fraction * (end.z - start.z));
			
			_target.position = _position;
			
			if (alignToPath) {
				
				//calculate tangent
				_tangent.x = _position.x + control.x - start.x + _fraction * (end.x - 2 * control.x + start.x);
				_tangent.y = _position.y + control.y - start.y + _fraction * (end.y - 2 * control.y + start.y);
				_tangent.z = _position.z + control.z - start.z + _fraction * (end.z - 2 * control.z + start.z);
				
				if(rotations.length) {
					if(rotations[_currentFrame+1] == null) {
						_rotation.x = rotations[rotations.length-1].x;
						_rotation.y = rotations[rotations.length-1].y;
						_rotation.z = rotations[rotations.length-1].z;
					} else {
						_rotation.x = rotations[_currentFrame].x*_invFraction + rotations[_currentFrame+1].x*_fraction;
						_rotation.y = rotations[_currentFrame].y*_invFraction + rotations[_currentFrame+1].y*_fraction;
						_rotation.z = rotations[_currentFrame].z*_invFraction + rotations[_currentFrame+1].z*_fraction;
					}
					
					_worldAxis = path.worldAxis;
					_worldAxis.x = 0;
					_worldAxis.y = 1;
					_worldAxis.z = 0;
					_worldAxis = PathUtils.rotatePoint(_worldAxis, _rotation);
					
					_target.lookAt(_tangent, _worldAxis);
				} else {
					_target.lookAt(_tangent);
				}
			} else if (targetObject != null) {
				_target.lookAt(targetObject.scenePosition);
			}
			if (offset) {
				_target.moveRight(offset.x);
				_target.moveUp(offset.y);
				_target.moveForward(offset.z);
			}
		}
		
		/**
    	* sets an optional offset to the position on the path, ideal for cameras or reusing the same <code>Path</code> object for parallel animations
    	*/
		public var offset:Vector3D;
		
		/**
    	* Defines an optional array of rotations in order to follow a path that is twisted along its axis.
    	*/
		public var rotations:Array;
		
		/**
    	* Defines a target object that the 3d object looks at while animating along the path. Defaults to null.
    	*/
		public var targetObject:Object3D;
		
		/**
    	* Defines whether the 3d object aligns its rotation to the axis of the path while animating along the path. Defaults to false.
    	*/
		public var alignToPath:Boolean;
		
		/**
    	* defines the path used by the animation.
    	* 
		* @see Path
    	*/
        public function get path():Path
        {
            return _path;
		}
        
		public function set path(val:Path):void
        {
        	_path = val;
        	_commands = path.array;
        	_totalFrames = _commands.length;
        }
		 
		/**
    	* returns the current interpolated position on the path with no optional offset applied
    	*/
		public function get position():Vector3D
		{
			return _position;
		}
		
		/**
    	* returns the current interpolated rotation along the path.
    	*/
		public function get rotation():Vector3D
		{
			return _rotation;
		}
		
		/**
		 * Creates a new <code>PathAnimator</code>
		 * 
		 * @param	path		[optional]	Defines the <code>Path</code> object used tp define the path of the animation.
		 * @param	target		[optional]	Defines the 3d object to which the animation is applied.
		 * @param	init		[optional]	An initialisation object for specifying default instance properties.
		 */
		function PathAnimator(path:Path = null, target:Object3D = null, init:Object = null)
		{
			super(target, init);
			
			this.path = path;
			
			targetObject = ini.getObject3D("targetObject");
			alignToPath = ini.getBoolean("alignToPath",false);
			offset = ini.getVector3D("offset");
			rotations = ini.getArray("rotations");
		}
		
		/**
    	* Updates a position Vector3D on the path at a given time. Do not use this handler to animate, it's in there to add dummy's or place camera before or after
		* the animated object. Use the update() method or progress property instead.
		*
		* @param p	Vector3D. The Vector3D to update according to the "t" time parameter.
		* @param t	Number. A Number  from 0 to 1
		* @see animateOnPath
		* @see update
    	*/
		public function getPositionOnPath( p:Vector3D, t:Number):void
		{
			t = (t<0)? 0 : (t>1)?1 : t;
			var m:Number = path.array.length*t;
			var i:int = Math.floor(m);
			var command:PathCommand = path.array[i];
			var nT:Number = m-i;
			var dt:Number = 2 * (1 - nT);
			p.x = command.pEnd.x + nT * (dt * (command.pControl.x - command.pStart.x) + nT * (command.pEnd.x - command.pStart.x));
			p.y = command.pStart.y + nT * (dt * (command.pControl.y - command.pStart.y) + nT * (command.pEnd.y - command.pStart.y));
			p.z = command.pStart.z + nT * (dt * (command.pControl.z - command.pStart.z) + nT * (command.pEnd.z - command.pStart.z));
		}

		
		/**
    	* returns the segment index that is used at a given time;
		* @param	 t		[Number]. A Number between 0 and 1. If no params, actual pathanimator time segment index is returned.
    	*/
		public function getTimeSegment(t:Number = NaN):Number
		{
			t = (isNaN(t))? _time : t;
			return Math.floor(path.array.length*t);
		}
		
		/**
		 * Returns a position on the path determined by the elapsed time given.
		 * 
		 * @param		time					A number representing the elapsed time in seconds.
		 * @param		position	[optional]	A 3d number object representing the instance to be returned. 
		 *
		 * @return								A Vector3D object representing the position.
		 */
		public function getPathPosition(time:Number, position:Vector3D = null):Vector3D
        {
            var p:Number = (time - delay)/length;
            p = (p<0)? 0 : (p>1)?1 : p;
            var m:Number = path.array.length*p;
            var i:int = Math.floor(m);
            var command:PathCommand = path.array[i];
            var nT:Number = m-i;
            var dt:Number = 2 * (1 - nT);
            
            if (!position)
            	position = new Vector3D();
            
            position.x = command.pStart.x + nT * (dt * (command.pControl.x - command.pStart.x) + nT * (command.pEnd.x - command.pStart.x));
            position.y = command.pStart.y + nT * (dt * (command.pControl.y - command.pStart.y) + nT * (command.pEnd.y - command.pStart.y));
            position.z = command.pStart.z + nT * (dt * (command.pControl.z - command.pStart.z) + nT * (command.pEnd.z - command.pStart.z));
			
			return position;
        }
	}
}