var msgfade_timer = 3000;
var msgfade_default = 3000;
var msg_last = '';

function SudokuBoard(selector,opt)
{
	var $obj = $(selector);
	var gridsize = 9;
	var enabled = true;
	var options = {};

	var BoardClock = function()/*{{{*/
	{
		var timer = 0;
		var paused = true;

		window.setInterval(function() {
			if (!paused) clock_beat();
			else clock_flasher();
		},1000);

		function clock_flasher()
		{
			get_ui_clock().toggle();
		};
		function reset()
		{
			timer = 0;
			paused = true;
		};
		function clock_beat()
		{
			timer++;
			update();
		};
		function start()
		{
			paused = false;
		};
		function pause()
		{
			paused = true;
		};
		function convert_time(time)
		{
			var hr = parseInt(time / 3600);
			var mi = (parseInt(time / 60) - (hr * 60));
			var se = time % 60;
			if (hr < 10) hr = '0' + hr.toString();
			if (mi < 10) mi = '0' + mi.toString();
			if (se < 10) se = '0' + se.toString();
			return [ hr, mi, se ].join(':');
		};
		function update()
		{
			var $clock = get_ui_clock();
			$clock.html(convert_time(timer)).show();
		};
		function get_ui_clock()
		{
			return $('#tpanel div .clock');
		};
		return {
			start: start,
			reset: reset,
			pause: pause
		};
	}();
	/*}}}*/

	setOptions(opt);

	function setOptions(opt)/*{{{*/
	{
		var opt = (typeof(opt) == 'object')? opt : {};
		options = $.extend({
			showConflicts: false
		},opt);
	};
	/*}}}*/

	var Cells = function()/*{{{*/
	{
		var board = [ ];
		function make_selector(col,row)
		{
			return 'td .box_c'+col+'r'+row;
		};
		function get_cell(col,row)/*{{{*/
		{
			return $obj.find(make_selector(col,row));
		};
		/*}}}*/
		function column_selectors(col)/*{{{*/
		{
			var rv = [];
			for(i=1;i<=gridsize;i++)
				rv.push(make_selector(col,i));
			return rv;
		};
		/*}}}*/
		function row_selectors(row)/*{{{*/
		{
			var rv = [];
			for(i=1;i<=gridsize;i++)
				rv.push(make_selector(i,row));
			return rv;
		};
		/*}}}*/
		function get_row(col)/*{{{*/
		{
			return $obj.find(row_selectors(col));
		};
		/*}}}*/
		function get_column(col)/*{{{*/
		{
			return $obj.find(column_selectors(col));
		};
		/*}}}*/
		function cell_value(col,row)/*{{{*/
		{
			return get_cell(col,row).html() || 0;
		};
		/*}}}*/
		function init_board()
		{
			board = empty_board();
		};
		function empty_board()/*{{{*/
		{
			var board = [ ];
			for(row=0;row<gridsize;row++)
			{
				if (!(row in board)) board[row] = [ ];
				for(col=0;col<gridsize;col++) board[row][col] = 0;
			}
			return board;
		};
		/*}}}*/
		function cell_value(col,row)
		{
			return board[row-1][col-1];
		};
		function cell_set(col,row,value)
		{
			board[row-1][col-1] = value;
			return true;
		};

		init_board();

		return {
			init: init_board,
			column: get_column,
			value: cell_value,
			row: get_row,
			cell_set: cell_set,
			cell_get: cell_value,
			cell: get_cell
		};
	}();
	/*}}}*/
	var Selection = function()/*{{{*/
	{
		var lastcol = 1;
		var lastrow = 1;

		function clear_selected()
		{
			$obj.find('.selected').removeClass('selected');
		};
		function clear_value(col,row)/*{{{*/
		{
			var col = col || lastcol;
			var row = row || lastrow;
			var $cell = Cells.cell(col,row);

			if (!$cell.hasClass('prefill'))
			{
				$cell.html('');
				Board.cell_set(col,row,0);
				Board.conflicts_clear();
			}
		};
		/*}}}*/
		function select_cell(col,row)/*{{{*/
		{
			clear_selected();
			Board.conflicts(col,row);
			Keys.enable();
			Cells.cell(col,row).addClass('selected');
			lastcol = col;
			lastrow = row;
		};
		/*}}}*/
		function move_down()/*{{{*/
		{
			select_offset(0,1);
		};
		/*}}}*/
		function move_up()/*{{{*/
		{
			select_offset(0,-1);
		};
		/*}}}*/
		function move_right()/*{{{*/
		{
			select_offset(1,0);
		};
		/*}}}*/
		function move_left()/*{{{*/
		{
			select_offset(-1,0);
		};
		/*}}}*/
		function select_offset(cols,rows)/*{{{*/
		{
			var cell = wrap_cell(lastcol + cols,lastrow + rows);
			select_cell(cell[0],cell[1]);
		};
		/*}}}*/
		function wrap_cell(col,row)/*{{{*/
		{
			if (col < 1) col = gridsize;
			if (col > gridsize) col = 1;
			if (row < 1) row = gridsize;
			if (row > gridsize) row = 1;
			return [ col, row ];
		};
		/*}}}*/
		function get_last() {
			return [ lastcol, lastrow ];
		};

		return {
			release: clear_selected,
			get_last: get_last,
			select: select_cell,
			clear: clear_value,
			up: move_up,
			left: move_left,
			right: move_right,
			down: move_down
		};
	}();
	/*}}}*/
	var Keys = function()/*{{{*/
	{
		var $key_watcher;
		function init()
		{
			$key_watcher = $('<input type="radio" />')
				.css({
					position: 'absolute',
					top: '-50px', left: '-50px'
				})
				.blur(blurred)
				.keydown(key_handler)
				.hide()
				.appendTo(document.body);
		};
		function key_handler(e)
		{
			if ((e.keyCode > 48) && e.keyCode < 58)
				cell_input_value(e.keyCode - 48);
			else if ((e.keyCode > 96) && e.keyCode < 106)
				cell_input_value(e.keyCode - 96);
			else {
				switch(e.keyCode)
				{
					case 27:
						Selection.release();
						Board.conflicts_clear();
						break;
					case 38: Selection.up(); break;
					case 40: Selection.down(); break;
					case 39: Selection.right(); break;
					case 37: Selection.left(); break;
					case 46:
					case 8:
						Selection.clear();
						break;
					default:
						if (e.ctrlKey) return true;
						//debug_msg('Keycode: '+e.keyCode);
						break;
				}
			}
			return nothing(e);
		};
		function blurred(e)
		{
			Board.conflicts_clear();
			disable();
			return nothing(e);
		};
		function disable()
		{
			Selection.release();
			$key_watcher.hide();
		};
		function enable()
		{
			$key_watcher.show().focus();
		};
		init();
		return { enable: enable };
	}();
	/*}}}*/

	var Board = function()
	{
		var board	= empty_board();
		var known	= empty_board();
		var solved	= empty_board();

		var known_count = 0;

		function board_populate(vboard,solution)/*{{{*/
		{
			known_count = 0;
			board = unserialize(vboard);
			known = unserialize(vboard);
			solved = unserialize(solution);

			board_redraw();
			$('#tpanel div .known').html(known_count = update_counters().toString());
			$('#tpanel div .filled').html('0');

			return true;
		};
		/*}}}*/
		function board_redraw()/*{{{*/
		{
			$obj.find('.sudoku-square').html('')
				.removeClass('prefill').removeClass('conflict');

			for(row=1;row<=gridsize;row++)
				for(col=1;col<=gridsize;col++)
					draw_square(col,row);

			return true;
		};
		/*}}}*/
		function board_reset()/*{{{*/
		{
			board = unserialize(serialize(known));
			update_counters();
			$('#tpanel div .hints').html('0');
			return true;
		};
		/*}}}*/
		function validate()/*{{{*/
		{
			for(row=1;row<=gridsize;row++)
				for(col=1;col<=gridsize;col++)
					if (cell_get(col,row) > 0)
						if (find_conflicts(col,row).length) return false;
			return true;
		};
		/*}}}*/
		function update_counters()/*{{{*/
		{
			var count = 0;

			for(row=1;row<=gridsize;row++)
				for(col=1;col<=gridsize;col++)
					if (cell_get(col,row) > 0) count++;

			var filled = count - known_count;
			var remain = 81 - count;

			$('#tpanel div .cempty').html(remain.toString());
			$('#tpanel div .filled').html(filled.toString());

			return filled;
		};
		/*}}}*/
		function is_complete()/*{{{*/
		{
			for(row=1;row<=gridsize;row++)
				for(col=1;col<=gridsize;col++)
					if (!(cell_get(col,row) > 0)) return false;
			return true;
		};
		/*}}}*/
		function disable()/*{{{*/
		{
			if (enabled) {
				enabled = false;
				$obj.addClass('disabled');
				$('#sudoku_hint').attr('disabled',true).addClass('disabled');
				$('#sudoku_clear').attr('disabled',true).addClass('disabled');
				BoardClock.pause();
			}
		};
		/*}}}*/
		function enable()/*{{{*/
		{
			enabled = true;
			$('#sudoku_hint').attr('disabled',false).removeClass('disabled');
			$('#sudoku_clear').attr('disabled',false).removeClass('disabled');
			$obj.removeClass('disabled');
			BoardClock.start();
		};
		/*}}}*/
		function empty_board()/*{{{*/
		{
			var board = [ ];
			for(row=0;row<gridsize;row++)
			{
				if (!(row in board)) board[row] = [ ];
				for(col=0;col<gridsize;col++) board[row][col] = 0;
			}
			return board;
		};
		/*}}}*/
		function is_known(col,row)/*{{{*/
		{
			if (known[row-1][col-1] && known[row-1][col-1] > 0) return true;
		};
		/*}}}*/
		function cell_set(col,row,value)/*{{{*/
		{
			if (!is_known(col,row))
			{
				if (value == '.') value = 0;
				board[row-1][col-1] = value;
				draw_square(col,row);
			};


			if (Board.complete())
				if (Board.validate())
				{
					debug_msg('<b>Success!</b> You completed the board accurately!<br />'+
						'To play again, click "New Board"');
					Board.disable();
				}
				else debug_msg('<b>Oops!</b> The board is incorrect.');

			update_counters();
		};
		/*}}}*/
		function cell_get(col,row)/*{{{*/
		{
			if (is_known(col,row)) return known[row-1][col-1];
				else return board[row-1][col-1];
		};
		/*}}}*/
		function serialize(data)/*{{{*/
		{
			var board = '';

			for(row=0;row<gridsize;row++)
				for(col=0;col<gridsize;col++)
					board += data[row][col].toString();

			return board;
		};
		/*}}}*/
		function unserialize(data)/*{{{*/
		{
			var board = empty_board();

			for(row=0;row<gridsize;row++)
				for(col=0;col<gridsize;col++)
					board[row][col] = data.substr((row*gridsize)+col,1);

			return board;
		};
		/*}}}*/
		function cell_answer(col,row)/*{{{*/
		{
			return solved[row][col];
		};
		/*}}}*/
		function make_selector(col,row)/*{{{*/
		{
			return '.box_c'+col+'r'+row;
		};
		/*}}}*/
		function get_cell_obj(col,row)/*{{{*/
		{
			return $obj.find(make_selector(col,row));
		};
		/*}}}*/
		function draw_square(col,row)/*{{{*/
		{
			var $cell = get_cell_obj(col,row);
			var value = cell_get(col,row);

			if ((value != '.') && value > 0)
				$cell.html(value.toString());

			if (is_known(col,row))
				$cell.addClass('prefill');
		};
		/*}}}*/
		function serialize_board()/*{{{*/
		{
			return serialize(board);
		};
		/*}}}*/
		function clear_conflicts()/*{{{*/
		/**
		 * Clear all cells marked "conflict"
		 */
		{
			$obj.find('td .conflict').removeClass('conflict');
		};
		/*}}}*/
		function get_cell_section(col,row)/*{{{*/
		/**
		 * Determine section x,y for col,row
		 * @return array section x,y
		 */
		{
			var sec_x = Math.floor((col-1)/3);
			var sec_y = Math.floor((row-1)/3);
			var sec_c = sec_x * 3;
			var sec_r = sec_y * 3;
			return [ sec_c + 1, sec_r + 1 ];
		};
		/*}}}*/
		function get_section_cells(col,row)/*{{{*/
		/**
		 * Return array of coordinate pairs for section x,y
		 * @return array of cell coordinates for section x,y
		 */
		{
			var scoord = get_cell_section(col,row);
			var scmax  = scoord[0] + 3;
			var srmax  = scoord[1] + 3;
			var cells = [];
			for(sc=scoord[0];sc<scmax;sc++)
				for(sr=scoord[1];sr<srmax;sr++)
					cells.push([ sc, sr ]);
			return cells;
		};
		/*}}}*/
		function find_conflicts(col,row)/*{{{*/
		{
			var r, c;
			var conflicts = [];
			var val = cell_get(col,row);

			function check_value(x,y)
			{
				if (!((x == col) && y == row))
					if (cell_get(x,y) == val)
						conflicts.push([ x,y ]);
			};
			function check_section(col,row)
			{
				var cl, cells = get_section_cells(col,row);
				for(cl in cells) check_value(cells[cl][0],cells[cl][1]);
			};
			if (options.showConflicts && val > 0)
			{
				for(r=1;r<=gridsize;r++) check_value(col,r);
				for(c=1;c<=gridsize;c++) check_value(c,row);
				check_section(col,row);
			}
			return conflicts;
		};
		/*}}}*/
		function remark_conflicts(col,row)/*{{{*/
		{
			clear_conflicts();
			if (options.showConflicts) {
				var conflicts = find_conflicts(col,row);
				var r, c;

				if (conflicts.length)
					for(i in conflicts)
					{
						c = conflicts[i][0];
						r = conflicts[i][1];
						get_cell_obj(c,r).addClass('conflict');
					}
			}
		};
		/*}}}*/

		return {
			complete: is_complete,
			validate: validate,
			conflicts: remark_conflicts,
			conflicts_clear: clear_conflicts,
			enable: enable,
			disable: disable,
			known: is_known,
			reset: board_reset,
			serialize_board: serialize_board,
			cell_answer: cell_answer,
			cell_set: cell_set,
			cell_get: cell_get,
			populate: board_populate
		};
	}();

	var Mapper = function()
	{
		return {
		};
	}();

	function nothing(e)/*{{{*/
	{
		e.stopPropagation();
		e.preventDefault();
		return false;
	};
	/*}}}*/
	function init_all()/*{{{*/
	{
		for(row=1;row<=gridsize;row++)
			for(col=1;col<=gridsize;col++)
				init_cell(col,row);
	};
	/*}}}*/
	function init_cell(col,row)/*{{{*/
	{
		var $cell = cell_selector(col,row);
		$cell.click(function(e) { cell_clicked(col,row); });
	};
	/*}}}*/
	function clear_all()/*{{{*/
	{
		Cells.init();
		$obj.find('.sudoku-square').html('')
			.removeClass('prefill').removeClass('conflict');
	};
	/*}}}*/
	function cell_selector(col,row)/*{{{*/
	{
		return $obj.find('.box_c'+col+'r'+row);
	};
	/*}}}*/
	function cell_clicked(col,row)/*{{{*/
	{
		if (enabled)
		{
			if (Board.known(col,row))
				debug_msg('This cell is a given, you cannot play here',50);

			Selection.select(col,row);
		}
	};
	/*}}}*/
	function select_cell(col,row)/*{{{*/
	{
		cell_selector(col,row).addClass('selected');
	};
	/*}}}*/
	function unselect_all()/*{{{*/
	{
		$obj.find('.selected').removeClass('selected');
	};
	/*}}}*/
	function cell_input_value(value)/*{{{*/
	{
		last_clicked = Selection.get_last();
		if (last_clicked.length)
		{
			var col = last_clicked[0];
			var row = last_clicked[1];
			var $cell = cell_selector(col,row);
			if (!$cell.hasClass('prefill'))
			{
				Cells.cell_set(col,row,value);
				Board.cell_set(col,row,value);
				Board.conflicts(col,row);

			}
		}
	};
	/*}}}*/
	function reset_entries()/*{{{*/
	{
		$obj.find('.sudoku-square').not('.prefill').html('');
		Board.conflicts_clear();
		Board.reset();
		BoardClock.reset();
		BoardClock.start();
	};
	/*}}}*/
	function get_board_data()/*{{{*/
	{
		return Board.serialize_board();
	};
	/*}}}*/

	function populate_json(json)
	{
		BoardClock.reset();
		Board.populate(json[0],json[1]);
		BoardClock.start();
	};
	function pause_game()
	{
		$obj.addClass('paused');
		BoardClock.pause();
		Board.disable();
	};
	function play_game()
	{
		$obj.removeClass('paused');
		BoardClock.start();
		Board.enable();
	};

	init_all();

	return {
		board: Board,
		show_conflicts: function(mode) { options.showConflicts = mode; },
		pause: pause_game,
		play: play_game,
		populate: populate_json,
		serialize: get_board_data,
		set_value: Board.cell_set,
		set_select: Selection.select,
		reset: reset_entries,
		is_enabled: function() { return enabled; },
		init: clear_all
	};
};

function debug_msg(msg,to)/*{{{*/
{
	if (msg == msg_last) return;
	var to = to || msgfade_default;
	var fade_timer = $('<div>'+msg+'</div>').prependTo('#debug').next();
	window.setTimeout(function() {
		fade_timer.slideUp('slow',function() { fade_timer.remove(); });
	},msgfade_timer);
	msgfade_timer = to;
	msg_last = msg;
};
/*}}}*/

$(function() {

	var sudoku = SudokuBoard('#sudoku');
	var hint_delay = 2000;
	var hint_maxdelay = 15000;
	var hint_increment = 1500;
	var hint_count = 0;
	var hint_text = hint_text_init();
	
	function hint_text_init()/*{{{*/
	{
		return [
			'The delay until you can ask for another hint is longer each time...',
			null,null,null,null,null,'Sudoku is hard, isn\'t it?', null, null, null,
			'What did you do before the "Solve Next" button?'
		];
	};
	/*}}}*/
	function motd()/*{{{*/
	{
		debug_msg('<b>Board Loaded.</b> Play Sudoku!');
	};
	/*}}}*/
	function nothing(e)/*{{{*/
	{
		e.stopPropagation();
		e.preventDefault();
		return false;
	};
	/*}}}*/
	function got_board(json)/*{{{*/
	{

		sudoku.populate(json);
		sudoku.play();
		$('#pauser').attr('checked',false);
		motd();
		init_interface();
	};
	/*}}}*/
	function load_new_board(board_type)/*{{{*/
	{
		debug_msg('Loading new board...please wait',100);
		$.getJSON('qqwing.php',{mode:'generate',type:board_type},got_board);
	};
	/*}}}*/
	function update_hintcounter()
	{
		$('#tpanel div .hints').html(hint_count.toString());
	};
	function init_interface()/*{{{*/
	{
		hint_delay = 500;
		hint_count = 0;
		update_hintcounter();
		hint_text = hint_text_init();
		$('#sudoku_load').attr('disabled',false).removeClass('disabled');
		$('#sudoku_hint').attr('disabled',false).removeClass('disabled');
		$('#sudoku_clear').attr('disabled',false).removeClass('disabled');
		sudoku.board.enable();
	};
	/*}}}*/
	function click_NewBoard(e)/*{{{*/
	{
		this.blur();
		$('#sudoku_load').attr('disabled',true).addClass('disabled');
		sudoku.board.disable();
		var board_type = $('#board_type').val();
		load_new_board(board_type);
		return nothing(e);
	};
	/*}}}*/
	function got_hint(json)/*{{{*/
	{
		//$('#debug').html('');
		if (json.length > 1)
		{
			var col = json[2];
			var row = json[1];
			var value = json[3];

			debug_msg('<b>Answer #'+(++hint_count)+':</b> '+json[3]+
				' is '+json[0]+' @ col:'+json[2]+' row:'+json[1],9000);

			update_hintcounter();
			sudoku.set_value(col,row,value);
			sudoku.set_select(col,row);

			hint_delay += hint_increment;
			if (hint_delay > hint_maxdelay) hint_delay = hint_maxdelay;

			// Disable the hint button for a bit... we don't want it to be too easy!
			window.setTimeout(function() {
				if (sudoku.is_enabled())
					$('#sudoku_hint').attr('disabled',false).removeClass('disabled');
			}, hint_delay);
		}
		else
		{
			debug_msg(json[0]);
			$('#sudoku_hint').attr('disabled',false).removeClass('disabled');
		}
	};
	/*}}}*/
	function ask_hint_msg()/*{{{*/
	{
		var txt;

		if (hint_text.length && (txt = hint_text.shift()))
			debug_msg(txt,12000);
	};
	/*}}}*/

	// Implement "new board" button
	$('#sudoku_load').click(click_NewBoard);

	// Implement the "clear entries" button
	$('#sudoku_clear').click(function(e) {
		this.blur();
		sudoku.reset();
		hint_delay = 500;
		hint_text = hint_text_init();
		debug_msg('<b>Cleared board.</b> Start again...');
		return nothing(e);
	});

	$('#show_conflicts').attr('checked',false).click(function(e) {
		sudoku.show_conflicts(($(this).attr('checked'))? true: false);
	});

	$('#pauser').attr('checked',false).click(function(e) {
		$(this).attr('checked') ? sudoku.pause() : sudoku.play();
	});

	// Implement the "hint" button
	$('#sudoku_hint').click(function(e) { 
		this.blur();
		ask_hint_msg();
		$('#sudoku_hint').attr('disabled',true).addClass('disabled');

		$.getJSON('qqwing.php',{
			action:'hint',board:sudoku.serialize()
		},got_hint);

		return nothing(e);
	});

	if ($.browser.msie)
		alert('IE USERS PLEASE NOTE:\n\nThis game is playable in Internet Explorer.\nHowever, there are some CSS/rendering problems that have not been fixed yet.\nFor the intended experience, please view in Firefox, Safari, Opera or Chrome.\n\nThanks! Sorry for the popup!');

	// Start by loading a new board
	load_new_board('simple');

});

