// ::: tween
// ::: version 0.96
// ::: 2003.05.15
// ::: 
// :::
// ::: tween usage: 	myDynLayer.tween(<property handle>, <amount>, <duration>, <easing equation>,<callbackFunc>,<callbackObj>,<percent complete>);
// :::
// :::	<property handle>	a required string representing a supported property to tween (examples: '_x','_y', or a user specifed method for 'method-marshaling').
// :::	<amount>			a required integer representing the target amount to tween by (example: 300, would move 300 pixels from current position).
// :::	<duration>			a required integer representing the amount of time in milliseconds for which to perform the tween (example: 2000 would be a two second tween).
// :::	<easing equation>	a required string representing one of Robert Penners .js included math functions (example: 'easeOutCubic').
// :::	<callbackFunc>		an optional string representing a callback function to execute when the tween has reached the percent complete value (example: 'myFunction').
// :::	<callbackObj>		an optional object reference specifing the object the function resides in (example: Window).
// :::	<percent complete>	an optional integer representing the percentage of completion needed until the callback function is executed (example: 80)
// :::
// :::	Send bugs or comments to jalcide@jalcide.com

// ::: Public Methods

TweenClass = function(){
	// constructor	
};
TweenClass.prototype.tweenGetVersion = function(oThis){
	return "1.0.1";	
};
TweenClass.prototype.tweenShowVersion = function(oThis){
	oThis = $(oThis);
	alert(oThis.tweenGetVersion());	
};
TweenClass.prototype.tween = function(oThis, prop, tarVal, dur, strEasingFunc, cbFunc, cbMc, cbPrcCmplt, cbArgs){
	oThis = $(oThis);
	// if a tween for given property exsist, cancel it
	if(!oThis._oTweens){
		oThis._oTweens = new Object();
	}
	if(oThis._oTweens[prop]){
		oThis.tweenCancel(prop);
	}
	// create a new tween
	oThis.tweenAddTween(prop, tarVal, dur, strEasingFunc, cbFunc, cbMc, cbPrcCmplt, cbArgs);
	oThis.tweenAddOnEnterFrameListener(prop);
	return oThis;
};
TweenClass.prototype.tweenCancel = function(oThis, prop){
	oThis = $(oThis);
	//cancel a tween in progress
	oThis.tweenRemove(prop);
};
// ::: Private Methods
TweenClass.prototype.tweenAddOnEnterFrameListener = function(oThis, prop){
	oThis = $(oThis);
	oThis.tweenDoTweens();
};
TweenClass.prototype.tweenAddTween = function(oThis, prop, tarVal, dur, strEasingFunc, cbFunc, cbMc, cbPrcCmplt, cbArgs){
	oThis = $(oThis);
	// create and store a tween (a prop data object with all the data needed to do the tween) in tween data object container
	var useMethodAsProp;
	if(!oThis._oTweens){
		oThis._oTweens = new Object();
	}
	if(!cbPrcCmplt){
		cbPrcCmplt = 100;
	}
	var fps = 30;
	var myDate = new Date();
	var easingFunc = Math[strEasingFunc];
	var delta = tarVal - oThis.tweenGetPropVal(prop);
	// prop handles
	if(typeof prop == "function"){
		useMethodAsProp = true;
	}
	else{
		useMethodAsProp = false;
	}
	oThis._oTweens[prop] = {
		delta:delta,
		origVal:oThis.tweenGetPropVal(prop),
		prop:prop,
		startTime:myDate.getTime(),
		dur:dur,
		easingFunc:easingFunc,
		cbMc:cbMc,
		cbFunc:cbFunc,
		cbPrcCmplt:cbPrcCmplt,
		cbArgs:cbArgs,
		useMethodAsProp:useMethodAsProp,
		fps:fps,
		fps_updateInterval:1000/fps,
		onEnterFrameListener_intervalId:-1
	};
};
TweenClass.prototype.tweenGetPropVal = function(oThis, prop){
	oThis = $(oThis);
	var result;
	switch(prop){
		case "_x":
  			result = parseFloat(oThis.getStyle("left").substr(0, oThis.getStyle("left").length-2));
  			break;   
		case "_y":
  			result = parseFloat(oThis.getStyle("top").substr(0, oThis.getStyle("top").length-2));
  			break;
		default:
  			result = parseFloat(oThis.getStyle(prop).substr(0, oThis.getStyle(prop).length-2));
	}
	return result;
};
TweenClass.prototype.tweenSetPropVal = function(oThis, prop, val){
	oThis = $(oThis);
	val = parseFloat(val);
	var oProp = new Object();
	oProp[prop] = val+(prop == "left" || prop == "top" || prop == "width" || prop == "height" ? "px" : "");
	switch(prop){
		case "_x":
  			oThis.setStyle({left: val+"px"});
  			break;   
		case "_y":
  			oThis.setStyle({top: val+"px"});
  			break;
		default:
  			oThis.setStyle(oProp);
	}
};
TweenClass.prototype.tweenRemove = function(oThis, prop){
	oThis = $(oThis);
	clearInterval(oThis._oTweens[prop].onEnterFrameListener_intervalId);
	// remove a tween from the tween data object
	delete oThis._oTweens[prop];
	// if there is no prop (tween) in the tween data object, remove the onEnterFrame listener and finish	
	var propCount = 0;
	for(var p in oThis._oTweens){
		propCount++;
	}
	if(propCount == 0){ // if no more tweens exsist on oThis layer
		// clear tween container 
		oThis._oTweens = new Object();
	}
};
TweenClass.prototype.tweenInvokeCb = function(oThis, cbFuncStr, cbMcObj, cbArgsObj, prop){
	oThis = $(oThis);
	// invoke the callback function (if present)
	if(cbMcObj && cbFuncStr && cbArgsObj){
		cbMcObj[cbFuncStr](cbArgsObj);
		//delete oThis._oTweens[prop].cbFunc
		//delete oThis._oTweens[prop].cbMc
		//delete oThis._oTweens[prop].cbArgs
	}
	else{
		if(cbMcObj && cbFuncStr){
			cbMcObj[cbFuncStr]();
			//delete oThis._oTweens[prop].cbFunc
			//delete oThis._oTweens[prop].cbMc
		}
	}
};
TweenClass.prototype.tweenDoTweens = function(oThis){
	oThis = $(oThis);
	// perform the tween
	var cbMc = new Object();
	var cbArgs = new Object();
	var elapsed;
	var cbFunc;
	var cbPrcCmplt;
	var prcCmplt;
	var amt;
	var myDate = new Date();
	// process the tweens in the tween data object
	for(var prop in oThis._oTweens){
		if(oThis._oTweens[prop].onEnterFrameListener_intervalId == -1){
			function boundClosure(a, b, c){
				return (function(){
					a[b](c);
				});
			}
			var closureRef = boundClosure(oThis, "tweenDoTweens", oThis); // javascript "closure" gets around scope limitations of setInterval by encapsulating original object ref
			oThis._oTweens[prop].onEnterFrameListener_intervalId = setInterval(closureRef, oThis._oTweens[prop].fps_updateInterval);
		}
		cbFunc = oThis._oTweens[prop].cbFunc; // ::: does oThis need to be converted to string...??? revisit, if there are problems with cbFunc callback functionality.
		cbMc = oThis._oTweens[prop].cbMc;
		cbArgs = oThis._oTweens[prop].cbArgs;
		elapsed = myDate.getTime() - oThis._oTweens[prop].startTime;
		cbPrcCmplt = oThis._oTweens[prop].cbPrcCmplt;
		prcCmplt = (100 * elapsed) / oThis._oTweens[prop].dur;
		//oThis.update(oThis.id+" - "+oThis._oTweens[prop].onEnterFrameListener_intervalId+" - "+prcCmplt);
		if(elapsed < oThis._oTweens[prop].dur){
			if(oThis._oTweens[prop].useMethodAsProp){ // method marshal, calls a method on 'enterFrame' and passes in percent complete as arg, as per easing equation.
				amt = oThis._oTweens[prop].easingFunc(elapsed, 0, 100, oThis._oTweens[prop].dur);
				window[prop](amt);
			}
			else{
				amt = oThis._oTweens[prop].easingFunc(elapsed, oThis._oTweens[prop].origVal, oThis._oTweens[prop].delta, oThis._oTweens[prop].dur);
				// perform tween delta
				oThis.tweenSetPropVal(prop, amt);
			}
		}
		else{
			// set tween delta to final value
			if(oThis._oTweens[prop].useMethodAsProp){
				window[prop](100);
			}
			else{
				// perform tween final
				amt = oThis._oTweens[prop].delta + oThis._oTweens[prop].origVal;
				oThis.tweenSetPropVal(prop, amt);
			}
			oThis.tweenRemove(prop);
		}
		if(prcCmplt >= cbPrcCmplt){
			oThis.tweenInvokeCb(cbFunc, cbMc, cbArgs, prop);
		}
	}
};
Element.addMethods(TweenClass.prototype); // inherits TweenClass