var styleSets = { 
	width:Array('width'), 
	height:Array('height'), 
	x:Array('left'),
	y:Array('top'),
	z:Array('zIndex'),
	marginLeft:Array('marginLeft'),
	marginTop:Array('marginTop'),
	alpha:Array('opacity')
};
var filterSets = {
	alpha:Array('alpha(opacity=#X#)')
};
var filterScale = {
	alpha:100
};
var styleSuffixes = {
	width:'px',
	height:'px',
	x:'px',
	y:'px',
	z:'px',
	marginLeft:'px',
	marginTop:'px',
	alpha:''
};
var easeStrengths = {
	xstrong:0.51,
	strong:0.56,
	normal:0.6,
	weak:0.8
};

var _tweenInstances = Array();
var _tweenInstances = Array();

var Tween = function(obj, prop, easeMethod, begin, finish, duration, autostart, fps) {
	this.obj = obj;
	this.prop = prop;
	
	this.begin = begin?parseFloat(begin):0;
	this.finish = parseFloat(finish);
	this.duration = parseFloat(duration);

	// global properties
	this.FPS = fps ? fps :30;			// set frame rate
	//if(isIE)	this.FPS = this.FPS/3;

	// internal properties
	//this.instanceId = _tweenInstances.length;
	if(!obj.id) {					// give it a unique id if none
		if(!window['unique_counter'])	window['unique_counter'] = 0;
		obj.id = '_anonymousTweenObj|'+unique_counter;
		unique_counter++;
	}
	this.instanceId = obj.id+'|tween|'+prop;
	if(_tweenInstances[this.instanceId])	_tweenInstances[this.instanceId].stop(false, true);
	_tweenInstances[this.instanceId] = this;

	this.isPlaying = false;
	this.distance;
	this.curInterval;
	this.dir = (this.begin>this.finish) ? -1 : 1;

	this.timer = false;

	this.start = function() {
		if(this.isPlaying)	this.stop();
		this.position = parseFloat(this.begin);

		this.setDistance();
		this.setInterval();
		this.calcTweens(); 

		this.startTime = getTime();
		this.endTime = getTime() + (this.duration*1000);
		this.progress = 0;

		this.setProperty(this.begin);
		var obj = this;
		this.isPlaying = true;
		this.timer = setTimeout('_tweenInstances["'+this.instanceId+'"].continueTo()', Math.ceil(1000/this.FPS));
	};
	this.reverse = function(autostart) { 
		var tmp = this.begin;
		this.begin = this.finish;
		this.finish = tmp;
		this.dir = this.dir*-1;
		if(autostart && !this.isPlaying)	this.start();
	}
	this.continueTo = function(to) {
		if(!this.isPlaying)	return;

		// there are two methods of determining the position for the next advancement
		// if the tween is easing (end of transition) the progress will be calculated based on position
		// otherwise it will be based on the time in milliseocnds.  This solution ensures that delays on the
		// CPU side do not affect the overall duration of the transition
		if(this.easing || ((this.easeOn=='out') && (Math.abs(this.finish-this.position)<=Math.abs(this.easeAt)))) {
			this.easing = true;
			this.curInterval= this.curInterval*(easeStrengths[this.easeStrength]);
			//this.calcTweens();
			if(Math.abs(this.curInterval)<.005) this.stop();
			this.setDistance();

			if(!to) to = parseFloat(this.position)+parseFloat(this.curInterval);
			else	to = parseFloat(to);
		} else {
			this.progress = (getTime()-this.startTime) / (this.endTime - this.startTime);
			var prior = this.position;
			to = this.begin + this.distance*this.progress;
		}

		
		if(to*this.dir>this.finish*this.dir) {
			to = this.finish;
		}
		//this.trace(to*this.dir + ' > '+this.finish*this.dir);

		this.setProperty(to);
		this.position = to;
		if(this.onchange)	this.onchange(this.position);

		if(this.position*this.dir < this.finish*this.dir) {
			if(this.isPlaying) this.timer = setTimeout('_tweenInstances["'+this.instanceId+'"].continueTo()', Math.ceil(1000/this.FPS));
		} else {
			this.stop();
		}
	};
	this.resume = function() {
		this.isPlaying = true;
	};
	this.stop = function(easeStop, noPostFn) {
		if(easeStop) {
			this.easing = true;
			var origFinish = this.finish;
			this.finish = this.position;
			this.calcTweens(Math.abs(this.begin - this.position));
			this.finish = this.position+this.easeAt;
			if(this.finish*this.dir>origFinish*this.dir) 
				this.finish = origFinish;
			this.setInterval();
		} else {
			this.isPlaying = false;
			if(this.timer)	clearTimeout(this.timer);
			this.timer = false;
			if(!noPostFn && this.onstop)	this.onstop();
		}
	};
	this.changeDuration = function(d) {
		if(duration<0) return;
		this.trace('new duration set: '+d);

		this.duration = d;
		this.begin = this.position;
		this.start();
		return;
	}
	this.setDistance = function() { 
		//this.distance = parseFloat(this.finish) - parseFloat(this.begin);
		this.distance = parseFloat(this.finish) - parseFloat(this.position);
	}
	this.setInterval = function() { 
		this.curInterval = this.distance / (this.duration*this.FPS);
	}
	this.calcTweens = function(distance) {
		distance = distance ? distance : this.distance;
		if(typeof easeMethod == 'string') {
			this.easeOn = easeMethod.substring(0, easeMethod.indexOf('.'));
			this.easeStrength = easeMethod.substring(easeMethod.indexOf('.')+1);
			if(!this.easeStrength)	this.easeStrength = 'normal';
		}
		if(this.easeStrength) {
			var speed = this.curInterval * this.FPS;
			this.easeAt = this.curInterval*(1+easeStrengths[this.easeStrength])*2.0;
		}
		return this.easeDist;
	}
	this.setProperty = function(val) {
		// set styles
		if(styleSets[this.prop]) {
			for(var i in styleSets[this.prop]) {
				var prop = styleSets[this.prop][i];
				if((typeof prop=='string') && (prop.indexOf('#X#')!=-1)) {
					val = 100;
					this.obj.style[prop] = prop.replace(/#X#/g, val);
				} else	{
					try {
						if(isNaN(val))	val = 0;
						this.obj.style[styleSets[this.prop][i]] = val+(styleSuffixes[this.prop]?styleSuffixes[this.prop]:'');
					} catch(e) {
						alert('style = '
								+styleSets[this.prop][i]
								+"\nval = "
								+val
								+"\nintvl = "
								+(styleSuffixes[this.prop]?styleSuffixes[this.prop]:'non-existant')+"\nerror = "+e);
					}
				}
			}
		} else {
			this.obj.style[this.prop] = val;
		}

		// set filters
		if(filterSets[this.prop]) {
			if(filterScale && filterScale[this.prop])	val = parseInt(val*filterScale[this.prop]);
			for(var i in filterSets[this.prop]) {
				var prop = filterSets[this.prop][i];
				if((typeof prop=='string') && (prop.indexOf('#X#')!=-1)) {
					this.obj.style.filter = prop.replace(/#X#/g, val);
				} else	{
					this.obj.style.filter[filterSets[this.prop][i]] = val+(styleSuffixes[this.prop]?styleSuffixes[this.prop]:'');
				}
			}
		}
	};
	this.trace = function(txt) {
		if(this.debugBox)	this.debugBox.value = txt+"\n"+this.debugBox.value;
	}
	if(autostart)	this.start();
}
function getTime() {
	var dt = new Date();
	return  dt.getTime();
}

