// ::: tween
// ::: version 1.0.2
// ::: original creation date: 2003.05.15 (for flash)
// ::: last updated: 2010.11.14 (for prototypejs)
// :::
// ::: 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
	window.__oTweenClassEventsBroadcaster = new Object();
};
TweenClass.prototype.tweenGetVersion = function(oThis){
	return "1.0.3";	
};
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(typeof oThis._oTweens == "undefined"){
		oThis._oTweens = new Object();
	}
	if(typeof oThis._oTweens[prop] != "undefined"){
		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;
	var useMethodAsPropFunc;
	if(typeof oThis._oTweens == "undefined"){
		oThis._oTweens = new Object();
	}
	if(typeof cbPrcCmplt == "undefined"){
		cbPrcCmplt = 100;
	}
	var fps = 30;
	var myDate = new Date();
	var easingFunc = Math[strEasingFunc];
	var delta = 0;
	var origVal = 0
	// prop handles
	if(typeof prop == "function"){
		useMethodAsProp = true;
		//document.title = prop.arguments.callee.toString();
		useMethodAsPropFunc = prop;
		prop = tarVal;
		
	}
	else{
		useMethodAsProp = false;
		origVal = oThis.tweenGetPropVal(prop);
		delta = tarVal - oThis.tweenGetPropVal(prop);
	}
	oThis._oTweens[prop] = {
		delta:delta,
		origVal:origVal,
		prop:prop,
		startTime:myDate.getTime(),
		dur:dur,
		easingFunc:easingFunc,
		cbMc:cbMc,
		cbFunc:cbFunc,
		cbPrcCmplt:cbPrcCmplt,
		cbArgs:cbArgs,
		useMethodAsProp:useMethodAsProp,
		useMethodAsPropFunc:useMethodAsPropFunc,
		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(typeof cbMcObj != "undefined" && typeof cbFuncStr != "undefined" && typeof cbArgsObj != "undefined"){
		cbMcObj[cbFuncStr](cbArgsObj);
		//delete oThis._oTweens[prop].cbFunc
		//delete oThis._oTweens[prop].cbMc
		//delete oThis._oTweens[prop].cbArgs
	}
	else{
		if(typeof cbMcObj != "undefined" && typeof cbFuncStr != "undefined"){
			cbMcObj[cbFuncStr]();
			//delete oThis._oTweens[prop].cbFunc
			//delete oThis._oTweens[prop].cbMc
		}
	}
};
TweenClass.prototype.addOnTweenUpdatedListener = function(oDiv){
	var sDivId = oDiv.id;
	var sProp = "any";
	if(typeof window.__oTweenClassEventsBroadcaster == "undefined"){
		window.__oTweenClassEventsBroadcaster = new Object();
	}
	window.__oTweenClassEventsBroadcaster[sDivId] = new Object();
	window.__oTweenClassEventsBroadcaster[sDivId][sProp] = oDiv;
	//alert(window.__oTweenClassEventsBroadcaster[sDivId]["any"].id);
}
TweenClass.prototype.removeOnTweenUpdatedListener = function(oDiv){
	var sDivId = oDiv.id;
	var sProp = "any";
	
}
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 useMethodAsPropFunc;
	var myDate = new Date();
	var oDivEventNotifications;
	var sThisId = oThis.id;
	// 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;
		useMethodAsPropFunc = oThis._oTweens[prop].useMethodAsPropFunc;
		if(typeof window.__oTweenClassEventsBroadcaster != "undefined"){
			if(typeof window.__oTweenClassEventsBroadcaster[sThisId] != "undefined"){
				//alert(window.__oTweenClassEventsBroadcaster[sThisId]["any"]);
				if(typeof window.__oTweenClassEventsBroadcaster[sThisId]["any"] != "undefined" ){
					oDivEventNotifications = window.__oTweenClassEventsBroadcaster[sThisId]["any"];
					oDivEventNotifications.onTweenUpdate(prcCmplt);
				}
				else{
					if(typeof window.__oTweenClassEventsBroadcaster[sThisId][prop] != "undefined" ){
						oDivEventNotifications =  window.__oTweenClassEventsBroadcaster[sThisId][prop];
						oDivEventNotifications.onTweenUpdate(prcCmplt);
					}
				}
			}
		}
		//oThis.update(oThis.id+" - "+oThis._oTweens[prop].onEnterFrameListener_intervalId+" - "+prcCmplt);
		if(elapsed < oThis._oTweens[prop].dur){
			if(oThis._oTweens[prop].useMethodAsProp == true){ // 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);
				useMethodAsPropFunc(amt);
				//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 == true){
				useMethodAsPropFunc(100);
				//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
