function random(x) {
  return Math.random()*x;
}

function randcolor() {
  return [Math.random()*255, Math.random()*255, Math.random()*255];
}

function randomSign() {
  var x = Math.random();
  if (x > 0.5)
    return 1;
  else
    return -1;
}

function getDistance(point1, point2) {
  return Math.sqrt(Math.pow(point2[0] - point1[0], 2)
                   + Math.pow(point2[1] - point1[1], 2));
}

function randomDirection() {
  var res = Math.random()*0.6*randomSign();
  if (Math.abs(res) < 0.13) {
    if (res < 0) return -0.13;
    else         return 0.13;
  }
  else return res;
}

var Text = klass.create();
Object.extend(Text, {
  drawText: function(g, text, position, opts) {
    if (text && g.mozDrawText) {
      g.save();
      g.fillStyle = "rgba(" + Math.round(opts.color[0]) + ", "
                            + Math.round(opts.color[1]) + ", "
                            + Math.round(opts.color[2]) + ", "
		            + opts.alpha + ")";
      g.mozTextStyle = Text.getTextStyle(opts);
      g.translate(position[0], position[1]);
      g.mozDrawText(text);
      g.restore();
    }
  },

  getTextStyle: function(opts) {
    return opts.style + ' ' + opts.size + "px " + opts.typefamily;
  },

  measureText: function(g, text, opts) {
    if (g.mozMeasureText) {
      g.save();
      g.mozTextStyle = Text.getTextStyle(opts);
      var res = g.mozMeasureText(text)
      g.restore();
      return res;
    } else {
      return 0;
    }
  }
});

Object.extend(Text.prototype, {
  initialize: function(scene, text, position, opts) {
    this.text = text || '';
    this.scene = scene;
    this.graphics = scene.graphics;
    this.position = position || [10, 10];
    this.scene.addChild(this);
    opts = opts || {}
    this.options = {};
    this.options.size = opts.size || 12;
    this.options.style = opts.style || '';
    this.options.typefamily = opts.typefamily || 'Arial';
    this.options.color = opts.color || [0, 0, 0];
    this.options.alpha = opts.alpha || 1;
  }, 

  setTypeFamily: function(typefamily) {
    this.options.typefamily = typefamily;
  },

  setStyle: function(style) {
    this.options.style = style;
  },

  setSize: function(size) {
    this.options.size = size;
  },

  setColor: function(color) {
    this.options.color = color;
  },

  setAlpha: function(alpha) {
    this.options.alpha = alpha;
  },

  setPosition: function(position) {
    this.position = position;
  },

  getWidth: function() {
    return Text.measureText(this.graphics, this.text, this.options);
  },

  onEnterFrame: function() {
    Text.drawText(this.graphics, this.text, this.position, this.options);
  }
});


var Ball = klass.create();
Object.extend(Ball.prototype, {
  initialize: function(scene, size, position, direction, color) {
    this.g = scene.graphics;
    this.scene = scene;
    this.scene.addChild(this);
    this.size = size || 50;
    this.position = position || [0, 0];
    this.direction = direction || [0, 2];
    this.color = color || [0, 0, 0];
    this.timeline = 0;
    this.exploding = false;
  },

  advance: function() {
    var sceneWidth = this.scene.width;
    var sceneHeight = this.scene.height;
    var oldPos = this.position;
    this.position = [this.direction[0] + this.position[0],
                     this.direction[1] + this.position[1]];
    if ((this.position[0] > sceneWidth) || (this.position[0] < 0)) {
      this.direction[0] = -(this.direction[0]);
    }

    if ((this.position[1] > sceneHeight) || (this.position[1] < 0)) {
      this.direction[1] = -(this.direction[1]);
    }
    this.position = [this.direction[0] + oldPos[0],
                     this.direction[1] + oldPos[1]];
  },

  colorDef: function(color) {
    var alpha = color[3] || 0.5;
    return  "rgba(" + Math.round(color[0]) + ", "
                    + Math.round(color[1]) + ", "
                    + Math.round(color[2]) + ", "
		    + alpha + ")";
  },

  setExploding: function() {
    this.exploding = true;
  },

  adjustChannel: function(colorChannel, value) {
    var res = colorChannel + value;
    if (res > 255)
      return 255;
    else if (res < 0)
      return 0;
    else
      return Math.round(res);
  },

  upLum: function(color) {
    return [this.adjustChannel(color[0], 2),
            this.adjustChannel(color[1], 2),
            this.adjustChannel(color[2], 2)];
  },

  downLum: function(color) {
    return [this.adjustChannel(color[0], -3),
            this.adjustChannel(color[1], -3),
            this.adjustChannel(color[2], -3)];
  },

  grow: function() {
    this.timeline++;

    // Less than ideal timeline transitions
    this.color = this.upLum(this.color);
    if (this.timeline > 120) {
      this.size -=5;
    } else if (this.timeline > 30) {
      this.size += 0.2;
    } else {
      this.size += 3;
    }

    if (this.size < 0) {
      this.scene.removeChild(this);
      this.scene.removeExploding(this);
    }
  },

  onEnterFrame: function() {
    if (this.exploding) {
      this.grow();
      this.scene.capture(this);
    } else {
      this.advance();
    }
    var g = this.g;
    g.save();
    g.beginPath();
    g.fillStyle = this.colorDef(this.color);
    g.arc(this.position[0], this.position[1], this.size/2,
	  0, Math.PI*2, 0);
    g.fill();
    g.closePath();
    g.restore();
  }
});

var GameState = {
  MENU: 1,
  GAME: 2,
  RESULTS: 3,
  GAME_ENDING: 4
};

var Scene = klass.create();
Object.extend(Scene.prototype, {
  initialize: function(canvas) {
    this.canvas = canvas;
    this.width = canvas.width;
    this.height = canvas.height;
    this.graphics = canvas.getContext('2d');
    this.children = [];
    Event.observe(this.canvas, 'mousedown',
		  this.onMouseDown.bind(this));

    this.framePlay = tasklet(this.enterFrame.bind(this), 10);

    this.moving = [];
    this.exploding = [];

    this.isSeeded = false;

    this.gameState = GameState.MENU;

    // Title screen
    this.titleText = new Text(this, 'moonshine', [0, 0],
			      {size: 32, style: 'bold',
			       color: [255, 255, 255]});
    this.menuText = new Text(this, 'click to start', [0, 0],
			      {size: 18, style: 'bold',
			       color: [200, 200, 200]});
    this.setupMenu();

    this.scoreText = null;

    this.levels = [{total:5, goal:1},
		   {total:8, goal:3},
		   {total:15, goal:5},
		   {total:28, goal:10},
		   {total:35, goal:19},
		   {total:41, goal:32},
		   {total:60, goal:55}];
    this.level = 0;
  },

  hideMenu: function() {
    this.removeChild(this.menuText);
    this.removeChild(this.titleText);
  },

  setupMenu: function() {
    var tw = this.menuText.getWidth();
    this.menuText.setPosition([this.width / 2 - (tw / 2), 250]);

    tw = this.titleText.getWidth();
    this.titleText.setPosition([this.width / 2 - (tw / 2), 150]);
  },

  removeChildren: function() {
    this.children = [];
  },

  score: function() {
    var level = this.levels[this.level];
    return level.total - this.moving.length;
  },

  setupResults: function() {
    this.gameState = GameState.RESULTS;
    this.removeChildren();

    this.resultsText = new Text(this);
    this.resultsText.setSize(32);
    this.resultsText.setStyle('bold');

    this.commentText = new Text(this);
    this.commentText.setSize(28);
    this.commentText.setStyle('bold');
    this.commentText.setColor([255, 255, 255]);

    var level = this.levels[this.level];
    this.resultsText.text = '' + this.score() + '/' + level.goal;
    if (this.score() < level.goal) {
      this.resultsText.setColor([244, 33, 33]);
      this.commentText.text = "d'oh! not quite there";
    } else {
      this.level++;
      this.level = this.level % this.levels.length;

      this.resultsText.setColor([33, 244, 33]);
      this.commentText.text = 'way to go you awesome person you';
    }

    tw = this.resultsText.getWidth();
    this.resultsText.setPosition([this.width / 2 - (tw / 2), 150]);

    tw = this.commentText.getWidth();
    this.commentText.setPosition([this.width / 2 - (tw / 2), 200]);
    this.addChild(this.menuText);
  },

  addChild: function(child) {
    this.children.push(child);
  },

  setExploding: function(child) {
    var newlist = [];
    for (var i=0; i<this.moving.length; ++i)
      if (this.moving[i] != child) newlist.push(this.moving[i]);
    this.moving = newlist;
    this.exploding.push(child);
    child.setExploding();
  },

  removeExploding: function(child) {
    var newlist = [];
    for (var i=0;i<this.exploding.length;++i)
      if (this.exploding[i] != child) newlist.push(this.exploding[i]);
    this.exploding = newlist;
    if (this.exploding.length == 0)
      this.setGameEnding()
  },

  setGameEnding: function() {
    this.gameState = GameState.GAME_ENDING;
    // Stop movement
    for (var i=0; i<this.moving.length; ++i)
      this.moving[i].direction = [0, 0];

    // Start fade-out
    this.fadeOut = 0;
  },

  translateScenePosition: function(evt) {
    var scrollX = window.scrollX != null ? window.scrollX : window.pageXOffset;
    var scrollY = window.scrollY != null ? window.scrollY : window.pageYOffset;
    var mouseX = evt.clientX - this.canvas.offsetLeft + scrollX;
    var mouseY = evt.clientY - this.canvas.offsetTop + scrollY;    
    return [mouseX, mouseY];
  },

  removeChild: function(child) {
    var newlist = [];
    for (var i=0; i<this.children.length; ++i)
      if (this.children[i] != child) newlist.push(this.children[i]);
    this.children = newlist;
  },

  capture: function(child) {
    var position = child.position;
    for (var i=0; i<this.moving.length; ++i) {
      var ball = this.moving[i];
      if (ball.exploding) continue;
      var dist = getDistance(position, ball.position);
      if (dist-(ball.size/2)<child.size/2) {
	this.setExploding(this.moving[i]);
      }
    }
  },

  getClosestBall: function(evt) {
    // not used...
    var minDistance = Number.MAX_VALUE;
    var index = -1;
    var mousePos = this.translateScenePosition(evt);
    for (var i=0; i<this.children.length; ++i) {
      var ball = this.children[i];
      var dist = getDistance(mousePos, ball.position);
      if (dist<minDistance) {
	minDistance = dist
	index = i;
      }
    }

    if (index == -1) return;

    if (minDistance < this.children[index].size/2) {
      // this.children[index].color = [255, 255, 255];
      this.children[index].setExploding();
    }
  },

  startGamePlay: function() {
    var level = this.levels[this.level];
    this.exploding = [];
    this.moving = [];
    for (var i=0; i<level.total; ++i) {
      var child 
        = new Ball(this, 18,
               [Math.random()*this.width, Math.random()*this.height],
               [randomDirection(), randomDirection()],
               randcolor());
      this.moving.push(child);
    }
  },

  startGame: function() {
    // this.hideMenu();
    this.removeChildren();
    this.scoreText = new Text(this, '', [5, 20],
                              {typefamily:'Arial', size: '12', style: 'bold',
			       color: [198, 198, 198]});
    this.startGamePlay();
    this.gameState = GameState.GAME;
    this.isSeeded = false;
    this.exhausted = false;
  },

  onMouseDown: function(evt) {
    if (this.gameState == GameState.GAME) {
      // in game
      if (!this.isSeeded) {
	var mousePos = this.translateScenePosition(evt);
	var seed = new Ball(this, 18, mousePos, [0, 0], [125, 125, 125]);
	this.setExploding(seed);
	this.isSeeded = true;
      }
    } else {
      // in menu
      this.startGame();
    }
  },

  buildScoreText: function() {
    var level = this.levels[this.level];
    return '' + (level.total - this.moving.length)
           + '/' + level.total
	   + ': ' + level.goal;
  },

  onEnterFrame: function() {
    if (this.gameState == GameState.GAME) {
      this.scoreText.text = this.buildScoreText();
    } else if (this.gameState == GameState.GAME_ENDING) {
      // render menu
    }
  },

  onExitFrame: function() {
    if (this.gameState == GameState.GAME_ENDING) {
      this.graphics.fillStyle 
        = "rgba(" + Math.round(0) + ", "
                  + Math.round(0) + ", "
                  + Math.round(0) + ", "
		  + this.fadeOut + ")";
      this.fadeOut += 0.125;
      if (this.fadeOut > 1) {
	this.setupResults();
      }
      this.graphics.fillRect(0, 0, this.width, this.height);
    }
  },

  enterFrame: function() {
    // clear scene
    this.graphics.clearRect(0, 0, this.canvas.width, this.canvas.height);

    // render top-level
    this.graphics.save();
    this.onEnterFrame();
    this.graphics.restore();

    // render children
    this.graphics.save();
    for (var i=0; i<this.children.length; ++i) {
      this.children[i].onEnterFrame();
    }
    this.graphics.restore();

    // render top-level
    this.graphics.save();
    this.onExitFrame();
    this.graphics.restore();
  }
});

function main() {
  var cvs = $('drawing');
  if (!cvs.getContext) { return; }
  var root = new Scene(cvs);
}

document.observe('dom:loaded', this.main.bind(this));
// vim: set sts=2 sw=2:

