/* play.js -- run the interactive Sudoku board                 */
/* Copyright (c) Daily Sudoku 2006-2007.  All rights reserved. */
/* http://www.dailysudoku.com/                                 */
/* Author: Sam Griffiths-Jones                                 */
/* Email: sudoku@dailysudoku.co.uk                             */

var size = 9;
var current;
var numbers = new Array(81);
var clues = new Array(81);
var solution = new Array(81);
var boxMask = new Array(81);
var pencilMarks = new Array(81);
var possibilityMatrix = new Array(81);
var title = "";
var diff = "0";
var row = new Array(9);
var col = new Array(9);
var box = new Array(9);

// We really need getElementById to work, so fake it for
// older IE versions.  Copied from 
// http://www.metalusions.com/backstage/articles/8/
if(document.all && !document.getElementById) {
    document.getElementById = function(id) {
         return document.all[id];
    }
}

// capture key presses
if( document.layers ) {
   document.captureEvents( Event.KEYPRESS );
   document.captureEvents( Event.KEYDOWN );
}
document.onkeydown = handleKeys;
document.onkeypress = ignoreKeys;

initialiseBoard();
initialiseBoxes();
initialisePencilMarks();

function initialiseBoard() {
    numbers = new Array(81);
    clues = new Array(81);
    solution = new Array(81);
    boxMask = new Array(81);
    pencilMarks = new Array(81);

    row = new Array(9);
    col = new Array(9);
    box = new Array(9);

    // setup rows, columns and boxes
    row[0] = new Array( 0,1,2,3,4,5,6,7,8 );
    row[1] = new Array( 9,10,11,12,13,14,15,16,17 );
    row[2] = new Array( 18,19,20,21,22,23,24,25,26 );
    row[3] = new Array( 27,28,29,30,31,32,33,34,35 );
    row[4] = new Array( 36,37,38,39,40,41,42,43,44 );
    row[5] = new Array( 45,46,47,48,49,50,51,52,53 );
    row[6] = new Array( 54,55,56,57,58,59,60,61,62 );
    row[7] = new Array( 63,64,65,66,67,68,69,70,71 );
    row[8] = new Array( 72,73,74,75,76,77,78,79,80 );

    col[0] = new Array( 0,9,18,27,36,45,54,63,72 );
    col[1] = new Array( 1,10,19,28,37,46,55,64,73 );
    col[2] = new Array( 2,11,20,29,38,47,56,65,74 );
    col[3] = new Array( 3,12,21,30,39,48,57,66,75 );
    col[4] = new Array( 4,13,22,31,40,49,58,67,76 );
    col[5] = new Array( 5,14,23,32,41,50,59,68,77 );
    col[6] = new Array( 6,15,24,33,42,51,60,69,78 );
    col[7] = new Array( 7,16,25,34,43,52,61,70,79 );
    col[8] = new Array( 8,17,26,35,44,53,62,71,80 );
}



function initialiseBoxes(mask) {
   if( !mask ) {
      mask = "000111222000111222000111222333444555333444555333444555666777888666777888666777888";
   }
   for( var b=0; b<9; b++ ) {
      box[b] = new Array;
   }
   boxMask = mask.split('');
   for( var i=0; i<81; i++ ) {
      box[ boxMask[i] ].push(i);
   }
}


function initialisePencilMarks() {
   for( var i=0; i<81; i++ ) {
      pencilMarks[i] = new Array;
   }
}


function getBoxByCell(i) {
   var b = boxMask[i];
   return b;
}

function getRowByCell(i) {
    var r = Math.floor( i/9 );
    return r;
}

function getColByCell(i) {
   var c = i%9;
   return c;
}

function getCellByIndex(i) {
    var cell = document.getElementById( "c"+i );
    return cell;
}

function getPencilByIndex(i) {
    var p = document.getElementById( "p"+i );
    return p;
}


function getAllRcbSets() {
    var set = new Array(); // this should be easier to set up
    for( var i=0; i<9; i++ ) {
	set.push( box[i] );
    }
    for( var i=0; i<9; i++ ) {
	set.push( row[i] );
    }
    for( var i=0; i<9; i++ ) {
	set.push( col[i] );
    }
    return set;
}


function getRcbSetByIndex( cellIndex ) {
    //    console.log( "call getRcbSetByIndex,"+cellIndex );
    var set = new Array(); // this should be easier to set up
    var boxSet = box[ getBoxByCell(cellIndex) ];
    var rowSet = row[ getRowByCell(cellIndex) ];
    var colSet = col[ getColByCell(cellIndex) ];

    for( var i=0; i<9; i++ ) {
	set.push( boxSet[i] );
	set.push( rowSet[i] );
	set.push( colSet[i] );
    }
    return set;
}


function deleteNumber( cell ) {
    if( !cell ) { cell = current; }
    var i = getCellIndex( cell );

    cell.value = '';
    numbers[i] = 0;
    // important to reset possibilityMatrix here to replace options
    // that were not previously possible
    possibilityMatrix = new Array(81);
}


function inputNumber( n, cell, nosweep ) {
    if( !cell ) {
	cell = current;
    }
    var i = getCellIndex( cell );

    if( cell.className == "fixed" ) {
	return false;
    }
    else {
      if( cell.className == 'highlight' ) {
         cell.className = 'cell';
      }
      enableSolutionButton();
      if( n == ' ' || n == '0' ) {
	  deleteNumber(cell);
      }
      else {
	  if( numbers[i] > 0 ) {
	      // need to deleteNumber before we carry on in order to
	      // reset the possibilityMatrix if we replace one number
	      // with another
	      deleteNumber(cell);
	  }
	  if( !nosweep ) {
	      deletePencilMarks(cell);
	  }
         cell.value = n;
	 numbers[i] = n;
	 // remove options from possibilityMatrix
	 removeRcbOptionsByCell(i);
      }
   }
   if( document.draw.automoveon.checked ) {
      i++;
   }

   setNumbers();

   if( !nosweep ) { // when we get a hint we don't want to destroy
                    // all info about why
       if( document.draw.autosweep.checked ) {
	   sweepBoard();
       }
   }

   if( isComplete() && checkValid() ) {
       pauseTime();
      alert( 'Congratulations: you are all done here!\nVisit the "Archive" section of the site for more puzzles.' );
   }
}



// get puzzles from the database
function getPuzzleByDate( y,m,d,type ) {
   var today = new Date();
   var thisYear = today.getYear();
   if( thisYear < 1900 ) {
      thisYear = thisYear + 1900;
   }
   var thisMonth = today.getMonth();
   thisMonth++;
   var thisDay = today.getDate();

   resetAll();

//   if( type == "monster" || type == "kids" ) {
   if( type == "monster" ) {
      alert( 'The Kids and Monster puzzles are not currently available in Draw/Play.' );
      return false;
   }

   window.d = d;
   if( window.d ) {
      if( y > thisYear ||
	  ( y == thisYear && m > thisMonth )
	  || ( y == thisYear && m == thisMonth && d > thisDay )
	  ) {
         alert( 'Cannot retrieve the puzzle you requested.  This could be a temporary network error - please try again later.  If the problem persists then please email sudoku@dailysudoku.co.uk with a description of the problem.' );
	 return false;
      }
   }
   else {
      y = thisYear;
      m = thisMonth;
      d = thisDay;
   }

   if( m.length < 2 ) {
      m = "0"+m;
   }

   document.draw.pyear.value = y;
   document.draw.pmonth.value = m;
   document.draw.pday.value = d;

   getJsonPuzzleByDate( y,m,d,type );
   enableSolutionButton();

   window.t = type;
   if( window.t ) {
       document.draw.asciiButton.disabled = true;
   }
   else {
       document.draw.asciiButton.disabled = false;
   }
   resetTime();
/*   setRevertButton(); */
}


function getToday( type ) {
    if( isEmpty() || confirm( "Retrieve today's puzzle and lose any changes?" ) ) {
      getPuzzleByDate( "", "", "", type );
    }
}


function showBoard() {
   if( title ) {
      showTitle();
   }
   if( diff ) {
      showDifficulty();
   }

   for ( var i=0; i<81; i++ ) {
      var cell = getCellByIndex(i);
      if( cell ) {
         cell.value = "";
         cell.className = "cell";
	 if( clues[i] >= 1 && clues[i] <= 9 ) {
            cell.value = clues[i];
	    cell.className = "fixed";
	 }
	 else if( numbers[i] >= 1 && numbers[i] <= 9 ) {
            cell.value = numbers[i];
	 }
	 else {
            cell.value = '';
         }
      }
   }

   setNumbers();
}


function parseHintData( hintData ) {
    //    console.log( "call parseHintData,"+hintData );
   var i = hintData['row']*9 + hintData['col'];
   if( hintData['error'] ) {
       alert( "Error: "+hintData['error'] );
       return false;
   }
   if( !hintData['number'] ) {
      alert( "Draw/Play is unable to suggest a hint - the puzzle may be too hard, or you may have made a mistake making the puzzle unsolvable from its current state." );
      return false;
   }

   var cell = getCellByIndex(i);
   if( cell ) {
       inputNumber( hintData['number'], cell, 1 ); // nosweep
       //      numbers[i] = hintData['number'];
       //      cell.value = hintData['number'];
      cell.className = "highlight";

      if( document.draw.development.value == 1 ) {
	  if( hintData.cellSet ) {
	      unHighlightAll();
	      getCellByIndex(i).focus();
	      highlightCellSet( hintData.cellSet, "#faa" );
	      highlightCellSet( [i], "#afa" );
	      
	      writeMessage( "r"+(hintData.row+1)+
			    "c"+(hintData.col+1)+
			    "="+hintData.number+": "+
			    hintData.message + "\n" );
	  }
      }
   }   
   setNumbers();
}


function writeMessage( message ) {
    var messageBox = document.getElementById('messages');
    messageBox.value = messageBox.value + message;
    scrollElementToEnd( messageBox );
}

function clearMessage() {
    var messageBox = document.getElementById('messages');
    messageBox.value = "Messages:\n";
}


function parseBoardData( boardData ) {
   title = boardData['title'];
   diff = boardData['difficulty'];

   if( boardData['maskname'] ) {
      document.draw.maskname.value = boardData['maskname'];
   }
   if( boardData['type'] ) {
      document.draw.type.value = boardData['type'];
   }

   if( boardData['clues'] ) {
      clues = boardData['clues'].split("");
   }
   if( boardData['numbers'] ) {
      numbers = boardData['numbers'].split("");
   }

   if( boardData['solution'] ) {
      solution = boardData['solution'].split("");
      if( boardData['show'] ) {
         showSolution();
      }
      else {
         showDifficulty();
      }
   }

   if( boardData['boxmask'] ) {
      initialiseBoxes( boardData['boxmask'] );
   }

   if( boardData['pencil'] ) {
      var ps = boardData['pencil'].split(';');
      for( var i=0; i<ps.length; i++ ) {
         var tmp = ps[i].split(':');
	 var p = getPencilByIndex( tmp[0] );
	 if( p ) {
	    p.innerHTML = tmp[1];
	    var nums = tmp[1].split('');

	    // Go through the array to make sure it doesn't contain
	    // any non numerical chars.  Old cookie saver
	    // implementation included <br> markup
	    for( var j=(nums.length-1); j>=0; j-- ) {
	       if( nums[j] >= 1 && nums[j] <= 9 ) {
	       }
	       else {
	          nums.splice( j, 1 );
	       }
	    }

//	    if( nums.length < 1 ) {
//	       nums = new Array;
//	    }
	    if( nums.length > 0 ) {
	       placePencilMarks( p, nums );
//	       pencilMarks[ tmp[0] ] = nums;
	    }
	 }
      }
   }


//   else {
     drawGrid();
      showBoard();
//   }
}



function automoveon( cell ) {
   var c = cell.id;
   ary = c.split("c");
   var i = ary[1];
   i++;
   if( i == 81 ) {
      i = 0;
   }
   var next = getCellByIndex(i);
   next.focus();
}


function focusfirst() {
   var cell = getCellByIndex(0);
   cell.focus(); 
}


function selectCell(cell) {
    if( isObject(cell) ) {
	// if we've clicked a pencil div then find the right cell
	if( cell.className == 'pencil' ) {
	    var pencil = cell.id.split("p");
	    cell = getCellByIndex( pencil[1] );
	}
    }
    else {
	// cell is just the index
	cell = getCellByIndex(cell);
    }

    current = cell;
    unHighlightAll();
    cell.style.backgroundColor = "#cce";
    cell.focus();
    if( document.draw.autocheck.checked ) {
       checkValid();
    }
    setFixButton();
}


function resetAll( check ) {
    window.check = check;
    if( window.check ) {
       if( confirm( '"Reset all" empties the grid completely.  Continue?\n\(Tip: Click "Cancel" and then "Revert" to reset to the original clues.\)' ) ) {
       }
       else {
          return false;
       }
    }

    for( var i=0; i<81; i++ ) {
        var cell = getCellByIndex(i);
	cell.className = "cell";
	cell.value = "";
	deletePencilMarks( cell );
    }

//    initialisePencilMarks();
    unHighlightAll();

    title = "";
    showTitle();
    diff = 0;
    showDifficulty();

    /* reset these things */
    numbers = new Array(81);
    clues = new Array(81);
    solution = new Array(81);
    possibilityMatrix = new Array(81);

    document.draw.pyear.value = '';
    document.draw.pmonth.value = '';
    document.draw.pday.value = '';
    document.draw.type.value = '';
    document.draw.maskname.value = '';

    /* reset the grid shape */
    initialiseBoxes();
    drawGrid();

    disableSolutionButton();
    document.draw.asciiButton.disabled = false;

/*    setRevertButton(); */
    checkValid();
    clearMessage();
}



function getCellIndex( cell ) {
   if( !cell ) {
       // we don't want to die if current hasn't been selected yet
       if( !current ) {
	   selectCell(1);
       }
       cell = current;
   }
   var id = cell.id;
   var num = id.split( 'c' );
   if( num.length < 2 ) {
     // perhaps cell is a pencil mark 
     num = id.split( 'p' );
   }
   return num[1];
}


function getNumberString() {
   var string = "";
   for ( var i=0; i<81; i++ ) {
      if( numbers[i] ) {
         string = string + numbers[i];
      }
      else {
         string = string + "0";
      }
   }
   return string;
}



// Checks!
function isEmpty() {
   for( var i=0; i<81; i++ ) {
      if( numbers[i] ) {
         return false;
      }
   }
   return true;
}

function isComplete() {
   for( var i=0; i<81; i++ ) {
      if( !numbers[i] || numbers[i] == 0) {
         return false;
      }
   }
   return true;
}


function checkValid() {
   var fail;
   for( var j=0; j<9; j++ ) {
      var r = row[j];
      var c = col[j];
      var b = box[j];
      var rcb = new Array( r, c, b );
      for( var k=0; k<3; k++ ) {
         var set = rcb[k];
	 if( checkRowColBox( set ) ) {
	 }
	 else {
	    fail = true;
	    highlightCellSet( set );
         }
      }
   }
   if( fail ) {
       disableSolutionButton();
       return false;
   }
   else {
//       enableSolutionButton();
       return true;
   }
}


function unHighlightAll() {
    for ( var i=0; i<81; i++ ) {
        var cell = getCellByIndex(i);
	var pencil = getPencilByIndex(i);
	cell.style.backgroundColor = "";
	pencil.style.backgroundColor = "";
    }
}



function highlightCellSet( set, color ) {
    //    unHighlightAll();
    if( !color ) {
	color = "#faa";
    }
    for( var i=0; i<set.length; i++ ) {
	var cell = getCellByIndex( set[i] );
	cell.style.backgroundColor = color;
    }
}

function highlightObjectSet( set, color ) {
    if( !color ) {
	color = "#faa";
    }
    for( var i=0; i<set.length; i++ ) {
	set[i].style.backgroundColor = color;
    }
}


function checkRowColBox( set ) {
   var count = new Array( 0,0,0,0,0,0,0,0,0 );
   for( var i=0; i<9; i++ ) {
      var cell = getCellByIndex( set[i] );
      if( cell.value ) {
         count[ cell.value -1 ] = count[ cell.value -1 ] + 1;
      }
   }
   for( var i=0; i<9; i++ ) {
      if( count[i] > 1 ) {
          return false;	    
      }
   }
   return true;
}



// change the form html
function showTitle() {
   document.draw.title.value = title;
}

function showDifficulty() {
   var di = document.getElementById('puzzleDifficulty');
   var dindex = new Array( 'unknown', 'easy', 'medium', 'hard', 'very hard', 'too hard' );
   if(di) {
      di.innerHTML = dindex[diff];
      document.draw.difficulty.value = diff;
   }
}

function setNumbers() {
   for( var i=0; i<81; i++ ) {
      if( !numbers[i] || numbers[i] == '.' ) {
         numbers[i] = 0;
      }
      if( !clues[i] || clues[i] == '.' ) {
         clues[i] = 0;
      }
   }
   document.draw.numbers.value = numbers.join('');
   document.draw.clues.value = clues.join('');
}


function numbersToGrid( nums ) {
    numbers = nums.split('');
    clues = nums.split('');
    setNumbers();
    drawGrid();
    showBoard();
}

function pencilsToGrid( pens ) {
    var p = pens.split(':');

    // not sure why we need to do this
    numbers = new Array(81);
    pencilMarks = new Array(81);
    initialisePencilMarks();

    for( var i=0; i<81; i++ ) {
	if( i < p.length ) {
	    if( p[i].length == 1 ) {
		clues[i] = p[i];
		numbers[i] = p[i];
	    }
	    else if( p[i].length > 1 ) {
		var cell = getPencilByIndex(i);
		placePencilMarks( cell, p[i].split('') );
	    }
	}
    }
    setNumbers();
    drawGrid();
    showBoard();
}


// Fix and unfix
function fixAll() {
    for ( var i=0; i<81; i++ ) {
        var cell = getCellByIndex(i);
        if( cell.value >= 1 && cell.value <=9 ) {
	   cell.className = "fixed";
	   clues[i] = cell.value;
	}
    }
    setNumbers();
}

function unfixCell() {
    current.className = "cell";
    clues[ getCellIndex(current) ] = 0;
    setNumbers();
}

function toggleFixCell() {
   if( current.value ) {
      if( current.className == "fixed" ) {
         current.className = "cell";
         clues[ getCellIndex(current) ] = 0;
      }
      else {
         current.className = "fixed";
         clues[ getCellIndex(current) ] = current.value;
      }
      setFixButton();
      setNumbers();
   }
}




// deal with solutions
function haveSolution() {
   for( var i=0; i<81; i++ ) {
      if( !solution[i] || solution[i] == 0 ) {
          return false;
      }
   }
   return true;
}

function getSolution( show ) {
   if( haveSolution() ) {
      if( show ) {
         showSolution();
      }
   }
   else {
      var numStr = getNumberString();
      var maskName = document.draw.maskname.value;
      if( checkValid() ) {
         getJsonSolution( numStr, maskName, show );
      }
      else {
         alert( 'Cannot solve - puzzle is invalid.  Please check the highlighted squares for repeated numbers in each row, column and box.' );
      }
   }
}

function getHint() {
   // see if we can get an easy hint
   unHighlightAll();
   var hint = calculateHint();
   if( hint.number > 0 ) {
       // we have a real hint
       parseHintData( hint );
       return true;
   }
   if( hint.opts ) {
       // we have removed some options, do nothing else
       return true;
   }

   // else go off and ask the server
   writeMessage( "*Dev interface cannot currently explain this hint*\n" );

   if( checkValid() ) {
       var numStr = getNumberString();
       var maskName = document.draw.maskname.value;
       getJsonHint( numStr, maskName );
   }
   else {
       alert( 'Cannot get a hint - puzzle is invalid.  Please check the highlighted squares for repeated numbers in each row, column and box.' );
   }
   disableSolutionButton();
   setTimeout( "enableSolutionButton()", 5000 );
}


function getGrade() {
   if( checkValid() ) {
      solution = new Array(81); // force a fresh solve
      getSolution();
   }
   else {
      alert( 'Cannot grade - puzzle is invalid.  Please check the highlighted squares for repeated numbers in each row, column and box.' );
   }
   document.draw.gradeButton.disabled = true;
}


function showSolution() {
   for( var i=0; i<81; i++ ) {
      numbers[i] = solution[i];
   }
   showBoard();
   disableSolutionButton();
}


function revert() {
    if( confirm( "Revert to the original puzzle, and lose any changes?" ) ) {
	if( document.draw.pyear.value && 
	    document.draw.pmonth.value && 
	    document.draw.pday.value ) {
	    getPuzzleByDate( document.draw.pyear.value,
			     document.draw.pmonth.value,
			     document.draw.pday.value,
			     document.draw.type.value
			     );
	}
	else {
	    numbers = clues.join(':').split(':');
	    showBoard();
	    deleteAllPencilMarks();
	}
	clearMessage();
    }
}


// Button functions
function disableSolutionButton() {
   document.draw.solutionButton.disabled = true;
   document.draw.gradeButton.disabled = true;
   document.draw.hintButton.disabled = true;
}

function enableSolutionButton() {
   document.draw.solutionButton.disabled = false;
   document.draw.gradeButton.disabled = false;
   document.draw.hintButton.disabled = false;
}

function setFixButton() {
    if( current.value ) {
        if( current.className == "fixed" ) {
	    document.draw.fixButton.disabled = false;
	    document.draw.fixButton.value = "Unfix cell";
	}
	else {
	    document.draw.fixButton.disabled = false;
	    document.draw.fixButton.value = "Fix cell";
	}
    }
    else {
	 document.draw.fixButton.disabled = true;
    }
}

function setRevertButton() {
   if( document.draw.pyear.value && 
         document.draw.pmonth.value && 
         document.draw.pday.value ) {
      document.draw.revertButton.disabled = false;
   }
   else {
      document.draw.revertButton.disabled = true;
   }
}      



// key handlers
function ignoreKeys(e) {
   if (!e) {
      e = window.event;
   }

   var targ;
   if (e.target) { 
      targ = e.target;
   }
   else {
      if (e.srcElement) {
         targ = e.srcElement;
      }
   }
      
//   if (targ.nodeType == 3) // defeat Safari bug
//      targ = targ.parentNode;
//   }

   if( targ.className != 'cell' && 
         targ.className != 'fixed' && 
         targ.className != 'highlight' ) {
      return true;
   }

   return false;
}

function handleKeys(e) {
   if (!e) {
      e = window.event;
   }

   var targ;
   if (e.target) { 
      targ = e.target;
   }
   else {
      if (e.srcElement) {
         targ = e.srcElement;
      }
   }
      
   //   if (targ.nodeType == 3) // defeat Safari bug
   //      targ = targ.parentNode;
   //   }

   if( targ.className != 'cell' && 
         targ.className != 'fixed' && 
         targ.className != 'highlight' ) {
      return true;
   }

   var chr;
   var charCode = (e.charCode) ? e.charCode :
                  ((e.keyCode) ? e.keyCode :
                  ((e.which) ? e.which : 0));

   if( charCode >= 97 && charCode <= 105 ) {
       // deal with number keypad
       charCode = charCode - 48;
   }
   if( charCode >= 81 && charCode <= 90 ) {
       // ctrl + numbers in OSX
       charCode = charCode - 32;
   }
   if( (charCode >= 49 && charCode <= 57) // numbers
       || charCode == 32 ) { // space
       chr = String.fromCharCode( charCode );
   }

   var c = current.id;
   ary = c.split("c");
   var i = ary[1];

   switch( charCode ) {
   case 37:
   case 63234: // safari oddity
       // left arrow
       i--;
       break;
   case 38:
   case 63232:
       // up arrow
       i -= 9;
       break;
   case 9: // tab
   case 39:
   case 63235:
       // right arrow
       i++;
       break;
   case 40:
   case 63233:
       // down arrow
       i++; // make i a number
       i += 8;
       break;
   case 8:
   case 46:
       // delete and backspace
       deletePencilMarks();
       chr = ' ';
       if( document.draw.automoveon.checked ) {
	   i --;
       }
       // read through with chr = ' '

   default:
       if( ((chr >= '0') && (chr <= '9' )) || (chr == ' ') ) {
	   if( e.ctrlKey || e.altKey ) {
	       addPencilMark( chr );
	   }
	   else {
	       inputNumber(chr);
               if( document.draw.automoveon.checked ) {
		   i++;
               }
	   }
       }
       else {
	   return false;
       }
   }

   if( i < 0 ) {
      i += 81;
   }
   if( i > 80 ) {
      i -= 81;
   }

   var cell = getCellByIndex(i);
   selectCell(cell);
   return false;
}


/* load and save */

function parseOldStyleCookie( string ) {
   var spl = string.split( "|" );
   if( spl.length < 2 ) {
//      alert( "Error: no saved puzzle!" );
      return false;
   }
   var boardData = new Object;
   boardData['clues'] = spl.shift();
   boardData['numbers'] = spl.shift();
   boardData['solution'] = spl.shift();
   boardData['difficulty'] = spl.shift();
   boardData['title'] = spl.shift();

   document.draw.pyear.value = spl.shift();
   document.draw.pmonth.value = spl.shift();
   document.draw.pday.value = spl.shift();

   parseBoardData( boardData );
}

function parseCookie( string ) {
    /* new style cookie */
    var spl = string.split( "|" );
    if( spl.length < 2 ) {
	alert( "Error: no saved puzzle!" );
	return false;
    }
    var boardData = new Object;
    
    for( var i=0; i<spl.length; i++ ) {
	var pair = spl[i].split('=');
	boardData[ pair[0] ] = pair[1];
    }
    
    document.draw.pyear.value = boardData['year'];
    document.draw.pmonth.value = boardData['month'];
    document.draw.pday.value = boardData['day'];
    
    parseBoardData( boardData );
}

function load() {
   if( isEmpty() || confirm( "Loading your saved puzzle will overwrite the current puzzle.  Continue?") ) {
      var string = getCookie( "ds-save" );
      if( !string ) {
         alert( "Error: no saved puzzle!" );
	 return false;
      }

      resetAll();

      var tmp = string.split( "=" );
      if( tmp.length < 2 ) {
	  parseOldStyleCookie( string );
      }
      else {
	  parseCookie( string );
      }
   }

   if( !isEmpty() ) {
      enableSolutionButton();
   }
}


function saveOldStyle() {
   if( confirm( "Overwrite any previous saved puzzle?") ) {
      var num = numbers.join('');   
      var clu = clues.join('');
      var sol = solution.join('');
      setCookie( "ds-save", clu+"|"+
                            num+"|"+
                            sol+"|"+
                            diff+"|"+
                            document.draw.title.value+"|"+
			    document.draw.pyear.value+"|"+
			    document.draw.pmonth.value+"|"+
			    document.draw.pday.value,
                            365, "/" );
      alert( "Puzzle saved!" );
   }
   else {
      alert( "Puzzle not saved!" );
   }
}


function makeCookieString() {
      var num = numbers.join('');   
      var clu = clues.join('');
      var sol = solution.join('');
      var mask = boxMask.join('');
      var type = document.draw.type.value;
      var maskName = document.draw.maskname.value;

      var pen = "";
      for( var i=0; i<81; i++ ) {
         if( pencilMarks[i].length > 0 ) {
            pen = pen + i + ":" + pencilMarks[i].join('') + ";";
         }
      }

      var cookieString = "";
      if( clu ) {
         cookieString = cookieString + "clues=" + clu + "|";
      }
      if( num ) {
         cookieString = cookieString + "numbers=" + num + "|";
      }
      if( sol ) {
         cookieString = cookieString + "solution=" + sol + "|";
      }
      if( pen ) {
         cookieString = cookieString + "pencil=" + pen + "|";
      }
      if( mask ) {
         cookieString = cookieString + "boxmask=" + mask + "|";
      }
      if( type ) {
         cookieString = cookieString + "type=" + type + "|";
      }
      if( maskName ) {
         cookieString = cookieString + "maskname=" + maskName + "|";
      }
      if( diff ) {
         cookieString = cookieString + "difficulty=" + diff + "|";
      }
      if( document.draw.title.value ) {
         cookieString = cookieString + "title=" + document.draw.title.value + "|";
      }
      if( document.draw.pyear.value ) {
         cookieString = cookieString + "year=" + document.draw.pyear.value + "|";
      }
      if( document.draw.pmonth.value ) {
         cookieString = cookieString + "month=" + document.draw.pmonth.value + "|";
      }
      if( document.draw.pday.value ) {
         cookieString = cookieString + "day=" + document.draw.pday.value + "|";
      }

      return cookieString;
}


function makeAscii() {
    var colWidth = new Array(1,1,1,1,1,1,1,1,1);
    for( var i=0; i<81; i++ ) {
	var c = getColByCell(i);
	if( pencilMarks[i].length > colWidth[c] ) {
	    colWidth[c] = pencilMarks[i].length;
	}
    }

    var totalWidth = 0;
    var separator = '+';
    for( var t=0; t<9; t++ ) {
    	totalWidth = totalWidth + colWidth[t];
	for( var v=0; v<(colWidth[t]+1); v++ ) {
	    separator = separator + '-';
	}
	if( t%3 == 2 ) {
	    separator = separator + '-+';
	}
    }

    var boxWidth = (totalWidth+20)*8;
    if( boxWidth < 400 ) {
	boxWidth = 400;
    }
    var win=window.open('','ascii','height=400,width='+boxWidth );
    win.document.writeln( '<html><body>' );
    if( document.draw.title.value ) {
	win.document.write( '<h4>' + document.draw.title.value + '</h4>' );
    }
    else {
	win.document.write( '<h4>Your puzzle</h4>' );
    }
    win.document.writeln( '<center><form><textarea cols="'+(totalWidth+14)+'" rows="16" wrap="off" readonly="1" onFocus="this.select()">\n[code]' );
    //    win.document.writeln( '<pre>\n[code]' );

    var pen = "";
    for( var i=0; i<81; i++ ) {
	var c = getColByCell(i);
	if( getColByCell(i) == 0 && i>0 ) {
	    win.document.write( '|\n' );
	}
	if( i%27 == 0 ) {
	    win.document.write( separator + '\n' );
	}
	if( i%3 == 0 ) {
	    win.document.write( '| ' );
	}
	if( numbers[i] > 0 ) {
	    win.document.write( numbers[i] + ' ' );
	    for( var t=1; t<colWidth[c]; t++ ) {
		win.document.write( ' ' );
	    }
	}
	else {
	    if( pencilMarks[i].length > 0 ) {
		win.document.write( pencilMarks[i].join('') + ' ' );
		for( var t=0; t<(colWidth[c]-pencilMarks[i].length); t++ ) {
		    win.document.write( ' ' );
		}
	    }
	    else {
		win.document.write( '. ' );
		for( var t=1; t<colWidth[c]; t++ ) {
		    win.document.write( ' ' );
		}
	    }
	}

	if( numbers[i] > 0 ) {
	    pen = pen + numbers[i] + ":";
	}
	else if( pencilMarks[i].length > 0 ) {
            pen = pen + pencilMarks[i].join('') + ":";
	}
	else {
	    pen = pen + "0:";
	}
    }

    var s = new String( numbers.join('') );
    s = s.replace( /0/g, "." );

    win.document.write( '|\n' + separator + '\n[/code]\n' );
    win.document.write( '[url=http://www.dailysudoku.com/sudoku/play.shtml?p='+pen+']Play this puzzle online[/url] at the Daily Sudoku site' );
    //    win.document.write( '[url=http://www.dailysudoku.com/sudoku/play.shtml?n='+s+']Play this puzzle online[/url] at the Daily Sudoku site' );
    win.document.write( '</textarea></form></center>An ascii version of the puzzle is shown above.  Select the text by clicking, and then copy and paste.  For example, you might want to include the grid in a message in the <a target="_blank" href="/sudoku/forums">discussion forums</a>, including the "code" tags.  Include the "url" line to provide a link to play your puzzle online.</p></body></html>' );

    win.document.close();
}


function save() {
    if( confirm( "Overwrite any previous saved puzzle?") ) {
	var cookieString = makeCookieString();
	setCookie( "ds-save", cookieString, 365, "/" );
	alert( "Puzzle saved!" );
    }
    else {
	alert( "Puzzle not saved!" );
    }
}


function drawGrid() {
   for( var i=0; i<81; i++ ) {
      var classList = "";
      var b = getBoxByCell(i);
      if( b != getBoxByCell( i+1 ) ) {
         classList = classList + "rightLine ";
      }
      if( b != getBoxByCell( i-1 ) ) {
         classList = classList + "leftLine ";
      }
      if( b != getBoxByCell( i+9 ) ) {
         classList = classList + "bottomLine ";
      }
      if( b != getBoxByCell( i-9 ) ) {
         classList = classList + "topLine ";
      }
      var cell = document.getElementById( "t"+i );
      cell.className = classList;
   }
}


/* deal with pencil marks */

function addPencilMark(n) {
   var i = getCellIndex( current );
   if( numbers[i] && numbers[i] > 0 ) {
      return false;
   }
   var p = getPencilByIndex(i);
   if( p ) {
      var nums = pencilMarks[i];
      if( !nums ) {
         nums = new Array;
      }
      var seen;
      for ( var j=0; j<nums.length; j++ ) {
         if( nums[j] == n ) {
	    seen = 1;
	    nums.splice( j, 1 );
         }
      }
      if( !seen  ) {
         nums.push( n );
      }
      
      placePencilMarks( current, nums );
   }
}


function deletePencilMarks( cell ) {
   if( !cell ) {
      cell = current;
   }
   var i = getCellIndex( cell );
   var pencil = getPencilByIndex(i);
   if( pencil ) {
      pencil.innerHTML = '';
      pencilMarks[i] = new Array;
   }
}



function sweepBoard() {
    for( var i=0; i<81; i++ ) {
	var c = getCellByIndex(i);
	/*
	var opts = calculateOptions( c );
	placePencilMarks( c, opts );
	*/
	if( !isObject( possibilityMatrix[i] ) ) {
	    possibilityMatrix[i] = calculateOptions( c );
	}
	placePencilMarks( c, possibilityMatrix[i] );
    }
}

function clickAutoSweep() {
    if( document.draw.autosweep.checked ) {
	sweepBoard();
	return true;
    }
    return false;
}



function placePencilMarks( cell, numbers ) {
   if( !cell ) {
      cell = current;
   }
   var i = getCellIndex( cell );
   var p = getPencilByIndex(i);
   var sort = numbers.sort();
   pencilMarks[i] = sort;

   // make a copy so that the splice later doesn't
   // affect the original
   var copy = new Array();
   for ( var j in sort ) {
       copy[j] = sort[j];
   }

   if( copy.length > 5 ) {
       copy.splice( 5, 0, "<br>" );
   }
   p.innerHTML = copy.join('');
}


function deleteAllPencilMarks() {
   for( var i=0; i<81; i++ ) {
       var cell = getCellByIndex(i);
      deletePencilMarks( cell );
   }
}



/* hint */

function calculateOptions( cell ) {
   if( !cell ) {
      cell = current;
   }
   var i = getCellIndex( cell );
   var opts = new Array;
   if( numbers[i] && numbers[i] > 0 ) {
      return opts;
   }

   var nums = new Object;
   var r = row[ getRowByCell(i) ];
   var b = box[ getBoxByCell(i) ];
   var c = col[ getColByCell(i) ];

   for( var j=0; j<9; j++ ) {
      if( numbers[ r[j] ] ) {
	 nums[ numbers[r[j]] ] = 1;
      }
      if( numbers[ b[j] ] ) {
         nums[ numbers[b[j]] ] = 1;
      }
      if( numbers[ c[j] ] ) {
	 nums[ numbers[c[j]] ] = 1;
      }
   }

   for( var k=1; k<=9; k++ ) {
      if( !nums[k] ) {
	 opts.push( k );
      }
   }

   return opts;
}


function calculateHint() {
    /* look for the really easy stuff */
    //possibilityMatrix = new Array(81);
    var hint = new Object;

    // only build possibilityMatrix if it isn't populated
    for( var i=0; i<81; i++ ) {
	var c = getCellByIndex(i);
	if( !isObject( possibilityMatrix[i] ) ) {
	    possibilityMatrix[i] = calculateOptions( c );
	}
    }

    // scan for boxes, rows, cols with only one possible 
    // location option for a number 
    var set = getAllRcbSets();
    for( var s=0; s<set.length; s++ ) {
	var cellSet = set[s];
	var hintSet = new Array();
       
	// for message
	var setType = "";
	var setNum = s%9 +1;
	if( s < 9 ) { setType = "box"; }
	else if( s < 18 ) { setType = "row"; }
	else { setType = "col"; }

	for( var i=0; i<cellSet.length; i++ ) {
	    if( !numbers[ cellSet[i] ] || numbers[ cellSet[i] ] == 0 ) {
		hintSet.push( cellSet[i] );
	    }
	}
	
	var cellCount = new Array( 0,0,0,0,0,0,0,0,0 );
	for( var i=1; i<10; i++ ) {
	    cellCount[i] = new Array();
	}

	for( var i=0; i<9; i++ ) {
	    var cellIndex = cellSet[i];
	    var nums = possibilityMatrix[cellIndex];
	    for( var n=0; n<nums.length; n++ ) {
		cellCount[ nums[n] ].push( cellIndex );
	    }
	}
      
	for( var i=1; i<10; i++ ) {
	    if( cellCount[i].length == 1 ) {
		hint.row = getRowByCell( cellCount[i][0] );
		hint.col = getColByCell( cellCount[i][0] );
		hint.number = i;
		hint.cellSet = hintSet;
		hint.message = "locked candidate in "+setType+" "+setNum;
		return hint;
	    }
	}
    }

    // return straight off if we find a cell with one option
    for( var i=0; i<81; i++ ) {
	if( possibilityMatrix[i].length == 1 ) {
	    hint.row = getRowByCell(i);
	    hint.col = getColByCell(i);
	    hint.number = possibilityMatrix[i][0];
	    hint.cellSet = [ i ];
	    hint.message = "sole candidate";
	    return hint;
	}
    }

    if( document.draw.development.value == 1 ) {
	//console.log( "Nothing straightforward -- trying more complex things" );
	// remove options
	hint = removeOptions();
    }

    return hint;
}


function removeRcbOptionsByCell( cellIndex ) {
    //    console.log( "call removeRcbOptionsByCell,"+cellIndex );
    var count = 0;
    possibilityMatrix[ cellIndex ] = new Array;
    if( numbers[cellIndex] > 0 ) {
	var rcbSet = getRcbSetByIndex( cellIndex );
	for( var j=0; j<rcbSet.length; j++ ) {
	    if( isObject( possibilityMatrix[ rcbSet[j] ] ) ) {
		var rtn = removeOptionFromCell( rcbSet[j], numbers[cellIndex] );
		count = count + rtn;
	    }
	}
    }
    return count;
}


function removeOptionFromCell( cellIndex, num ) {
    //    console.log( "call removeOptionFromCell,"+cellIndex+","+num );
    var numList = new Array;
    var count = 0;
    for( var i=0; i<possibilityMatrix[cellIndex].length; i++ ) {
	if( possibilityMatrix[cellIndex][i] == num ) {
	    count = 1;
	}
	else {
	    numList.push( possibilityMatrix[cellIndex][i] );
	}
    }

    possibilityMatrix[cellIndex] = numList;
    return count;
}


function makeListNonRedundant( list ) {
    var rtnList = new Array();
    var inputList = list.sort();
    var lastEntry;
    for( var i=0; i<list.length; i++ ) {
	if( !lastEntry || inputList[i] != lastEntry ) {
	    rtnList.push( inputList[i] );
	}
	lastEntry = inputList[i];
    }
    return rtnList;
}


function removeOptions() {
    var remove;
    if( !remove ) { remove = removeBoxLine(); }
    if( !remove ) { remove = removeNakedSets(); }
    if( !remove ) { remove = removeHiddenSets(); }

    if( remove ) {
	//console.log( "remove "+remove.opts+" from "+remove.cells );
	highlightRemoveOptions( remove );
	var cellText = new Array();
	for( var i=0; i<remove.cells.length; i++ ) {
	    for( var j=0; j<remove.opts.length; j++ ) {
		//console.log( "remove "+remove.cells[i]+" "+remove.opts[j] );
		removeOptionFromCell( remove.cells[i], remove.opts[j] );
	    }
	    var row = getRowByCell( remove.cells[i] ) ;
	    var col = getColByCell( remove.cells[i] ) ;
	    cellText.push( "r" + (row+1) + "c" + (col+1) );
	}
	
	writeMessage( remove.message +": remove "+remove.opts+" from "+cellText+"\n" );
	return remove;
    }
    return false;
}


function removeBoxLine() {
    var remove = new Object;
    var cellSets = getAllRcbSets();
    for( var s=0; s<9; s++ ) { // just the boxes
	var options = new Array();
	var set = cellSets[s];

	// set this up so it exists even if we don't use it later
	for( var i=1; i<10; i++ ) {
	    options[i] = new Array();
	}

	for( var i=0; i<9; i++ ) {
	    var ci = set[i];
	    for( var j=0; j<possibilityMatrix[ci].length; j++ ) {
		var n = possibilityMatrix[ci][j];
		if( options[n] ) {
		    options[n].push( ci );
		}
		else {
		    options[n] = [ ci ];
		}
	    }
	}

	for( var n=1; n<10; n++ ) {
	    if( options[n].length > 0 ) {
		var r = getRowByCell( options[n][0] );
		var c = getColByCell( options[n][0] );
		var notRow = 0;
		var notCol = 0;
		for( var i=0; i<options[n].length; i++ ) {
		    if( r != getRowByCell( options[n][i] ) ) { notRow = 1; }
		    if( c != getColByCell( options[n][i] ) ) { notCol = 1; }
		}
		
		var set = new Array;
		if( notRow < 1 ) { set = row[r]; }
		if( notCol < 1 ) { set = col[c]; }
		
		if( set.length > 0 ) {
		    var remove = new Object;
		    remove.opts = [n];
		    remove.cells = new Array();
		    remove.highlight = new Array();
		    remove.message = 'Box-line interaction';

		    for( var i=0; i<set.length; i++ ) {
			var notThis = 0;
			for( var j=0; j<options[n].length; j++ ) {
			    if( set[i] == options[n][j] ) {
				notThis = 1;
				remove.highlight.push( set[i] );
			    }
			}
			if( notThis < 1 && hasOption( set[i], n ) ) {
			    remove.cells.push( set[i] );
			}
		    }

		    if( remove.cells.length > 0 ) {
			return remove;
		    }
		}
	    }
	}
    }
    return false;
}


function removeNakedSets() {
    var cellSets = getAllRcbSets();
    var allRemoves = new Array();

    for( var s=0; s<27; s++ ) {
	var set = cellSets[s];
	var options = new Array();

	for( var i=0; i<9; i++ ) {
	    var ci = set[i];

	    if( possibilityMatrix[ci].length > 1 ) {
		var opts = possibilityMatrix[ci].join(':');
		
		if( options[opts] ) {
		    options[opts].push( ci );
		}
		else {
		    options[opts] = [ ci ];
		}
	    }
	}

	for( var prop in options ) {
	    var optList = prop.split(':');
	    if( optList.length == options[prop].length ) {
		var remove = new Object();
		remove.cells = new Array();
		remove.highlight = new Array();
		// so we can sort for boxes first later
		remove.set = s;

		// go through the cells in the set and check which
		// need to have options removed
		for( var i=0; i<9; i++ ) {
		    var notThis = 0;
		    // don't bother with cells without the options
		    var yesThis = 0;
		    for( var k=0; k<optList.length; k++ ) {
			if( hasOption( set[i], optList[k] ) > 0 ) {
			    yesThis = 1;
			}
		    }
		    if( yesThis < 1 ) {
			notThis = 1;
		    }

		    // don't remove from the naked set cells
		    for( var j=0; j<options[prop].length; j++ ) {
			if( options[prop][j] == set[i] ) {
			    notThis = 1;
			}
		    }

		    if( notThis < 1 ) {
			remove.cells.push( set[i] );
		    }

		    remove.highlight = options[prop];
		    //		    else {
		    //	remove.highlight.push( set[i] );
		    //}
		}
		remove.opts = new Array();
		for( var i=0; i<optList.length; i++ ) {
		    remove.opts.push( optList[i] );
		}
		// wasteful -- keep 'em all for now, but only use
		// the easiest later
		if( remove.cells.length > 0 ) {
		    allRemoves.push( remove );
		}
	    }
	}
    }


    var returnRemove;

    if( allRemoves.length > 0 ) {
	// now find the easiest
	var sortedRemoves = allRemoves.sort( sortRemoves );
	for( var i=0; i<sortedRemoves.length; i++ ) {
	    if( sortedRemoves[i].cells.length > 0 ) {
		returnRemove = sortedRemoves[i];
		//console.log( returnRemove.cells, returnRemove.opts );
		var lengthTag = [ 'single', 'pair', 'triple', 'quad' ];
		returnRemove.message = 'Naked '+lengthTag[ returnRemove.highlight.length-1 ];
	    }
	}
    }

    return returnRemove;
}


function removeHiddenSets() {
    var combinations = new Array( [1,2], [1,3], [1,4], [1,5], [1,6], [1,7], [1,8], [1,9],
				  [2,3], [2,4], [2,5], [2,6], [2,7], [2,8], [2,9],
				  [3,4], [3,5], [3,6], [3,7], [3,8], [3,9],
				  [4,5], [4,6], [4,7], [4,8], [4,9],
				  [5,6], [5,7], [5,8], [5,9],
				  [6,7], [6,8], [6,9],
				  [7,8], [7,9],
				  [8,9]
				  );
    
    var cellSets = getAllRcbSets();

    for( var s=0; s<27; s++ ) {
	var set = cellSets[s];

	// replace
	var cellsWithOpt = new Array(10);
	for( var i=0; i<9; i++ ) {
	    var ci = set[i];
	    var opts = possibilityMatrix[ci];
	    for( var j=0; j<opts.length; j++ ) {
		if( !isObject( cellsWithOpt[ opts[j] ] ) ) { 
		    cellsWithOpt[ opts[j] ] = new Array();
		}
		cellsWithOpt[ opts[j] ].push( ci );
	    }
	}

	// doubles has numbers with only two possible placings
	var doubles = new Array;
	var triples = new Array;
	var quads = new Array;
	for( var n=1; n<10; n++ ) {
	    if( isObject( cellsWithOpt[n] ) ) {
		if( cellsWithOpt[n].length == 2 ) { doubles.push( n ); }
		/* if( cellsWithOpt[n].length == 3 ) { triples.push( n ); }
		   if( cellsWithOpt[n].length == 4 ) { quads.push( n ); } */
	    }
	}

	/*var combo = new Array;
	for( var i=0; i<doubles.length; i++ ) {
	    var n1 = doubles.shift();
	    for( var j=0; j<doubles.length; j++ ) {
		var n2 = doubles[j];
		combo.push( n1+':'+n2 );
	    }
	}
	for( var i=0; i<triples.length; i++ ) {
	    var n1 = triples.shift();
	    for( var j=0; j<triples.length; j++ ) {
		var n2 = triples.shift();
		for( var k=0; k<triples.length; k++ ) {
		    var n3 = triples[k];
		    combo.push( n1+':'+n2+':'+n3 );
		}
	    }
	}
	for( var i=0; i<quads.length; i++ ) {
	    var n1 = quads.shift();
	    for( var j=0; j<quads.length; j++ ) {
		var n2 = quads.shift();
		for( var k=0; k<quads.length; k++ ) {
		    var n3 = quads.shift();
		    for( var l=0; l<quads.length; l++ ) {
			var n4 = quads[l];
			combo.push( n1+':'+n2+':'+n3+':'+n4 );
		    }
		}
	    }
	    }*/

	for( var i=0; i<(doubles.length); i++ ) {
	    var n1 = doubles.shift();
	    for( var j=0; j<doubles.length; j++ ) {
		var n2 = doubles[j];
		var notThis = 0;
		for( var k=0; k<2; k++ ) {
		    if( cellsWithOpt[n1][k] != cellsWithOpt[n2][k] ) {
			notThis = 1;
		    }
		}

		if( notThis < 1 ) {
		    // we have a hidden double
		    var remove = new Object();
		    // so we can sort for boxes first later
		    remove.set = s;
		    remove.cells = new Array();
		    remove.opts = new Array();
		    remove.highlight = new Array();
		    remove.message = 'Hidden pair ['+n1+','+n2+']';

		    for( var l=0; l<2; l++ ) {
			var ci = cellsWithOpt[n1][l];
			var opts = possibilityMatrix[ ci ];
			remove.highlight.push( ci );
			if( opts.length >2 ) {
			    remove.cells.push( ci );

			    for( var m=0; m<opts.length; m++ ) {
				if( opts[m] != n1 && opts[m] != n2 ) {
				    remove.opts.push( opts[m] );
				}
			    }
			}
		    }
		    if( remove.cells.length > 0 ) {
			var tmpList = makeListNonRedundant( remove.opts );
			remove.opts = tmpList;
			return remove;
		    }
		}
	    }
	}
    }
    return false;
}


	/*
	var doubles = new Object();
	var triples = new Object();
	var quads = new Object();
	for( var i=0; i<9; i++ ) {
	    var ci = set[i];

	    var opts = possibilityMatrix[ci];
	    for( var j=0; j<opts.length; j++ ) {
		for( var k=0; k<opts.length; k++ ) {
		    var key2 = j+':'+k;
		    if( j!=k ) {
			if( !isObject( doubles[key2] ) ) { doubles[key2] = new Array(); }
			doubles[key2].push( ci );
		    }

		    for( var l=0; l<opts.length; l++ ) {
			if( j!=k && j!=l && k!=l ) {
			    var key3 = key2+':'+l;
			    if( !isObject( triples[key3] ) { triples[key3] = new Array(); }
			    triples[key3].push( ci );
			}

			for( var m=0; m<opts.length; m++ ) {
			    if( j!=k && j!=l && j!=m k!=l && k!=m && l!=m ) {
			        var key4 = key3+':'+m;
				if( !isObject( quads[key4] ) { quads[key4] = new Array(); }
				quads[key4].push( ci );
			    }

			}
		    }
		}
	    }
	}
	
	for( var i=0; i<doubles.length; i++ ) {
	}
	*/

	//    }

    //return remove;



function highlightRemoveOptions( remove ) {
    var highlight = new Array();
    for( var i=0; i<remove.cells.length; i++ ) {
	var ci = remove.cells[i];
	var cell = getCellByIndex( ci );
	placePencilMarks( cell, possibilityMatrix[ci] );
	var p = getPencilByIndex( ci );
	highlight.push( p );
    }
    highlightObjectSet( highlight, "#faa" );

    highlight = new Array();
    for( var i=0; i<remove.highlight.length; i++ ) {
	var ci = remove.highlight[i];
	var cell = getCellByIndex( ci );
	placePencilMarks( cell, possibilityMatrix[ci] );
	var p = getPencilByIndex( ci );
	highlight.push( p );
    }
    highlightObjectSet( highlight, "#afa" );
}


function hasOption( cellIndex, n ) {
    var hasIt = 0;
    for( var i=0; i<possibilityMatrix[cellIndex].length; i++ ) {
	if( possibilityMatrix[cellIndex][i] == n ) {
	    hasIt = 1;
	}
    }
    return hasIt;
}



function sortRemoves(a,b) {
    // pairs before triples, boxes before rows before cols
    return a.opts.length - b.opts.length || a.set - b.set;
}

function openImporter() {
    // open a new window with a text input 
    // to paste in a puzzle
    window.open( '/sudoku/includes/importer.html', 'importer' );
}


/* utility stuff */
function isObject(a) {
    return (typeof a == 'object' && !!a) || isFunction(a);
}
function isFunction(a) {
    return typeof a == 'function';
}

function scrollElementToEnd (element) {
    if (typeof element.scrollTop != 'undefined' &&
	typeof element.scrollHeight != 'undefined') {
	element.scrollTop = element.scrollHeight;
    }
}

