var bokeh = new function()
{
    var _config     = {};
    _config.minSprites  = 16;
    _config.maxSprites  = 64;
    _config.minFps      = 20;
    _config.frameChunk  = 50;
    _config.canvasWidth = '';
    var _               = {};


	/*
	 *  Initialize the canvas and sprites for the animation.
	 */
    this.init = function(elem,attr)
    {
        //  create target canvas
        _.canvas = document.createElement('canvas');
        _.canvas.id = elem.id;
        _.canvas.width = elem.width;
        _.canvas.height = elem.height;
        if (attr != '') {_config.canvasWidth = attr};

		// replace original element with canvas
		var elemParent = elem.parentNode;
		elemParent.removeChild(elem);
		elemParent.appendChild(_.canvas);
		
		
		//  prepare the sprites
    	_.spritesCanvas	= document.createElement('canvas');
        var _steps              = 16;
        _.spritesCanvas.width   = 64;
        _.spritesCanvas.height  = _.spritesCanvas.width*_steps;
        var ctx = _.spritesCanvas.getContext('2d');

        var w_2 = _.spritesCanvas.width/2;
        ctx.save();
        for( var i=0; i<_steps; i++ )
        {
            var _gradient = ctx.createRadialGradient( w_2,w_2,w_2/2,w_2,w_2,w_2 );
            _gradient.addColorStop( .33+.5*(1-i/(_steps-1)), 'rgba(255,255,255,.2)' );
            _gradient.addColorStop( 1, 'rgba(255,255,255,0)' );
            ctx.fillStyle = _gradient;
            ctx.fillRect( 0, 0, w_2*2, w_2*2 );
            ctx.translate( 0, w_2*2 );
        }
        ctx.restore();
    }


    /*
     *  Start the animation/
     */
    this.start = function()
    {
        if( !_.inProgress )
        {
            _.frame     = 0;
            _.timeStart = _.timeLast  = Date.now();
            _.fps       = _config.minFps;
            _.sprites   = _.spritesBefore = (_config.minSprites+_config.maxSprites)>>1;

            _.inProgress = true;
            _update();
        }
    }


    /*
     *  Stop the animation/
     */
    this.stop = function()
    {
        _.inProgress = false;
    }


    /*
     *  Set the color of the sprites
     */
    this.setColor = function( color )
    {
        var ctx = _.spritesCanvas.getContext('2d');
        ctx.globalCompositeOperation = 'source-atop';
        ctx.fillStyle = color;
        ctx.fillRect( 0,0, _.spritesCanvas.width, _.spritesCanvas.height );
    }


	/*
	 *  Render the current frame of the animation.
	 *  @private
	 */
	function _update()
	{
		var now = Date.now();
	    _.frame++;
	    if( _.frame==_config.frameChunk )
	    {
		    _.fps       = _.frame*1000/( now-_.timeLast );
		    _.frame     = 0;
    		_.timeLast  = now;

            _.spritesBefore = Math.round(_.sprites );

            var n = _config.minSprites+(_config.maxSprites-_config.minSprites)*Math.max( 0, Math.min( 2, _.fps/_config.minFps ) )/2;
            _.sprites = _.sprites*.7+n*.3;
	    }


		//  clear the canvas
		var	W		= _.canvas.width >>3,
			H		= _.canvas.height >>3,
			S		= (W+H)/64,
			C		= Math.cos;
		
		if (_config.canvasWidth == 'full') {
		  _.canvas.width  = ( window.innerWidth || document.documentElement.offsetWidth );
		} else {
		  _.canvas.width	= _.canvas.width;
		};
		_.canvas.height	= _.canvas.height;
		
        var ctx = _.canvas.getContext('2d'),
			w	= _.spritesCanvas.width,
			h	= _.spritesCanvas.height;
        //  render the sprites
        var an      = now/30000,
		    step    = 16*Math.PI/_config.maxSprites;
		for( var i=0; i<_.sprites; i++ )
		{
		    ctx.globalAlpha = (i>=_.spritesBefore?_.frame/_config.frameChunk:1) //*(.8+.2*C(an*2.7));

			var r = .5+.5*C( an+C(an*1.1) );
			var x = W*(1+.6*C( an*1.1+2.3*C( an*.7 ))+.3*C( an*1.8-2.3*C( an*1.33 )) );
			var y = H*(1+.4*C( an*0.9-1.5*C( an*.4 ))+.5*C( an*.7+1.9*C( an*0.92 )) );
			var s = S+S*( 1+7*r );

			ctx.drawImage( _.spritesCanvas,0,w*(r*h/w>>0),w,w,(x-s)*7,(y-s)*7,s*14,s*14);
			an += step;
		}

		//  next frame
		if( _.inProgress )
		{
		    setTimeout( arguments.callee, 33 );
		}
	}
};