function SudokuBlock(blockid) {
  this.id = blockid;
  this.row = parseInt(blockid / 9);
  this.col = parseInt(blockid % 9);
  this.family = parseInt(this.row / 3) * 3 + parseInt(this.col / 3);
  this.values = [1,2,3,4,5,6,7,8,9];
  this.locked = false;
}

SudokuBlock.prototype.draw = function () {
  var el = document.getElementById(this.id);
  if (el) {
    var txt;
    if (this.values.length > 5) {
      txt = "?";
    } else if (this.values.length > 1) {
      txt = this.values.join(",");
    } else {
      txt = this.values[0];
      el.className += " locked";
    }
    el.innerHTML = txt;
    delete(el);
  }
}

function SudokuGroup(row, col, family) {
  this.row = row;
  this.col = col;
  this.family = family;

  var iRw = row * 9;
  this.rowBlocks = [ iRw, iRw+1, iRw+2, iRw+3, iRw+4, iRw+5, iRw+6, iRw+7, iRw+8 ];
  this.colBlocks = [ col, col+9, col+18, col+27, col+36, col+45, col+54, col+63, col+72 ];
  var famStart = parseInt(family / 3) * 27 + parseInt(family % 3) * 3;
  this.familyBlocks = [ famStart, famStart+1, famStart+2, 
                        famStart+9, famStart+10, famStart+11,
                        famStart+18, famStart+19, famStart+20
                        ];
}

SudokuGroup.prototype.checkGroup = function ( blocks, oSP ) {
  var values = [ [], [], [], [], [], [], [], [], [] ];
  for (var i=0; i<this.rowBlocks.length; i++) {
    var block = blocks[this.rowBlocks[i]];
    for (var j=0; j<block.values.length; j++) {
      var iPossible = block.values[j] - 1;
      values[iPossible][values[iPossible].length] = block.id;
    }
  }
  for (var i=0; i<values.length; i++) {
    if (values[i].length == 1 && !blocks[values[i][0]].locked) {
      //alert(values[i][0] + " = " + i );
      var block = blocks[values[i][0]];
      block.values = [ i+1 ];
      block.locked = true;
      oSP.updateOthers(values[i][0], i+1);
      block.draw();
    }
  }
  var values = [ [], [], [], [], [], [], [], [], [] ];
  for (var i=0; i<this.colBlocks.length; i++) {
    var block = blocks[this.colBlocks[i]];
    for (var j=0; j<block.values.length; j++) {
      var iPossible = block.values[j] - 1;
      values[iPossible][values[iPossible].length] = block.id;
    }
  }
  for (var i=0; i<values.length; i++) {
    if (values[i].length == 1 && !blocks[values[i][0]].locked) {
      //alert(values[i][0] + " = " + i );
      var block = blocks[values[i][0]];
      block.values = [ i+1 ];
      block.locked = true;
      oSP.updateOthers(values[i][0], i+1);
      block.draw();
    }
  }
  var values = [ [], [], [], [], [], [], [], [], [] ];
  for (var i=0; i<this.familyBlocks.length; i++) {
    var block = blocks[this.familyBlocks[i]];
    for (var j=0; j<block.values.length; j++) {
      var iPossible = block.values[j] - 1;
      values[iPossible][values[iPossible].length] = block.id;
    }
  }
  for (var i=0; i<values.length; i++) {
    if (values[i].length == 1 && !blocks[values[i][0]].locked) {
      //alert(values[i][0] + " = " + i );
      var block = blocks[values[i][0]];
      block.values = [ i+1 ];
      block.locked = true;
      oSP.updateOthers(values[i][0], i+1);
      block.draw();
    }
  }
}

SudokuPuzzle.prototype.init = function (id) {
  if (document.getElementById) {
    el = document.getElementById(id);
    if (el) {
      this.blocks[id].draw();
      YAHOO.util.Event.addListener(el, "click", edit, this);
    }
  }
}

SudokuBlock.prototype.removeValue = function (iValue) {
  for (var i = this.values.length; i>=0; --i) {
    if (this.values[i] == iValue) {
      this.values.splice(i, 1);
      if (this.values.length == 1) {
        this.locked = true;
      }
    }
  }
}

function SudokuPuzzle() {
  this.blocks = new Array();
  for (var i = 0; i<81; i++) {
    this.blocks[i] = new SudokuBlock(i);
    this.init(i);
  }
}

SudokuPuzzle.prototype.update = function (iBlock, iValue) {
  var block = this.blocks[iBlock];

  if (block.locked == false) {
    block.values = [ iValue ];
    block.locked = true;
    this.updateOthers(iBlock, iValue);
    block.draw();
  }
}

SudokuPuzzle.prototype.updateOthers = function (iBlock, iValue) {
  var block = this.blocks[iBlock];
  for (var i = 0; i<this.blocks.length; i++) {

    if (this.blocks[i].locked == false && 
        i != iBlock && 
      ( this.blocks[i].row == block.row ||
        this.blocks[i].col == block.col ||
        this.blocks[i].family == block.family ) ) {
      this.blocks[i].removeValue(iValue);
      this.blocks[i].draw();
      if (i != iBlock && this.blocks[i].locked) {
        this.updateOthers(i, this.blocks[i].values[0]);
      }
    }
    ///* This will slow down the computation but is more exhaustive *///
    var oSBG = new SudokuGroup(this.blocks[i].row, this.blocks[i].col, this.blocks[i].family);
    oSBG.checkGroup(this.blocks, this);
  }
}

function edit(e, obj) {
  //alert(obj.blocks.length);
  var input = document.createElement("INPUT");
  var id = this.id;
  //input.value = obj.blocks[id].values.join(",");
  this.removeChild(this.childNodes[0]);
  this.appendChild(input);
  input.focus();
  YAHOO.util.Event.removeListener(this, "click", edit);
  YAHOO.util.Event.addListener(input, "blur", update, obj);
}

function update(e, obj) {
  var iValue = parseInt(this.value);
  var id = this.parentNode.id;
  obj.update(id, iValue);
}
