/**
 * dropquotes.js
 *
 * Defines the DropQuotes game objects and functionality.
 * Dependencies on: 
 *   /js/base.js
 *   /js/form-submit.js
 * @author Stefan T. 
 * @version 1.0
 */

/**
 * The container object for the DropQuotes game.
 * @global
 */
var dropquotes = function() {
	var that = {}; // the actual game object
	that.boxes = {}; // container object for boxes
	that.columns = {}; // container objects for columns
	
	/**
	 * Adds as column to the game.
	 * @param {String} id The column identifier.
	 * @param {String} val The characters that can be used in this column.
	 * @throws Error
	 */ 
	that.addColumn = function(id, val) {
		var col = {}; // new column object
		col.id = id; // column identifier
		if (!col.id) {
			throw 'Column identifier missing.';
		}
		col.elem = document.getElementById(col.id); // get DOM element with the same ID
		if (!col.elem) {
			throw 'No DOM element found for given column identifier.';
		}
		col.used = ''; // holds chars that are "used" by boxes in that column
		col.available = val.toUpperCase(); // holds chars that are unassigned in that column
		/**
		 * Adds a given char to the list of available values for this column.
		 * @param {String} ch the character to add
		 * @return {Boolean} TRUE on success, FALSE otherwise
		 */ 
		col.addChar = function(ch) {
			// must be a single character
			if (!ch || 1 != ch.length) {
				return false;
			}
			// check if the the char is on the list of currently used ones
			var pos = this.used.indexOf(ch);
			if (-1 == pos) { // reject character if it cannot be found
				return false;
			}
			// remove it from the used list and append it it the list of available ones
			this.used = this.used.replace(ch, '');
			this.available += ch; 
			return true;
		};
		/**
		 * Removes a given char from the list of availables ones in this column.
		 * @param {String} ch the character to remove
		 * @return {Boolean} TRUE on success, FALSE otherwise 
		 */
		col.removeChar = function(ch) {
			if (!ch || 1 != ch.length) {
				return false;
			}
			var pos = this.available.indexOf(ch);
			if (-1 == pos) {
				return false;
			}
			this.available = this.available.replace(ch, '');
			this.used += ch; 
			return true;
		};
		/**
		 * Refreshes the display element for this column
		 */
		col.refreshDisplay = function() {
			var out = this.available.split('').join('<br />');
			this.elem.innerHTML = out;
		};
		col.refreshDisplay(); //populate display elem.
		this.columns[col.id] = col; // assign column as new property of the columns container object
	};
	/**
	 * Adds as column to the game.
	 * @param (String) id The box identifier.
	 * @param (String) nextId The identifier of the next box. 
	 * @param (String) colId  The identifier of the column that this box belongs to.
	 * @throws Error
	 */ 
	that.addBox = function(id, nextId, colId) {
		var box = {}; // new box object
		box.id = id;
		if (!box.id) {
			throw 'Box identifier missing.';
		}
		box.elem = document.getElementById(box.id); // get input element for this box
		// elaborate way of checking that the element is indeed an input field
		if (!box.elem || 'object' != typeof box.elem || 'text' != box.elem.type) {
			throw 'No input element found for given box identifier.';
		}
		
		box.nextElem = (nextId) ? document.getElementById(nextId) : null; // get the next box' input element
		// PARANOID MODE
		// IF a next element was found then check if it can be focused on.
		// Otherwise things may break during gameplay
		if (box.nextElem) {
			if ('object' != typeof box.nextElem || !box.nextElem.focus) {
				throw 'Next element cannot be focused on.';
			}
		}
		// get the column that this box belongs to.	
		box.col = this.columns[colId]; 
		if (!box.col) {
			throw 'Box could not be associated with a column.';
		}
		
		/**
		 * Event handler function on the box input element for the "keydown" event.
		 * @param (Event} event
		 */
		box.elem.onkeydown = function(event) {
			event = event || window.event; // IE needs this
			var box = dropquotes.boxes[this.id]; // get the box associated with this input element
			var col = box.col; // get the column that the box belongs to 
						
			// check the event's key codes and handle them accordingly
			if (65 <= event.keyCode && 90 >= event.keyCode) { // a-z
				// write the given value to the box input element, replacing the existing value if applicable
				// then refresh the column display and move on to the next input element.
				if (box.setValue(String.fromCharCode(event.keyCode))) {
					col.refreshDisplay();
					if (box.nextElem) {
						box.nextElem.focus(); // set the focus on the next input element
					}
				}
			} else if (9 == event.keyCode) { // tab
				// do nothing, tab key needs no special handling. leave event handler here.
				return; 
			} else if (46 == event.keyCode || 8 == event.keyCode) { // delete
				// clear out the current box element and move on to the next box
				// also refresh the display of available column values
				box.clearValue(); 
				col.refreshDisplay();
				if (box.nextElem) {
					box.nextElem.focus(); // set the focus on the next input element
				}
			} else if (46 == event.keyCode || 8 == event.keyCode) { // backspace
				// clear out the current box element
				// and refresh the display of available column values
				box.clearValue();
				col.refreshDisplay();
			} else if  (13 == event.keyCode) { // enter
				// submit the game for checking
				dropquotes.submit();
			} // ignore all other keystrokes
			stopEvent(event); // ALWAYS ALWAYS ALWAYS stop the event propagation and default behavior
		};
		/**
		 * Clears the box element's value and sets it to a blank string.
		 * @return {Boolean} TRUE on success, FALSE on failure.
		 */
		box.clearValue = function() {
			var current = this.elem.value.toUpperCase();
			this.elem.value = ''; //always clear the input field
			// attempt to push the existing value back into the list of available values
			// this may fail if the user previously copied/pasted an invalid value into the text box, 
			// e.g. via the mouse/context menus
			// in that case we just discard it and move on
			if ('' == current || this.col.addChar(current)) { 
				return true;
			}
			return false;
		};
		/**
		 * Sets/replaces the box element's value to/with a given new value.
		 * @param {String} ch The new character value.
		 * @return {Boolean} TRUE on success, FALSE on failure.
		 */
		box.setValue = function(ch) {
			var current = this.elem.value.toUpperCase();
			if ('' == current || this.col.addChar(current)) { // attempt to add the current char to the list of used ones
				if (this.col.removeChar(ch)) { // attempt to remove the new char from list of available ones
					this.elem.value = ch; 
					return true; 
				} else { // revert changes on failure
					if ('' != current) {
						this.col.removeChar(current);
					}
					this.elem.value = current;
					return false;
				}
			}
			return false;
		};
		this.boxes[box.id] = box; // add box to boxes-container
	};
	
	/**
	 * sets the internal game form object.
	 * @param {Object} form The form object 
	 */
	that.setForm = function(form) {
		this.form = form;
	};
	
	/**
	 * Submits the game/form.
	 */
	that.submit = function() {
		var submitButton = document.getElementById('submitButton');
		//HACK: check if the submit button still exists on the page
		// if not we must assume that it has been removed due to puzzle completion
		if (!submitButton) {
			this.form = null;
			return;
		}
		if (this.form && this.form.submit) {
			this.form.submit();
		}
	};
	
	return that; // return game object	
}();
