/*
 * Cruciverb v1.0 - Crossword App
 *
 * Copyright (c) 2008 Jim Krehl (http://jimmyk.org)
 * Licensed under the GPL (http://www.gnu.org/licenses/gpl.txt)
 *
 * Date: Wed Oct  8 16:22:44 EDT 2008
 */
function CrosswordGrid (selectors, prefs)
{
	if (selectors == null)
		this.selectors = new Object ();
	else
		this.selectors = selectors;

	if (typeof (this.selectors.grid_container) == 'undefined')
		this.selectors.grid_container = 'div#crossword-grid';

	if (typeof (this.selectors.across_clues_container) == 'undefined')
		this.selectors.across_clues_container = 'div#across-clues';

	if (typeof (this.selectors.down_clues_container) == 'undefined')
		this.selectors.down_clues_container = 'div#down-clues';

	if (typeof (this.selectors.status_container) == 'undefined')
		this.selectors.status_container = 'div#status';

	if (typeof (this.selectors.title_container) == 'undefined')
		this.selectors.title_container = 'div#title';

	if (typeof (this.selectors.author_container) == 'undefined')
		this.selectors.author_container = 'div#author';

	if (typeof (this.selectors.copyright_container) == 'undefined')
		this.selectors.copyright_container = 'div#copyright';

	if (prefs == null)
		this.prefs = new Object ();
	else
		this.prefs = prefs;

	if (typeof (this.prefs.selected_color) == 'undefined')
		this.prefs.selected_color = 'green';

	if (typeof (this.prefs.highlight_color) == 'undefined')
		this.prefs.highlight_color = 'grey';

	if (typeof (this.prefs.default_color) == 'undefined')
		this.prefs.default_color = 'white';

	this.cursor = [];
	this.cursor [0] = -1;
	this.cursor [1] = -1;

	this.across = true;

	this.puzzle = new Puzzle (null);

	this.drawMeta ();
	this.drawGrid ();
}

CrosswordGrid.prototype.drawMeta = function ()
{
	$(this.selectors.title_container    ).text (this.puzzle.title);
	$(this.selectors.author_container   ).text (this.puzzle.author);
	$(this.selectors.copyright_container).text (this.puzzle.copyright);
}

CrosswordGrid.prototype.drawGrid = function ()
{
	$(this.selectors.grid_container).empty ();
	$(this.selectors.grid_container).append ('<table id="crossword-table"></table>');

	grid_instance = this;

	for (j = 0; j < this.puzzle.height; ++j) {
		$('table#crossword-table').append ('<tr id="row-' + j + '"></tr>');

		for (i = 0; i < this.puzzle.width; ++i) {
			$('tr#row-' + j).append ('<td id="cell-' + i + 'x' + j + '"></td>');

			if (this.puzzle.cells [j] [i].label == Puzzle.BLANK_CELL)
				$('td#cell-' + i + 'x' + j).addClass ('blank-cell');
			else
				$('td#cell-' + i + 'x' + j)
					.addClass ('answer-cell')
					.click (function () { grid_instance.selectCell (this); });

			$('td#cell-' + i + 'x' + j)
				.append ('<div class="cell"></div>')
				.append ('<div class="cell-entry"></div>');

			if (this.puzzle.cells [j] [i].label >= 1) {
				label =
					'<div class="cell-label">' + this.puzzle.cells [j] [i].label +
					'</div>';

				$('td#cell-' + i + 'x' + j + ' div.cell').append (label);
			}
		}
	}

	$(document).unbind ('keypress');
	$(document).keypress (function (ev) { return grid_instance.handleKeyPress (ev); });
}

CrosswordGrid.prototype.drawClues = function ()
{
	$(this.selectors.across_clues_container).empty ();
	$(this.selectors.across_clues_container).append ('<table></table>');

	grid_instance = this;

	for (clue in this.puzzle.acrossClues) {
		tr_id = 'ac-' + this.puzzle.acrossClues [clue].label;

		$(this.selectors.across_clues_container + ' table').append ('<tr id="' + tr_id + '"></tr>');

		$('tr#' + tr_id)
			.append ('<td>' + this.puzzle.acrossClues [clue].label + '.</td>')
			.append ('<td>' + this.puzzle.acrossClues [clue].text  + '</td>')
			.click (function () { grid_instance.selectClue (this); });
	}

	$(this.selectors.down_clues_container).empty ();
	$(this.selectors.down_clues_container).append ('<table></table>');

	for (clue in this.puzzle.downClues) {
		tr_id = 'dc-' + this.puzzle.downClues [clue].label;

		$(this.selectors.down_clues_container + ' table').append ('<tr id="' + tr_id + '"></tr>');

		$('tr#' + tr_id)
			.append ('<td>' + this.puzzle.downClues [clue].label + '.</td>')
			.append ('<td>' + this.puzzle.downClues [clue].text  + '</td>')
			.click (function () { grid_instance.selectClue (this); });
	}
}

CrosswordGrid.prototype.loadPuzzle = function (url, callback)
{
	$(this.selectors.status_container).text ('Processing ' + url);

	grid_instance = this;

	$.ajax ({
		url: url,

		beforeSend: function () {
			$(grid_instance.selectors.status_container).text ('Loading ... ' + url);
		},

		success: function (data) {
			$(grid_instance.selectors.status_container).text ('Loaded.');

			grid_instance.puzzle = new Puzzle (data);

			grid_instance.drawMeta  ();
			grid_instance.drawGrid  ();
			grid_instance.drawClues ();

			callback ();
		}
	});
}

CrosswordGrid.prototype.selectClue = function (row)
{
	row_id = new String ($(row).attr ('id'));
	label  = parseInt (row_id.substring (3, row_id.length));

	this.setHighlight (false);

	this.across = (row_id.charAt (0) == 'a');

	if (this.across)
		this.cursor = this.puzzle.acrossClues [label].start.slice ();
	else
		this.cursor = this.puzzle.downClues [label].start.slice ();

	if (this.cellHasEntry (this.cursor))
		this.advanceCursor (true, false);

	this.setHighlight (true);
}

CrosswordGrid.prototype.selectCell = function (cell)
{
	cell_id = new String ($(cell).attr ('id'));
	pivot = cell_id.indexOf ('x');

	coords = [];
	coords [0] = parseInt (cell_id.substring (5, pivot));
	coords [1] = parseInt (cell_id.substring (pivot + 1, cell_id.length));

	this.selectCellByCoords (coords);
}

CrosswordGrid.prototype.selectCellByCoords = function (coords)
{
	this.setHighlight (false);

	if (this.cursor [0] == coords [0] && this.cursor [1] == coords [1])
		this.across = ! this.across;
	else
		this.cursor = coords.slice ();

	this.setHighlight (true);
}

CrosswordGrid.prototype.setHighlight = function (on)
{
	if (this.cursor [0] < 0 || this.cursor [1] < 0)
		return;

	if (on) {
		cursor_color   = this.prefs.selected_color;
		adjacent_color = this.prefs.highlight_color;
	}
	else
		cursor_color = adjacent_color = this.prefs.default_color;

	$('td#cell-' + this.cursor [0] + 'x' + this.cursor [1]).css ('background-color', cursor_color);

	coords = this.findAdjacentCells ();

	for (i = 0; coords != null && i < coords.length; ++i) {
		cell_i = $('td#cell-' + coords [i] [0] + 'x' + coords [i] [1]);

		$(cell_i).css ('background-color', adjacent_color);
	}

	across_label = this.puzzle.cells [this.cursor [1]] [this.cursor [0]].acrossClue;
	down_label   = this.puzzle.cells [this.cursor [1]] [this.cursor [0]].downClue;

	ac_id = 'tr#ac-' + across_label;
	dc_id = 'tr#dc-' + down_label;

	if (this.across) {
		$(ac_id).css ('background-color', cursor_color);
		$(dc_id).css ('background-color', adjacent_color);
	}
	else {
		$(ac_id).css ('background-color', adjacent_color);
		$(dc_id).css ('background-color', cursor_color);
	}

	if (on) {
		$(this.selectors.across_clues_container).scrollTo (ac_id);
		$(this.selectors.down_clues_container  ).scrollTo (dc_id);
	}
}

CrosswordGrid.prototype.findAdjacentCells = function ()
{
	if (this.cursor [0] < 0 || this.cursor [1] < 0)
		return null;

	coords = [];

	if (this.across) {
		for (n = 0, i = this.cursor [0] + 1; i < this.puzzle.width && this.puzzle.cells [this.cursor [1]] [i].label >= 0; ++i, ++n) {
			coords [n] = [];
			coords [n] [0] = i;
			coords [n] [1] = this.cursor [1];
		}

		for (i = this.cursor [0] - 1; i >= 0 && this.puzzle.cells [this.cursor [1]] [i].label >= 0; --i, ++n) {
			coords [n] = [];
			coords [n] [0] = i;
			coords [n] [1] = this.cursor [1];
		}
	}
	else {
		for (n = 0, i = this.cursor [1] + 1; i < this.puzzle.height && this.puzzle.cells [i] [this.cursor [0]].label >= 0; ++i, ++n) {
			coords [n] = [];
			coords [n] [0] = this.cursor [0];
			coords [n] [1] = i;
		}

		for (i = this.cursor [1] - 1; i >= 0 && this.puzzle.cells [i] [this.cursor [0]].label >= 0; --i, ++n) {
			coords [n] = [];
			coords [n] [0] = this.cursor [0];
			coords [n] [1] = i;
		}
	}

	return coords;
}

CrosswordGrid.prototype.setCellEntry = function (coords, entry)
{
	$('td#cell-' + coords [0] + 'x' + coords [1] + ' div.cell-entry').text (
		entry != null ? entry.toUpperCase () : '');
}

CrosswordGrid.prototype.cellHasEntry = function (coords)
{
	return (
		this.puzzle.isBlankCell (coords) ||
		$('td#cell-' + coords [0] + 'x' + coords [1] + ' div.cell-entry').text () != '');
}

CrosswordGrid.prototype.handleKeyPress = function (ev)
{
	if (this.cursor [0] < 0 && this.cursor [1] < 0)
		return;

	cursor_new = this.cursor.slice ();

	if ((0x41 <= ev.which && ev.which <= 0x5A) || (0x61 <= ev.which && ev.which <= 0x7A)) {
		this.setCellEntry (this.cursor, String.fromCharCode (ev.which));
		this.advanceCursor (true, true);
	}

	else if (ev.which == 0x20) {
		this.setCellEntry (this.cursor, null);

		this.advanceCursor (false, true);

		return false;
	}

	else if (ev.which == 0x08) {
		this.setCellEntry (this.cursor, null);

		this.regressCursor ();

		return false;
	}

	else if (0x25 <= ev.keyCode && ev.keyCode <= 0x28) {
		if ((ev.keyCode == 0x25 && this.across) || (ev.keyCode == 0x26 && ! this.across))
			this.regressCursor ();

		else if ((ev.keyCode == 0x27 && this.across) || (ev.keyCode == 0x28 && ! this.across))
			this.advanceCursor (false, true);

		else
			this.selectCellByCoords (this.cursor);

		return false;
	}
}

CrosswordGrid.S_ADVANCE = 0;
CrosswordGrid.S_CYCLE   = 1;
CrosswordGrid.S_DONE    = 2;

CrosswordGrid.prototype.advanceCursor = function (skip_filled, advance_word)
{
	if (this.across) {
		dim = 0;
		lim = this.puzzle.width;
	}
	else {
		dim = 1;
		lim = this.puzzle.height;
	}

	state  = CrosswordGrid.S_ADVANCE;
	cycled = false;

	c_new = this.cursor.slice ();

	while (state != CrosswordGrid.S_DONE) {
		switch (state) {
			case CrosswordGrid.S_ADVANCE:
				++ c_new [dim];
				break;

			case CrosswordGrid.S_CYCLE:
				if (this.across) {
					label = this.puzzle.cells [c_new [1]] [c_new [0] - 1].acrossClue;
					clue  = this.puzzle.acrossClues [label];
				}
				else {
					label = this.puzzle.cells [c_new [1] - 1] [c_new [0]].downClue;
					clue  = this.puzzle.downClues [label];
				}

				c_new = clue.start.slice ();

				cycled = true;

				break;
		}

		if (c_new [dim] >= lim) {
			if (skip_filled && ! cycled)
				state = CrosswordGrid.S_CYCLE;
			else
				state = CrosswordGrid.S_DONE;
		}

		else if (this.puzzle.isBlankCell (c_new)) {
			if (skip_filled && ! cycled)
				state = CrosswordGrid.S_CYCLE;
			else
				state = CrosswordGrid.S_ADVANCE;
		}

		else if (this.cellHasEntry (c_new) && skip_filled)
			if (! advance_word && cycled)
				return;
			else
				state = CrosswordGrid.S_ADVANCE;

		else
			state = CrosswordGrid.S_DONE;
	}

	if (c_new [dim] >= lim)
		return;

	this.selectCellByCoords (c_new);
}

CrosswordGrid.prototype.regressCursor = function ()
{
	c_new = this.cursor.slice ();

	-- c_new [(dim = this.across ? 0 : 1)];

	while (c_new [0] >= 0 && this.puzzle.isBlankCell (c_new))
		-- c_new [dim];

	if (c_new [dim] < 0)
		return;

	this.selectCellByCoords (c_new);
}
