// game state
var AppStartTime = 0;
var LastGameStartTime = 0;
var LastGameCalculationTime = 0;
var GameState = 'menu';
var GameTimeRemainingSpan = null;
var GameScoreSpan = null;
var GameScore = 0;

// 3d objects
var MainMenuCam = null;
var GameCam = null;
var AsteroidTemplates = new Array();
var PhotonTemplate = null;
var SmokeTemplate = null;
var Asteroids = new Array();
var TheEngine = null;
var MovingPhotons = new Array();
var MovingAsteroids = new Array();
var MovingSmokes = new Array();

// keys
var leftKeyDown = false;
var rightKeyDown = false;
var upKeyDown = false;
var downKeyDown = false;
var enterKeyDown = false;
var spaceKeyDown = false;
var shiftKeyDown = false;

// movement
var GameCamRelativeRotationX = 0;
var GameCamRelativeRotationY = 0;
var verticalLookMoveSpeed = 0;
var horizontalLookMoveSpeed = 0;
var movementSpeed = 0;

// shooting
var LastShootTime = 0;
	
// own implementation of a deterministic randomizer
function Randomizer()
{
	// all constants:
	this.m = 2147483399;	// a non-Mersenne prime
	this.a = 40692;		// another spectral success story
	this.q = Math.floor(this.m/this.a);
	this.r = Math.floor(this.m%this.a);		// again less than q
	this.detRandSeed = 0x0f0f0f0f;
}

Randomizer.prototype.seed = function(s)
{
	this.detRandSeed = s;
}

Randomizer.prototype.getRandf = function()
{
	return (this.getRand() % 100000) / 100000;
}

Randomizer.prototype.getRand = function()
{
	// calculate new seed
	this.detRandSeed = this.a * (this.detRandSeed%this.q) - this.r* (Math.floor(this.detRandSeed/this.q));
	if (this.detRandSeed<0) this.detRandSeed += this.m;

	return Math.floor(this.detRandSeed);
}

var GameRandomizer = new Randomizer();

// ---------------------------------------------------------
// The game				
// ---------------------------------------------------------

// called each frame by the 3d engine
function onAnimate() 
{
	if (GameState == 'menu')
	{
		if (MainMenuCam)
		{
			var now = new Date().getTime() - AppStartTime;
			var t = (now + 5000) / 10000;
			MainMenuCam.setTarget(new CL3D.Vect3d(Math.cos(t), 0, Math.sin(t)));
		}						
	}
	else
	if (GameState == 'game')
		doGameLogic();
}

function createNewAsteroid(engine, typenr, x, y, z, rsx, rsy, rsz)
{
	var scene = engine.getScene();
	if (!scene)
		return;
	if ( AsteroidTemplates.length == 0)
		return;
		
	var template = AsteroidTemplates[typenr];
	if (template)
	{
		var clone = template.createClone(scene.getRootSceneNode());
		clone.Pos.X = x;
		clone.Pos.Y = y;
		clone.Pos.Z = z;
		clone.Visible = true;
		
		clone.addAnimator(new CL3D.AnimatorRotation(new CL3D.Vect3d(rsx, rsy, rsz)));
		
		MovingAsteroids.push(clone);
	}
}

function createRandomAsteroidField(engine, level)
{
	var asteroidCount = 10 * level;
	var radius = 1000;
	var centerArea = 100;
	var r = new Randomizer();
	r.seed(level);
	
	for (var i=0; i<asteroidCount; ++i)
	{
		var x = r.getRandf() * radius*2 - radius;
		var y = r.getRandf() * radius*2 - radius;
		var z = r.getRandf() * radius*2 - radius;
		var rsx = r.getRandf() * 0.2 - 0.1;
		var rsy = r.getRandf() * 0.2 - 0.1;
		var rsz = r.getRandf() * 0.2 - 0.1;
		var type = r.getRand() % AsteroidTemplates.length;	

		createNewAsteroid(engine, type, x, y, z, rsx, rsy, rsz);
	}
}

function collect3dObjectPrototypes(engine)
{
	var scene = engine.getScene();
		
	if (!scene)
		return;
	
	// collect all 3 asteroids
	
	for (var i=0; i<3; ++i)
	{
		var asteroid = scene.getSceneNodeFromName('asteroid' + (i+1));
		if (asteroid)
		{
			AsteroidTemplates.push(asteroid);
			asteroid.Visible = false;
		}
		else
			AsteroidTemplates.push(null);
	}
	
	// collect photon
	
	PhotonTemplate = scene.getSceneNodeFromName('photon');
	if (PhotonTemplate)
	{
		PhotonTemplate.Visible = false;
	}
	
	// collect smoke
	
	SmokeTemplate = scene.getSceneNodeFromName('smoke');
	if (SmokeTemplate)
	{
		SmokeTemplate.Visible = false;
	}
}

function showInfoPlate(text)
{
	var gamehud = document.getElementById('gamehud');
	if (gamehud)
		gamehud.style.display = 'none';
		
	var mainmenupanel = document.getElementById('game_mainmenu');
	if (mainmenupanel)
		mainmenupanel.style.display = 'none';
		
	var infoplate = document.getElementById('infoplate');
	if (infoplate)
		infoplate.style.display = 'block';
		
	var infoplatetext = document.getElementById('infoplattext');
	if (infoplatetext)
		infoplatetext.innerHTML = text;
		
		
}

function backToMainMenu()
{
	GameState = 'menu';
	
	showInfoPlate('Game ended. Your score:' + GameScore);
		
	playmusic('mainmenumusic');
}

function clearScene()
{
	// delete smokes
	
	for (var k=0; k<MovingSmokes.length;)
	{
		var s = MovingSmokes[k];
		s.getParent().removeChild(s);
		MovingSmokes.splice(k,1);
	}
	
	// delete photons
	
	for (var i=0; i<MovingPhotons.length;)
	{
		var p = MovingPhotons[i];
		p.getParent().removeChild(p);
		MovingPhotons.splice(i,1);
	}
	
	// delete asteroids
	
	for (var j=0; j<MovingAsteroids.length; ++j)
	{
		var ast = MovingAsteroids[j];
		ast.getParent().removeChild(ast);
		MovingAsteroids.splice(j,1);
	}
}

function startGame()
{
	var engine = TheEngine;
	var gamehud = document.getElementById('gamehud');
	if (gamehud)
		gamehud.style.display = 'block';
		
	var mainmenupanel = document.getElementById('game_mainmenu');
	if (mainmenupanel)
		mainmenupanel.style.display = 'none';
		
	GameTimeRemainingSpan = document.getElementById('gametimeremaining');
	GameScoreSpan = document.getElementById('gamescore');
	
	GameScore = 0;
	GameCamRelativeRotationX = 0;
	GameCamRelativeRotationY = 0;
	verticalLookMoveSpeed = 0;
	horizontalLookMoveSpeed = 0;
	movementSpeed = 0;
	LastShootTime = 0;
	
	clearScene();
	createRandomAsteroidField(engine, 11);
		
	playmusic('gamemusic');
	
	var scene = engine.getScene();
		
	if (!scene)
		return;
	
	// create game camera
	if (GameCam == null)
	{
		GameCam = new CL3D.CameraSceneNode();
		scene.getRootSceneNode().addChild(GameCam);	

		// there is no camera, so probably also no keys registered yet
		
		document.onkeydown = handleKeyDown;
		document.onkeyup = handleKeyUp;
	}	

	scene.setActiveCamera(GameCam);	
	GameCam.setTarget(new CL3D.Vect3d(1,1,1));
	GameCam.Pos.set(0,0,0);
	
	GameState = 'game';
	LastGameCalculationTime = new Date().getTime();
	LastGameStartTime = LastGameCalculationTime;
}

function handleKeyDown(event) 
{
	setKeyBool(true, event.keyCode);
}

function handleKeyUp(event) 
{
	setKeyBool(false, event.keyCode);
}

function setKeyBool(down, code)
{
	// 37 = left arrow key
	// 38 = up arrow key
	// 39 = right arrow key
	// 40 = down arrow key
	// 65 = a or A
	// 87 = w or W
	// 68 = d or D
	// 83 = s or S

	switch(code)
	{
	case 37:
	case 65: leftKeyDown = down; break;
	case 39:
	case 68: rightKeyDown = down; break;
	case 38:
	case 87: upKeyDown = down; break;
	case 40:
	case 83: downKeyDown = down; break;
	case 13: enterKeyDown = down; break;
	case 32: spaceKeyDown = down; break;
	case 18: //alt
	case 17: // ctrl
	case 9: // tab
	case 16: shiftKeyDown = down; break;
	case 27: if (GameState == 'game') backToMainMenu(); break;
	}		
}

function startMapLoading()
{
	// hide loading button
	var loadingbutton = document.getElementById('cl_loadingbutton');
	if (loadingbutton)
		loadingbutton.style.display = 'none';
									
	var engine = new CL3D.CopperLicht('3darea', true, 30);
	TheEngine = engine;
	
	if (!engine.initRenderer())
	{
		// no webgl, show warning message
		var nowebglmessage = document.getElementById('cl_nowebgl');
		if (nowebglmessage)
			nowebglmessage.style.display = 'block';
		return;
	}
	
	// show loading text
	var loadinglabel = document.getElementById('cl_loadinglabel');
	if (loadinglabel)
		loadinglabel.style.display = 'block';
	
	// start loading
	engine.load('copperlichtdata/scene1.ccbjs');
	
	engine.OnLoadingComplete = function() 
	{							
		// record game starting time for deteministic behavior
		
		AppStartTime = new Date().getTime();
		
		// hide loading display
		
		var loadinglabel = document.getElementById('cl_loadinglabel');
		if (loadinglabel)
			loadinglabel.style.display = 'none';
			
		// register animation function
		
		engine.OnAnimate = onAnimate;
			
		// show main menu, start menu music
		
		collect3dObjectPrototypes(engine);
		createRandomAsteroidField(engine, 10);
		
		playmusic('mainmenumusic');
		var mainmenupanel = document.getElementById('game_mainmenu');
		if (mainmenupanel)
			mainmenupanel.style.display = 'block';
		
		var scene = engine.getScene();
		
		if (scene)
		{
			// create a camera for the main menu, rotating through the scene
			MainMenuCam = new CL3D.CameraSceneNode();
			MainMenuCam.setTarget(new CL3D.Vect3d(1,1,1));
			scene.getRootSceneNode().addChild(MainMenuCam);								
			scene.setActiveCamera(MainMenuCam);
			scene.setBackgroundColor(Core.createColor(255,0,0,0));
		}
		
	};
}

function decceleratevalue(timeDiff, value, deceleratefact, maxspeed)
{
	var p = deceleratefact * timeDiff;
	
	if (value < 0)
	{
		value += p;
		if (value > 0) value = 0;
	}
	else
	if (value > 0)
	{
		value -= p;
		if (value < 0) value = 0;
	}
	
	if (value > maxspeed)
		value = maxspeed;
	if (value < -maxspeed)
		value = -maxspeed;
	
	return value;
}

function normalizeAngle(angle)
{
	if (angle < 0.0)
		angle += 360.0;
	if (angle >= 360.0)
		angle -= 360.0;
		
	return angle;
}

function doCameraMovement(timeDiff)
{
	if (!GameCam)
		return;
		
	var accelspeed = 0.10 * timeDiff;
	if (enterKeyDown || shiftKeyDown)
		movementSpeed += accelspeed;
		
	movementSpeed = decceleratevalue(timeDiff, movementSpeed, 0.03, 20);
	
	var moveDir = GameCam.getTarget().substract(GameCam.Pos).multiplyWithScal(movementSpeed);
	GameCam.Pos = GameCam.Pos.add(moveDir);
	GameCam.setTarget(GameCam.getTarget().add(moveDir));
	GameCam.updateAbsolutePosition();
}

function doCameraLookMovement(timeDiff)
{
	if (!GameCam)
		return;
		
	// accelerate/desellerate look movement:
	var accelspeed = 0.10 * timeDiff;
	if (upKeyDown)
		verticalLookMoveSpeed += accelspeed;
	if (downKeyDown)
		verticalLookMoveSpeed -= accelspeed;
	if (leftKeyDown)
		horizontalLookMoveSpeed -= accelspeed;
	if (rightKeyDown)
		horizontalLookMoveSpeed += accelspeed;
		
	verticalLookMoveSpeed = decceleratevalue(timeDiff, verticalLookMoveSpeed, 0.03, 20);
	horizontalLookMoveSpeed = decceleratevalue(timeDiff, horizontalLookMoveSpeed, 0.03, 20);
	
	// constants:	
	var RotateSpeed = 200.0;
	var MaxVerticalAngle = 88.0;
	
	// start calculating:
	var target = new CL3D.Vect3d(0,0,1);
	var mat = new CL3D.Matrix4();
	mat.setRotationDegrees(new CL3D.Vect3d( GameCamRelativeRotationX, GameCamRelativeRotationY, 0));
	mat.transformVect(target);	
	
	// move lookat target up / down
	
	var maxdiff = 300; // to limit the maximum diff in pixels			
	var ydiff = 0;
	var RotateSpeedFactX = 1 / 50000.0;
	var RotateSpeedFactY = 1 / 50000.0;
	
	//if (this.ExactRotation)
	//	RotateSpeedFact *= 3.0;
		
	if (verticalLookMoveSpeed)
		ydiff = verticalLookMoveSpeed;
		
	if (ydiff > maxdiff) ydiff = maxdiff;
	if (ydiff < -maxdiff) ydiff = -maxdiff;
	GameCamRelativeRotationX += ydiff * (timeDiff * (RotateSpeed * RotateSpeedFactY));
	
	if (GameCamRelativeRotationX < -MaxVerticalAngle)
 		GameCamRelativeRotationX = -MaxVerticalAngle;
	if (GameCamRelativeRotationX > MaxVerticalAngle)
		GameCamRelativeRotationX = MaxVerticalAngle;
		
	// move lookat target left / right
		
	var xdiff = 0;
	
	if (horizontalLookMoveSpeed)
		xdiff = horizontalLookMoveSpeed;
			
	if (xdiff > maxdiff) xdiff = maxdiff;
	if (xdiff < -maxdiff) xdiff = -maxdiff;
	GameCamRelativeRotationY += xdiff * (timeDiff * (RotateSpeed * RotateSpeedFactX));	
		
	// finally set target
	
	GameCam.setTarget(GameCam.Pos.add(target));
}

function addSmoke(count, radius, center, timeDiff, now)
{
	if (!GameCam || SmokeTemplate == null)
		return;
		
	var engine = TheEngine;
	var scene = engine.getScene();
	if (!scene)
		return;
		
	for (var i=0; i<count; ++i)
	{
		var clone = SmokeTemplate.createClone(scene.getRootSceneNode());
		clone.Pos.X = center.X + (Math.random() * radius*2) - radius;
		clone.Pos.Y = center.Y + (Math.random() * radius*2) - radius;
		clone.Pos.Z = center.Z + (Math.random() * radius*2) - radius;
		clone.Visible = true;
		
		clone.gameEndLiveTime = now + (Math.random() * 2000);
		
		var moveDir = clone.Pos.substract(center);
		moveDir.normalize();
		clone.gameMoveDir = moveDir;
				
		MovingSmokes.push(clone);
	}
}

function doShoot(timeDiff, now)
{
	if (!GameCam || !spaceKeyDown || PhotonTemplate == null)
		return;
		
	var engine = TheEngine;
	var scene = engine.getScene();
	if (!scene)
		return;
		
	if (LastShootTime == 0 || now - LastShootTime > 100)
	{
		LastShootTime = now;
		
		var clone = PhotonTemplate.createClone(scene.getRootSceneNode());
		clone.Pos.X = GameCam.Pos.X;
		clone.Pos.Y = GameCam.Pos.Y;
		clone.Pos.Z = GameCam.Pos.Z;
		clone.Visible = true;
		
		var moveDir = GameCam.getTarget().substract(GameCam.Pos);
		moveDir.normalize();
		clone.gameMoveDir = moveDir;
		clone.gameEndLiveTime = now + 1000;
		
		MovingPhotons.push(clone);
		
		// play sound
		playsound('shootsound');
	}
}

function doMoveAndCollideObjects(diff, now)
{
	// move smokes
	for (var k=0; k<MovingSmokes.length;)
	{
		var s = MovingSmokes[k];
				
		if (s.gameEndLiveTime < now)
		{
			s.getParent().removeChild(s);
			MovingSmokes.splice(k,1);
		}
		else
		{
			s.Pos.addToThis(s.gameMoveDir.multiplyWithScal(diff * 0.1));
			s.updateAbsolutePosition();
			
			++k;
		}
	}
	
	// move photons and delete them
	
	for (var i=0; i<MovingPhotons.length;)
	{
		var p = MovingPhotons[i];
		var deletephoton = false;
		
		if (p.gameEndLiveTime < now)
		{
			deletephoton = true;
		}
		else
		{	
			// move
			
			p.Pos.addToThis(p.gameMoveDir.multiplyWithScal(diff * 0.8));
			p.updateAbsolutePosition();
			
			// collide with all asteroids
			
			for (var j=0; j<MovingAsteroids.length; ++j)
			{
				var ast = MovingAsteroids[j];
				if (ast.Pos.getDistanceTo(p.Pos) < 60)
				{
					// collision
					
					// remove asteroid
					ast.getParent().removeChild(ast);
					MovingAsteroids.splice(j,1);
					deletephoton = true;
					playsound('explosion');
					
					GameScore += 110;
					
					addSmoke(40, 40, ast.Pos, diff, now);
					
					break;
				}
			}
		}
		
		if (deletephoton)
		{
			// delete photon
			p.getParent().removeChild(p);
			MovingPhotons.splice(i,1);
		}
		else
		{
			// to next photon
			++i;
		}
	}
}

function doGameLogic()
{
	var now = new Date().getTime();
	var diff = now - LastGameCalculationTime;
	if (diff > 500)
		diff = 500;
		
	// animate main camera
	doCameraLookMovement(diff);
	doCameraMovement(diff);
	doShoot(diff, now);
	doMoveAndCollideObjects(diff, now);
	
	LastGameCalculationTime = now;
	
	// update hud
	
	var timeRemaining = 0;
	if (GameTimeRemainingSpan != null)
	{
		var maxGameTime = 60000;
		timeRemaining = ((LastGameStartTime + maxGameTime) - now) / 1000;
		if (timeRemaining < 0)
			timeRemaining = 0;
		else
		{
			timeRemaining = timeRemaining.toFixed(2);
		}
		GameTimeRemainingSpan.innerHTML = timeRemaining;
	}
	
	if (GameScoreSpan != null)
	{
		GameScoreSpan.innerHTML = GameScore;
	}
	
	// back to main menu when time over
	if (timeRemaining <= 0)
	{
		backToMainMenu();
	}
}
