package classes.away3d.animators
{
	import classes.away3d.arcane;
	import classes.away3d.core.base.*;
	import classes.away3d.core.utils.*;
	import classes.away3d.events.*;
	
	import flash.display.*;
	import flash.events.*;
	import flash.utils.*;
	
	use namespace arcane;
	
	/**
	 * Dispatched when the animation starts.
	 * 
	 * @eventType away3d.events.AnimatorEvent
	 */
	[Event(name="start",type="away3d.events.AnimatorEvent")]
	
	/**
	 * Dispatched when the  animation stops.
	 * 
	 * @eventType away3d.events.AnimatorEvent
	 */
	[Event(name="stop",type="away3d.events.AnimatorEvent")]
	
	/**
	 * Dispatched when a looping animation starts a new cycle.
	 * 
	 * @eventType away3d.events.AnimatorEvent
	 * @see	#loop
	 */
	[Event(name="cycle",type="away3d.events.AnimatorEvent")]
	
	/**
	 * Dispatched when entering a new keyframe.
	 * 
	 * @eventType away3d.events.AnimatorEvent
	 */
	[Event(name="enterKeyFrame",type="away3d.events.AnimatorEvent")]
	
    /**
     * Base class for all animator objects.
     */
	public class Animator extends EventDispatcher
	{
        arcane var _totalFrames:Number = 0;
        
		private var _broadcaster:Sprite = new Sprite();
		private var _startTime:Number;
		private var _startEvent:AnimatorEvent;
		private var _stopEvent:AnimatorEvent;
		private var _enterKeyFrameEvent:AnimatorEvent;
		private var _cycleEvent:AnimatorEvent;
		private var _isPlaying:Boolean;
		private var _cycleNumber:int;
		private var _oldProgress:Number;
		private var _oldFrame:int;
		
		private function notifyStart():void
        {
			if (_isPlaying)
				return;
			
			_isPlaying = true;
			
            if (!hasEventListener(AnimatorEvent.START))
                return;
			
            if (!_startEvent)
                _startEvent = new AnimatorEvent(AnimatorEvent.START, this);
            
            dispatchEvent(_startEvent);
        }
        
		private function notifyStop():void
        {
			if (!_isPlaying)
				return;
			
			_isPlaying = false;
			
			if (_broadcaster.hasEventListener(Event.ENTER_FRAME))
				_broadcaster.removeEventListener(Event.ENTER_FRAME, _onEnterFrame);
			
            if (!hasEventListener(AnimatorEvent.STOP))
                return;
			
            if (!_stopEvent)
                _stopEvent = new AnimatorEvent(AnimatorEvent.STOP, this);
            
            dispatchEvent(_stopEvent);
        }
        
		private function notifyEnterKeyFrame():void
        {
        	
            if (!hasEventListener(AnimatorEvent.ENTER_KEY_FRAME))
                return;
			
            if (!_enterKeyFrameEvent)
                _enterKeyFrameEvent = new AnimatorEvent(AnimatorEvent.ENTER_KEY_FRAME, this);
            
            dispatchEvent(_enterKeyFrameEvent);
        }
        
		private function notifyCycle():void
        {
        	
            if (!hasEventListener(AnimatorEvent.CYCLE))
                return;
			
            if (!_cycleEvent)
                _cycleEvent = new AnimatorEvent(AnimatorEvent.CYCLE, this);
            
            dispatchEvent(_cycleEvent);
        }
        
		private function _onEnterFrame(event:Event = null):void
		{
			update(_startTime + getTimer()/1000);
		}
		
		protected var _progress:Number = 0;
		protected var _target:Object3D;
        protected var _time:Number;
        protected var _currentFrame:uint;
        protected var _fraction:Number;
        protected var _invFraction:Number;
        
        
        /**
         * Instance of the Init object used to hold and parse default property values
         * specified by the initialiser object in the 3d object constructor.
         */
		protected var ini:Init;
		
		protected function getDefaultFps():Number
		{
			return 25;
		}
		
        protected function updateTarget():void
        {
        	throw new Error("Not implemented");
        }
        
        protected function updateProgress(val:Number):void
        {
        	if (val < 0) {
        		if (loop)
					_progress = val % 1 + 1;
				else
					_progress = 0;
			} else if (val >= 1) {
				if (loop) {
					_progress = val % 1;
				} else {
					_progress = 1;
				}
			} else {
				_progress = val;
        	}
        	
        	//handle cycle event
        	if (_cycleNumber != int(val/_progress)) {
        		
        		if (!isNaN(_cycleNumber))
        			notifyCycle();
        		
        		_cycleNumber = int(val/_progress);
        	}
			
        	//handle start/stop events
			if (_isPlaying != (_oldProgress != _progress)) {
				if (_isPlaying) {
					notifyStop();
				} else {
					notifyStart();
				}
			}
			
			_oldProgress = _progress;
			
			//update variables
			_time = _progress*length;
        	_currentFrame = uint(_time*fps);
        	_fraction = _time*fps - _currentFrame;
        	_invFraction = 1 - _fraction;
        	
        	if (_oldFrame != _currentFrame) {
        		_oldFrame = _currentFrame;
        		
        		notifyEnterKeyFrame();
        	}
        }
    	/**
    	 * Represents the progress of the animation playhead from the start (0) to the end (1) of the animation.
    	 */
        public function get progress():Number
        {
            return _progress;
        }
        
        public function set progress(val:Number):void
        {
        	if (_progress == val)
        		return;
        	
        	updateProgress(val);
        }
        
        /**
         * Defines the 3d object to which the animation is applied.
         */
        public function get target():Object3D
        {
            return _target;
        }
        
        public function set target(val:Object3D):void
        {
        	if (_target == val)
        		return;
        		
        	_target = val;
        	
        	addEventListener(AnimatorEvent.START, _target.onAnimatorStart);
        	addEventListener(AnimatorEvent.STOP, _target.onAnimatorStop);
        	
			updateTarget();
		}
        
    	/**
    	 * Defines the keyframes per second of the animation
    	 */
		public var fps:Number;
		        
    	/**
    	 * Defines whether the animation will loop. Defaults to true.
    	 */
		public var loop:Boolean;
		
		/**
		 * Defines the total length of the animation in seconds
		 */
        public function get length():Number
        {
            return _totalFrames/fps;
		}
        
		public function set length(val:Number):void
        {
        	fps = _totalFrames/val;
        }
        
		/**
		 * Defines whether the animation interpolates between channel points. Defaults to true.
		 */
		public var interpolate:Boolean;
		
		/**
		 * Defines the delay to the start of the animation in seconds
		 */
        public var delay:Number;
		
		/**
		 * The name of the animation used as a unique reference.
		 */
		public var name:String;
		
		/**
		 * Returns the total length of the animation in frames
		 */
        public function get totalFrames():Number
        {
            return _totalFrames;
        }
        
		/**
		 * Returns the current keyframe number
		 */
        public function get currentFrame():int
        {
            return _currentFrame;
        }
        
        /**
         * Returns the number of the current loop cycle
         */
        public function get cycleNumber():int
        {
        	return _cycleNumber;
        }
        
        /**
         * Returns true when the animation is playing
         */
        public function get isPlaying():Boolean
        {
        	return _isPlaying;
        }
        
		/**
		 * Creates a new <code>Animator</code> object.
		 * 
		 * @param	target		[optional]	Defines the 3d object to which the animation is applied.
		 * @param	init		[optional]	An initialisation object for specifying default instance properties.
		 */
		public function Animator(target:Object3D = null, init:Object = null)
		{
			this.target = target;
			
			ini = Init.parse(init);
			
			progress = ini.getNumber("progress", 0);
			fps = ini.getNumber("fps", getDefaultFps());
			loop = ini.getBoolean("loop", true);
			interpolate = ini.getBoolean("interpolate", true);
			delay = ini.getNumber("delay", 0);
		}
		
		/**
		 * Updates the position of the playhead to the given time in seconds.
		 * 
		 * @param	time	Defines the time in seconds of the playhead of the animation.
		 */
		public function update(time:Number):void
		{
			progress = (time - delay)/length;
		}
		
		public function stop():void
		{
			notifyStop();
		}
		
		public function play():void
		{
			_startTime = _progress*length + delay - getTimer()/1000;
			
			if (!_broadcaster.hasEventListener(Event.ENTER_FRAME))
				_broadcaster.addEventListener(Event.ENTER_FRAME, _onEnterFrame);
			
			notifyStart();
		}
		
		public function gotoAndStop(frame:uint):void
		{
			update(frame/fps);
			
			stop();
		}
		
		public function gotoAndPlay(frame:uint):void
		{
			update(frame/fps);
			
			play();
		}
		
		/**
		 * Duplicates the animators properties to another <code>Animator</code> object
		 * 
		 * @param	animator	[optional]	The new animator instance into which all properties are copied
		 * @return							The new animator instance with duplicated properties applied
		 */
        public function clone(animator:Animator = null):Animator
        {
            var anim:Animator = (animator as Animator) || new Animator();
            anim.progress = _progress;
            anim.fps = fps;
            anim.loop = loop;
            anim.interpolate = interpolate;
            anim.delay = delay;
            return anim;
        }
        		
		/**
		 * Default method for adding a cycle event listener.
		 * 
		 * @param	listener		The listener function
		 */
		public function addOnCycle(listener:Function):void
        {
			addEventListener(AnimatorEvent.CYCLE, listener, false, 0, false);
        }
		
		/**
		 * Default method for removing a cycle event listener
		 * 
		 * @param		listener		The listener function
		 */
		public function removeOnCycle(listener:Function):void
        {
            removeEventListener(AnimatorEvent.CYCLE, listener, false);
        }
        		
		/**
		 * Default method for adding an enterKeyFrame event listener
		 * 
		 * @param		listener		The listener function
		 */
		public function addOnEnterKeyFrame(listener:Function):void
        {
			addEventListener(AnimatorEvent.ENTER_KEY_FRAME, listener, false, 0, false);
        }
		
		/**
		 * Default method for removing a enterKeyFrame event listener
		 * 
		 * @param		listener		The listener function
		 */
		public function removeOnEnterKeyFrame(listener:Function):void
        {
            removeEventListener(AnimatorEvent.ENTER_KEY_FRAME, listener, false);
        }
	}
}
