// NEST OF BEES - a Javascript and Box2D tech demo
// by brandon flowers / headwinds.net
// inspired by the work of http://box2d-js.sourceforge.net/#about
// and an episode of the "Deadliest Warrior" featuring the Ming Warrior vs. Musketeer 

////////////////////////////////////////////////////////////////////// VARIABLES

var canvas;
var stage;
var graphicsReady = false;

// constant
var PIXELS_IN_METER = 30; // scale factor having 1 would be a 100 pixels to 1 meter relationsip
// conversion mypixels * 2 * PIXELS_IN_METER;

var worldWidthPixels = 940;
var worldHeightPixels = 300;

var worldWidthMeters = worldWidthPixels * PIXELS_IN_METER;
var worldHeightMeters = worldHeightPixels * PIXELS_IN_METER;

var warrior;		//the actual warrior
var alive = true;	// mark the warrior alive at the start

var DIFFICULTY = 2;			//how fast the game gets mor difficult
var TERRACOTTA_TIME = 40;	//aprox tick count untill a new terracotta defender gets introduced

var stepsUntilNextArrow = 500;
var stepCounter = 0;			//ticks between arrows

var nestReadyAfter = 3;			// destroy 3 warriors and earn the ability to fire the special nest of bees		
var terracottaDestroyedCounter = 0;	
var nestOfBeesReady = false;

var shootHeld;			//is the user holding a shoot command
var lfHeld;				//is the user holding a turn left command
var rtHeld;				//is the user holding a turn right command
var fwdHeld;			//is the user holding a forward command

var timeToArrow;		//difficulty adjusted version of TERRACOTTA_TIME
var nextArrow;			//ticks left untill a new space rock arrives
var arrowNotched = true;		//at the start the ming warrior has 1 arrow notched and must wait stepsUntilNextArrow

var arrowQuiver = [];		//arrow belt - arrows in quiver
var arrowStream = [];		//arrow stream - arrows in the air 

var arrowBodies = [];
var arrowCostumes = [];
var bowRotation = 5;

var mingHealth = 10; // ming can get hit 10 times before game over

var messageField;		//Message display field
var messageFieldBees;	//Message Bees display field
var scoreField;			//score Field
var score = 0; 
var scoreInc = 10;

var terracottaWarriorsList = [];
var terracottaWarriorsCostumeList = [];

var world = createWorld();
var ctx;
var canvasWidth;
var canvasHeight;
var canvasTop;
var canvasLeft;
var terracottaWarriorsOnGround = false; // they start in the air and drop to the ground

var turnOnDrawingPhysics = false; // a flag to turn on or off the visual drawing of the physics body so that you see the polygons or circles  
var turnOnDrawingCostumes = true; 
var gameOn = false;
var gameOver = false;

var worldTimer; // a timer ID that is set and can be cleared

canvas = document.getElementById("nest");

////////////////////////////////////////////////////////////////////// KEYBOARD

var KEYCODE_SPACE = 32;		
var KEYCODE_UP = 38;		
var KEYCODE_LEFT = 37;		
var KEYCODE_RIGHT = 39;		
var KEYCODE_W = 87;			
var KEYCODE_A = 65;			
var KEYCODE_D = 68;			
var KEYCODE_DOWN = 40;
var KEYCODE_SHIFT = 16;
var KEYCODE_B = 66;

var shootHeld;			//is the user holding a shoot command
var lfHeld;				//is the user holding a turn left command
var rtHeld;				//is the user holding a turn right command
var fwdHeld;			//is the user holding a forward command

var maxBowRotationUp = 70;
var maxBowRotationDown = -50;

//allow for WASD and arrow control scheme
function handleKeyDown(e) {
	//cross browser issues exist
	if(!e){ var e = window.event; }
	
	switch(e.keyCode) {
		case KEYCODE_SPACE:	shootHeld = true; break;
		case KEYCODE_A:;
		case KEYCODE_LEFT:	lfHeld = true; break;
		case KEYCODE_D:;
		case KEYCODE_RIGHT: rtHeld = true; break;
		case KEYCODE_W:;
		case KEYCODE_UP:	fwdHeld = true; break;
		case KEYCODE_DOWN:	fwdHeld = true; break;
	}
}

function handleKeyUp(e) {
	//cross browser issues exist
	if(!e){ var e = window.event; }
	
	//////console.log("hey key: " + e.keyCode);
	
	switch(e.keyCode) {
		case KEYCODE_SPACE:	
		{
			if (gameOn) jump();
			break;
		}
		case KEYCODE_SHIFT:	
		{
			if (gameOn) fireArrow();
			break;
		}
		case KEYCODE_A:;
		case KEYCODE_LEFT:	lfHeld = false; break;
		case KEYCODE_D:;
		case KEYCODE_RIGHT: rtHeld = false; break;
		case KEYCODE_W:;
		case KEYCODE_UP:
		{	
			if ( bowRotation < maxBowRotationUp ) bowRotation += 10;
			break;
		}
		case KEYCODE_DOWN:
		{	
			if ( bowRotation > maxBowRotationDown ) bowRotation -= 10;
			break;
		}
		case KEYCODE_B:
		{
			if (gameOn) fireNestOfBees();
			break;
		}
	}
	
}


function registerKeys()
{
	//register key functions
	document.onkeydown = handleKeyDown;
	document.onkeyup = handleKeyUp;
}

////////////////////////////////////////////////////////////////////// DRAWING ON CANVAS


// each time the timer calls step, we redraw the entire world with this function 
function drawWorld(world, context) {
	
	
	// has anything collided in our world?
		
	for (var c = world.m_contactList; c; c = c.m_next) 
	{
	
		var contact1 = c.m_node1.contact;
		var contact2 = c.m_node2.contact;
	
		//
		var body1 = c.m_shape1.m_body;
		var body2 = c.m_shape2.m_body;
		
		if ( body1.GetUserData() != null &&  body2.GetUserData() != null) 
		{
			
			
			var body1Name = body1.GetUserData().name;
			var body1Type =  body1.GetUserData().bodyType;
			//
			var body2Name = body2.GetUserData().name;
			var body2Type =  body2.GetUserData().bodyType;
			
			//////console.log("Collision between body1Name: " + body1Name + " and body2Name: " + body2Name );
			//////console.log("------------------------------------------------------------------------");		
	
			if ( body1Name == "ground" && body2Type == "arrow") 
			{
				world.DestroyBody(body2);
			}
			
			if ( body1Name == "ground" && body2Type == "rock") 
			{
				world.DestroyBody(body2);
			}
			
			if ( body1Name == "ground" && body2Type == "bee") 
			{
				world.DestroyBody(body2);
			}
			
			
			if ( body1Name == "ground" && body2Name == "ming") 
			{
				// the ming warrior is on the ground again so is allowed to jump -- see jump()
				body2.GetUserData().airborne = false;
			}
			
			
			
			if (  (body1Name == "ming" && body2Type == "terracotta") || (body1Name == "greatWall" && body2Type == "terracotta") ) 
			{
				
				mingHealth--;
				
				//////console.log('body mingHealth: ' + mingHealth );
			}
			
			
			if ( (body1Type == "terracotta" && body2Type == "arrow") || (body1Type == "arrow" && body2Type == "terracotta") )
			{			
				
				//////console.log("Collision between body1Type: " + body1Type + " and body2: " + body2Name );
				
				for (var terracottaCounter = 0; terracottaCounter < terracottaWarriorsList.length; terracottaCounter++)
				{
					terracottaName = "terracotta" + terracottaCounter;
					
					if ( terracottaName == body1Name && body1Type == "terracotta" )
					{
						 
						 var tX = body1.GetOriginPosition().x;
						 var tY = body1.GetOriginPosition().x;
						 explodeTerrocatta(tX, tY); // blow it up visually then remove it from the world
						
						 world.DestroyBody(body1); // destroy the terracotta warrior 
						 world.DestroyBody(body2); // destroy the arrow 
						 
						 terracottaDestroyedCounter++;
						 
						 if ( terracottaDestroyedCounter == 1 ) 
						 {
							 nestOfBeesReady = true;
							 drawNestOfBeesAlert();
						 }
						 
						increaseScore(); 
					} else {
						/*
						 var tX = body2.GetOriginPosition().x;
						 var tY = body2.GetOriginPosition().x;
						 explodeTerrocatta(tX, tY); // blow it up visually then remove it from the world
						
						 world.DestroyBody(body1); // destroy the arrow
						 world.DestroyBody(body2); // destroy the warrior 
						 
						 terracottaDestroyedCounter++;
						 
						 if ( terracottaDestroyedCounter == 3 ) 
						 {
							 nestOfBeesReady = true;
							 drawNestOfBeesAlert();
						 }
						 
						increaseScore();
						*/ 
						
					}
					
				}
			}

			if ( body1Name == "ground" && body2Type == "terracotta" ) terracottaWarriorsOnGround = true;		
		}

	}

	for (var j = world.m_jointList; j; j = j.m_next) 
	{
		drawJoint(j, context);
	}
	
	//////////////////////////////////////////////////// DRAW & UPDATE COSTUMES
	
	for (var b = world.m_bodyList; b; b = b.m_next) 
	{
		
		var mingFlag = false;
		var bodyName; 
		var bodyType;
		
		
		if ( b.GetUserData() != null ) 
		{
			bodyName = b.GetUserData().name;
			bodyType = b.GetUserData().bodyType;
			////////console.log('body name: ' + bodyName );
			////////console.log('body type: ' + bodyType );
			////////console.log('body count: ' + world.m_bodyCount );
			////////console.log('------------------------');
		}
		
		
		// the ming warrior has been defeated
		if (( mingHealth <= 0) && (b.GetUserData().name == "ming") )
		{
			
			if (b.GetUserData().name == "ming") 
			{
				////console.log('game over for: ' + b.GetUserData().name );
				
				world.DestroyBody(b);
				drawGameOver();
				return;
			}
			
		}
		
		// we can turn on or off the stroke of the physics body to see the shapes while testing
		if (turnOnDrawingPhysics)
		{
			var contextStrokeColor;
			var contextLineWidth = 0.5;
			var brownStrokeColor = '#663300';
			var yellowStrokeColor = '#FF0000';
			var blackStrokeColor = '#000000';
			
			if ( bodyName == "ming" )
			{
				contextStrokeColor = yellowStrokeColor;
				contextLineWidth = 3.5;
			} else if  ( bodyName == "wall" ||  bodyName == "ground"  )
			{
				contextLineWidth = 1;
				contextStrokeColor = blackStrokeColor;
			} else if ( bodyName == "arrow" ) 
			{
				contextLineWidth = 0.5;
				contextStrokeColor = yellowStrokeColor;	
			}
			else 
			{
				contextLineWidth = 3.5;
				contextStrokeColor = brownStrokeColor ;
			}
			
			// draw all the shapes within each body
			// for instance, the ming warrior has a body and a bow
			// while the terracotta warriors only have bodies 
			// the same goes for the walls and ground 
			
			for (var s = b.GetShapeList(); s != null; s = s.GetNext() ) 
			{
				drawShape(s, context, contextStrokeColor, contextLineWidth);
			}
		}
		
		// move the terracotta warriors towards the ming warrior so that his arrows can reach them 
		// the undefined body is the world itself 
		if ( bodyName != "ming" && bodyName != "wall" &&  bodyName != "ground" &&  bodyName != undefined && terracottaWarriorsOnGround ) 
		{
			////////console.log('body name: ' + b.GetUserData().name );
			if (terracottaWarriorsOnGround) advanceTerracottaWarrior(b);
		}
		
	
		// we give each phsyics body a costume and have that costume track the x and y of each body
		// after each step, the graphics are clears so we redraw them on the next step at their new position 
		// I started by creating external draw function then I realized that I could just create an 
		// update costume function within the user data object 
		
		if (turnOnDrawingCostumes)
		{
			// TERRACOTTA WARRIOR COSTUMES
			if ( bodyType == "terracotta" ) 
			{
				
				var terracottaCostumeWidth = 20;
				var terracottaCostumeHeight = 50;
				
				terracottaX = b.GetOriginPosition().x - (terracottaCostumeWidth / 2);
				terracottaY = b.GetOriginPosition().y - terracottaCostumeHeight + 22; 
				
				drawTerracottaWarrior( terracottaX, terracottaY);
			
			}
			
			// MING WARRIOR COSTUME
			
			if ( bodyName == "ming" ) 
			{
				//////console.log('body name: ' + bodyName );
				
				var mingCostumeWidth = 20;
				var mingCostumeHeight = 50;
				
				mingX = b.GetOriginPosition().x - (mingCostumeWidth / 2);
				mingY = b.GetOriginPosition().y - mingCostumeHeight + 10;  
				
				drawMingWarrior(mingX, mingY, b);
			
			}
			
			// MING WARRIOR COSTUME
			
			if ( bodyName == "greatWall" ) 
			{
				drawGreatWall( b.GetCenterPosition() );	
			}
			
			// ARROW COSTUMES
			 
			if ( bodyType == "arrow" || bodyType == "bee" || bodyType == "rock" )
			{
				 b.GetUserData().updateCostume(b);
			}
		
		}
		
		drawScoreTextField();
		drawHealth();
		drawArrowsRemaining();
		drawNestOfBeesAlert();
		
		// instead of firing arrows like bullets
		// let's simulate the time it would take for the archer to notch an arrow 
		// as long as the archer has one notched, he can fire it
		if ( stepCounter >= stepsUntilNextArrow )
		{
			arrowNotched = true;
			stepCounter = 0;
		}
		
		//
		stepCounter++;
		
	}
	
	
}

function drawPreloading()
{
	var c = document.getElementById("nest");
	var cxt=c.getContext("2d");
	cxt.font = "bold 60px arial";
	cxt.textAlign = "left";

	cxt.fillStyle = "#000000";
	cxt.fillText( "- LOADING -", 300, 150);
	ctx.fillRect(300,150,430,0);  
}

function drawBeginGame()
{
	var c = document.getElementById("nest");
	var cxt=c.getContext("2d");
	cxt.font = "bold 60px arial";
	cxt.textAlign = "left";
	cxt.shadowOffsetX = 2;
	cxt.shadowOffsetY = 2;
	cxt.shadowBlur = 10;
	cxt.shadowColor = "black";
	
	var gradient = cxt.createLinearGradient(300,150,430,0);
	gradient.addColorStop(0, '#FFFF00');
	gradient.addColorStop(1, '#FFCC00'); 

	cxt.fillStyle = gradient;
	cxt.fillText( "NEST OF BEES", 300, 150);
	ctx.fillRect (300,150,430,0);  

	cxt.shadowOffsetX = 0;
	cxt.shadowOffsetY = 0;
	cxt.shadowBlur = 0;
	cxt.shadowColor = "black";

	cxt.font = "normal 12px arial";
	cxt.textAlign = "left";
	cxt.fillStyle = "#111111";
	cxt.fillText( "- PRESS SHIFT TO FIRE ARROWS - UP AND DOWN KEYS ADJUST THE BOW -", 300, 180);
	cxt.font = "bold 16px arial";
	cxt.textAlign = "left";
	cxt.fillStyle = "#000000";
	cxt.fillText( "- CLICK ANYWHERE TO BEGIN - ", 390, 220);
}


function drawGameOver()
{
	
	clearTimeout(worldTimer);
	
	var clearFinal = setTimeout(function(){
		ctx.clearRect(0, 0, canvasWidth, canvasHeight);
		canvas = document.getElementById("nest");
		canvas.width = canvas.width;
	
		ctx.font = "bold 60px arial";
		ctx.textAlign = "left";
		ctx.fillStyle = "#000000";
		ctx.fillText( "GAME OVER", 300, 150);
		clearTimeout(clearFinal);
		},5);	
		
	gameOn = false;
	gameOver = true;
	
}

function drawScoreTextField()
{
	var c = document.getElementById("nest");
	var cxt=c.getContext("2d");
	cxt.font = "bold 30px arial";
	cxt.textAlign = "left";
	cxt.fillStyle = "#FF0000";
	cxt.fillText( String(score), 850, 60);
}

function drawHealth()
{
	if ( mingHealth >= 0 ) 
	{
		var c = document.getElementById("nest");
		var cxt=c.getContext("2d");
		cxt.fillStyle="#FF0000";
        
		var width = 10 * mingHealth;
		var height = 10;
		
		ctx.fillRect (50, 30, width, height);
		
	   // cxt.rect(50,30,width,height);
	}
}

function drawArrowsRemaining()
{
	var c = document.getElementById("nest");
	var cxt=c.getContext("2d");
	cxt.font = "bold 20px arial";
	cxt.textAlign = "left";
	cxt.fillStyle = "#000000";
	
	var arrowsInQuiver = arrowQuiver.length;
	
	cxt.fillText( String(arrowsInQuiver), 50, 60);
}

function drawMingWarrior(mingX, mingY, mingBody)
{
	var c = document.getElementById("nest");
	var cxt=c.getContext("2d");
	
	// draw the complete costume covering the figure
	
	var mingCostume = new Image();
	
	var imageWidth = 20;
	var imageHeight = 50;
	
	mingCostume.onload = function() {
		cxt.save();
		cxt.drawImage(mingCostume, mingX, mingY, imageWidth, imageHeight);
		cxt.restore();
	}
	
	mingCostume.src="img/ming.png";
	
	// draw the bow costume covering the bow
	
	for (var s = mingBody.GetShapeList(); s != null; s = s.GetNext() ) 
	{
		if (s.GetUserData().name == "bow")
		{
			
			boxX = s.GetPosition().x - 10;
			bowY = s.GetPosition().y - 15;
			
			var bowCostume = new Image();
	
			var bowImageWidth = 20;
			var bowImageHeight = 30;
			
			bowCostume.onload = function() 
			{	
				ctx.save();
				var transX = boxX + bowImageWidth / 2;
				var transY = bowY + bowImageHeight / 2
				ctx.translate (transX,transY);
				ctx.rotate (bowRotation * Math.PI / 180);
				var drawWidth = -(bowImageWidth / 2);
				var drawHeight = -(bowImageHeight / 2);
				ctx.drawImage(bowCostume, drawWidth, drawHeight, bowImageWidth, bowImageHeight);
				ctx.restore();     	
			}
			
			////console.log("bowRotation: " + bowRotation);
			
			
			
			bowCostume.src="img/bow.png";
		}
	}
}

function drawNestOfBeesAlert()
{
	if(nestOfBeesReady)
	{
		var c = document.getElementById("nest");
		var cxt=c.getContext("2d");
		
		var beeCostume = new Image();
		
		var imageWidth = 20;
		var imageHeight = 20;
		
		beeCostume.onload = function() {
			cxt.save();
			cxt.drawImage(beeCostume, 50, 100, imageWidth, imageHeight);
			cxt.restore();
		}
		
		beeCostume.src="img/beeNest.png";
		
		cxt.font = "bold 16px arial";
		cxt.textAlign = "left";
		cxt.fillStyle = "#00000";
		
		cxt.fillText( "PRESS B TO FIRE NEST OF BEES!!!", 80, 120);
	}
}

function drawTerracottaWarrior(terracottaBodyX, terracottaBodyY)
{
	var c = document.getElementById("nest");
	var cxt=c.getContext("2d");
	
	var terracottaCostume = new Image();
	
	var imageWidth = 20;
	var imageHeight = 50;
	
	terracottaCostume.onload = function() {
		cxt.save();
		cxt.drawImage(terracottaCostume, terracottaBodyX, terracottaBodyY, imageWidth, imageHeight);
		cxt.restore();
	}
	
	
	terracottaCostume.src="img/warrior.png";
}


function drawGreatWall( vec ) 
{

	var c = document.getElementById("nest");
	var cxt=c.getContext("2d");
	
	var greatWallCostume = new Image();
	
	var imageWidth = 50;
	var imageHeight = 100;
	
	var leftX = vec.x - (imageWidth/2);
	var topY = vec.y - (imageHeight/2);
	
	greatWallCostume.onload = function() {
		cxt.save();
		cxt.drawImage(greatWallCostume, leftX, topY, imageWidth, imageHeight);
		cxt.restore();
	};
	
	var wallAssetPath;
	
	if ( mingHealth == 10 )
	{ 
		wallAssetPath = "img/greatWall.png"; 
	} else {
		
		var damageLvl;
		
		switch(mingHealth)
		{
			case 8 : 
				damageLvl = 1;
				break;
			case 6 : 
				damageLvl = 2;
				break;
			case 4 : 
				damageLvl = 3;
				break;
			case 2 : 
				damageLvl = 4;
				break;
			case 1 : 
				damageLvl = 5;
				break;				
		}; 
		
		wallAssetPath = "img/greatWall" + String(damageLvl) + ".png"; 
	}
		
	greatWallCostume.src=wallAssetPath;
}	


//////////////////////////////////////////////////////////

function fireArrow()
{
		if(arrowNotched) createArrow(world, false, "arrow");
		arrowNotched = false;
}

// fires 30 bees 
function fireNestOfBees()
{
		if(nestOfBeesReady) 
		{
			var totalBeesInNest = 10;
			
			for ( var beeCounter = 0; beeCounter < totalBeesInNest; beeCounter++)
			{
				var beeName = "bee" + String(beeCounter);
				createNestOfBees(world, false, beeName);
				
			}
		}
		nestOfBeesReady = false;
}


// at one point, I decided ming should be on the ground instead of the wall and experimented with having him jump
function jump()
{
	var mingBody; 
	
	for (var b = world.m_bodyList; b; b = b.m_next) 
	{
		if ( b.GetUserData() != null )
		{
			if ( b.GetUserData().name == "ming" ) 
			{
				mingBody = b;
				
				//////console.log("jumping...");
				
				if ( !mingBody.GetUserData().airborne )
				{
					
					mingBody.GetUserData().airborne = true;
					
					var dirVec = new b2Vec2();
					var Force_in_Newton = 190;
					var rot = 0; // when rot is 45, both x and y are the same -- increases rot causes x to decrease, and y to increase 
					
					dirVec.x = 5;//mingBody.GetCenterPosition().x //Force_in_Newton * Math.cos(rot * Math.PI/180);
					dirVec.y = -30; //-( Force_in_Newton * Math.sin(rot * Math.PI/180) );
					
					 var applyMass = 2000; //need at least 10 mass to break gravity 
					 dirVec.Multiply(applyMass);
							
					mingBody.ApplyImpulse(dirVec, mingBody.GetCenterPosition() );
					
					
				}
				
			}
		}
	}
	
}



// experiment -- you could make ming invincible as he's charges to cause damage like a human bowling ball :-D
// but since the ming warrior is now fixed on the wall, this doesn't work anymore so let's save it for another level 
function charge()
{
	var mingBody; 
	
	for (var b = world.m_bodyList; b; b = b.m_next) 
	{
		if ( b.GetUserData() != null )
		{
			if ( b.GetUserData().name == "ming" ) 
			{
				mingBody = b;
				
				////console.log("jumping...");
				
				var dirVec = new b2Vec2();
				var Force_in_Newton = 190;
				var rot = 0; // when rot is 45, both x and y are the same -- increases rot causes x to decrease, and y to increase 
				
				dirVec.x = 100;//mingBody.GetCenterPosition().x //Force_in_Newton * Math.cos(rot * Math.PI/180);
				dirVec.y = 0; //-( Force_in_Newton * Math.sin(rot * Math.PI/180) );
				
				 var applyMass = 10; //need at least 10 mass to break gravity 
				 dirVec.Multiply(applyMass);
				  		
				//mingBody.ApplyForce(dirVec, mingBody.GetCenterPosition() );
				
				mingBody.SetLinearVelocity(dirVec);
				
			}
		}
	}
	
}

//////////////////////////////////////////////////////////////////////////////////////////// CREATE PHYSICS BODIES

function drawJoint(joint, context) {
	var b1 = joint.m_body1;
	var b2 = joint.m_body2;
	var x1 = b1.m_position;
	var x2 = b2.m_position;
	var p1 = joint.GetAnchor1();
	var p2 = joint.GetAnchor2();
	context.strokeStyle = '#00eeee';
	context.beginPath();
	
	switch (joint.m_type) 
	{
	case b2Joint.e_distanceJoint:
		context.moveTo(p1.x, p1.y);
		context.lineTo(p2.x, p2.y);
		break;

	case b2Joint.e_pulleyJoint:
		// TODO
		break;

	default:
		if (b1 == world.m_groundBody) {
			context.moveTo(p1.x, p1.y);
			context.lineTo(x2.x, x2.y);
		}
		else if (b2 == world.m_groundBody) {
			context.moveTo(p1.x, p1.y);
			context.lineTo(x1.x, x1.y);
		}
		else {
			context.moveTo(x1.x, x1.y);
			context.lineTo(p1.x, p1.y);
			context.lineTo(x2.x, x2.y);
			context.lineTo(p2.x, p2.y);
		}
		break;
	}
	context.stroke();
}

function drawShape(shape, context, strokeColor, contextLineWidth)
{
	
	// all about context http://www.html5canvastutorials.com/tutorials/html5-canvas-line-caps/ -- its an html5 element not part of box2d
	
	context.strokeStyle = strokeColor;
	context.lineWidth = contextLineWidth;
	context.beginPath();
	
	switch (shape.m_type) 
	{
	case b2Shape.e_circleShape:
		{
			var circle = shape;
			var pos = circle.m_position;
			var r = circle.m_radius;
			var segments = 16.0;
			var theta = 0.0;
			var dtheta = 2.0 * Math.PI / segments;
			
			// draw circle
			context.moveTo(pos.x + r, pos.y);
			for (var i = 0; i < segments; i++) {
				var d = new b2Vec2(r * Math.cos(theta), r * Math.sin(theta));
				var v = b2Math.AddVV(pos, d);
				context.lineTo(v.x, v.y);
				theta += dtheta;
			}
			context.lineTo(pos.x + r, pos.y);
	
			// draw radius
			context.moveTo(pos.x, pos.y);
			var ax = circle.m_R.col1;
			var pos2 = new b2Vec2(pos.x + r * ax.x, pos.y + r * ax.y);
			context.lineTo(pos2.x, pos2.y);
		}
		break;
	case b2Shape.e_polyShape:
		{
			var poly = shape;
			var tV = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[0]));
			
			if ( !isNaN(tV.x) && !isNaN(tV.y) )
			{
				context.moveTo(tV.x, tV.y);
			
				for (var i = 0; i < poly.m_vertexCount; i++) {
					var v = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[i]));
					context.lineTo(v.x, v.y);
				}
				
				context.lineTo(tV.x, tV.y);
			}
		}
		break;
	}
	
	context.stroke();
}


////////////////////////////////////////////////////////////////////// CREATE THE GROUND AND WALLS FOR THE WORLD


function createWorld() 
{
	var worldAABB = new b2AABB();
	worldAABB.minVertex.Set(-1000, -1000);
	worldAABB.maxVertex.Set(1000, 1000);
	
	var gravity = new b2Vec2(0, 300);
	var doSleep = true;
	var world = new b2World(worldAABB, gravity, doSleep);
	
	// the world body is the only body that will have null for its user data 
	
	var contactListener = new b2ContactManager();

	world.SetListener( contactListener );
	
	// ground
	createGround(world);
	
	var wallWidth = 5;
	var wallHeight = worldHeightPixels;
	
	var boxFixed = true;
	
	// walls 
	 var leftWallX = 0 - wallWidth;
	 var leftWallY = 0;
	 var leftWallWidth = wallWidth;
	 var leftWallHeight = worldHeightPixels;
	
	// CREATE LEFT WALL
	createBox(world, leftWallX, leftWallY, leftWallWidth, leftWallHeight, boxFixed, "wall");
	
	 var rightWallX = worldWidthPixels + wallWidth;
	 var rightWallY = 0;
	 var rightWallWidth = wallWidth;
	 var rightWallHeight = worldHeightPixels;
	
	// CREATE RIGHT WALL
	createBox(world, rightWallX, rightWallY, rightWallWidth, rightWallHeight, boxFixed, "wall");
	
	return world;
}

function createGround(world) 
{
	var groundSd = new b2BoxDef();
	groundSd.extents.Set(worldWidthPixels, 50);
	groundSd.restitution = 0.25; // bounciness 
	groundSd.friction = 0.2;
	
	var bodyDef = new b2BodyDef();
	bodyDef.AddShape(groundSd);
	bodyDef.position.Set(0, 334);
	
	var userDataObj = new Object();
	userDataObj.name = "ground";
	userDataObj.bodyType = "ground";
	
	bodyDef.userData = userDataObj;
	
	return world.CreateBody(bodyDef)
}

function createBall(world, x, y) 
{
	
	var ballSd = new b2CircleDef();
	ballSd.density = 1.0;
	ballSd.radius = 20;
	ballSd.restitution = 1.0;
	ballSd.friction = 0;
	
	var ballBd = new b2BodyDef();
	ballBd.AddShape(ballSd);
	ballBd.position.Set(x,y);
	
	return world.CreateBody(ballBd);
}

function createBox(world, x, y, width, height, fixed, name, bulletType) 
{
	if (typeof(fixed) == 'undefined') fixed = true;
	
	if (typeof(bulletType) == 'undefined') bulletType = false;
	
	var boxSd = new b2BoxDef();
	
	if (!fixed) boxSd.density = 1.0;
	boxSd.extents.Set(width, height);
	
	var bodyDef = new b2BodyDef();
	bodyDef.AddShape(boxSd);
	bodyDef.position.Set(x,y);
	
	var userDataObj = new Object();
	userDataObj.name = name;
	userDataObj.bodyType = "box";
	
	bodyDef.userData = userDataObj;
	
	return world.CreateBody(bodyDef)
}

/*
createBall = function(world, x, y, rad, fixed) 
{
	var ballSd = new b2CircleDef();
	if (!fixed) ballSd.density = 1.0;
	ballSd.radius = rad || 10;
	ballSd.restitution = 0.2;
	
	var ballBd = new b2BodyDef();
	ballBd.AddShape(ballSd);
	ballBd.position.Set(x,y);
	
	return world.CreateBody(ballBd);
};
*/


// problem with createPoly -- getting a bad vertice 
createPoly = function(world, x, y, points, fixed) 
{
	var polySd = new b2PolyDef();
	if (!fixed) polySd.density = 1.0;
	polySd.vertexCount = points.length;
	
	for (var i = 0; i < points.length; i++) {
		polySd.vertices[i].Set(points[i][0], points[i][1]);
	}
	
	var polyBd = new b2BodyDef();
	polyBd.AddShape(polySd);
	polyBd.position.Set(x,y);
	return world.CreateBody(polyBd)
};

createTerracottaWarrior = function(world, x, y, width, height, fixed, name)
{
	// BOX DEF
	
	if (typeof(fixed) == 'undefined') fixed = true;
	var boxSd = new b2BoxDef();
	
	if (!fixed) boxSd.density = 1.0;
	boxSd.extents.Set(width, height);
	boxSd.friction = 0.1; // 0.2 is the default
	boxSd.density = 1; // 1 is the default 
	boxSd.mass = 10;
	
	// BODY DEF
	
	var bodyDef = new b2BodyDef();
	bodyDef.AddShape(boxSd);
	bodyDef.position.Set(x,y);

	var userDataObj = new Object();
	userDataObj.name = name;
	userDataObj.bodyType = "terracotta";
	
	bodyDef.userData = userDataObj;

	terracottaWarriorsList.push(bodyDef);
	
	return world.CreateBody(bodyDef)
};

function explodeTerrocatta(tX, tY)
{
	
	var totalRocks = 5;
	var rockX = tX;
	var rockY = tY - 30; 
	
	for (var rockCounter = 0; rockCounter < totalRocks; rockCounter++)
	{
	
		var ballSd = new b2CircleDef();
		ballSd.density = 1.0;
		ballSd.radius = 2;
		ballSd.restitution = 0.5;
		ballSd.groupIndex = -2;
		
		var rockBody = new b2BodyDef();
		rockBody.AddShape(ballSd);
		rockBody.position.Set(rockX,rockY);
		
		var userDataObj = new Object();
		userDataObj.name = "rock" + String(rockCounter);
		userDataObj.bodyType = "rock";
		userDataObj.updateCostume = function(b) {
			var c = document.getElementById("nest");
			var cxt=c.getContext("2d");
			var rockCostume = new Image();
			
			var imageWidth = 10;
			var imageHeight = 10;
			
			rockCostume.onload = function() {
				cxt.save();
				cxt.drawImage(rockCostume, rockX, rockY, imageWidth, imageHeight);
				cxt.restore();
			}
		
			rockCostume.src="img/rock.png";
		}
					
		rockBody.userData = userDataObj;
					
		var dirVec = new b2Vec2();
		var Force_in_Newton = 50//( Math.random() * 5 ) + 25;
		var rot =  Math.floor( ( Math.random() * 80 ) ); //90; //( Math.random() * bowRotation ); 
		
		dirVec.x = 0;//Force_in_Newton * Math.cos(rot * Math.PI/180);
		dirVec.y = -50; //-( Force_in_Newton * Math.sin(rot * Math.PI/180) );
	
		var applyMass = 5; //need at least 10 mass to break gravity 
		dirVec.Multiply(applyMass);
		  
		var rockBody = world.CreateBody(rockBody);
		
		rockBody.SetLinearVelocity(dirVec);
		
		//rockBody.ApplyImpulse(dirVec, rockBody.GetCenterPosition() );
		
		return rockBody;
	}
		
}

createMingWarrior = function(world, x, y, width, height, fixed, name)
{
	
	// the ming warrior is different from the terracotta warrior because it has 2 shapes: a body and a bow
	// we use multiple shapes to make up 1 body
	// here is a good tutorial about the differenece between a shape and body:
	// http://www.kerp.net/box2d/displaylesson.php?chapter=1&lesson=20
	// basically, think of the body as a container 
	
	
	// we need one box for the figure and one box for the bow 
	
	// BOX 1 define the figure box 
 	// I'm using the word figure here to mean the shape that includes the head,body,feet -- basically the box around the entire body 
	
	// we also don't want the bow and figure to collide with the arrows 
	// and need to set its groupIndex that manage collision filtering -- sounds complex but 
	// see: 
	// http://blog.allanbishop.com/box-2d-2-1a-tutorial-part-5-collision-filtering/#more-340
	// Alan simplifies it nicely 
	
	// create the box def 
	if (typeof(fixed) == 'undefined') fixed = true;
	var figureBoxDef = new b2BoxDef();
	figureBoxDef.friction = 0.2;
	figureBoxDef.groupIndex = -2; // negative group index means it will never collide with other bodies with the same negative number thus we'll give the arrows a -2 groupIndex as well
	
	var userDataObj = new Object();
	userDataObj.name = "figure";
	
	figureBoxDef.userData = userDataObj;
	
	
	if (!fixed) figureBoxDef.density = 1.0;
	figureBoxDef.extents.Set(width, height);
	
	// BOX 2 define the bow box 
	
	// create the box def 
	if (typeof(fixed) == 'undefined') fixed = true;
	var bowBoxDef = new b2BoxDef();
	bowBoxDef.friction = 0.2;
	bowBoxDef.groupIndex = -2;
	
	var bowWidth = 3;
	var bowHeight = 5; 
	
	var userDataObj = new Object();
	userDataObj.name = "bow";
	
	bowBoxDef.userData = userDataObj;
	
	if (!fixed) bowBoxDef.density = 1.0;
	bowBoxDef.extents.Set(bowWidth, bowHeight);
	
	var localPosVec = new b2Vec2(10, -20);
	
	bowBoxDef.localPosition = localPosVec;
	
	// add both boxes to the body 	
	var bodyDef = new b2BodyDef();
	// add the figure
	bodyDef.AddShape(figureBoxDef);
	// add the bow 
	bodyDef.AddShape(bowBoxDef);
	//	
	bodyDef.position.Set(x,y);
	
	var userDataObj = new Object();
	userDataObj.name = name;
	userDataObj.bodyType = "MingWarrior";
	userDataObj.airborne = true;
	
	bodyDef.userData = userDataObj;
	
	return world.CreateBody(bodyDef)
};

createArrow = function(world, fixed, name)
{
	var mingBody; 
	
	for (var b = world.m_bodyList; b; b = b.m_next) 
	{
		if ( b.GetUserData() != null )
		{
			if ( b.GetUserData().name == "ming" ) 
			{
				mingBody = b;
				
				// create the box def
				if (typeof(fixed) == 'undefined') fixed = true;
				var boxSd = new b2BoxDef();
				boxSd.friction = 0.1;
				boxSd.mass = 1;
				boxSd.groupIndex = -2;
				
				var width = 10;
				var height = 3;
				
				if (!fixed) boxSd.density = 1.0;
				boxSd.extents.Set(width, height);
				
				// add the box def to the body def 	
				var bodyDef = new b2BodyDef();
				bodyDef.AddShape(boxSd);
				
				var x = Math.floor( mingBody.GetOriginPosition().x );
				
				// we want the arrow to fly out of the bow so we need find where the bow box def is...
				
				// how do we find the bow shape within the body?
				// b2BoxDef like b2Body has a userData property where we can set the name 
				// when you call AddShape from b2Body it adds the b2BoxDef as a b2Shape 
					
				var bowY;	
								
				for (var s = b.GetShapeList(); s != null; s = s.GetNext() ) 
				{
					if (s.GetUserData().name == "bow")
					{
						bowY = s.GetPosition().y;
					}
				}
				
				var y = bowY;
				
								
				bodyDef.position.Set(x,y);
				
				var userDataObj = new Object();
				userDataObj.name = name;
				userDataObj.bodyType = "arrow";
				userDataObj.updateCostume = function(b) {
				
					var arrowX = Math.floor( b.GetOriginPosition().x );
					var arrowY = Math.floor( b.GetOriginPosition().y ); 
				
					var c = document.getElementById("nest");
					var cxt=c.getContext("2d");
					var arrowCostume = new Image();
					
					var imageWidth = 30;
					var imageHeight = 10;
					
					arrowCostume.onload = function() {
						cxt.drawImage(arrowCostume, arrowX, arrowY, imageWidth, imageHeight);
					}
				
					arrowCostume.src="img/beeArrow.png";
				}
				
				bodyDef.userData = userDataObj;
				
				var dirVec = new b2Vec2();
				var Force_in_Newton = 70;
				var rot = bowRotation; // when rot is 45, both x and y are the same -- increases rot causes x to decrease, and y to increase 
				
				dirVec.x = Force_in_Newton * Math.cos(rot * Math.PI/180);
				dirVec.y = -( Force_in_Newton * Math.sin(rot * Math.PI/180) );
				
				//dirVec.x = 50;  // increses x will push arrow farther to the right;
				//dirVec.y = -50;  // decrease y will push the angle of the arrow up 
				
				// APPLY VELOCITY TO THE ARROW 
				
				 var applyMass = 10; //need at least 10 mass to break gravity 
				 dirVec.Multiply(applyMass);
				  
				var arrowBody = world.CreateBody(bodyDef);
				
				arrowBody.SetLinearVelocity(dirVec);
				
				arrowBodies.push(arrowBody);
				
				return arrowBody;
			}
		}
	}
}


createNestOfBees = function(world, fixed, name)
{
	var mingBody; 
	
	//console.log("createNestOfBees!");
	
	for (var b = world.m_bodyList; b; b = b.m_next) 
	{
		if ( b.GetUserData() != null )
		{
			if ( b.GetUserData().name == "ming" ) 
			{
				mingBody = b;
				
				// unlike the arrows that used boxes, let's try balls for the bees
				// create the ball def for each bee
				
				var ballSd = new b2CircleDef();
				if (!fixed) ballSd.density = 1.0;
				ballSd.radius = 5;
				ballSd.restitution = 0.5;
				ballSd.groupIndex = -2;
	
				var x = Math.floor( mingBody.GetOriginPosition().x );
				
				// we want the bees to fly out of the bow so we need find where the bow box def is...
				
				var bowY;	
				for (var s = b.GetShapeList(); s != null; s = s.GetNext() ) 
				{
					if (s.GetUserData().name == "bow")
					{
						bowY = s.GetPosition().y;					
					}
				}
	
				var beeBody = new b2BodyDef();
				beeBody.AddShape(ballSd);
				beeBody.position.Set(x,bowY);
	
				var userDataObj = new Object();
				userDataObj.name = name;
				userDataObj.bodyType = "bee";
				userDataObj.updateCostume = function(b) {
				
					var nestX = Math.floor( b.GetOriginPosition().x );
					var nestY = Math.floor( b.GetOriginPosition().y ); 
				
					var c = document.getElementById("nest");
					var cxt=c.getContext("2d");
					var beeCostume = new Image();
					
					var imageWidth = 20;
					var imageHeight = 20;
					
					beeCostume.onload = function() {
						cxt.save();
						cxt.drawImage(beeCostume, nestX, nestY, imageWidth, imageHeight);
						cxt.restore();
					}
				
					beeCostume.src="img/beeNest.png";
				}
				
				beeBody.userData = userDataObj;
				
				var dirVec = new b2Vec2();
				var Force_in_Newton = ( Math.random() * 75 ) + 50;
				var rot =  ( Math.random() * bowRotation ); 
				
				// the bees should scatter as they explode out so we'll introduce some randomness 
				//rot = rot * Math.random();
				//////console.log("random angle: " + rot);
				//////console.log("random force: " + Force_in_Newton);
				
				dirVec.x = Force_in_Newton * Math.cos(rot * Math.PI/180);
				dirVec.y = -( Force_in_Newton * Math.sin(rot * Math.PI/180) );
				
				//dirVec.x = 50;  // increses x will push arrow farther to the right;
				//dirVec.y = -50;  // decrease y will push the angle of the arrow up 
				
				// APPLY VELOCITY TO THE ARROW 
				
				 var applyMass = 10; //need at least 10 mass to break gravity 
				 dirVec.Multiply(applyMass);
				  
				var beeBody = world.CreateBody(beeBody);
				
				beeBody.SetLinearVelocity(dirVec);
				
				return beeBody;
			}
		}
	}
	
	
}


////////////////////////////////////////////////////////////////////// SETUP WORLD 



function setupWorld(did) 
{
	
	if ( $('nest') != null ) //////console.log("nest is ready");
	
	if (!did) did = 0;
	world = createWorld();
}

function redrawCanvas()
{
	ctx.save();
	ctx.clearRect(0, 0, canvasWidth, canvasHeight);
	ctx.restore();
}



function step() 
{
	var stepping = false;
	// var timeStep = 1.0/60; original
	var timeStep = 1.0/60;
	var iteration = 1;
	world.Step(timeStep, iteration);
	//
	redrawCanvas(); // Flickering is probably being caused by the graphics being loaded and drawn before they are ready.
	//
	drawWorld(world, ctx);
	
	if (gameOn) 
	{
		worldTimer = setTimeout('step()', 10);
		
	}
}


/////////////////////////////////////////////////////////////////////////////////// GAME 

var totalTerracottaWarriors = 5;

function createActors()
{
	
	// create the ming warrior and position him on the wall 
	var dropMingWarriorHeight = 180;
	
	var mingWarriorWidth = 10;
	var mingWarriorHeight = 15;
	
	var mingLeftPos = 40; 
	
	var name = "ming";
	var fixedPos = true; // the ming warrior stands on the wall and is thus fixed in place
	
	createMingWarrior(world, mingLeftPos, dropMingWarriorHeight, mingWarriorWidth, mingWarriorHeight, fixedPos, name);
	
	// create 10 opponents and position them off screen 
	
	var terracottaArray = [];
	for (var terracottaCounter = 0; terracottaCounter < totalTerracottaWarriors; terracottaCounter++)
	{
		//var terracottaWarrior = nestofbees.createWarrior(world, Event.pointerX(e) - canvasLeft, Event.pointerY(e) - canvasTop, 10, 10, false);
		
		// creates the physics body and adds it to the world
		
		var terracottaName = "terracotta" + terracottaCounter;
		
		var dropWarriorHeight = 250;
		
		var warriorWidth = 10;
		var warriorHeight = 20;
		
		var warriorBuffer = 30;
		
		var leftPos = 500 + ( (warriorWidth + warriorBuffer) * terracottaCounter); 
		var terracottaBody = createTerracottaWarrior(world, leftPos, dropWarriorHeight, warriorWidth, warriorHeight, false, terracottaName);
		
		terracottaArray.push(terracottaBody);
	}
	
	// add the great wall which the ming warrior will stand on to rain down death
	
	 var greatWallX = 40;
	 var greatWallY = 235;
	 var greatWallWidth = 30;
	 var greatWallHeight = 40;
	
	// create the great wall
	createBox(world, greatWallX, greatWallY, greatWallWidth, greatWallHeight, true, "greatWall");
}

function advanceTerracottaWarrior( terracottaBody )
{
	
	var curVec = new b2Vec2();
	curVec = terracottaBody.m_position.Copy();
	////////console.log("terracottaBody x: " + curVec.x);
	
	var newVec = new b2Vec2();
	
	newVec.x = -100//-(curVec.x + terracottaSpeed); // a positive value would make the terracotta warriors go to the right
	newVec.y = curVec.y;
	
	// for force, we need a force vec and a world vec 
	var worldVec = new b2Vec2();
	worldVec.x = 0;
	worldVec.y = 0;
	
	 var terracottaMass = terracottaBody.GetMass(); // each terracotta body currently has a mass of 400
	 
	 var applyMass = 12;
	 
	 newVec.Multiply(applyMass);
	 
	 //GetCenterPosition() gets the center of the mass on the body not the entire world
	 terracottaBody.ApplyImpulse(newVec, terracottaBody.GetCenterPosition() );
	 
}


function increaseScore()
{
	score += scoreInc;
}

function beginGame()
{
	createActors();	
}

/////////////////////////////////////////////////////////////////////////////////// PRELOAD ALL THE GRAPHICS


function preloadAllCostumes()
{
	drawPreloading();

	
	var costumeLoadCount = 0;
	var costumeArray = ["beeArrow.png", 
						"beeNest.png", 
						"bow.png", 
						"ming.png", 
						"warrior.png", 
						"rock.png",
						"greatWall.png", 
						"greatWall1.png", 
						"greatWall2.png", 
						"greatWall3.png", 
						"greatWall4.png", 
						"greatWall5.png" ];
	
	var totalCostumes = costumeArray.length;					
						

	for ( var costumeCounter = 0; costumeCounter < totalCostumes; costumeCounter++ )
	{

		var costumeImage = new Image();
	
		costumeImage.onload = function() 
		{
			
			costumeLoadCount++;
			if (costumeLoadCount == totalCostumes)
			{
			
			ctx.clearRect(0, 0, canvasWidth, canvasHeight);
			canvas = document.getElementById("nest");
			canvas.width = canvas.width;
			
			registerKeys();
			
			drawBeginGame();
			
			}
		}
		
		costumeImage.src = "img/" + costumeArray[costumeCounter];
	}	
}

/////////////////////////////////////////////////////////////////////////////////// OBSERVE WORLD 

Event.observe(window, 'load', function() 
{
	setupWorld();
	
	ctx = $('nest').getContext('2d');
	
	var canvasElm = $('nest');
	canvasWidth = parseInt(canvasElm.width);
	canvasHeight = parseInt(canvasElm.height);
	canvasTop = parseInt(canvasElm.style.top);
	canvasLeft = parseInt(canvasElm.style.left);
	
	Event.observe('nest', 'click', function(e) {
		
		if (!gameOn && !gameOver) 
		{
			gameOn = true;
			
			beginGame();
			step();
		}
	
	});
	
	////console.log("game ready");
	
	preloadAllCostumes();

});



