/*
 * Supporting JavaScript for the Sudoku HTML solver
 * © Copyright 2005 Gaby Vanhegan <gaby@vanhegan.net>
 *  
 * This class is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  
 * 02110-1301, USA
 */

// Solver version
var version_maj			= 1;
var version_min			= 6;

// Define these for use in other functions.  These can be overridden 
// later on in the class if necessary.
var cols				= 9;
var rows				= 9;

// How long have we been running for?
var timer				= 0;
var completed			= false;

// Currently selected square in the grid
var curX				= 0;
var curY				= 0;

// How many cubes across or up is the puzzle?
var cubeX				= 3;
var cubeY				= 3;

// Store the initial colours of the cells in a list
var initialColours		= [];

// Which cell have we moved from?
var cellCurrent			= false;
var cellPrevious		= false;

var highlightColour		= "#aaaaff";
var normalColour		= "#ffffff";

/*
 * Utility function to find an object in the various DOM trees, independant
 * of the browser or platform being used.  In the event of failure, false is
 * returned
 * 
 * Heads up to Tom Wiltshire and Chris Tebb for this one.  Cheers boys...
 */
function getElement ( objectId ) {

  // checkW3C DOM, then MSIE 4, then NN 4.
  if ( document.getElementById && document.getElementById( objectId ) ) {
      return document.getElementById( objectId ); 
  }
  else if ( document.all && document.all( objectId ) ) {  
      return document.all(objectId).style;
  } 
  else if ( document.layers && document.layers[ objectId ] ) { 
      return document.layers[objectId];
  } 
  else {
      return false;
  }
}

/*
 * Move the cursor one cell up, rolling back on to the bottom of the board
 * if we roll off the edge.  Optionally, we can tick over to the next column
 * in the board when we roll off the current one, scanning over the board
 * in the process.
 * @param	boolean	tickOver	Tick over to next set of cells
 */
function moveUp ( tickOver ) {
	curY	= ( curY - 1 );
	if ( curY < 0 ) {
		curY = rows - 1;
		if ( tickOver != false ) {
			moveLeft( false );
		}
	}
	highlightCurCell();
}

/*
 * Move the cursor one cell up, rolling back on to the bottom of the board
 * if we roll off the edge.  Optionally, we can tick over to the next column
 * in the board when we roll off the current one, scanning over the board
 * in the process.
 * @param	boolean	tickOver	Tick over to next set of cells
 */
function moveDown ( tickOver ) {
	curY	= ( curY + 1 );
	if ( curY >= rows ) {
		curY = 0;
		if ( tickOver != false ) {
			moveRight( false );
		}
	}
	highlightCurCell();
}

/*
 * Move the cursor one cell left, rolling back on to the right of the board
 * if we roll off the side.  Optionally, we can tick over to the next row
 * in the board when we roll off the current one, scanning over the board
 * in the process.
 * @param	boolean	tickOver	Tick over to next set of cells
 */
function moveLeft ( tickOver ) {
	curX	= ( curX - 1 );
	if ( curX < 0 ) {
		curX = ( cols - 1 );
		if ( tickOver != false ) {
			moveUp( false );
		}
	}
	highlightCurCell();
}

/*
 * Move the cursor one cell left, rolling back on to the right of the board
 * if we roll off the side.  Optionally, we can tick over to the next row
 * in the board when we roll off the current one, scanning over the board
 * in the process.
 * @param	boolean	tickOver	Tick over to next set of cells
 */
function moveRight ( tickOver ) {
	curX	= ( curX + 1 );
	if ( curX >= cols ) {
		curX = 0;
		if ( tickOver != false ) {
			moveDown( false );
		}
	}
	highlightCurCell();
}

/*
 * Highlight the cell currently pointed at by the curX and curY variables
 */
function highlightCurCell ( ) {

	var curCell		= ( curY * ( cols ) + curX );
	var elem		= getElement( "cell" + curCell );
	
	if ( elem != false ) { 

		// Rememver the previous and current cells
		cellPrevious	= Number( cellCurrent );
		cellCurrent		= Number( curCell );
		
		// Bring focus to this cell
		elem.focus();

		var prevCell	= getElement( "cell" + cellPrevious );
		var prevTd		= getElement( "td" + cellPrevious );
		
		if ( prevCell != false ) {
			prevCell.style.background	= initialColours[ cellPrevious ];
			prevTd.style.background		= initialColours[ cellPrevious ];
		}
		
		// Set the colour of the current cell to the highlight colour
		if ( cellCurrent >= 0 ) {
			
			// Remember the initial colour of the cell
			var theCol						= getElement( "cell" + cellCurrent ).style.background;
			//if ( theCol == "" ) { theCol = normalColour; }
			
			initialColours[ cellCurrent ]	= theCol;
			
			// Set the cell to the new colour
			getElement( "cell" + cellCurrent ).style.background		= highlightColour;
			getElement( "td" + cellCurrent ).style.background		= highlightColour;
		}
		
		return true;
		//alert( "Moving to: " + curX + "," + curY + " (" + curCell + ")" );
	}
}

/*
 * Clear the currently selected cell
 */
function clearCurCell ( ) {
	var curCell	= ( curY * ( cols ) + curX );
	var elem	= getElement( "cell" + curCell );
	if ( elem ) {
		if ( !( elem.readOnly ) ) {
			elem.value	= "";
			//alert( "Clearing: " + curX + "," + curY );
		}
	}
}

/*
 * Set the currently selected cell to the given value
 * @param	string	theValue	The value to set the cell to
 */
function setCurCell ( theValue ) {
	var curCell	= ( curY * ( cols ) + curX );
	var elem	= getElement( "cell" + curCell );
	if ( elem ) {
		if ( !( elem.readOnly ) ) {
			elem.value	= theValue;
		}
		// Optionally, move onto the next cell.
		// moveRight(); 
		// alert( "Setting: " + curX + "," + curY + " to " + theValue );
	}	
}

/*
 * Set the current cell as the selected one, in order to handle people
 * clicking on on a given cell
 * @param	integer	x			The X coordinate of the cell
 * @param	integer	y			The Y coordinate of the cell
 */
function selectCell ( x, y ) {
	curX	= x;
	curY	= y;
	highlightCurCell();
}

/*
 * Handle a keypress, typically handling the arrow keys being
 * pressed to navigate around the input grid
 * @param	string	field		The name of the textfield that was selected
 * @param	mixed	evt			The event that was called
 * @param	string	nextField	The element ID of the next field to move to
 * @param	boolena	change		True if the cell is to be changed, false if not
 */
function keyWasPressed ( field, evt ) {

	// Work out what key was pressed
	var keyChar		= document.layers ? evt.which :
					    	document.all ? event.keyCode :
					  	   	document.getElementById ? evt.keyCode : 0;
					
	var returnVal	= true;
	
	/*
	 * The arrow keys have the following key codes:
	 * Left  		37		Move left one cell
	 * Up    		38		Move up one cell
	 * Right 		39		Move right one cell
	 * Down  		40		Move down one cell
	 * Tab			9		Move on to the next cell
	 * Space			32		Clear the current cell, move on to the next cell
	 * Backspace	8		Clear the current cell, move on to the next cell
	 * 1				49		Put number 1 into a cell
	 * 2				50		Put number 2 into a cell
	 * 3				51		Put number 3 into a cell
	 * 4				52		Put number 4 into a cell
	 * 5				53		Put number 5 into a cell
	 * 6				54		Put number 6 into a cell
	 * 7				55		Put number 7 into a cell
	 * 8				56		Put number 8 into a cell
	 * 9				57		Put number 9 into a cell
	 */
	if ( keyChar == 37 ) { moveLeft();   returnVal = false; }
	if ( keyChar == 38 ) { moveUp();     returnVal = false; }
	if ( keyChar == 39 ) { moveRight();  returnVal = false; }
	if ( keyChar == 40 ) { moveDown();   returnVal = false; }
	if ( keyChar == 9  ) { moveRight();  returnVal = false; }
	if ( keyChar == 32 ) { clearCurCell();  moveRight(); returnVal = false; }
	if ( keyChar == 8  ) { clearCurCell();  returnVal = false; }

	// Keys 1 to 9
	if ( keyChar == 49 ) { setCurCell( 1 ); returnVal = false; }
	if ( keyChar == 50 ) { setCurCell( 2 ); returnVal = false; }
	if ( keyChar == 51 ) { setCurCell( 3 ); returnVal = false; }
	if ( keyChar == 52 ) { setCurCell( 4 ); returnVal = false; }
	if ( keyChar == 53 ) { setCurCell( 5 ); returnVal = false; }
	if ( keyChar == 54 ) { setCurCell( 6 ); returnVal = false; }
	if ( keyChar == 55 ) { setCurCell( 7 ); returnVal = false; }
	if ( keyChar == 56 ) { setCurCell( 8 ); returnVal = false; }
	if ( keyChar == 57 ) { setCurCell( 9 ); returnVal = false; }

	// Num Pad Keys 1 to 9
	if ( keyChar == 97  ) { setCurCell( 1 ); returnVal = false; }
	if ( keyChar == 98  ) { setCurCell( 2 ); returnVal = false; }
	if ( keyChar == 99  ) { setCurCell( 3 ); returnVal = false; }
	if ( keyChar == 100 ) { setCurCell( 4 ); returnVal = false; }
	if ( keyChar == 101 ) { setCurCell( 5 ); returnVal = false; }
	if ( keyChar == 102 ) { setCurCell( 6 ); returnVal = false; }
	if ( keyChar == 103 ) { setCurCell( 7 ); returnVal = false; }
	if ( keyChar == 104 ) { setCurCell( 8 ); returnVal = false; }
	if ( keyChar == 105 ) { setCurCell( 9 ); returnVal = false; }

	// Store the current state of the puzzle, if we wanted to save it
	var theGuesses	= getLayoutGuesses();
	saveState( "guesses", theGuesses );
	
	// Check that the puzzle is complete after every keypress
	var complete	= checkComplete();
	if ( complete == true ) {
		puzzleComplete();
		}

	return returnVal;
	}

/*
 * Display the fact that the user has completed the puzzle
 */
function puzzleComplete ( ) {
	
	if ( completed != true ) {
		
		var milliSecs	= timerEnd();
		var seconds		= ( milliSecs / 1000 );
		var fullTime	= Math.floor( seconds );

		// How many mins/secs was that?
		var mins			= Math.floor( fullTime / 60 );
		var secs			= ( fullTime % 60 );

		// Format the time
		var elapsed		= secs + " secs";
		if ( mins > 0 ) {
			elapsed		= mins + " mins, "
						+ elapsed;
		}

		// Show the tick box for the completed puzzle by replacing the
		// empty square with the tick.  Also, store how long it took.

		// Mark the fact that this puzzle was solved
		var compImg	= new Image( 16, 16 );
		compImg.src	= "solve.complete.php?time=" + seconds;

		/*
		var elem = getElement( "puzzlecomplete" );
		if ( elem ) {
			elem.src	= "solve.complete.php?time=" + seconds;
		}
		*/

		// Display the alert message
		var msg			= "Well done!  You completed the Sudoku!\n"
						+ "Your time: " + elapsed;
		alert( msg );

		completed		= true;
		}
	}

/* 
 * Start the timer by setting it to the current time in seconds
 */
function timerStart ( ) {
	var theDate	= new Date();
	timer			= theDate.getTime();
	}

/*
 * End the timer, returning the time since it was started
 * @return	string	elapsed	The elapsed time since the timer was started
 */
function timerEnd ( ) {

	// When did we start?
	var started		= timer;

	// When did we end?
	var theDate		= new Date();
	var finished	= theDate.getTime();

	// Calculate the total amount of seconds
	var fullTime	= ( finished - started );
	
	return fullTime;
	}
	
/*
 * Check the puzzle state for completeness
 */
function checkComplete ( ) {

	var inBlock	= new Array();
	for ( var i = 0 ; i < rows ; i++ ) {
		inBlock[ i ]	= new Array();
	}
	
	for ( var a = 0 ; a < rows ; a++ ) {
		
		var inRow	= new Array();
		var inCol	= new Array();
		
		for ( var b = 0 ; b < cols ; b++ ) {
			
			// Get the cell in this row and column
			var colCell		= getElement( "cell" + ( ( b * cols ) + a ) );
			var rowCell		= getElement( "cell" + ( ( a * rows ) + b ) );
			var blkCell		= getElement( "cell" + ( ( a * rows ) + b ) );

			var rowVal		= parse_int( rowCell.value );
			var colVal		= parse_int( colCell.value );
			var blkVal		= parse_int( blkCell.value );
			
			if ( rowVal > 0 ) {
				inRow[ rowVal ]	= true;
				}
			
			if ( colVal > 0 ) {
				inCol[ colVal ]	= true;
				}
			
			/*
			if ( typeof( inBlock[ blockNum ] ) != "Array" ) { inBlock[ blockNum ] = new Array(); }
			if ( blkVal > 0 ) {
				inBlock[ blockNum ][ blkVal ]	= true;
				}
			*/
			}

		// Do we have enough rows in this row?
		var rowCounter	= 0;
		for ( var item in inRow  ) { rowCounter++; }
		if ( rowCounter != 9 ) { 
			//alert( "Row: " + a + " unfilled, has " + rowCounter );
			return false; 
			}

		// Do we have enough cells in this column?
		var colCounter	= 0;
		for ( var item in inCol ) { colCounter++; }
		if ( colCounter != 9 )  { 
			//alert( "Col: " + a + " unfilled, has " + colCounter );
			return false; 
			}
		}
	
	/*
	// Finally, check the blocks for completeness
	for ( var blockNum in inBlock ) {
		blockSet		= inBlock[ blockNum ];
		var blkCounter	= 0;
		for ( var item in blockSet ) { blkCounter++; }
		if ( blkCounter != 9 ) { 
			alert( "Block: " + blockNum + " unfilled, has " + blkCounter );
			//return false; 
			}
		}
	*/
	
	// We have passed all the tests!
	//alert( "Puzzle complete, and valid" );
	return true;
	}

/*
 * Calculate the number of a block, based on a given cell x/y
 * @param	integer	cell_x	The X coord of the cell
 * @param	integer	cell_y	The Y coord of the cell
 * @return	integer	return	The block number
 */
_calc_blocknum = function ( cell_x, cell_y ) {
	var cube_x		= parse_int( cell_x / cubeX );
	var cube_y		= parse_int( cell_y / cubeY );
	var blockNum	= Number( ( cube_y * cubeY ) + cube_x );
	return blockNum;
	}

/*
 * Convert a string into an integer value
 * @param	mixed	subject		The value to convert to an integer
 * @return	integer				The value, as an integer
 */
parse_int = function ( subject ) {
	if (subject) { subject = subject.toString(); }
	if (subject.match && subject.match(/[.0-9]+/)) { return new Number(subject.match(/[.0-9]+/)); }
	else { return 0; }
}

/*
 * Reset the puzzle state
 */
function resetPuzzle ( ) {
	completed	= false;
	var elem	= getElement( "sudokuPuzzle" );
	elem.reset();
}

/* 
 * Get the layout of guesses in the puzzle
 * @return	string	theString	The single string state
 */
function getLayoutGuesses ( ) {
	
	var theString	= "";
	var look		= "";
	
	for ( var y = 0 ; y < rows ; y++ ) {
		for ( var x = 0 ; x < cols ; x++ ) {
			
			var theCell		= ( x + ( y * cols ) );
			look 			= look + theCell + ",";
			
			// Look in the clues list to see if it's a number, or a -
			var isGuess		= curClues[ theCell ];
			
			// If it's a blank, we could have filled in a number here
			// so we can take the value
			if ( isGuess == '-' ) {
				theCell			= "cell" + theCell;
				var theElem		= getElement( theCell );
				var theValue	= theElem.value;
				if ( theValue > 0 ) { 
					theString		+= theElem.value;
				}
				else {
					theString		+= "-";
				}
			}
			else {
				theString		+= "-";
			}
		}
		look 	= look + "\n";
	}
	return theString;
}