// Constants
var Radian = 180.0 / Math.PI;

// Namespaces
var Element = new Object();

Element.setStyle = function(elem, attribute, value) {
  if (elem.style) {
    var statement = "elem.style." + attribute + "='" + value + "'";
    eval(statement);
  } else {
    elem.setAttributeNS(null, attribute, value);
  }
}

var Keyboard = { control:17, down:40, left:37, right:39, shift:16, space:32, tab:9, up:38 };

Keyboard.doKey = function (e) {
  if (!e) e = window.event; // MSIE6 compatibility
  switch (e.keyCode) {
    case Keyboard.control: 
    case Keyboard.tab: // Opera compatiblity
      Ship.fire();
      break;
    case Keyboard.space:
      Ship.teleport();
      break;
    case Keyboard.left: 
      Ship.rotate(-15);
      break;
    case Keyboard.right: 
      Ship.rotate(15);
      break;
    case Keyboard.up:
      Ship.thrust(1);
      break;
    default:
  }
}

Keyboard.enable = function() {
  try {
    window.addEventListener("keydown", this.doKey, true);
  } catch (e) {
    document.attachEvent("onkeydown", this.doKey); // MSIE6 compatibility
  }
}

var Svg = { 
  Namespace:"http://www.w3.org/2000/svg",
  Document:null,
  Element:null,
  Height:460,
  Width:580
};

Svg.initialize = function() {
  var canvas = document.getElementById("canvas");
  if (typeof canvas.getSVGDocument != "undefined") {
    // MSIE6 + ASV3 compatibility
    this.Document = canvas.getSVGDocument();
    this.Element = Svg.Document.documentElement;
  } else {
    this.Document = document;
    this.Element = canvas;
  }
}

// Objects
var Bullets = new Array();
Bullets.Duration = 50;
Bullets.Prefix = "bullet";
Bullets.Speed = 6;
/* Hack to distribute bullets for ship and saucer */
Bullets.ForShip = 0;
Bullets.ForSaucer = 0;

Bullets.collision = function() {
  for (var i = 0; i < this.length; ++i) {
    var bullet = this[i];
    if (!bullet.active) continue;
    if (bullet.owner != Ship && bullet.collision(Ship)) {
      bullet.active = false;
      bullet.hide();
      Player.loseLife();
      Explosions.add(Ship);
      Ship.start();
      continue;
    }
    if (bullet.owner != Saucer && bullet.collision(Saucer)) {
      bullet.active = false;
      bullet.hide();
      Saucer.active = false;
      Saucer.hide();
      Explosions.add(Saucer);
      if (Saucer.type == Saucer.Smart) {
	Player.score += Saucer.SmartScore;
      } else {
	Player.score += Saucer.StupidScore;
      }
    }
  }
}

Bullets.getInactiveForShip = function() {
  for (var i = 0; i < Bullets.ForShip; ++i) {
    var bullet = this[i];
    if (bullet.active) continue;
    return bullet;
  }
  return null;
}

Bullets.getInactiveForSaucer = function() {
  for (var i = this.ForShip; i < this.length; ++i) {
    var bullet = this[i];
    if (bullet.active) continue;
    return bullet;
  }
  return null;
}

Bullets.initialize = function(forShip, forSaucer) {
  var radius = 1;
  this.ForShip = forShip;
  this.ForSaucer = forSaucer;
  var n = forShip + forSaucer;
  for (var i = 0; i < n; ++i) {
    var id = Bullets.Prefix + i;
    var elem = Svg.Document.createElementNS(Svg.Namespace, "circle");
    elem.setAttributeNS(null, "stroke", "white");
    elem.setAttributeNS(null, "r", radius);
    var bullet = new Sprite();
    bullet.active = false;
    bullet.duration = Bullets.Duration;
    bullet.elem = elem;
    bullet.radius = radius;
    bullet.hide();
    this[i] = bullet;
    Svg.Element.appendChild(elem);
  }
}

Bullets.update = function() {
  for (var i = 0; i < this.length; ++i) {
    var bullet = this[i];
    bullet.duration--;
    if (bullet.duration <= 0) {
      bullet.active = false;
      bullet.hide();
      continue;
    }
    bullet.update();
  }
}

var Explosions = new Array();
Explosions.Prefix = "explosion";
Explosions.StartRadius = 10;
Explosions.EndRadius = 20;
Explosions.StartScale = 1.0;
Explosions.EndScale = 2.0;

Explosions.add = function(sprite) {
  var explosion = this.getInactive();
  if (explosion === null) return;
  explosion.active = true;
  explosion.x = sprite.x;
  explosion.y = sprite.y;
  explosion.scale = Explosions.StartScale;
  explosion.opacity = 1.0;
}

Explosions.initialize = function(n) {
  var scale = this.StartScale;
  for (var i = 0; i < n; ++i) {
    var id = Explosions.Prefix + i;
    var elem = Svg.Document.createElementNS(Svg.Namespace, "circle");
    elem.setAttributeNS(null, "fill", "white");
    elem.setAttributeNS(null, "fill-opacity", "0.0");
    elem.setAttributeNS(null, "r", 10.0);
    var explosion = new Sprite();
    explosion.active = false;
    explosion.elem = elem;
    explosion.opacity = 0.0;
    explosion.scale = scale;
    explosion.hide();
    this[i] = explosion;
    Svg.Element.appendChild(elem);
  }
}

Explosions.getInactive = function() {
  for (var i = 0; i < this.length; ++i) {
    var explosion = this[i];
    if (explosion.active) continue;
    return explosion;
  }
  return null;
}

Explosions.update = function() {
  for (var i = 0; i < this.length; ++i) {
    var explosion = this[i];
    if (!explosion.active) continue;
    if (explosion.scale < Explosions.EndScale) {
      explosion.scale += 0.1;
      explosion.opacity -= 0.1;
      if (explosion.opacity <= 0.1) explosion.opacity = 0.0;
    } else {
      explosion.active = false;
      explosion.hide();
    }
    explosion.update();
  }
}

var Player = { lastThousand:0, lives:3, livesElement:null, score:0, scoreElement:null };

Player.initialize = function() {
  this.livesElement = document.getElementById("lives");
  this.scoreElement = document.getElementById("score");
}

Player.loseLife = function() {
  this.lives--;
}

Player.start = function() {
  this.lastThousand = 0;
  this.lives = 3;
  this.score = 0;
}

Player.update = function() {
  if (this.score - this.lastThousand > 1000) {
    ++this.lives;
    this.lastThousand += 1000;
  }
  this.livesElement.value = this.lives;
  this.scoreElement.value = this.score;
}

var Rocks = new Array();
Rocks.Prefix = "rock";
Rocks.InitialSpeed = [1, 2, 3];
Rocks.numActive = 0;
Rocks.DAngle = [0.5, 1, 2];
Rocks.Radius = [30, 15, 7];
Rocks.Score = [10, 20, 40];

Rocks.collision = function() {
  for (var i = 0; i < this.length; ++i) {
    var rock = this[i];
    if (!rock.active) continue;
    if (rock.collision(Ship)) {
      this.splitRock(rock);
      Player.loseLife();
      Explosions.add(Ship);
      Ship.start();
    }
    if (Saucer.active && rock.collision(Saucer)) {
      this.splitRock(rock);
      Explosions.add(Saucer);
      Saucer.active = false;
      Saucer.hide();
    }
    for (var j = 0; j < Bullets.length; ++j) {
      var bullet = Bullets[j];
      if (!bullet.active) continue;
      if (rock.collision(bullet)) {
        this.splitRock(rock);
        bullet.active = false;
        bullet.hide();
        Explosions.add(bullet);
        if (bullet.owner !== Ship) continue;
        var index = this.getRadiusIndex(rock.radius);
        if (index < 0) {
          alert("Unexpected radius index");
          return;
        }
        var score = this.Score[index];
        Player.score += score;
      }
    }
  }
}

Rocks.createPoints = function(radius) {
  var s = "";
  var angle = 0;
  var numVertices = 8;
  var dangle = 360 / numVertices;
  for (var i = 0; i < numVertices; ++i) {
    var x = radius * Math.cos(angle / Radian);
    var y = radius * Math.sin(angle / Radian);
    angle += dangle;
    var rangle = Math.random() * 5;
    if (Math.random() > 0.5) angle += rangle;
    else angle -= rangle;
    s += x + "," + y + " ";
  }
  return s;
}

Rocks.createRock = function(index, radiusIndex, state) {
  var radius = this.Radius[radiusIndex];
  var initialSpeed = this.InitialSpeed[radiusIndex];
  var id = Rocks.Prefix + index;
  var elem = Svg.Document.createElementNS(Svg.Namespace, "polygon");
  elem.setAttributeNS(null, "points", this.createPoints(radius));
  elem.setAttributeNS(null, "stroke", "powderblue");
  elem.setAttributeNS(null, "stroke-width", "1");
  var rock = new Sprite();
  rock.active = state;
  if (Math.random() > 0.5) rock.dx = -Math.random() * initialSpeed - 0.1;
  else rock.dx = Math.random() * initialSpeed + 0.1;
  if (Math.random() > 0.5) rock.dy = -Math.random() * initialSpeed - 0.1;
  else rock.dy = Math.random() * initialSpeed + 0.1;
  rock.elem = elem;
  rock.radius = radius;
  if (Math.random() > 0.5) rock.x = Math.random() * (Svg.Width / 2) - 20;
  else rock.x = Math.random() * (Svg.Width / 2) + Svg.Width / 2 + 20;
  rock.y = Math.random() * Svg.Height;
  if (Math.random() > 0.5) rock.dangle = Rocks.DAngle[radiusIndex];
  else rock.dangle = -Rocks.DAngle[radiusIndex];
  if (state == false) rock.hide();
  this[index] = rock;
  Svg.Element.appendChild(elem);
}

Rocks.getInactive = function(radius) {
  for (var i = 0; i < this.length; ++i) {
    var rock = this[i];
    if (rock.active) continue;
    if (rock.radius != radius) continue;
    return rock;
  }
  return null;
}

Rocks.getRadiusIndex = function(radius) {
  var radiusArray = this.Radius;
  var index = -1; // Error condition
  for (var i = 0; i < radiusArray.length; ++i) {
    if (radius != radiusArray[i]) continue;
    index = i;
    break;
  }
  return index;
}

Rocks.initialize = function(n) {
  var r = Rocks.Radius[0];
  var num = n;
  for (var i = 0; i < num; ++i) {
    this.createRock(i, 0, false);
  }
  r = Rocks.Radius[1];
  num += n * 2;
  for (; i < num; ++i) {
    this.createRock(i, 1, false);
  }
  r = Rocks.Radius[2];
  num += n * 4;
  for (; i < num; ++i) {
    this.createRock(i, 2, false);
  }
}

Rocks.splitRock = function(rock) {
  --this.numActive;
  rock.active = false;
  rock.hide();
  var radiusIndex = this.getRadiusIndex(rock.radius);
  if (radiusIndex == this.Radius.length - 1) {
    return;
  }
  ++radiusIndex;
  var radius = this.Radius[radiusIndex]
  var child = this.getInactive(radius);
  if (child === null) {
    alert("Unexpected error.  Cannot make child 1.");
    return;
  }
  child.active = true;
  child.x = rock.x;
  child.y = rock.y;
  child = this.getInactive(radius);
  if (child === null) {
    alert("Unexpected error.  Cannot make child 2.");
    return;
  }
  child.active = true;
  child.x = rock.x;
  child.y = rock.y;
  this.numActive += 2;
}

Rocks.start = function(n) {
  for (var i = 0; i < n; ++i) {
    var rock = this[i];
    rock.active = true;
    while (rock.collision(Ship)) {
      rock.x = Math.random() * Svg.Width;
      rock.y = Math.random() * Svg.Height;
    }
  }
  this.numActive = n;
}

Rocks.update = function() {
  for (var i = 0; i < this.length; ++i) {
    var rock = this[i];
    if (!rock.active) continue;
    rock.update();
  }
}

var Sprite = function() {
  this.active = false;
  this.angle = 0;
  this.dangle = 0;
  this.duration = 0; // Support time-limited sprites like bullets and flame.
  this.dx = 0;
  this.dy = 0;
  this.elem = null;
  this.opacity = 0.0; // Support explosions.
  this.owner = null; // Avoid hitting oneself by own bullets.
  this.radius = 0; // Use for collision detection.
  this.scale = 1.0;
  this.x = 0;
  this.y = 0;
}

// Very simple collision detection based on the radius of both sprites.
Sprite.prototype.collision = function(sprite) {
  if (!sprite.active) return false;
  var xDistance = Math.abs(this.x - sprite.x);
  var yDistance = Math.abs(this.y - sprite.y);
  var distance = Math.pow(xDistance,2) + Math.pow(yDistance,2);
  var minDistance = Math.pow(this.radius + sprite.radius, 2);
  return (distance < minDistance) ? true : false;
}

Sprite.prototype.hide = function() {
  this.elem.setAttributeNS(null, "stroke-opacity", "0.0");
}

Sprite.prototype.update = function() {
  var angle = this.angle + this.dangle;
  var scale = this.scale;
  var x = this.x + this.dx;
  var y = this.y + this.dy;
  if (x < 0) x = Svg.Width;
  else if (x > Svg.Width) x = 0;
  if (y < 0) y = Svg.Height;
  else if (y > Svg.Height) y = 0;
  var a = Math.cos(angle / Radian) * scale;
  var b = Math.sin(angle / Radian);
  var c = -b;
  var d = a;
  var e = x;
  var f = y;
  var sMatrix = "matrix(" + a + " " + b + " " + c + " " + d + " " + e + " " + f + ")";
/*
  var m = Svg.Element.createSVGMatrix();
  m.a = a;
  m.b = b;
  m.c = c;
  m.d = d;
  m.e = e;
  m.f = f;
  var t = Svg.Element.createSVGTransformFromMatrix(m);
  elem.transform.baseVal.initialize(t);
*/
  var elem = this.elem;
  elem.setAttributeNS(null, "transform", sMatrix);
  elem.setAttributeNS(null, "fill-opacity", this.opacity);
  elem.setAttributeNS(null, "stroke-opacity", "1.0");
  this.x = x;
  this.y = y;
  this.angle = angle;
}

// Has to be declared after Sprite()
var Saucer = new Sprite();
Saucer.id = "saucer";
Saucer.type = 0;
Saucer.Stupid = 0;
Saucer.StupidRadius = 24;
Saucer.StupidScore = 100;
Saucer.Smart = 1;
Saucer.SmartRadius = 16;
Saucer.SmartScore = 200;

Saucer.initialize = function() {
  this.elem = Svg.Document.getElementById(Saucer.id);
  this.hide();
}

Saucer.fire = function() {
  var bullet = Bullets.getInactiveForSaucer();
  if (bullet === null) return;
  var x = Ship.x - this.x;
  var y = Ship.y - this.y;
  var ratio = 0.0;
  if (x == 0) ratio = Number.POSITIVE_INFINITY;
  else ratio = y / x;
  var angle = 0.0;
  if (this.type == this.Smart) {
    angle = Math.atan(ratio) * Radian;
  } else {
    angle = Math.random() * 360;
  }
  var dx = Bullets.Speed * Math.cos(angle / Radian);
  var dy = Bullets.Speed * Math.sin(angle / Radian);
  if (x < 0) {
    dx = -dx;
    dy = -dy;
  }

  bullet.active = true;
  bullet.duration = Bullets.Duration;
  bullet.dx = dx;
  bullet.dy = dy;
  bullet.owner = this;
  bullet.x = this.x;
  bullet.y = this.y;
}

Saucer.move = function() {
  if (Math.random() < 0.005) this.dy = -this.dy;
  this.fire();
}

Saucer.start = function() {
  this.active = true;
  if (Math.random() > 0.5) {
    this.dx = 2;
    this.x = 0;
  } else {
    this.dx = -2;
    this.x = Svg.Width;
  }
  if (Math.random() > 0.5) this.dy = 2;
  else this.dy = -2;
  this.y = Math.random() * Svg.Height;
  var scale = 1;
  if (Math.random() > 0.5) {
    this.type = this.Stupid;
    this.radius = this.StupidRadius;
    this.scale = 1.5;
  } else {
    this.type = this.Smart;
    this.radius = this.SmartRadius;
    this.scale = 1.0;
  }
}

var Ship = new Sprite();
Ship.id = "ship";

Ship.fire = function() {
  var bullet = Bullets.getInactiveForShip();
  if (bullet === null) return;
  var angle = this.angle;
  var dx = Bullets.Speed * Math.cos(angle / Radian);
  var dy = Bullets.Speed * Math.sin(angle / Radian);
  bullet.active = true;
  bullet.duration = Bullets.Duration;
  bullet.dx = dx;
  bullet.dy = dy;
  bullet.owner = this;
  bullet.x = this.x;
  bullet.y = this.y;
}

Ship.initialize = function() {
  this.active = true;
  this.radius = 8;
  this.elem = Svg.Document.getElementById(Ship.id);
  this.flameElem = Svg.Document.getElementById(Ship.flameId);
}

Ship.rotate = function(dangle) {
  this.angle += dangle;
}

// Cannot overload update() and call this.prototype.update()?
Ship.slowdown = function() {
  this.dx *= 0.99;
  this.dy *= 0.99;
  --this.duration;
}

Ship.start = function() {
  this.dangle = 0;
  this.dx = 0;
  this.dy = 0;
  this.duration = 0;
  this.x = Svg.Width / 2;
  this.y = Svg.Height / 2;
}

Ship.teleport = function() {
  this.dx = 0;
  this.dy = 0;
  this.x = Math.random() * Svg.Width;
  this.y = Math.random() * Svg.Height;
}

Ship.thrust = function(power) {
  var angle = this.angle;
  var ddx = power * Math.cos(angle / Radian);
  var ddy = power * Math.sin(angle / Radian);
  this.dx += ddx;
  this.dy += ddy;
  this.dangle = 0;
}

var Tab = ["game", "instructions", "notes"];

// TODO!  Pause game when a tab control (not key) is pressed.
Tab.show = function (tabName) {
  for (var i = 0; i < this.length; ++i) {
    var div = document.getElementById(this[i]);
    if (tabName == this[i]) {
      Element.setStyle(div, "display", "block");
      continue;
    }
    Element.setStyle(div, "display", "none");
  }
}

var Game = { active:true, currentLevel:0, messageElement:null }
Game.levels = [3, 5, 7, 9]; // Number of rocks.

Game.end = function() {
  this.active = false;
  this.setMessage("Game Over");
  this.showMessage();
}

Game.hideMessage = function() {
  var elem = Game.messageElement;
  elem.setAttributeNS(null, "fill-opacity", "0.0");
  elem.setAttributeNS(null, "stroke-opacity", "0.0");
}

Game.initialize = function() {
  Svg.initialize();
  Saucer.initialize();
  Ship.initialize();
  var numLevels = Game.levels.length;
  var maxRocks = this.levels[numLevels - 1];
  Rocks.initialize(maxRocks);
  Bullets.initialize(5,1);
  Explosions.initialize(10);
  Player.initialize();
  Game.messageElement = Svg.Document.getElementById("message");
  Tab.show("game");
  Keyboard.enable();
  Game.hideMessage();
  Ship.start();
  Player.start();
  Player.update();
  Game.start(0);
  Game.update();
}

Game.showMessage = function() {
  var elem = Game.messageElement;
  elem.setAttributeNS(null, "fill-opacity", "1.0");
  elem.setAttributeNS(null, "stroke-opacity", "1.0");
}

Game.setMessage = function(text) {
  var textNode = this.messageElement.firstChild;
  var len = textNode.data.length;
  textNode.deleteData(0, 14);
  textNode.insertData(0, text);
}

Game.start = function(level) {
  var numRocks = Game.levels[level];
  Rocks.start(numRocks);
  Ship.start();
}

Game.update = function() {
  if (Saucer.active) {
    Saucer.move();
    Saucer.update();
  } else if (Rocks.numActive <= 2) {
    if (Math.random() > 0.99) Saucer.start();
  }
  if (Rocks.numActive <= 0) {
    var numLevel = Game.levels.length;
    var level = (this.currentLevel + 1) % numLevel;
    this.start(level);
  }
  Rocks.collision();
  Bullets.collision();
  Ship.slowdown();
  Ship.update();
  Rocks.update();
  Bullets.update();
  Player.update();
  Explosions.update();
  if (Player.lives <= 0) Game.end();
  if (this.active) setTimeout("Game.update()", 20);
}
