var SudokuBoard = function (wrapper) {
	wrapper = $(wrapper);
	var that = this;
	if($('board')) {
		// log('One board per page, for now...');
		return 0;
	}
	var NumPad = function () {
		this.ul = this.generateNumPad(wrapper);
		Event.observe(this.ul,'click',this.click.bindAsEventListener(this));
		Event.observe(document, 'keydown', this.keyPress.bindAsEventListener(this));
		$('take_notes').observe('click', this.takeNotes.bindAsEventListener(this));
		this.takingNotes = false;
		this.gettingValue = false;
	};
	NumPad.prototype = {
		generateNumPad: function (wrapElem) {
			var numPadUl = dce('ul'), nums = $A($R(1,9));
			nums.push('clear');
			nums.map(function (val) {
				var num = dce('li'), a = dce('a'), span = dce('span');
				if (val == 'clear') num.addClassName('clear');
				num.id = 'n_' + val;
				span.update(val);
				a.addClassName('button');
				a.href = '#';
				a.appendChild(span);
				num.appendChild(a);
				numPadUl.appendChild(num);
			});
			numPadUl.id = 'num_pad';
			numPadUl.hide();
			wrapElem.appendChild(numPadUl);
			return numPadUl;
		},
		keyPress: function(e) {
			if(!this.gettingValue)
			    return;
			    
			var value;
			if (e.keyCode == 46 || e.keyCode == 220 || (e.keyCode >= 48 && e.keyCode <= 57)) {
				switch(e.keyCode) {
					case 48:
					case 46:
					case 220:
						value = 'clear';
						break;
					default:
						value = e.keyCode - 48;
				}
				this.sendValue(value);
			}
		},
		click: function (event) {
			event.stop();
			var li = Event.findElement(event,'li');
			if (!li) {
				var a = Event.findElement(event,'a');
				if(a && a == $('take_notes'))
					this.takeNotes();
				else
					return false;
			}
			if(!this.takingNotes) {
				this.sendValue(li.id.substr(2));
			} else {
				var note = li.id.substr(2);
				if(li.hasClassName('down')) {
					li.removeClassName('down');
					that.removeNote(note);
				} else {
					li.addClassName('down');
					that.addNote(note);
				}
			}
		},
		getValue: function(pos) {
			this.gettingValue = true;
			this.setPosition(pos);
			if(this.takingNotes)
				this.drawNotes();
			this.ul.show();
		},
		sendValue: function (value) {
			this.ul.hide();
			that.returnValue(value);
			this.gettingValue = false;
		},
		setPosition: function (pos) {
			// Set's position of the numpad -> constant + rowHeight * numsRows + rowGapHeight * numRowGaps, 
			// minimum allowable position is -12, max is 302 (just b/c that's what looks good -- no seriously)
			var down = Math.min(Math.max(-31 + pos.row * 43 + Math.floor(pos.row/3) * 5, -12), 302);
			if (pos.column >= 4)	{this.ul.addClassName('right'); this.ul.removeClassName('left');}
			else					{this.ul.addClassName('left'); this.ul.removeClassName('right');}
			this.ul.style.top = down + 'px';
		},
		takeNotes: function (e) {
			e.stop();
			var self = this;
			if(that.elementSelected) that.elementSelected.removeClassName('current');
			var disable = function () {
				self.ul.hide();
				self.clearNotes();
				self.ul.removeClassName('taking_notes');
				$('take_notes').removeClassName('down');
			};
			var enable = function () {
				self.ul.addClassName('taking_notes');
				$('take_notes').addClassName('down');
			};
			
			if (this.gettingValue && this.takingNotes) this.takingNotes = false;
			else if (this.gettingValue && !this.takingNotes) {this.takingNotes = true; disable();}
			else this.takingNotes = !this.takingNotes;
			
			this.takingNotes ? enable() : disable();
		},
		drawNotes: function (li) {
			this.clearNotes();
			var cell = that.elementSelected.id.substr(2);
			if(that.game.board[cell].notes)
				that.game.board[cell].notes.each(function(num) {
					$('n_' + num).addClassName('down');
				});
		},
		clearNotes: function () {
			$$('ul#num_pad li').each(function (li) {
				li.removeClassName('down');
			});
		}
	};
	
	this.game = $A();
	this.board = this.generateBoard(wrapper);
	this.numPad = new NumPad();
	this.elementSelected = null;
	
	/*  We unregister this event handler after the game is completed
	 *  to prevent weird behavior   
	 */
	var board_click_handler = Event.observe(this.board,'click',function(event) {
		var li = event.findElement('li');
		if (!that.numPad.ul.visible() || li) {
			event.stop();
		}
		if (!Event.element(event).hasClassName('fixed') && Event.element(event) != that.board) {
			that.selectValue(li);
		}
	});
	$(document).observe('sh:game_completed', function (e) {
	    Event.stopObserving(board_click_handler);
	});
	Event.observe(document,'click',function(event) {
		// that.numPad.ul.removeClassName('taking_notes');
		// $('take_notes').removeClassName('down');
		// that.takingNotes = false;
		that.numPad.ul.hide();
		that.numPad.clearNotes();
		that.numPad.gettingValue = false;
		if (that.elementSelected)
			that.elementSelected.removeClassName('current');
	});
	Event.observe('save_game','mousedown', function (e) {
	    e.stop();
	    that.updateServer(true);
	});
	
	/* Game complete flag to prevent weird behavior after the game is done */
	this.game_completed = false;
	
	/* Caches for row, group, column retrieval */
	this._row = {};
	this._group = {};
	this._column = {};
};
SudokuBoard.prototype = {
	generateBoard: function (wrapElem) {
		this.messageWrap = $('board_message_wrap');
		this.message = $('board_message');
		
		var boardUl = dce('ul');
		var cells = $A($R(0,80));
		var that = this;
		cells.map(function (val) {
			var cell = dce('li');//tempLi.cloneNode(false);
			var cellPos = that.location(val);
			cell.id = 'i_' + val;
			cell.addClassName('r_' + cellPos.row);
			cell.addClassName('c_' + cellPos.column);
			cell.addClassName('g_' + cellPos.group);
			boardUl.appendChild(cell);
		});
		boardUl.id = 'board';
		
		wrapElem.appendChild(boardUl);
		
		return boardUl;
	},
	location: function (cell) {
		if(typeof(cell) == 'object') {
			cell = parseInt(cell.id.substr(2));
		} else if(cell != parseInt(cell)) {
			cell = parseInt(cell.substr(2)); // Strip off the 'i_'
		}
		var cellPos = {};
		cellPos.row = Math.floor(cell/9);
		cellPos.column = cell%9;
		cellPos.group = Math.floor(Math.floor(cell/9)/3)*3 + Math.floor(cell%9/3);
		return cellPos;
	},
	group: function (num) {
		var key = '.g_' + num;
		return this._group[key] || ( this._group[key] = this.board.select(key) );
	},
	row: function (num) {
		var key = '.r_' + num;
		return this._row[key] || ( this._row[key] = this.board.select(key) );
	},
	column: function (num) {
		var key = '.c_' + num;
		return this._column[key] || ( this._column[key] = this.board.select(key) );
	},
	getGame: function () {
		var urlBase = '/game/get/';
		var url = urlBase + $('board_wrap').readAttribute('data-slug');
		var self = this;
		var request = new Ajax.Request(url, {
			method: 'get',
			onSuccess: function(transport) {
				response = transport.responseText.evalJSON();
				log(response);
				self.game = response.game;
				self.loadGame(response.game, response.score);
			}
		});
	},
	loadGame: function (game, score) {
		var self = this;
		game.board.each(function (value,index) {
			var cellID = 'i_' + index;
			var cell = $(cellID);
			if (value.val == 0) {
				cell.addClassName('input');
				if (value.u_val && value.u_val != 0)
					cell.update(value.u_val);
				else if (value.notes)
					self.drawNotes(index);
			} else {
				cell.addClassName('fixed');
				cell.update(value.val);
			}
		});
		this.checkBoard();
		this.updateServer(false);
		this.showMessage('Game loaded','success');
	},
	updateServer: function (save) { // Scores game and saves progress
		if (this.game_completed)
	        return;
		this.clearMessage();
		$('ajax_activity').addClassName('loading');
		var url = '/game/update/', game = Object.toJSON(this.game), self = this;
		if (!save) url += '0';
	    var request = new Ajax.Request(url, {
			method: 'post',
			postBody: game,
			onComplete: function(transport) {
				var response = transport.responseText.evalJSON();
				if (response) {
				    $(document).fire('sh:score_updated', response.score);
				    if (response.message)
				        self.showMessage(response.message, response.result);
				    if (response.score.complete) {
				        self.game_completed = true;
				        $(document).fire('sh:game_completed', response.score);
				    }
				} else {
				    $(document).fire('sh:communication_error', null);
					self.showMessage('Your game could not be saved', 'fail');
				}
				$('ajax_activity').removeClassName('loading');
			},
			onFailure: function () {
				self.showMessage('Your game could not be saved', 'fail');
				$('ajax_activity').removeClassName('loading');
			},
			onException: function () {
				self.showMessage('Your game could not be saved', 'fail');
				$('ajax_activity').removeClassName('loading');
			}
		});
	},
	selectValue: function (element) {
		var cell = Number(element.id.substr(2));
		if (this.numPad.takingNotes == true && this.game.board[cell].u_val && this.game.board[cell].u_val != 0) return;
		if (this.elementSelected) this.elementSelected.removeClassName('current'); // Old selected item
		this.elementSelected = $(element);
		this.elementSelected.addClassName('current');
		this.numPad.getValue(this.location(element));
	},
	returnValue: function (value) {
		this.setValue(value, this.elementSelected.id.substr(2));
		this.checkBoard();
		this.updateServer(true);
	},
	setValue: function (value, cell) {
	    var board = this.game.board;
		if (value == 'clear') {
			if (!board[cell].u_val)
			    board[cell].notes = undefined;
			board[cell].u_val = 0;
			$('i_' + cell).update('');
			this.drawNotes(cell);
		} else {
			board[cell].u_val = value;
			$('i_' + cell).update(value);
		}
		this.elementSelected.removeClassName('current');
	},	
	checkBoard: function () {
		var that = this;
		var inputs = this.board.select('.input');
		var results = inputs.map(function(cell) {
			var value = that.game.board[cell.id.substr(2)].u_val;
			var valid = that.checkCell(cell, value);
			valid ? cell.removeClassName('warning') : cell.addClassName('warning');
			return valid && value != 0;
		});
		return !results.some(function (valid) {return !valid});
	},
	checkCell: function (cell, value) {
		var value = value || this.game.board[cell.id.substr(2)].u_val;
		if (value == null || value == '') return true;
		
		var elemsArr = $A(), checkArr = $A();
		var pos = this.location(cell);
		
		elemsArr = this.group(pos.group).concat(this.row(pos.row)).concat(this.column(pos.column));
		
		var len = elemsArr.length - 1;
		do {
			var elem = elemsArr[len];
			if (cell !== elem && elem.innerHTML == value)
				return false;
		} while (--len)
		
		return true;
	},
	showMessage: function (message, type, duration) {
		var type = type || 'success', duration = duration || 7000;
		clearTimeout(this.messageTimeout);
		this.messageWrap.hide();
		this.message.update(message);
		this.messageWrap.className = type;
		this.messageWrap.setStyle({
		    'left': $('main_wrapper').getWidth()/2 - this.messageWrap.getWidth()/2 + 'px'
		});
		this.messageWrap.show();
		this.messageTimeout = setTimeout(this.clearMessage.bindAsEventListener(this),duration);
	},
	clearMessage: function () {
		this.messageWrap.hide();
		this.message.update('');
	},
	addNote: function (note) {
		var cell = Number(this.elementSelected.id.substr(2));
		if(!this.game.board[cell].notes)
			this.game.board[cell].notes = $A(this.game.board[cell].notes);
		this.game.board[cell].notes.push(note);
		this.drawNotes();
	},
	removeNote: function (note) {
		var cell = Number(this.elementSelected.id.substr(2));
		this.game.board[cell].notes = this.game.board[cell].notes.without(note);
		this.drawNotes()
	},
	drawNotes: function (cell) {
		if(!this.elementSelected && !cell) return;
		
		var cell = cell || Number(this.elementSelected.id.substr(2));
		var notes = this.game.board[cell].notes;
		if (notes) {
			var cellLi = $('i_' + cell);
			var oldSpan = cellLi.firstDescendant();
			if (oldSpan) oldSpan.remove();
			var span = dce('span');
			span.update(notes.join(', '));
			cellLi.appendChild(span);
		}
	}
};

$(document).observe('sh:score_updated', function(e) {
  var score = e.memo, pts = score.points,
      level = Math.min(parseInt(pts/140, 10)+1, 3); // 1, 2, or 3
	$('current-score').update(pts);
	$('kid').className = 'kid-' + level;
});

$(document).observe('sh:game_completed', function (e) {
  var pts = e.memo.points, board = $('board'), winbox = $('win_box'),
      total_score = parseInt($$('#total .score').reduce().innerHTML, 10);
	winbox.setStyle({ // Position winbox
		left: parseInt(board.getStyle('left'), 10) + (board.getWidth()/2 - winbox.getWidth()/2) + 'px',
		top: parseInt(board.getStyle('top'), 10) + (board.getHeight()/2 - winbox.getHeight()/2) + 'px'
	});
	$$('#earned .score').reduce().update(pts);
	$('kid').className = 'kid-4'; // Show the win kid image
	winbox.show();
	(function (elem, ceil, cur) { // Animate the counting up
		if (cur >= ceil) return;
		elem.innerHTML = cur += (ceil-cur >= 20) ? 20 : ceil-cur;
		var fn = arguments.callee;
		setTimeout(function () {fn(elem, ceil, cur)}, 60);
	})($$('#total .score').reduce(), pts + total_score, total_score);
});


var theBoard;
Event.observe(window,'load', function (event) {
	$$('input.styled').each(function (input) {
		var value = input.hasClassName('ghost-label') ? input.value : '';
		input.observe('focus', function (e) {
			if (input.value == value) {
				input.value = '';
				input.setStyle({'color':'#333'});
			}
			input.setStyle({'background':'#fff'});
		});
		input.observe('blur', function (e) {
			if (input.value == '') {
				input.value = value;
				input.setStyle({'color':'#999'});
			}
			input.setStyle({'background':'#f3f3f3'});
		});
	});
	$('save_game').observe('mousedown', function (e) {
		$('save_game').addClassName('down');
	});
	Event.observe(window, 'mouseup', function (e) {
		$('save_game').removeClassName('down');
	});
	theBoard = new SudokuBoard('board_wrap');
	theBoard.getGame();
});

// Helpful Stuff

function dce (string) {
	return $(document.createElement(string));
}

function log () {
	// if (console && console.log) console.log.apply(console, arguments);
}

if (!Array.prototype.some) {
  Array.prototype.some = function(fun /*, thisp*/) {
    var len = this.length;
    if (typeof fun != "function")
      throw new TypeError();

    var thisp = arguments[1];
    for (var i = 0; i < len; i++) {
      if (i in this &&
          fun.call(thisp, this[i], i, this))
        return true;
    }

    return false;
  };
}
