
Array.prototype.insertAt = function(obj,index)
{
	this.push(null);
	for(i = this.length-2;i>=index;i--)
	{
		this[i+1]= this[i];
	}
	this[index] = obj;
}
Array.prototype.removeAt = function (index)
{
	for(i=index; i < this.length;i++)
	{
		this[i] = this[i+1];
	}
	this.pop();
}

Array.prototype.contains = function(obj)
{
	for(i=0;i<this.length;i++)
	{
		if(obj == this[i])
			return true;
	}
	return false;
}

Array.prototype.indexOf = function(obj)
{
	for(i=0;i<this.length;i++)
	{
		if(obj == this[i])
			return i;
	}
	return false;

}

Array.prototype.remove = function(obj)
{
	
	if(this.contains(obj))
	{
	    var index = this.indexOf(obj);
		this.removeAt(index);
	}
}

var LaboDotnet= new Object();
LaboDotnet.Animations = new Object();

/*
*
*
* Base class of all usable animations (do nothing by itself, just compute the progress of the animation)
*
*/
LaboDotnet.Animations.BaseAnimation = function(startDate, lengthInMiliseconds,accelerationTime,decelerationTime)
{
    // Starting date of the animation
    this.start = startDate;
    
    // end of the acceleration (in proportion of the total animation time)
    this.accelerationEndOffset = accelerationTime / lengthInMiliseconds;
    
    // begin of the deceleration (in proportion of the total animation time)
    this.decelerationBeginOffset = 1.0 - (decelerationTime / lengthInMiliseconds);
    
    // length of the animation
    this.length = lengthInMiliseconds;
    
    // proportion of the time of uniform move
    this.normalProportion = this.decelerationBeginOffset - this.accelerationEndOffset;
    
    // speed max (speed of the uniform move
    this.vmax = 1/(1 - (this.accelerationEndOffset+(1-this.decelerationBeginOffset))/2);
    
    // value of the uniform acceleration
    this.accelerationValue = this.vmax / (this.accelerationEndOffset);
    
    // value of the uniform deceleration
    this.decelerationValue = this.vmax / (1- this.decelerationBeginOffset) ;

    // finish flag
    this.finished = false;
    
    // end of the animation
    if(startDate)
    {
    this.end = new Date(startDate.getFullYear()
                        ,startDate.getMonth()
                        ,startDate.getDate()
                        ,startDate.getHours()
                        ,startDate.getMinutes()
                        ,startDate.getSeconds()
                        ,startDate.getMilliseconds()+lengthInMiliseconds);
    }  
}

// provide a way to know the progress of the animation (0.0 to 1.0)
LaboDotnet.Animations.BaseAnimation.prototype.getProgress = function()
{
    var d = new Date();
    
    // current proportion of time
    var curOffset = (d - this.start) / this.length;
    var retval = 0;
    
    // we are in the acceleration period
    if(curOffset<=this.accelerationEndOffset)
    {
        // D(t) = a * t^2
         retval = this.accelerationValue*(curOffset*curOffset) /2;
    }   
    // we are in the uniform period (acceleration period computing is simplified)
    else if (curOffset<=this.decelerationBeginOffset)
    {
        retval =  + this.vmax * (curOffset - this.accelerationEndOffset + this.accelerationEndOffset/2);
    }
    // we are in the deceleration period
    else
    {
        retval =  this.vmax * (this.normalProportion+ this.accelerationEndOffset/2);
        
        // same as acceleration period but inversed
        retval += (1-retval) - (this.decelerationValue*((1-curOffset)*(1-curOffset)) /2);        
    }
    return retval;
}

// Has the animation ended?
LaboDotnet.Animations.BaseAnimation.prototype.hasEnded = function()
{
    var now = new Date();
    
    if(now > this.end)
    {
        return true;
    }
    return false;
};

// Has the animation started?
LaboDotnet.Animations.BaseAnimation.prototype.hasStarted = function()
{
    var now = new Date();
    
    if(now < this.start)
    {
        return false;
    }
    return true;
};

// Close the animation (and get the final value). to be overriden
LaboDotnet.Animations.BaseAnimation.prototype.finish = function(){this.finished = true;};

//Apply the animation (must be overriden)
LaboDotnet.Animations.BaseAnimation.prototype.doAnimate = function(){};


/*
*
*
* Animate a CSS numeric attribute (width, height, left, top...)
*
*
*/
LaboDotnet.Animations.CSSNumericAnimation = function(startDate, lengthInMiliseconds,accelerationTime,decelerationTime, elementId, styleProperty, unit, startValue, endValue)
{
    LaboDotnet.Animations.BaseAnimation.call(this,startDate, lengthInMiliseconds,accelerationTime,decelerationTime);
    if(elementId)
    {
        this.element = document.getElementById(elementId);
        this.styleProperty = styleProperty;
        this.unit = unit;
        this.startValue = startValue;
        this.endValue = endValue;
        this.totalOffset = endValue - startValue;
    }    
}
// Apply inheritance from Base Animation
LaboDotnet.Animations.CSSNumericAnimation.prototype = new LaboDotnet.Animations.BaseAnimation();

LaboDotnet.Animations.CSSNumericAnimation.prototype.doAnimate= function()
{
    if(!this.finished)
    {
        this.element.style[this.styleProperty] = (this.startValue + this.getProgress() * this.totalOffset)+this.unit;
    }
}
LaboDotnet.Animations.CSSNumericAnimation.prototype.finish = function()
{
    if(!this.finished)
    {
        this.element.style[this.styleProperty] = this.endValue+this.unit;
        this.finished = true;
    }
};

/*
*
*
* Custom animation (gives a progress parameter between 0.0 and 1.0 to the callback function)
*
*
*/
LaboDotnet.Animations.CustomAnimation = function(startDate, lengthInMiliseconds,accelerationTime,decelerationTime, progressCallback)
{
    LaboDotnet.Animations.BaseAnimation.call(this,startDate, lengthInMiliseconds,accelerationTime,decelerationTime);
    this.progressCallback = progressCallback;
};
LaboDotnet.Animations.CustomAnimation.prototype = new LaboDotnet.Animations.BaseAnimation();

LaboDotnet.Animations.CustomAnimation.prototype.doAnimate= function()
{
    if(!this.finished)
    {
        this.progressCallback( this.getProgress());
    }
}
LaboDotnet.Animations.CustomAnimation.prototype.finish = function()
{
    if(!this.finished)
    {
        this.progressCallback(1.0);
        this.finished = true;
    }
};

/*
*
*
* Engine manager (manage the execution loops)
*
*
*/
LaboDotnet.Animations.EngineManager = new Object();
LaboDotnet.Animations.EngineManager.Engines = new Array();
LaboDotnet.Animations.EngineManager.loop = function (name)
{
    LaboDotnet.Animations.EngineManager.Engines[name].loop();
    clearTimeout(LaboDotnet.Animations.EngineManager.Engines[name].intervalID);
    LaboDotnet.Animations.EngineManager.Engines[name].intervalID = setTimeout("LaboDotnet.Animations.EngineManager.loop('"+name+"')",LaboDotnet.Animations.EngineManager.Engines[name].step);
}

/*
*
*
* Animation engine (ordonances the animations)
*
*
*/
LaboDotnet.Animations.Engine=function(name,stepInMilliseconds)
{
	this.step = stepInMilliseconds;
	this.onLooping = function(){};
	this.name = name;
	this.animations = new Array();
	LaboDotnet.Animations.EngineManager.Engines[name] = this;
	
	this.loop = function()
    {
        this.onLooping();
        var newTick = new Date();
        var elapsed = newTick.getMilliseconds() - this.lastTick.getMilliseconds();
        var toRemoveIndices = new Array();
        
        for (i=0 ;i< this.animations.length;i++)
        {
            if(this.animations[i].hasEnded())
            {
                this.animations[i].finish();
                toRemoveIndices.push(i);
            }
            else if(this.animations[i].hasStarted())
            {
                this.animations[i].doAnimate();
            }
        }
        
        for (i= toRemoveIndices.length -1 ; i>=0; i--)
        {
            for (j = toRemoveIndices[i]; j<this.animations.length-1;j++)
            {
                this.animations[j] = this.animations[j+1];
            }
            this.animations.pop();
        }
        
        
        this.lastTick = newTick;
    }
    this.removeAnimation = function(toRemove)
    {
        for (i=0 ;i< this.animations.length;i++)
        {
            if(toRemove == this.animations[i])
            {
                for(j=i+1;j<this.animations.length;j++)
                {
                    this.animations[j-1]= this.animations[j];
                }
                this.animations.pop();
                break;
            }
        }
    }
    
    // start the engine
    this.start = function()
    {
	    this.lastTick = new Date();
	    this.intervalID = setTimeout("LaboDotnet.Animations.EngineManager.loop('"+this.name+"')",this.step);
    }
    // stop the engine
    this.stop = function()
    {
        clearTimeout(this.intervalID);
    }
}



