/*
 *  FAV race
 *
 *  A basic racing game in a favicon, using JavaScript, Canvas and data:URIs.
 *
 *  (c) January 2009
 *  Mathieu 'p01' Henri for Jung von Matt.
 *  http://www.p01.org - httm://www.jvm.de
 */

var faviconGame = new (function()
{
    Function.prototype.bind = function( what )
    {
    	var	_method = this;
    	return function(){ return _method.apply( what, arguments ) };
    }

	var h               = {},
	    ctx				= null,
	    favicon         = null,
		data            = {},
        game            =
        {
            state       :'',
    		stateBefore	:'',
    		update		:{},
    		now			:0,
    		then		:0,
    		warmup      :1500
        },
		keys            =
		{
		    up          :{},
		    down        :{},
		    mapping     :
            {
    			38      :'up',
    			87      :'up',
    			40      :'down',
    			83      :'down',
    			37      :'left',
    			65      :'left',
    			39      :'right',
    			68      :'right',
    			68      :'right',
    			78      :'fire',
    			13      :'select',

    			0       :'** dummy **'
    		}
		};

	/*
	 *	initialize
	 */
	window.addEventListener
	(
		'load',
		(function(/*onload*/)
		{
            //  create canvas
		    var canvas  = document.createElement('canvas');
		    canvas.width = canvas.height = 16;
			//	does this browser support canvas ?
			if( 'function'!=typeof(canvas.getContext) )
			{
			    return false;
			}
			//	get rendering context
			ctx	= canvas.getContext('2d');
			//	does this browser support canvas.toDataURL ?
			if( 'function'!=typeof(ctx.canvas.toDataURL) )
			{
			    return false;
			}
//document.body.appendChild(canvas);canvas.setAttribute('style','width:64px;height:64px;');

            //  get the handle of the favicon
            h.favicon   = document.getElementById('favicon');
            if( !h.favicon )
            {
                h.favicon   = document.createElement('link');
                h.favicon.setAttribute( 'id',    'favicon' );
                h.favicon.setAttribute( 'rel',   'shortcut icon' );
                h.favicon.setAttribute( 'type',  'image/png' );
                var head    = document.getElementsByTagName('head')[0];
                head.appendChild( h.favicon );
            }

			// keyboard
			for( var i in keys.mapping )
			{
				keys.down[ keys.mapping[i] ] = keys.up[ keys.mapping[i] ] = 0;
			}
			onkeyup = onkeydown = handleKeyEvents.bind(this);

			//	blur/focus = pause/resume
			onblur	= onfocus	= (function(event){ var event = event||window.event; if(game.state=='play'||game.state=='pause')setState( event.type.toLowerCase()=='blur'?'pause':game.stateBefore); }).bind(this);

			//	start with the 'title' state
			setState( 'title' );

		    //  get the handle of the sprites
		    h.sprites = document.createElement('img');
		    h.sprites.onload    = function(){ this.isReady = true; };
            h.sprites.src       = 'faviconGame.favRace.sprites.png?t='+new Date().getTime();

			// there we go
			main();

			return true;
		}).bind(this),
		false
	);


	/*
	 *	handleKeyEvents
	 */
	function handleKeyEvents( event )
	{
		var event	= event||window.event,
			type	= event.type.toLowerCase(),
			keyCode	= keys.mapping[event.keyCode]||'unknown';

		keys.down[keyCode]=type=='keydown'?1:0;
		keys.up[keyCode]=type=='keyup'?1:0;

		if( keyCode!='unknown' && event.preventDefault )
		{
		    event.preventDefault();
		}
	}


	/*
	 *	setState
	 */
	function setState( newState )
	{
		if( 'function'!==typeof(game.update[newState]) )
		{
		    throw new Error('state "'+ newState +'" not supported' );
		}

		game.then 		    = new Date().getTime();
		game.stateBefore	= game.state;
		game.state		    = newState;
	}


	/*
	 *	main
	 */
	function main()
	{
	    if( h.sprites.isReady )
	    {
    		game.now = new Date().getTime()-game.then;

    		//	clear
    		ctx.globalCompositeOperation = 'source-over';
    		ctx.globalAlpha = 1;
    		ctx.fillStyle   = '#000';
    		ctx.fillRect( 0,0,16,16 );

    		// actual update
    	    game.update[game.state]();

    		// set favicon
    		var newIcon = h.favicon.cloneNode(true);
    		newIcon.setAttribute('href',ctx.canvas.toDataURL());
    		h.favicon.parentNode.replaceChild(newIcon,h.favicon);
    		h.favicon=newIcon;

    		//	reset keyUp
    		for( var i in keys.mapping )
    		{
    			keys.up[keys.mapping[i]]=0;
    		}
		}

		//	update
		setTimeout( arguments.callee.bind(this), 50 );
	}


	/*
	 *	initializeGame
	 */
	function initializeGame()
	{
		//	reset data
		data.frame  = 0;
        data.road   = [4,4,4,4,4];
        data.px     = 5.5;
        data.ps     = 0;
        data.ph     = 12;

		//	play!
		setState('play');
	}


	/*
	 *	game.update.title
	 */
	game.update.title = function()
	{
        ctx.drawImage( h.sprites, 4,0,16,16, 0,0,16,16 );

        var x= Math.round(16-16*Math.min(1,game.now/game.warmup) +2*Math.cos( (3.14*game.now/1000)%6.283) );
        ctx.drawImage( h.sprites, 0,72,16,7,  x,  2,16,7 );
        ctx.drawImage( h.sprites, 0,80,16,5, -x, 10,16,5 );

		if( keys.up.fire && game.now>game.warmup )
		{
			initializeGame();
		}
	}


	/*
	 *	game.update.gameover
	 */
	game.update.gameover = function()
	{
		if( game.now<game.warmup*2 )
		{
		    var n = game.now/game.warmup/2;
    
            ctx.globalAlpha = 1-.67*n;

            drawRoad();
            ctx.drawImage( h.sprites, 0,64,5,8, Math.round(data.px), 9,5,8 );
            ctx.drawImage( h.sprites, 5,64,16,3, 0,0,16,3 );
    
            ctx.globalAlpha = 1;

            var x = Math.round( 32-32*n );
            var s = Math.min( 99999, data.frame>>3 );

		    ctx.drawImage( h.sprites, 16,80,10,6, x,5+Math.round( 2*Math.cos(n+x/30) ),10,6 );
            for( var i=0; s>0; i++ )
            {
                x-=4;
    		    ctx.drawImage( h.sprites, (s%10)*3,86,3,6, x,5+Math.round( 2*Math.cos(n+x/30) ),3,6 );
                s=Math.floor(s/10);
            }
		}
		else
	    {
            ctx.fillStyle = '#fff';
            ctx.fillRect( 0,0,16,16);
			setState('title');
		}
	}


	/*
	 *	game.update.pause
	 */
	game.update.pause = function()
	{
        ctx.globalAlpha = .67+.33*Math.cos( (game.now/game.warmup*2.5*3.14)%6.283 );

        drawRoad();
        ctx.drawImage( h.sprites, 0,64,5,8, Math.round(data.px), 9,5,8 );
        ctx.drawImage( h.sprites, 5,64,16,3, 0,0,16,3 );
        ctx.fillStyle = '#cf3';
        ctx.fillRect( 2,0,data.ph,1 );

        ctx.globalAlpha = 1;
        
        var x = Math.round( 4+12*Math.cos( (game.now/game.warmup*.5*3.14)%6.283 ) );
        var y = Math.round( Math.cos( (game.now/game.warmup*1.7*3.14)%6.283 ) )
        ctx.drawImage( h.sprites, 0,80,16,5, -x, 10-y,16,5 );
        ctx.drawImage( h.sprites, 0,72,16,7,  x,  2+y,16,7 );

	}

    /*
     * drawRoad
     */
    function drawRoad()
    {
        for( var i=0; i<5; i++ )
        {
            var slice = ((data.frame>>2)&7)*4
            ctx.drawImage( h.sprites, data.road[i], (32-i*4+slice)&31,16,4, 0,i*4+(data.frame&3)-4,16,4 );
        }
    }

	/*
	 *	game.update.play
	 */
	game.update.play = function()
	{
		var isStarting	= game.now<game.warmup;

        //  increase frame
        data.frame++;
        if( (data.frame&3)==0 )
        {
            var x = data.road[0];
            data.road.pop();
            x = Math.max( 0, Math.min( 8, x+(Math.random()>.5?-1:1) ) );
            data.road.unshift( x );
        }

        drawRoad();

		//	start transition
		if( isStarting )
		{
            var n = game.now/game.warmup;
            var x= Math.round(16*Math.min(1,n) );

            ctx.drawImage( h.sprites, 0,72,16,7,  x,  2,16,7 );
            ctx.drawImage( h.sprites, 0,80,16,5, -x, 10,16,5 );

            ctx.drawImage( h.sprites, 0,64,5,8, Math.round(data.px), 16-Math.round(8*n), 5,8 );

            ctx.drawImage( h.sprites, 5,64,16,3, 0,(n*2-2),16,3 );
            ctx.fillStyle = '#cf3';
            ctx.fillRect( 2,(n*2-2),data.ph,1 );
		}
		//	playing
		else
		{
            //  player logic
            if( data.px>0 && keys.down.left )
            {
                data.px-=.25;
            }
            if( data.px<11 && keys.down.right )
            {
                data.px+=.25;
            }
            var collide = ( data.px<7-data.road[2] || data.px<7-data.road[3] || data.px>12-data.road[2] || data.px>12-data.road[3] );

            //  display
            ctx.drawImage( h.sprites, 0,64,5,8, Math.round(data.px), 8,5,8 );

            if( collide )
            {
                data.ph-=.25;
                ctx.fillStyle = '#600';
                ctx.globalCompositeOperation = 'lighter';
                ctx.fillRect( 0,0,16,16 );
                ctx.globalCompositeOperation = 'source-over';
            }
            ctx.drawImage( h.sprites, 5,64,16,3, 0,0,16,3 );
            ctx.fillStyle = '#cf3';
            ctx.fillRect( 2,0,data.ph,1 );


            //  gameover?
            if( data.ph<=0 )
            {
                setState('gameover');
            }
		}
	}


})();

