/*!
 * jQuery Sprite Plugin v0.1
 *
 * Copyright 2011, Matt Robenolt
 * Licensed under the MIT license.
 * http://www.opensource.org/licenses/mit-license.php
 */

(function($){
    
    $.fn.sprite = function(options)
    {
        var config = {
            size: false, // [width, height]
            speed: 200, // delay between frames
            frames: 5,
            mode: 'once', // once, loop, or bounce
            columns: -1 // -1 is unlimited, used to wrap a wide sprite sheet into rows if necessary
            //onComplete: $.noop, // onComplete: function()
            //onStep: $.noop // onStep: function(e)
        },

        KEY = '__sprite__', // data key to detect if the sprite sheet is running or not
        _setTimeout = setTimeout; // shortcut to remove a few bytes when minified

        $.extend(config, options);
        
        function setBackgroundPosition(el, x, y)
        {
            el.style.backgroundPosition = x+'px '+y+'px';
        }
        
        !config.size && (config.size = [this.width(), this.height()]); // try and guess the width and height of the frame if not defined
        
        return this.each(function()
        {
            var t = this,
                step = 1, // start on the second frame, since we're manually setting it to the first frame.
                stepDirection = 1;
            
            // check if sprite is currently running, if so, bail out
            if($(t).data(KEY)) return;
            
            $(t).data(KEY, 1);
            
            // move the background to 0,0 to start
            setBackgroundPosition(t, 0, 0);
            _setTimeout(doStep, config.speed);
            
            function doStep()
            {
                // calculate current column and row to be in based on the current step
                var column = (config.columns > -1) ? step % config.columns : step,
                    row = (config.columns > -1) ? Math.floor(step / config.columns) : 0;
                
                setBackgroundPosition(t, -(column*config.size[0]), -(row*config.size[1]));
                
                typeof config.onStep === 'function' && config.onStep.apply(t, [{column: column, row: row, step: step}]);
                
                switch(config.mode)
                {
                    case 'loop':
                        // if the next step is the last frame, jump back to the beginning
                        if(++step === config.frames)
                        {
                            step = 0;
                        }
                    break;
                    case 'bounce':
                        // if the loop is currently reversed, and the next step is 0, bounce forward
                        if(stepDirection < 0)
                        {
                            if(--step === 0) stepDirection = 1;
                            break;
                        }
                        // if the loop is current going forward, and the next step is the last, reverse it.
                        if(++step === config.frames)
                        {
                            stepDirection = -1;
                            step-=2; // subtract two because the we need the frame before (config.frames-1)
                            break;
                        }
                    break;
                    case 'once':
                    default:
                        // if the next step is the last, trigger the onComplete callback
                        if(++step === config.frames)
                        {
                            typeof config.onComplete === 'function' && config.onComplete.apply(t);
                            $(t).removeData(KEY);
                            return;
                        }
                    break;
                }
                
                _setTimeout(doStep, config.speed);
            }
        });
    };
    
}(jQuery));