/* $Id: animation.js 163 2010-08-10 01:34:45Z victor $ */


/*
 *  Get a callback (to 'Advance') on a given fps
*/
var tooDeeFPSTimer = function(world,fps)
{
    if( !world ) // just making a prototype
        return;
    
    this.world = world;
    this.fps = fps;
    this.frozen = false;
    
    tooDeeEventDelegator.apply(this);
}


tooDeeMixin(tooDeeFPSTimer,tooDeeEventDelegator,
{    
    Reset: function()
    {
        // override this derivations
    },

    Advance: function()
    {
        // override this derivations        
    },
    
    Freeze: function(flag)
    {
        this.frozen = flag;
        
        if( flag )
        {
            this.world.Unbind('preStep',this.regToken);
        }
        else
        {
            this.Reset();
            this.regToken = this.world.Bind('preStep', this.Advance, this, this.world.FPFPS(this.fps) );
        }
    },
    
    Destroy: function()
    {
        this.world.Unbind('preStep',this.regToken);
        tooDeeEventDelegator.prototype.Destroy.apply(this);
        this.world = null;
    }
});

/*
 
    To animate an action or paint frames you start
    by creating a controller:
    
        var animator = new tooDeeAnimation( world, numStepsOrFrames, fps  )
    
    Create a callback:
    
        function perFrameCallback( animator )
        {
            ...
        }

    Then register the callback:
    
        this.scroller.bind( 'advance', this.perFrameCallback, this );
        
    You can register as many of these callbacks as you like.
    
    If you are doing frame animation, you will not store the actual frames
    with the animator, but you can use the animator to figure out the 'current'
    frame in your callback:

        var myFrames = [ ... ];
        
        function perFrameCallback( )
        {
            var currentFrame = animator.CurrentFrame( myFrames );
            
            ...
        }
    
    You can register another callback (or several of them) for when frame
    cycles are complete:
    
        function perCycleCallback( )
        {
            ...
        }
        
        animator.bind( 'cycle', this.perCycleCallback, this, 2 );
        
    The numeric parameter is the number of times to cycle before invoking
    the callback.
 
*/

// one of these per figure (not per shape)
var tooDeeAnimation = function(world,numFrames,fps)
{
    this.numFrames = numFrames;
    this.currentFrameNum = 0;
    
    tooDeeFPSTimer.apply(this, [world,fps] );
}


tooDeeMixin(tooDeeAnimation,tooDeeFPSTimer,
{
    
    Reset: function()
    {
        this.currentFrameNum = 0;        
    },
    
    CurrentFrame: function(frames)
    {
        return frames[this.currentFrameNum];
    },

    Advance: function()
    {
        if( this.frozen )
        {
            throw { x: 'Calling on frozen animation (' + this.forSprite + ')' };
        }
        
        //
        // sry, because of the way the tool emit the deltas
        // you start at frame 1 (not 0)
        //
        this.currentFrameNum = (this.currentFrameNum + 1) % this.numFrames;
        
        if( !this.currentFrameNum )
            this.Trigger('cycle')
 
        this.Trigger('advance');       
    }
});

function tooDeeLineFrameAnimation( options )
{
    var defaults = {
        looping: false,
        showIfFrozen: true,
        scale: 1,
        center: { x: 1, y: 1 },
        figureName: 'default',
        visible: true,
        stationary: false,
        autoStart: true
    };
    tooDeeSprite.apply( this, [ tooDeeOptMix( {}, defaults, options ) ] );
    
    var code = localStorage['figure.' + this.figureName];
    this.frames = eval( code );    
    this.frames.position = this.center;
    var numFrames = this.frames.NumFrames(),
        worldScale = 1/this.world.config.scaling.x;
        
    if( this.scale != 1 )
    {
        var scale = 1/this.scale;
        for( var i = 0; i < numFrames; i++ )
        {
            var F = this.frames.frames[i];
            var fLen = F.length;
            for( var n = 0; n < fLen; n++ )
            {
                var P = F[n].p;
                var pLen = P.length;
                for( var p = 0; p < pLen; p++ )
                {
                    P[p] *= scale;
                }
                F[n].w *= worldScale;
            }
            
        }
    }

    // this has to be AFTER you set the
    // .position and scale everything
    this.frameBounds = this.frames.CalculateBounds();
    this.dimensions = this.frameBounds[0];
    
    this.animator = new tooDeeAnimation(this.world,numFrames,this.frames.fps);
    this.animator.Freeze( !this.autoStart );
    this.desc = { frame: 0, line: 0 };
    this.lastFrame = numFrames - 1;
    
    this.animator.Bind( 'advance', this._updateDimensions, this );
    
    if( !this.looping )
    {
        // tell the animator to freeze itself after 1 iteration
        this.animator.Bind( 'cycle', this.animator.Freeze, this.animator, 1, [true] );
        this.animator.Bind( 'cycle', this._updateDimensions, this );
    }
}

tooDeeMixin(tooDeeLineFrameAnimation,tooDeeSprite,
{
    dbg: 'tooDeeLineFrameAnimation',
    
    Draw: function()
    {
        if( this.animator.frozen )
        {
            if( !this.showIfFrozen )
                return;
            this.desc.frame = this.lastFrame;
        }
        else
        {
            this.desc.frame = this.animator.currentFrameNum;
        }
        var ctx = this.GetContext();
        this.frames.Draw( ctx, this.desc, false );
    },
    
    Destroy: function()
    {
        this.animator.Destroy();
        tooDeeSprite.prototype.Destroy.apply(this);
        this.animator = null;        
    },
    
    _updateDimensions: function()
    {
        var fnum = this.animator.frozen ? this.lastFrame : this.animator.currentFrameNum;
        this.dimensions = this.frameBounds[fnum];
    }
});

function tooDeeFader( options )
{
    var defaults = {
        dec: false,
        inc: false,
        min: 0.0,
        max: 1.0,
        steps: 20,
        fps: 6
    };

    tooDeeOptMix(this,defaults,options || {});

    tooDeeFPSTimer.apply( this, [ this.world, this.fps ] );

    this.Freeze( !(this.dec || this.inc) );

}

tooDeeMixin(tooDeeFader, tooDeeFPSTimer,
{
    dbg: 'tooDeeFader',
    
    Reset: function()
    {
        this.rate = (this.max - this.min) / this.steps;
        this.value = this.dec ? this.max : this.min;
    },
    
    SetAlphaTarget: function(obj)
    {
        this.obj = obj;
        this.oldDraw = obj.Draw;
        var me = this;
        obj.Draw = function()
        {
             var alpha = me.value;
             if( alpha > 0.01 )
             {
                if( me.frozen )
                {
                    me.oldDraw.apply(obj)
                }
                else
                {
                    var ctx = obj.GetContext()
                    ctx.save();
                    ctx.globalAlpha = alpha;
                    me.oldDraw.apply(obj)
                    ctx.restore();
                }
             }
             
        }
    },
    
    Destroy: function()
    {
        if( this.obj )
        {
            if( this.obj.hasOwnProperty('Draw') )
                delete this.obj.Draw;
            this.obj = null;
        }
        tooDeeFPSTimer.prototype.Destroy.apply(this);
    },
    
    Advance: function()
    {
        if( this.inc )
        {
            this.value += this.rate;
            if( this.dec || this.value >= this.max )
            {
                this.value = this.max;
                this.inc = false;
                this.Trigger('max');
                if( !this.dec )
                    this.Freeze(true);
            }
        }
        
        if( this.dec )
        {
            this.value -= this.rate;
            if( this.inc || this.value <= this.min )
            {
                this.value = this.min;
                this.dec = false;
                this.Trigger( 'min' );
                if( !this.inc )
                    this.Freeze(true);
            }
        }
        
        return this.value;
    },
    
    CurrentValue: function()
    {
        return this.value;
    }
});

