
var life =
  {    
    CELLDIM: 8,        // Pixels per side of each square cell.  Size of 
                       // canvas is assumed to be square.

    CHANCE: 0.2,       // With CHANCE probability a cell is set alive during
                       // seeding.

    STYLE: {
      acolor: '#9BCAE1',     // Alive color.
      dcolor: '#FFFFFF'      // Dead color.
    },

    _size: 0,           // number of cells in the population.
    _rw: 0,             // number of cells in a row.
    _pane: null,        // The canvas element.
    _ctx: null,         // The graphics context.
    _neighbors: null,   // Current population.

    _timer: null,


    start : function() { 
      life._pane = $('lifepane');
      life._size = (life._pane.height / life.CELLDIM) * (life._pane.width / life.CELLDIM);
      life._rw = Math.floor(life._pane.width / life.CELLDIM);
      life._neighbors = new Array(life._size);
      
      while(life.seed() == 0) { }

      life._timer = setTimeout(life.nextGeneration, 1000);
    },


    /*  Compute the next generation. 
     */
    nextGeneration : function() {
      var changed;

      life.countNeighbors();
      changed = life.update();
      
      if (changed == 0) {
        while(life.seed() == 0) { }
      }        
      life._timer = setTimeout(life.nextGeneration, (Math.random() * 500) + 900);
    },


    /*  Stop timer.  Reseed population.  Start timer. 
     */
    reset : function() { 
      clearTimeout(life._timer);
      while(life.seed() == 0) { }
      life._timer = setTimeout(life.nextGeneration, 1000);
    },


    /*  Seed world with random cells.  Return number of live cells. 
     */
    seed : function() { 
      var count;
      var x;

      life._ctx = life._pane.getContext("2d");
      count = 0;
      for(x=0; x < life._size; ++x) {
        if (Math.random() < life.CHANCE) {
          life.setAlive(x);
          ++count;
        }
        else {
          life.setDead(x);
        }
      }
      return count;
    },


    /*  Kill or birth cells depending on the rules. 
     */
    update : function() {      
      var x, c, alive;
      var change = 0;
      for(x = 0; x < life._size; ++x) {
        alive = ((life._neighbors[x] & 0x1) == 1);
        c = (life._neighbors[x] >> 1);
        if (!alive && (c == 3)) {
          life.setAlive(x);
          ++change;
        }
        else if (alive && ((c < 2) || (c > 3))) {
          life.setDead(x);
          ++change;
        }
      }
      return change;
    },



    /*  Count neighbors.  
     * 
     *  We store the neighbor count in bits 1-3 of each cells _neighbor
     *  value.  
     */
    countNeighbors : function() {
      var x, c;
      for(x = 0; x < life._size; ++x) {
        c = 0;
        c += life.check(x + 1);
        c += life.check(x - 1);

        c += life.check(x + life._rw);
        c += life.check((x + life._rw) - 1);
        c += life.check((x + life._rw) + 1);

        c += life.check(x - life._rw);
        c += life.check((x - life._rw) - 1);
        c += life.check((x - life._rw) + 1);
        
        // note: 0 <= c <= 8 
        life._neighbors[x] &= 0x01;
        life._neighbors[x] |= (c << 1);
      }
    },


    /*  Return 1 if the given cell is alive.
     */
    check : function(cellnum) {
      if ((cellnum > 0) && (cellnum < life._size)) {
        return (life._neighbors[cellnum] & 0x1);
      }
      else {
        return 0;
      }
    },


   
    /*  Set given cell to be alive (updates the screen and bit 0 in the
     *  _neighbor array). 
     */
    setAlive : function(cellnum) {
      life.set(cellnum, true);
      life._neighbors[cellnum] |= 0x1;
    },


    /*  Set given cell to be dead (updates the screen and bit 0 in the
     *  _neighbor array).
     */
    setDead : function(cellnum) {
      life.set(cellnum, false);
      life._neighbors[cellnum] &= 0x0;
    },


    /*  Update the screen.
     */
    set : function(cellnum, isalive) {
      orig = life.getAddr(cellnum);
      if (isalive) {
        life._ctx.fillStyle = life.STYLE.acolor;
      }
      else {
        life._ctx.fillStyle = life.STYLE.dcolor;
      }
      life._ctx.fillRect(orig.x, orig.y, life.CELLDIM, life.CELLDIM);
    },


    /*  Convert a cell number into screen coordinates.  Return top-left
     *  corner of screen cell.
     */
    getAddr : function(cellnum) {
      var dy,dx;
      var x,y;
      dy = Math.floor(cellnum / life._rw);
      dx = cellnum % life._rw;

      x = dx * life.CELLDIM;
      y = dy * life.CELLDIM;

      return ({x: x, y: y});
    },

  }; /* end life */


var behaviors =
  {
    '#lifepane' : function(element) {
      element.onmousedown = function(ev) {
        life.reset();
        return true;
      }
    }
  };

Behaviour.register(behaviors);
Behaviour.addLoadEvent(life.start);
