// insert characters in a textarea or text input field
// special characters are enclosed in {}; use {{} for the { character itself
(function($){

$.fn.sendkeys = function sendkeys(x){
	return this.each( function(){
		var localkeys = $.extend({}, $.metadata && $(this).metadata()['sendkeys'], $(this).data('sendkeys')); // allow for element-specific key functions
		var o = this;
		// IE puts the insertion point at the start of the field on focus. We need to correct that
		// we'll record the actual selection in $.data('sendkeys.selection')
		// Opera is flaky: it impelements both document.selection and element.setSelectionRange, so we have to exclude that
		if (document.selection && !this.setSelectionRange && !$.data(this, 'sendkeys.selection')){
			var rng = this.createTextRange();
			rng.collapse(false); // append to end
			$.data(this, 'sendkeys.selection', rng);
			$(this).bind('beforedeactivate', function(){
				$.data(this, 'sendkeys.selection', document.selection.createRange());
			}).bind('focus', function(){
				$.data(this, 'sendkeys.selection').select();
			});
		}
		this.focus();
		x.replace(/{[^}]*}|[^{]+/g, function(s){
			(localkeys[s] || $.fn.sendkeys.defaults[s] || simplechar).call(o, s);
		});
		$(o).trigger({type: 'sendkeys', which: x});
	});
}; // sendkeys

// for IE, which isn't smart enough to constrain a selection to a single element
function constrainAndRecord (rng, o){
	// Stupid IE can't treat these the same
	if (o.tagName == 'TEXTAREA'){
		var orng = rng.duplicate();
		orng.moveToElementText(o);
	}else{
		orng = o.createTextRange();
	}
	if (orng.compareEndPoints('StartToStart', rng) == 1) rng.setEndPoint('StartToStart', orng);
	if (orng.compareEndPoints('EndToEnd', rng) == -1) rng.setEndPoint('EndToEnd', orng);
	rng.select();
	$.data(o, 'sendkeys.selection', rng);	
} // constrainAndRecord

if ($('<input>')[0].setSelectionRange){
	// Standards
	var _simplechar = function(c){
		var val = this.value;
		var start =this.selectionStart;
		this.value = val.substring(0, start)+ c + val.substring(this.selectionEnd);
		this.setSelectionRange(start+c.length, start+c.length);
	};
	var backspace = function(c){
		var start = this.selectionStart, end = this.selectionEnd;
		var val = this.value;
		if (start != end){
			this.value = val.substring(0,start) + val.substring(end);
			this.setSelectionRange(start, start);
		}else if (start) {
			// start == end and not start != 0
			this.value = val.substring(0, start-1)+val.substring(start);
			this.setSelectionRange(start-1, start-1);
		}
	};
	var rightarrow = function(c){
		this.setSelectionRange(this.selectionStart+1, this.selectionStart+1);
	};
	var leftarrow = function(c){
		this.setSelectionRange(this.selectionStart-1, this.selectionStart-1);
	};
	var del = function(c){
		var start = this.selectionStart, end = this.selectionEnd;
		var val = this.value;
		if (start != end){
			this.value = val.substring(0,start) + val.substring(end);
			this.setSelectionRange(start, start);
		}else{
			this.value = val.substring(0, start)+val.substring(start+1);
			this.setSelectionRange(start, start);
		}
	};
	var all = function(c){
		this.setSelectionRange(0, this.value.length);
	};
}else if (document.selection){
	// IE
	_simplechar = function(c){
		// add the char c to the selection
		var rng = $.data(this, 'sendkeys.selection'); // assume o already has the focus
		rng.text = c;
		rng.collapse(false); // move to end of selection
		constrainAndRecord(rng, this);
	};
	backspace = function (c){
		var rng = $.data(this, 'sendkeys.selection');
		if (rng.text.length == 0) rng.moveStart('character',-1);
		constrainAndRecord(rng, this);
		rng.text = "";
		constrainAndRecord(rng, this); // need to constrain before and after. I don't trust IE
	};
	rightarrow = function(c){
		var rng = $.data(this, 'sendkeys.selection');
		rng.move('character', 1);
		constrainAndRecord(rng, this);
	}
	leftarrow = function(c){
		var rng = $.data(this, 'sendkeys.selection');
		rng.move('character', -1);
		constrainAndRecord(rng, this);
	}
	del = function (c){
		var rng = $.data(this, 'sendkeys.selection');
		if (rng.text.length == 0) rng.moveEnd('character',1);
		constrainAndRecord(rng, this);
		rng.text = "";
		constrainAndRecord(rng, this); // need to constrain before and after. I don't trust IE
	};
	all = function(c){
		var rng = this.createTextRange();
		constrainAndRecord(rng, this);
	};
}else{
	// selections not supported. Just append the character
	_simplechar = function(c){
		this.value += c;
	};
	backspace = del = function(c){
		this.value = ""; 
	}
	rightarrow = leftarrow = all = function(){};
}

// simulate a keypress (not keydown/keyup) for simplechar. Any other key has to be done by hand in the code
simplechar = function(c){
	_simplechar.call(this,c);
	for (var i =0; i < c.length; ++i){
		var x = c.charCodeAt(i);
		$(this).trigger({type: 'keypress', keyCode: x, which: x, charCode: x});
	}
}

// add the functions publicly so they can be overridden
$.fn.sendkeys.defaults = {
	'{backspace}': backspace,
	'{rightarrow}': rightarrow,
	'{leftarrow}': leftarrow,
	'{del}': del,
	'{selectall}' : all,
	'{{}': function() {simplechar.call(this, '{')},
	'simplechar': simplechar // make it publicly callable
};

})(jQuery)