/*
ALL SCRIPTING IN THIS DOCUMENT WAS WRITTEN BY NICK ANNIS FOR THE ENGINES AND ENERGY CONVERSION LAB (CSU)
	-last revised: 5/20/09
	
	-requires additional files:
		vector.js
		
	MOVEMENT SIGN CONVENTIONS:
		Velocity:
			Up:   +		Down:  -
			Left: -		Right: +
		Acceleration:
			accelerate: +		decelerate: -
			

AVAILABLE FUNCTIONS:
-constructor		:	initializes all necessary variables for animation
									-args:	name		- STRING: what the motion object will reference itself as in
														  window["<name>"] calls
											object		- ELEMENT: to modify
											modify		- STR: movement type, can be one of the following:
															"x","y","width","height","opacity"
-motionType			:	setter and getter for type of movement attributed to the current object. takes a string and
						sets which attribute the object animates/modifies
									-args:	newVal		- STR (optional): new movement type, can be one of the following:
															"x","y","width","height","opacity"
														  leaving this argument out will return one of the previous strings,
														  correlating to what type of movement the object currently has set
-valueX				:	setter and getter for x-axis movement, retrieves or sets element's "left" css value
									-args:	newVal 		- INT (optional): new value, leaving this argument
														  out will return the current value
														  
-valueY				:	identical setter and getter for y-axis movement, correlates to element's "top" css value
									-args:	newVal 		- INT (optional): new value, leaving this argument
														  out will return the current value
-valueWidth			:	identical setter and getter for width movement, correlates to element's "width" css value
									-args:	newVal 		- INT (optional): new value, leaving this argument
														  out will return the current value
-valueHeight		:	identical setter and getter for height movement, correlates to element's "height" css value
									-args:	newVal 		- INT (optional): new value, leaving this argument
														  out will return the current value
-valueOpacity		:	identical setter and getter for opacity, correlates to "opacity" and IE's "filter"
									-args:	newVal 		- INT (optional): new value, leaving this argument
														  out will return the current value
-constMove			:	starts motion animation at a constant velocity, does not end until the user specifies
									-args:	Vo			- INT: velocity to inialize movement at, according to
														  previously defined conventions
-moveToFinal		:	animates object to a specified final position, according to user-defined acceleration or velocity
									-args:	finalVal	- INT: value to end animation at
-moveByAmount		:	animates object by specified amount according to previously specified sign conventions
									-args:	amount		- INT: amount to animate the object by
-startMotion		:	starts animation according to what all variables currently are, not for general use
-motionStep			:	individual "step" or "frame" of animating, not for general use
-terminate			:	stops all motion
-addTermEval		:	adds a string to be evaluated when the animation is terminated
									-args:	aEval		- STR: valid javascript to be evaluated with "eval()"
											key			- (optional): any comparable variable to be associated
														  with the valid javascript expression
-removeTermEval		:	removes a termination-eval string
									-args:	rEval		- either a "key" value or string matching the aEval 
														  argument of "addTermEval" function
-runTermEvals		:	runs all termination evaluation strings, not for general use
-addEval			:	adds a string to be evaluated at every step, or "frame", of the animation
									-args:	aEval		- STR: valid javascript to be evaluated with "eval()"
											key			- (optional): any comparable variable to be associated
														  with the valid javascript expression
-removeEval			:	removes a step-eval string
									-args:	rEval		- either a "key" value or string matching the aEval 
														  argument of "addEval" function
-runEvals			:	runs all step evaluation strings, not for general use
-isMoving			:	BOOL returns "true" if animation is running, "false" otherwise
-rate				:	sets/gets number of milliseconds between each step of the animation
									-args:	newVal		- INT (optional): desired number of milliseconds to wait
														  before calling "motionStep" again, leaving this argument
														  out will return the current rate
-initValue			:	sets/gets initial value for animation, does not actually move the object
									-args:	newVal		- INT (optional): sets a new "initial" value to use when
														  calculating animation, leaving this argument out will return
														  the current "initial" value
-currValue			:	sets/gets current value for animation, DOES move the object
									-args:	newVal		- INT (optional): sets a new "current" value of the element
														  and sets it to that value, leaving this argument out will 
														  return the "current" value
-initVelocity		:	sets/gets initial velocity value for animation
									-args:	newVal		- INT (optional): sets a new initial velocity to use when
														  calculating animation, leaving this argument out will 
														  return the current initial velocity
-currVelocity		:	returns current velocity
-acceleration		:	sets/gets acceleration
									-args:	newVal		- INT (optional): sets a new acceleration to use when
														  calculating animation, leaving this argument out will 
														  return the current acceleration
-maxCount			:	sets/gets max count for animation (when count >= max count, the animation terminates)
									-args:	newVal		- INT (optional): sets a new max count value to use when
														  calculating animation, leaving this argument out will return
														  the current max count
-currCount			:	sets/gets current time count for the animation
									-args:	newVal		- INT (optional): sets a new "count" value to use when
														  calculating animation, leaving this argument out will return
														  the current "count" value

*/

function genMotion(name, object, modify){
	this.name = name;
	this.divObj = object;
	window[this.name] = this;
	this.object = "window[\""+this.name+"\"]";
	this.modStr;
	this.modFunc;
	this.motionType(modify);
	
	this.debug = false;
	this.selfTerm = false;
	
	this.evals = new Vector();
	this.termEvals = new Vector();
	
	this.termEvalKey = 0;
	
	
/*---------- MOTION VARIABLES ----------*/	
	this.mRate = 32;
	
	this.moving = false;
	this.move;
	
	    this.initial = 0;
	    this.current = 0;
		this.Vo = 0;
		this.accel = -300;
		this.tMax = 0;
		this.count = 0;
		this.mod = 1;
}

genMotion.prototype.motionType = function(newVal){
	if(typeof newVal == "undefined"){ return this.modStr; }
	this.modStr = newVal;
	switch(newVal){
		case "x":
			this.modFunc = this.valueX;
			break;
		case "y":
			this.modFunc = this.valueY;
			break;
		case "width":
			this.modFunc = this.valueWidth;
			break;
		case "height":
			this.modFunc = this.valueHeight;
			break;
		case "opacity":
			this.modFunc = this.valueOpacity;
			break;
	}
}

genMotion.prototype.valueX = function(newVal){
	if(typeof newVal == "undefined"){ return parseInt(this.divObj.style.left.replace("px","")); }
	this.divObj.style.left = newVal+"px";
}

genMotion.prototype.valueY = function(newVal){ 
	if(typeof newVal == "undefined"){ return parseInt(this.divObj.style.top.replace("px","")); }
	this.divObj.style.top = newVal+"px";
}

genMotion.prototype.valueWidth = function(newVal){
	if(typeof newVal == "undefined"){ return parseInt(this.divObj.style.width.replace("px","")); }
	this.divObj.style.width = newVal+"px";
}

genMotion.prototype.valueHeight = function(newVal){
	if(typeof newVal == "undefined"){ return parseInt(this.divObj.style.height.replace("px","")); }
	this.divObj.style.height = newVal+"px";
}

genMotion.prototype.valueOpacity = function(newVal){
	if(typeof newVal == "undefined"){ 
		if(typeof this.divObj.style.filter != "undefined"){
			return parseInt(this.divObj.style.filter.replace("alpha(opacity=","").replace(")",""));
		}
		return parseInt(this.divObj.style.opacity*100);
	}
	if( newVal > 100 ){ newVal = 100; }
	else if(newVal < 0 ){ newVal = 0; }
	if(typeof this.divObj.style.filter != "undefined"){
		this.divObj.style.filter = "alpha(opacity="+newVal+")";
		return;
	}
	this.divObj.style.opacity = newVal/100;
}

genMotion.prototype.constMove = function(Vo){
	if(this.moving){ this.terminate(); }
	this.Vo = Vo;
	if(this.Vo == 0){ return; }
	this.tMax = -1;
	
	this.addTermEval("this.accel="+this.accel+";"+
					 "this.removeTermEval( this.termEvals.findKey('termEvalKey#"+this.termEvalKey+"') );",
					 "termEvalKey#"+(this.termEvalKey++));
	
	this.accel = 0;
	
	this.startMotion();
}

genMotion.prototype.moveToFinal = function(finalVal){
	this.moveByAmount(finalVal - this.modFunc());
}

genMotion.prototype.moveByAmount = function(amount){
	if(this.moving){ this.terminate(); }
	if(amount == 0){ return; }
	this.mod = amount/Math.abs(amount);
	
	if( this.accel < 0 ){ //decelerate
		this.tMax = Math.sqrt((-2*Math.abs(amount))/this.accel);
		this.Vo = -1*this.accel*this.tMax;
	}
	else if( this.accel == 0 ){
		if(this.Vo == 0){ return; }
		this.Vo = Math.abs(this.Vo);
		this.tMax = Math.abs(amount/this.Vo);
	}
	else if( this.accel > 0 ){ //accelerate
		this.Vo = 0;
		this.tMax = Math.sqrt((2*Math.abs(amount))/this.accel);
	}
	
	this.addTermEval("if(this.selfTerm){ this.modFunc("+(this.modFunc()+amount)+"); this.runEvals(); }"+
					 "this.removeTermEval( 'termEvalKey#"+this.termEvalKey+"' );",
					 "termEvalKey#"+(this.termEvalKey++));
	
	this.startMotion();
}

genMotion.prototype.startMotion = function(){
	if(this.moving){ this.terminate(); }
	this.moving = true;
	if(this.accel<0 && this.Vo<0){ this.mod = -1; }
	this.initial = this.modFunc();
	this.motionStep();
}

genMotion.prototype.motionStep = function(){
	if(this.moving){
		this.current = parseInt(this.initial + (this.Vo*(this.count) + .5*this.accel*(this.count*this.count))*this.mod);
		this.modFunc(this.current);
		this.runEvals();
		this.count+=(this.mRate/1000);
			
		if(this.tMax == -1 || this.count<this.tMax){
			this.move = setTimeout(this.object+".motionStep();",this.mRate);
		}
		else{
			this.selfTerm = true;
			this.terminate();
		}
	}
}

genMotion.prototype.terminate = function(){
	if(this.moving){
		clearTimeout(this.move);
		this.moving = false;
		this.runTermEvals();
		this.count = 0;
		this.mod = 1;
		this.selfTerm = false;
	}
}

genMotion.prototype.addTermEval = function(aEval,key){
	this.termEvals.add(aEval,key);
}

genMotion.prototype.removeTermEval = function(rEval){
	var index = this.termEvals.findKey(rEval);
	if(index==-1){ index = this.termEvals.findValue(rEval); }
	return this.termEvals.remove(index);
}

genMotion.prototype.runTermEvals = function(){
	var size = this.termEvals.size();
	for(var i = 0;i<this.termEvals.size();++i){
		eval(this.termEvals.get(i));
		if(size>this.termEvals.size()){ 
			i-=1;
			size = this.termEvals.size();
		}
	}
}

genMotion.prototype.addEval = function(aEval,key){
	this.evals.add(aEval,key);
}

genMotion.prototype.removeEval = function(rEval){
	var index = this.evals.findKey(rEval);
	if(index==-1){ index = this.evals.findValue(rEval); }
	return this.evals.remove(index);
}

genMotion.prototype.runEvals = function(){
	var size = this.evals.size();
	for(var i = 0;i<this.evals.size();++i){
		eval(this.evals.get(i));
		if(size>this.evals.size()){ 
			i-=1;
			size = this.evals.size();
		}
	}
}

genMotion.prototype.isMoving = function(){
	return this.moving;
}

genMotion.prototype.rate = function(newVal){
	if(typeof newVal == "undefined"){ return this.mRate; }
	this.mRate = newVal;
}

genMotion.prototype.initValue = function(newVal){
	if(typeof newVal == "undefined"){ return this.initial; }
	this.initial = newVal;
}

genMotion.prototype.currValue = function(newVal){
	if(typeof newVal == "undefined"){ return this.modFunc(); }
	this.modFunc(newVal); 
	this.current = newVal;
}

genMotion.prototype.initVelocity = function(newVal){
	if(typeof newVal == "undefined"){ return this.Vo; }
	this.Vo = newVal;
}

genMotion.prototype.currVelocity = function(){
	return this.Vo+this.accel*this.count;
}

genMotion.prototype.acceleration = function(newVal){
	if(typeof newVal == "undefined"){ return this.accel; }
	this.accel = newVal;
}

genMotion.prototype.maxCount = function(newVal){
	if(typeof newVal == "undefined"){ return this.tMax; }
	this.tMax = newVal;
}

genMotion.prototype.currCount = function(newVal){
	if(typeof newVal == "undefined"){ return this.count; }
	this.count = newVal;
}