var Effects = new Object(); // effects namespace;


Effects.Effect = function() {
	this.init();
	this.chain = null;
}
Effects.Effect.prototype = {
	init: function() {
		var this_ = this;
		this._timeoutFunction = function() {
			this_.run();
		};
		this.reset();
	},
	run: function() {
		var timeout = this.processStep();
		if (timeout) 
			setTimeout(this._timeoutFunction, timeout);
		else if (this.chain)
		  this.chain();
	},
	processStep: function() {
		//process one step of the effect, return the next timeout. If returned value evaluates to false, stop
		
	},
	reset: function() {
		//reset all variables to 1st step. called by init, should be manually called before reapplying effect or included in last invocation of processStep()
	},
	
	setAfter : function(afterFunc) {
	   this.chain = afterFunc;
	},
    getAfter : function() {
        return this.chain;
    }
	
}


// effect will run for x number of steps or until processStep returns false
// interval is the millisecond timeout, can be 0
// processStep takes a single int parameter, currentStep
// speed is one of Effects.Speed members or any such function that takes 0<=x<=1 and returns 0<=y<=1, where f(0) = 0 and f(1) = 1. undefined will result in y=x
Effects.SteppedChangeEffect = function(steps, interval, speed) {
	// chain constructor
	Effects.Effect.call(this);
	this._maxSteps = steps;
	this._interval = interval;
	this._speed = speed || Effects.Speed.constant;
}

Effects.SteppedChangeEffect.prototype = new Effects.Effect();
Effects.SteppedChangeEffect.prototype.reset = function() {
	this._currentStep = 0;
}
// override to handle steps
Effects.SteppedChangeEffect.prototype.run = function() {
	if (this._currentStep >= this._maxSteps) 
	   return;
	   
	var ret =  this.processStep(this._currentStep);
	if (ret === false)
		return;
	
	this._currentStep++;
	if (this._currentStep < this._maxSteps)
    	setTimeout(this._timeoutFunction, this._interval);
	else 
	   if (this.chain)
	       this.chain();
}
Effects.SteppedChangeEffect.prototype.getCurrentStep = function() {
	return this._currentStep;
}
Effects.SteppedChangeEffect.prototype.getInterval = function() {
	return this._interval;
}
Effects.SteppedChangeEffect.prototype.getSteps = function() {
	return this._maxSteps;
}
Effects.SteppedChangeEffect.prototype.setSpeed = function(speed) {
    this._speed = speed || Effects.speed.constant;
}


Effects.UP      = 0;
Effects.DOWN    = 1;
Effects.LEFT    = 2;
Effects.RIGHT   = 3;
Effects.Horizontal = 0;
Effects.Vertical   = 1;

Effects.Speed = new Object();
//change speeds : pass in pct. complete (0<x<=1) and get back the percentage adjusted with a function
Effects.Speed.constant = function(x) {
    return x;
}
// change from slow to fast back to slow along a sine curve
Effects.Speed.sineFull = function(x) {
    return (Math.sin( Math.PI * (x - 0.5)) + 1) / 2;
}
// change from slow to fast
Effects.Speed.sineUp = function(x) {
    return Math.sin( Math.PI * (x - 1) / 2) + 1;
}
// change from fast to slow
Effects.Speed.sineDown = function(x) {
    return Math.sin( Math.PI * x / 2);
}


Effects.FadeEffect = function(steps, interval, speed) {
	Effects.SteppedChangeEffect.call(this, steps, interval, speed || Effects.Speed.constant);
}

Effects.FadeEffect.prototype = new Effects.SteppedChangeEffect();
Effects.FadeEffect.prototype.processStep = function(step) {
    var pct = this._speed((step + 1)/this.getSteps());
    var o = this.fadeFrom + (this.fadeTo - this.fadeFrom) * pct;

	this.element.style.opacity = "" + o;
	this.element.style.filter = "alpha(opacity=" + o * 100 + ")"; // internet explorer 

}
Effects.FadeEffect.prototype.setElement = function(element) {
	this.element = element;
}
Effects.FadeEffect.prototype.getElement = function() {
	return this.element;
}

Effects.FadeEffect.prototype.apply = function(element, fadeFrom, fadeTo) {
	this.setElement(element);
	this.fadeFrom = fadeFrom;
	this.fadeTo = fadeTo;

	this.reset();
	this.run();
}


Effects.ScrollEffect = function(steps, interval, speed) {
    Effects.SteppedChangeEffect.call(this, steps, interval, speed || Effects.Speed.sineDown);
}

Effects.ScrollEffect.prototype = new Effects.SteppedChangeEffect();

Effects.ScrollEffect.prototype.apply  = function(element, direction, distance) {
    this.setElement(element);
    this.setDistance(distance);
    this.setDirection(direction);
    this.reset();
    this.run();
}

Effects.ScrollEffect.prototype.setElement = function(element) {
    this.element = element;
}
Effects.ScrollEffect.prototype.setDirection = function(direction) {
    this.direction = direction;
}
Effects.ScrollEffect.prototype.setDistance = function(distance) {
    this.distance = distance;
}

Effects.ScrollEffect.prototype.getElement = function() {
    return this.element;
}
Effects.ScrollEffect.prototype.getDirection = function() {
    return this.direction;
}
Effects.ScrollEffect.prototype.getDistance = function() {
    return this.distance;
}

Effects.ScrollEffect.prototype.isVertical = function() {
    return this.getDirection() == Effects.UP || this.getDirection() == Effects.DOWN;
}

Effects.ScrollEffect.prototype.isPositive = function() {
    return this.getDirection() == Effects.DOWN || this.getDirection() == Effects.RIGHT;
}

Effects.ScrollEffect.prototype.processStep = function(step) {
    if (step == 0) {
        this.initialPosition = this.isVertical() ? this.getElement().scrollTop : this.getElement().scrollLeft;
    }

    var pctComplete = (step + 1) / this.getSteps();
    pctComplete = this._speed(pctComplete);
    var newPos = this.initialPosition + Math.round( (this.isPositive() ? 1 : -1) * this.getDistance() * pctComplete);
    
    if (this.isVertical()) {
        this.getElement().scrollTop = newPos;
    }
    else {
        this.getElement().scrollLeft = newPos;
    }
    //var dist = Math.sin( (step + 1) / this.getSteps)
}


Effects.MoveEffect = function(steps, interval, speed) {
    Effects.SteppedChangeEffect.call(this, steps, interval, speed || Effects.Speed.sineFull);
}

Effects.MoveEffect.prototype = new Effects.SteppedChangeEffect();
Effects.MoveEffect.prototype.setElement = function(element) {
    this.element = element;
}
Effects.MoveEffect.prototype.getElement = function() {
    return this.element;
}

Effects.MoveEffect.prototype.apply = function(element, fromLeft, fromTop, toLeft, toTop) {
    this.setElement(element);
    this.fromLeft = fromLeft;
    this.fromTop  = fromTop;
    this.toLeft   = toLeft;
    this.toTop    = toTop;
    this.reset();
    this.run();
}

Effects.MoveEffect.prototype.processStep = function(step) {
    var pct = this._speed((step + 1)/this.getSteps());
    var newLeft = this.fromLeft + Math.round((this.toLeft - this.fromLeft) * pct);
    var newTop = this.fromTop + Math.round((this.toTop - this.fromTop) * pct);
    
    this.getElement().style.left = newLeft + "px";
    this.getElement().style.top = newTop + "px";
}


function SampleEffect() {
	Effects.SteppedChangeEffect.call(this, 10, 500);
}

SampleEffect.prototype = new Effects.SteppedChangeEffect();
SampleEffect.prototype.processStep = function(step) {
	alert(this._speed(step));
}
