/*
 *  Defender of the favicon
 *
 *  A thrilling retro shoot in 16x16 pixels using JavaScript, Canvas and data:URIs.
 *
 *  (c) July 2008 - February 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
        },
	    images          =
	    {
        	city    :'city.png',
        	player  :'player.png',
        	digits  :'digits.png',
        	sprites :'sprites.png',
        	title   :'title.png'
	    },
		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;
			}

            //  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
            for( k in images )
            {
    		    h[k] = document.createElement('img');
    		    h[k].onload    = function(){ this.isReady = true; };
                h[k].src       = images[k];
            }

			// 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()
	{
	    var isReady = false;
	    if( !data.isReady )
	    {
	        data.isReady    = true;
            for( k in images )
            {
                if( !h[k].isReady )
                    data.isReady    = false;
            }
	    }
	    else
	    {
    		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 );
	}

	/*
	 *	initializeWave
	 */
	function initializeWave( newWave )
	{
		data.goLeft			= true;
		data.speed			= 0;
		data.fireDelay		= 0;
		data.fireRate		= 3;
		data.enemies		= [];
		data.humanoids		= [];
		data.frame			= 0;


		data.wave	= newWave||1;
		if( data.wave==1 )
		{
			data.score	= 0;
		}

		//	reset player
		data.x		= 0;
		data.y		= 8;
		data.shield	= 16;

		//	enemies
		data.enemies = [];
		var len = data.wave+10;
		for( var i=0; i<len; i++ )
		{
			data.enemies[ i ] =
			{
				x:Math.random()*255&255,
				y:-8,
				type:(i<10?0:(1+(i&1))),	// 10 Landers then it's half Baiter half Bomber
				isAlive:true
			};
		}

		//	humanoids
		for( var i=0; i<10; i++ )
		{
			data.humanoids[ i ] =
			{
				x:Math.random()*255&255,
				y:Math.random()*13&15,
				isGrabbed:false
			};
		}

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

	/*
	 *	game.update.title
	 */
	game.update.title = function()
	{
		ctx.drawImage( h.title, 0,0, 55,16, Math.round(39*Math.cos((game.now/1337)%6.283)-16 ),0, 55,16  );

		if( keys.up.fire && game.now>500 )
		{
			initializeWave(1);
		}
	}
	/*
	 *	gameover
	 */
	game.update.gameover = function()
	{
		// ...
		if( game.now<game.warmup )
		{
			game.now = game.warmup-game.now;
			game.update.play();

			ctx.globalCompositeOperation	= 'lighter';
			ctx.fillStyle					= '#f00';
			ctx.globalAlpha					= game.now/game.warmup;
			ctx.fillRect( 0,0,16,16 );
			ctx.globalCompositeOperation	= 'source-over';
			ctx.globalAlpha					= 1;

			game.now = game.warmup-game.now;
		}
		else
		{
			var yOfs = -Math.round(4*Math.sin( ((game.now-game.warmup)/241)%6.283 ));
			ctx.drawImage( h.digits, 0,(game.state=='gameover'?10:5),16,5, 0, yOfs+2,16,5 );
			ctx.globalAlpha= game.now&256?1:.5;
			ctx.drawImage( h.digits, Math.floor(data.wave/10%10)*5,0,5,5, 2, yOfs+9,5,5 );
			ctx.drawImage( h.digits, Math.floor(data.wave%10)*5,0,5,5, 9, yOfs+9,5,5 );
			ctx.globalAlpha= 1;
		}


		if( keys.up.fire && game.now>2000 )
		{
			setState('title');
		}
	}

	/*
	 *	game.update.pause
	 */
	game.update.pause = function()
	{
		ctx.drawImage( h.digits, 0,5,16,5, 0, 2,16,5 );

		ctx.globalAlpha= game.now&256?1:.5;
		ctx.drawImage( h.digits, Math.floor(data.wave/10%10)*5,0,5,5, 2, 9,5,5 );
		ctx.drawImage( h.digits, Math.floor(data.wave%10)*5,0,5,5, 9, 9,5,5 );
		ctx.globalAlpha= 1;
	}

	/*
	 *	game.update.play
	 */
	game.update.play = function()
	{
		var isStarting	= game.now<game.warmup;
		var	isTouched	= false;
		var	yOfs = isStarting?Math.round((game.stateBefore=='play'?48:32)*Math.cos(Math.PI/2*game.now/game.warmup)):0;

		if( !isStarting && data.shield )
		{
			// update player's position
			data.speed		= Math.max( -3, Math.min( 3, data.speed-(!data.speed?0:data.speed/Math.abs(data.speed)/4)+keys.down.left-keys.down.right ) );
			data.x			= (data.x+256-Math.round(data.speed))&255;
			data.goLeft		= data.speed?data.speed>0:data.goLeft;
			data.y			= Math.max( 0, Math.min( 14, data.y+keys.down.down-keys.down.up ) );
		}
		var	xxPlayer	= Math.round(5+data.speed*4/3);


		//	draw city
		ctx.drawImage( h.city, -((data.x+16384)&255), yOfs-16 );

		//	start transition
		if( isStarting )
		{
			ctx.drawImage( h.digits, 0,(game.state=='gameover'?10:5),16,5, 0, yOfs-(game.state=='gameover'?48:32)+2,16,5 );
			ctx.globalAlpha= game.now&256?1:.5;
			ctx.drawImage( h.digits, Math.floor(data.wave/10%10)*5,0,5,5, 2, yOfs-(game.state=='gameover'?48:32)+9,5,5 );
			ctx.drawImage( h.digits, Math.floor(data.wave%10)*5,0,5,5, 9, yOfs-(game.state=='gameover'?48:32)+9,5,5 );
			ctx.globalAlpha= 1;
		}
		//	playing
		else
		{
			//	fire
			if( data.fireDelay )
			{
				data.fireDelay--;
			}
			else if( keys.up.fire && data.shield )
			{
				data.fireDelay = data.fireRate;
				var fireX	= ([0,Math.round(5+data.speed+3),16]).splice(data.goLeft?0:1,2);

				ctx.fillStyle = (['#ff0','#0f0','#0ff'])[data.frame%3];
				ctx.fillRect( fireX[0], data.y+1, fireX[1]-fireX[0], 1  );
			}

			//	enemies
			var	enemiesAlive = 0;
			for( var i=0, enemy; enemy=data.enemies[i]; i++ )
			{
				if( !enemy.isAlive )
				{
					continue;
				}

				enemiesAlive++;

				var iCanHasThrust = Math.random()*4&3;
				//	y
				if( iCanHasThrust==0 )
				{
					var s = data.y-enemy.y+Math.random()*2-1
					//	landers ?
					if( !enemy.type )
					{
						if( data.humanoids[i].isGrabbed )
						{
							//	abduct humanoid
							s = -1;
						}
						else
						{
							s = enemy.x==data.humanoids[i].x?1:Math.random()-.5;
						}
					}
					if( (s<0 && enemy.y>(enemy.type?0:-5)) || (s>0 && enemy.y<(enemy.type?13:11)) )
					{
						enemy.y += s/Math.abs(s);
						if( !enemy.type )
						{
							if( enemy.y==11 && enemy.x==data.humanoids[i].x && data.humanoids[i].y==14 )
							{
								data.humanoids[i].isGrabbed = true;
							}
							else if( enemy.y==-5 && data.humanoids[i].isGrabbed )
							{
								data.humanoids[i].y 			= 16;
								data.humanoids[i].isGrabbed	= false;
							}
						}
					}
				}
				//	x
				else if( iCanHasThrust==2 )
				{
					var s = (enemy.type?data.x+Math.random()-.5:data.humanoids[i].x+Math.random()-.5)-enemy.x;
					if( s )
					{
						enemy.x = (enemy.x+256+s/Math.abs(s))&255;
					}
				}

				//	lander grabbing a humanoid ?
				if( !enemy.type && data.humanoids[i].isGrabbed )
				{
					data.humanoids[i].x = enemy.x;
					data.humanoids[i].y = enemy.y+3;
				}

				//	draw enemy
				var xx = (Math.round(256+16+enemy.x-data.x)&255)-8;
				ctx.drawImage( h.sprites, 4*((i+data.frame)&1),enemy.type*4,4,3, xx-2, Math.round(enemy.y)+yOfs,4,3  );


				//	aligned with the player ?
				if( data.shield && Math.abs(enemy.y-data.y)<(enemy.type?3:2) )
				{
					//	shoot ?
					if( data.fireDelay==data.fireRate && xx-2<fireX[1] && xx+2>fireX[0] )
					{
						enemy.isAlive = false;
						if( !enemy.type && data.humanoids[i].isGrabbed )
						{
							data.humanoids[i].isGrabbed = false;
						}
					}
					//	crash on player ?
					else if( xx-2+(enemy.type&1)<xxPlayer+5 && xx+2-(enemy.type&1)>xxPlayer )
					{
						isTouched = true;
					}
				}
			}

			var	lostHumanoids = 0;
			//	humanoids' "logic"
			for( var i=0, humanoid; humanoid=data.humanoids[i]; i++ )
			{
				//	abducted ?
				if( humanoid.y==16 )
				{
					humanoid.x = Math.random()*255&255;	// toss the lander around
					lostHumanoids++;
				}
				//	free fall
				else if( !humanoid.isGrabbed && humanoid.y<14 )
				{
					humanoid.y++;
				}
			}
		}




		//	draw humanoids
		for( var i=0, humanoid; humanoid=data.humanoids[i]; i++ )
		{
			var xx = (Math.round(256+16+humanoid.x-data.x)&255)-8;
			ctx.drawImage( h.sprites, (i+data.frame)&1,12,1,2, xx-(humanoid.isGrabbed?(data.frame+i)&1:0), Math.round(humanoid.y)+yOfs,1,2  );
		}

		//	draw player
		if( data.shield )
		{
			ctx.drawImage( h.player, 0,(data.goLeft?16:0)+Math.round(Math.abs(data.speed))*4, 6,4, xxPlayer,data.y-1+yOfs, 6,4 );
			if( isTouched )
			{
				ctx.globalCompositeOperation	= 'lighter';
				ctx.fillStyle					= '#f00';
				ctx.fillRect( 0,0,16,16 );
				ctx.globalCompositeOperation	= 'source-over';
				data.shield--;
				if( lostHumanoids==10 || !data.shield )
				{
					setState('gameover');
				}
			}

			//	no more enemies ? -> next wave
			if( !isStarting && !enemiesAlive )
			{
				initializeWave( data.wave+1 );
			}
		}
	}

})();

