
function derive(aChild, aParent)
{
    aChild.prototype = new aParent();
    aChild.prototype.constructor = aChild;
}

function pp(o)
{
    var ret = "";
    for(var lkey in o) ret = ret + lkey + ": " + o[lkey] + ", \n";
    return ret;
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

function AnimationDriver(aFps)
{
    this._animations = [];
    this._fps = aFps || 25;
    this.reset();
    return this;
}

AnimationDriver.prototype.reset = function()
{
    this.pause();
    this._frame = 0;
    this._update();
}

AnimationDriver.prototype.start = function()
{
    this.reset();
    this.resume();
}

AnimationDriver.prototype.pause = function()
{
    if(this._timer != null)
    {
        clearInterval(this._timer);
        this._timer = null;
    }
}

AnimationDriver.prototype.resume = function()
{
    var lThis = this;

    if(this._timer)
    {
        this.pause();
    }

    this._timer = setInterval(function() { 
        lThis._flip();
    }, 1000 / this._fps);
}


AnimationDriver.prototype.addAnimation = function(aAnimation)
{
    this._animations.push(aAnimation);
}

AnimationDriver.prototype._update = function(aFrame)
{
    aFrame = aFrame || this._frame;

    for(var lIdx=0; lIdx<this._animations.length; lIdx++)
    {
        this._animations[lIdx].showFrame(aFrame);
    }
}

AnimationDriver.prototype._flip = function()
{
    this._frame = this._frame + 1;
    this._update();
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

function Animation()
{
}

Animation.prototype.showFrame = function(aFrame)
{
    throw "Abstract.";
}

Animation.prototype.length = function()
{
    throw "Abstract.";
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

function AnimateConcurrently()
{
    this._animations = [];
    this._length = 0;
    return this;
}

derive(AnimateConcurrently, Animation);

AnimateConcurrently.prototype.addAnimation = function(aAnimation)
{
    this._animations.push(aAnimation);
    if(aAnimation.length() > this._length)
    {
        this._length = aAnimation.length();
    }
}

AnimateConcurrently.prototype.length = function()
{
    return this._length;
}

AnimateConcurrently.prototype.showFrame = function(aFrame)
{
    for(var lIdx=0; lIdx<this._animations.length; lIdx++)
    {
        this._animations[lIdx].showFrame(aFrame);
    }

}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

function AnimateSequence()
{
    this._segments = [ { _frame: 0, _animation: null } ];
    this._seekToSegment(0);
    return this;
}

derive(AnimateSequence, Animation);

AnimateSequence.prototype.length = function()
{
    var lLastSeg = this._segments[this._segments.length-1];

    if(lLastSeg._animation)
    {
        return lLastSeg._frame + lLastSeg._animation.length();
    } else
    {
        return lLastSeg._frame;
    }
}

AnimateSequence.prototype._seekToSegment = function(aSegmentIndex)
{
    this._curSegmentIndex = aSegmentIndex;
    this._curSegment = this._segments[aSegmentIndex];
    this._curAnimation = this._segments[aSegmentIndex]._animation;
    this._curOfs = this._segments[aSegmentIndex]._frame;
    
    if(aSegmentIndex+1<this._segments.length)
    {
        this._segmentEnd = this._segments[aSegmentIndex+1]._frame;
    } else
    {
        this._segmentEnd = -1;
    }
}

AnimateSequence.prototype.addAnimation = function(aAnimation, aLength)
{
    var lCurSegment = this._segments[this._segments.length-1];

    aLength = aLength || aAnimation.length();
    lCurSegment._animation = aAnimation;

    if(aLength>=0)
    {
        this._segments.push( { _frame: lCurSegment._frame + aLength, _animation: null } );
    }

    this._seekToSegment(0);
}

AnimateSequence.prototype.showFrame = function(aFrame)
{
    if(aFrame < this._curOfs)
    {
        this._seekToSegment(0);
    }

    while(this._segmentEnd>=0 && aFrame>=this._segmentEnd)
    {
        this._seekToSegment(this._curSegmentIndex+1);
    }
            
    if(this._curAnimation)
    {
        this._curAnimation.showFrame(aFrame - this._curOfs);
    }
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

function AnimateInterpolate(aLength, aInterpolations)
{
    this._length = aLength;
    this._interpolations = aInterpolations;
    return this;
}

AnimateInterpolate.ObjectAttribute = function(aObj, aAttr, aMin, aMax, aMethod)
{
    this._object = aObj;
    this._attr = aAttr;
    this._min = aMin;
    this._max = aMax;
    this._method = aMethod || AnimateInterpolate.Linear;
}

AnimateInterpolate.ObjectAttribute.prototype.apply = function(aRelative)
{
    var lApplyVal = this._method(this._min, this._max, aRelative);
    this._object[this._attr] = lApplyVal; //setProperty(this._attr, lApplyVal);
}

AnimateInterpolate.Linear = function(aMin, aMax, aRelative)
{
    return aMin + (aMax - aMin) * aRelative;
}

AnimateInterpolate.Select = function(aMin, aMax, aRelative)
{
    return aRelative > 0.5 ? aMax : aMin;
}

derive(AnimateInterpolate, Animation);

AnimateInterpolate.prototype.length = function()
{
    return this._length;
}

AnimateInterpolate.prototype.showFrame = function(aFrame)
 {
    var lRel = (aFrame<=this._length-1) ? (aFrame / (this._length-1)) : 1;

    for(var lIdx=0; lIdx<this._interpolations.length; lIdx++)
    {
        this._interpolations[lIdx].apply(lRel);
    }
}

