Multiedit = {
	BACKSPACE: 8,
	LEFT: 37,
	RIGHT: 39,
	FORWARD_DELETE: 46,
	UNDERSCORE: 818,
	caseButtonFocusHandler: function() {
		window.setTimeout(Multiedit.clearSelectionTimer, 50);
	},
	clearSelectionTimer: function() {
		if (Multiedit.timer) {
			window.clearTimeout(Multiedit.timer);
		}
	},
	changeCase: function() {
		var method = this.id.match(/(.+)Button/)[1]; // grab the string method from the button's ID
		var c = Multiedit.currentColumnValue;
		if (c.field) {
			var oldValue = c.field.val();
			var newValue = '';
			var start = '';
			var selection = '';
			var end = '';
			var numberOfUnderscores;
			var originalSelectionLength;
			if (c.selection.start != c.selection.end) {
				start = (c.selection.start > 0 ? oldValue.slice(0, c.selection.start) : '');
				selection = oldValue.slice(c.selection.start, c.selection.end);
				end = oldValue.slice(c.selection.end);
			} else {
				selection = oldValue;
			}
			originalSelectionLength = selection.length;
			selection = selection.replace(/([^\u0332]+)(\u0332+)/g, function (str, p1, p2) {
				if (p2.length % 2 == 0) {
					p2 += '\u0332';
				}
				return (p1 + p2);
			});
			newValue = start + selection[method]() + end;
			c.field.val(newValue);
			this.blur();
			c.field.focus();
			if (c.selection.start != c.selection.end) {
				c.selection.end += (selection.length - originalSelectionLength); // increase selection length when we've added extra entities
				c.field.setSelection(c.selection);
			}
		}
		return false;
	},
	columnMouseHandler: function(evt) {
		var buttons = Multiedit.getColumnButtons(this.cellIndex - 1);
		if (evt.type == 'mouseover') {
			buttons.addClass('hovering');
		} else {
			buttons.removeClass('hovering');		
		}
	},
	columnValues: null,
	columnValueBlurHandler: function(evt) {
		var $this = $(this);
		Multiedit.getColumnButtons($this.parent().attr('cellIndex') - 1).removeClass('focused');
		$this.removeClass('columnValueFocused');
		Multiedit.currentColumnValue.field = $this;
		Multiedit.currentColumnValue.selection = $this.getSelection();
		Multiedit.setSelectionTimer();
	},
	columnValueFocusHandler: function(evt) {
		var $this = $(this);
		Multiedit.clearSelectionTimer();
		$('.caseButton').attr('disabled', false);
		$this.addClass('columnValueFocused');
		Multiedit.getColumnButtons($this.parent().attr('cellIndex') - 1).addClass('focused');
	},
	columnValueKeyHandler: function(evt) {
	// For Firefox and IE, make sure that backspace, delete and left/right actually go to the right spot (before/after the underscore entities)
	// For inserting other characters, make sure they go after the underscores
		var keyCode = evt.keyCode;
		var $this = $(this);
		var selection = $this.getSelection();
		var noSelectionMade = (selection.start == selection.end); // if user has made a selection, default behaviour works fine
		if (keyCode == Multiedit.BACKSPACE || keyCode == Multiedit.LEFT) {
			while ($this.val().charCodeAt(selection.end) == Multiedit.UNDERSCORE) {
				selection.end++;
			}
			selection.start--;
			while ($this.val().charCodeAt(selection.start) == Multiedit.UNDERSCORE && selection.start >= 0) {
				selection.start--;
			}
			// for backspace, set a selection; for left, simply move cursor to the correct spot
			if (keyCode == Multiedit.BACKSPACE && noSelectionMade) {
				$this.setSelection({ start: selection.start, end: selection.end });
			} else if (keyCode == Multiedit.LEFT) {
				if (evt.shiftKey) { // while holding shift, make a selection as would normally occur
					$this.setSelection({ start: selection.start, end: selection.end });
					return false; // FF/Mac needs actual keystroke to be ignored
				} else {
					selection.start++;
					$this.setSelection({ start: selection.start, end: selection.start });
				}
			}
		} else {
			while ($this.val().charCodeAt(selection.start) == Multiedit.UNDERSCORE && selection.start >= 0) {
				selection.start++;
			}
			if (keyCode == Multiedit.FORWARD_DELETE || keyCode == Multiedit.RIGHT) {
				selection.end++;
				while ($this.val().charCodeAt(selection.end) == Multiedit.UNDERSCORE) {
					selection.end++;
				}
				// for forward delete, set a selection; for right, simply move cursor to the correct spot
				if (keyCode == Multiedit.FORWARD_DELETE && noSelectionMade) {
					$this.setSelection({ start: selection.start, end: selection.end });
				} else if (keyCode == Multiedit.RIGHT) {
					selection.end--;
					if (evt.shiftKey) {
						$this.setSelection({ start: selection.start, end: selection.end });
					} else {
						$this.setSelection({ start: selection.end, end: selection.end });
					}
				}
			} else if (!evt.ctrlKey && !evt.altKey && !evt.metaKey && keyCode != Multiedit.LEFT && noSelectionMade) {
				// for all other characters, make sure they're inserted after the underscores by moving the cursor, if no selection is made
				$this.setSelection({ start: selection.start, end: selection.start });
			}
		}
	},
	columnValueMouseupHandler: function(evt) {
		var $this = $(this);
		var selection = $this.getSelection();
		var value = $this.val();
		if (selection.start == selection.end) return;
		// for mouse-click/drag selections, make sure they start and end in the right place
		while (value.charCodeAt(selection.start) == Multiedit.UNDERSCORE) {
			selection.start++;
		}
		while (value.charCodeAt(selection.end) == Multiedit.UNDERSCORE) {
			selection.end++;
		}
		$this.setSelection({ start: selection.start, end: selection.end });
	},
	currentColumnValue: {
		field: null,
		selection: 0
	},
	deleteColumn: function(evt) {
		var $this = $(this);
		var columnIndex = $this.parent().attr('cellIndex');
		$('#columnInputTable tr').each(function () {
			var column = $(this).children(':eq(' + columnIndex + ')');
			if (column.hasClass('deletedColumn')) {
				column.removeClass('deletedColumn');
				column.find('input.columnValue').attr('disabled', false);
			} else {
				if (column.hasClass('insertedColumn')) {
					column.remove();
				} else {
					column.addClass('deletedColumn');
					column.find('input.columnValue').attr('disabled', true);
				}
			}
		});
		if ($this.hasClass('deleted')) {
			$this.find('img').attr('src', 'img/delete.png').removeClass('deleted');
		} else {
			$this.find('img').attr('src', 'img/undo.png').addClass('deleted');		
		}
		return false;
	},
	editorPreviewHandler: function (evt) {
		var $this = $(this.form);
		var data = $this.serialize();
		$.ajax({
			data: data,
			dataType: 'json',
			type: 'POST',
			success: Multiedit.updatePreview,
			error: Multiedit.updateError,
			timeout: 5000,
			url: './preview'
		});
		$('#previewSpinner').show();
		return false;
	},
	getColumnButtons: function(cellIndex) {
		return $('#inputColumnButtons td:eq(' + cellIndex + ') a, #inputColumnButtons td:eq(' + (cellIndex - 1) + ') a.insertAfterColumnButton');
	},
	insertColumn: function(evt) {
		var idMatch = this.id.match(/insertAfterColumn(\d+)/);
		idMatch[1] = parseInt(idMatch[1], 10);
		var insertAfterIndex = $(this).parent().attr('cellIndex');
		var newIDIndex = 0;
		var newID;
		do {
			newIDIndex++;
			newID = idMatch[1] + '-' + newIDIndex;
		} while ($('#columnValue' + newID).length > 0);
		var header = $('<th class="insertedColumn">&nbsp;</th>').insertAfter('#inputHeaderValues > *:eq(' + insertAfterIndex + ')');
		var from = $('<td class="insertedColumn">&nbsp;</td>').insertAfter('#inputFromValues > *:eq(' + insertAfterIndex + ')');
		var to = $('<td class="insertedColumn"><input id="columnValue' + newID + '" value="" name="' + newID + '" type="text" class="text columnValue" /></td>').insertAfter('#inputToValues > *:eq(' + insertAfterIndex + ')');
		var insert = $('<td class="insertedColumn"><a href="javascript:void(0)" class="insertAfterColumnButton" id="insertAfterColumn' + newID + '"><img src="img/insert.png" alt="^" title="Insert a new column above" /></a><a href="javascript:void(0)" class="deleteColumnButton" id="deleteColumn' + newID + '"><img src="img/delete.png" alt="X" /></a></td>').insertAfter('#inputColumnButtons > *:eq(' + insertAfterIndex + ')');
		header.add(from).add(to).add(insert).mouseover(Multiedit.columnMouseHandler).mouseout(Multiedit.columnMouseHandler);
		$('#insertAfterColumn' + newID).click(Multiedit.insertColumn);
		$('#deleteColumn' + newID).click(Multiedit.deleteColumn);
		$('#columnValue' + newID).blur(Multiedit.columnValueBlurHandler).focus(Multiedit.columnValueFocusHandler).focus();
		return false;
	},
	maximizeColumnValueInputWidths: function() {
		function maximize() {
			var $this = $(this);
			var parentWidth = $this.parent().width();
			if ($this.width() < parentWidth) {
				$this.width((parentWidth + $this.height()) + 'px');
			}
		}
		Multiedit.columnValues.each(maximize);
	},
	selectFieldIfUnchanged: function() {
		var $this = $(this);
		if ($this.hasClass('unchanged')) {
			window.setTimeout(function () { $this.select(); }, 10);
			return false;
		}
	},
	setSelectionTimer: function() {
		Multiedit.timer = window.setTimeout(function () {
			Multiedit.currentColumnValue.field = null;
			Multiedit.currentColumnValue.selection = { start: 0, end: 0 };
			$('.caseButton').attr('disabled', true);
		}, 150);
	},
	shiftKeyOnSubmit: false,
	timer: null,
	updateError: function(request, status, error) {
		var message = '';
		switch (status) {
			case 'timeout':
				message = 'Timed out trying to fetch the preview. Please wait a few minutes and try again.';
				break;
			default:
				message = 'An error occurred trying to fetch the preview. Please contact info@cleanupdata.com for support.';
		}
		$('#previewStatus').html('<p class="error">' + message + '</p>');
		$('#previewSpinner').hide();
	},
	updatePreview: function(data, status) {
		var oldTable = $('#columnPreviewTable');
		var tableRows = oldTable.find('tr').not('.headerValues');
		var newTable = $('<table class="columnPreviewTable" />').insertBefore(oldTable);
		var newTbody = $('<tbody />').appendTo(newTable);
		var numDataRows = data.length;
		$('#previewStatus').empty();
		$('#previewSpinner').hide();
		tableRows.slice(numDataRows).remove(); // if there are more rows on the old preview than in the new data, remove them
		tableRows.eq(tableRows.length - 1).children().each(function () {
			var $this = $(this);
			$this.width($this.width());
		});
		var i, j;
		i = 0;
		function updateNextRow() {
			if (i >= numDataRows) {
				oldTable.remove();
				newTable.attr('id', 'columnPreviewTable');
				return;
			}
			var currentRow = tableRows.eq(i);
			var newRow = $('<tr />').appendTo(newTbody);
			var currentData = [];
			var newData;
			var className;
			if (currentRow) {
				currentData = currentRow.children();
				currentRow.remove();
			}
			for (j = 0, numDataColumns = data[i].length; j < numDataColumns; j++) {
				newData = data[i][j].encodeXML();
				if (!currentData[j] || (newData != currentData[j].innerHTML)) {
					className = 'updatedCell';
				}
				newRow.append('<td class="' + className + '">' + newData + '</td>');
			}		
			i++;
			window.setTimeout(updateNextRow, 75);
		}
		updateNextRow();
	},
	initialTablePasteValue: 'Paste a table of data here.',
	initializeField: function(evt) {
		var $this = $(this);
		if ($this.val() != Multiedit.initialTablePasteValue) {
			$this.removeClass('unchanged').unbind('keydown keyup change', Multiedit.initializeField);
		}
	},
	initialize: function() {
		Multiedit.columnValues = $('.columnValue');
		Multiedit.maximizeColumnValueInputWidths();
		var unchanged = $('.unchanged');
		unchanged.bind('keydown keyup change', Multiedit.initializeField);
		unchanged.focus(Multiedit.selectFieldIfUnchanged).mousedown(Multiedit.selectFieldIfUnchanged);
		Multiedit.columnValues.blur(Multiedit.columnValueBlurHandler).focus(Multiedit.columnValueFocusHandler);
		Multiedit.columnValues.mouseup(Multiedit.columnValueMouseupHandler);
// OFA, would you believe Firefox works with keypress, and IE works with keydown?
		if ($.browser.msie) {
			Multiedit.columnValues.bind('keydown', Multiedit.columnValueKeyHandler);
		} else if ($.browser.mozilla) {
			Multiedit.columnValues.bind('keypress', Multiedit.columnValueKeyHandler);
		}
		$('.caseButton').mousedown(Multiedit.caseButtonFocusHandler).click(Multiedit.changeCase);
		$('#columnInputTable tr > *').mouseover(Multiedit.columnMouseHandler).mouseout(Multiedit.columnMouseHandler);
		$('.insertAfterColumnButton').click(Multiedit.insertColumn);
		$('.deleteColumnButton').click(Multiedit.deleteColumn);
		$('#previewButton').click(Multiedit.editorPreviewHandler);
	}
};

// IE/Firefox compatible methods for getting and setting the selection of <input type="text">
jQuery.fn.extend({
	getSelection: function() {
		var el = this[0];
		var position = { start: 0, end: 0 };
		if (!el) return null;
		if (el.selectionStart !== undefined) {
			position.start = el.selectionStart;
			position.end = el.selectionEnd;
		} else if (el.createTextRange !== undefined) {
			var range = el.createTextRange();
			range = range.duplicate();
			var selection = document.selection.createRange();
			try {
				range.setEndPoint("EndToStart", selection);
			} catch(exception) {};
			position.start = range.text.length;
			position.end = range.text.length + selection.text.length;
		}
		return position;
	},
	setSelection: function(p) {
	// p should be an object with start and end parameters
		var el = this[0];
		if (!el) return null;
		if (el.selectionStart !== undefined) {
			if (p.start || p.start === 0) {
				el.selectionStart = p.start;
			}
			if (p.end || p.end === 0) {
				el.selectionEnd = p.end;
			}
		} else if (el.createTextRange !== undefined) {
			el.focus();
			var text = el.createTextRange();
			if (p.start !== 0 && !p.start) {
				p.start = 0;
			}
			if (p.end !== 0 && !p.end) {
				p.end = p.start;
			}
			text.move("character", p.start);
			text.moveEnd("character", p.end - p.start); // IE does end point relative to start point
			text.select();
		}
		return this;
	}
});

if (String.prototype.toCapitalCase === undefined) {
	String.prototype.toCapitalCase = function() {
		return this.replace(/(\s*)(\S+)(\s*)/g, function(str, p1, p2, p3) {	
			return p1 + p2.charAt(0).toUpperCase() + p2.slice(1).toLowerCase() + p3;
		});
	};
}

if (String.prototype.encodeXML === undefined) {
	String.prototype.encodeXML = function() {
		return this.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
	};
}
