text: /*jshint camelcase:true, plusplus:true, forin:true, noarg:true, noempty:true, eqeqeq:true, bitwise:true, strict:true, undef:true, unused:true, curly:true, browser:true, devel:true, maxerr:100, white:false, onevar:false */
  /*global jQuery:true $:true bililiteRange:true */
  
  /* jQuery Simulate Key-Sequence Plugin 1.3.0
   * http://github.com/j-ulrich/jquery-simulate-ext
   * 
   * Copyright (c) 2014 Jochen Ulrich
   * Licensed under the MIT license (MIT-LICENSE.txt).
   * 
   * The plugin is an extension and modification of the jQuery sendkeys plugin by Daniel Wachsstock.
   * Therefore, the original copyright notice and license follow below.
   */
  
  // insert characters in a textarea or text input field
  // special characters are enclosed in {}; use {{} for the { character itself
  // documentation: http://bililite.com/blog/2008/08/20/the-fnsendkeys-plugin/
  // Version: 2.0
  // Copyright (c) 2010 Daniel Wachsstock
  // MIT license:
  // Permission is hereby granted, free of charge, to any person
  // obtaining a copy of this software and associated documentation
  // files (the "Software"), to deal in the Software without
  // restriction, including without limitation the rights to use,
  // copy, modify, merge, publish, distribute, sublicense, and/or sell
  // copies of the Software, and to permit persons to whom the
  // Software is furnished to do so, subject to the following
  // conditions:
  //
  // The above copyright notice and this permission notice shall be
  // included in all copies or substantial portions of the Software.
  //
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  // OTHER DEALINGS IN THE SOFTWARE.
  
  ;(function($, undefined){
  	"use strict";
  	
  	$.simulate.prototype.quirks = $.simulate.prototype.quirks || {};
  	
  	$.extend($.simulate.prototype.quirks, 
  
  	/**
  	 * @lends $.simulate.prototype.quirks
  	 */		
  	{
  		/**
  		 * When simulating with delay in non-input elements,
  		 * all spaces are simulated at the end of the sequence instead
  		 * of the correct position.
  		 * @see {@link https://github.com/j-ulrich/jquery-simulate-ext/issues/6|issues #6}
  		 */
  		delayedSpacesInNonInputGlitchToEnd: undefined
  
  	});
  	
  	$.extend($.simulate.prototype,
  			
  	/**
  	 * @lends $.simulate.prototype
  	 */		
  	{
  		
  		/**
  		 * Simulates sequencial key strokes.
  		 * 
  		 * @see https://github.com/j-ulrich/jquery-simulate-ext/blob/master/doc/key-sequence.md
  		 * @public
  		 * @author Daniel Wachsstock, julrich
  		 * @since 1.0
  		 */
  		simulateKeySequence: function() {
  			var target = this.target,
  				$target = $(target),
  				opts = $.extend({
  					sequence: "",
  					triggerKeyEvents: true,
  					eventProps: {},
  					delay: 0,
  					callback: undefined
  				}, this.options),
  				sequence = opts.sequence;
  			
  			opts.delay = parseInt(opts.delay,10);
  
  			var localkeys = {};
  
  			// Fix for #6 (https://github.com/j-ulrich/jquery-simulate-ext/issues/6)
  			if ($.simulate.prototype.quirks.delayedSpacesInNonInputGlitchToEnd && !$target.is('input,textarea')) {
  				$.extend(localkeys, {
  					' ': function(rng, s, opts) {
  						var internalOpts = $.extend({}, opts, {
  							triggerKeyEvents: false,
  							delay: 0,
  							callback: undefined
  						});
  						$.simulate.prototype.simulateKeySequence.defaults.simplechar(rng, '\xA0', internalOpts);
  						$.simulate.prototype.simulateKeySequence.defaults['{leftarrow}'](rng, s, internalOpts);
  						$.simulate.prototype.simulateKeySequence.defaults.simplechar(rng, s, opts);
  						$.simulate.prototype.simulateKeySequence.defaults['{del}'](rng, s, internalOpts);
  					}
  				});
  			}
  
  			$.extend(localkeys, opts, $target.data('simulate-keySequence')); // allow for element-specific key functions
  
  			// most elements to not keep track of their selection when they lose focus, so we have to do it for them
  			var rng = $.data (target, 'simulate-keySequence.selection');
  			if (!rng){
  				rng = bililiteRange(target).bounds('selection');
  				$.data(target, 'simulate-keySequence.selection', rng);
  				$target.bind('mouseup.simulate-keySequence', function(){
  					// we have to update the saved range. The routines here update the bounds with each press, but actual keypresses and mouseclicks do not
  					$.data(target, 'simulate-keySequence.selection').bounds('selection');
  				}).bind('keyup.simulate-keySequence', function(evt){
  					// restore the selection if we got here with a tab (a click should select what was clicked on)
  					if (evt.which === 9){
  						// there's a flash of selection when we restore the focus, but I don't know how to avoid that.
  						$.data(target, 'simulate-keySequence.selection').select();
  					}else{
  						$.data(target, 'simulate-keySequence.selection').bounds('selection');
  					}	
  				});
  			}
  			$target.focus();
  			if (typeof sequence === 'undefined') { // no string, so we just set up the event handlers
  				return;
  			}
  			sequence = sequence.replace(/\n/g, '{enter}'); // turn line feeds into explicit break insertions
  			
  			/**
  			 * Informs the rest of the world that the sequences is finished.
  			 * @fires simulate-keySequence
  			 * @requires target
  			 * @requires sequence
  			 * @requires opts
  			 * @inner
  			 * @author julrich
  			 * @since 1.0
  			 */
  			function sequenceFinished() {
  				$target.trigger({type: 'simulate-keySequence', sequence: sequence});
  				if ($.isFunction(opts.callback)) {
  					opts.callback.apply(target, [{
  						sequence: sequence
  					}]);
  				}
  			}
  			
  			/**
  			 * Simulates the key stroke for one character (or special sequence) and sleeps for
  			 * <code>opts.delay</code> milliseconds.
  			 * @requires lastTime
  			 * @requires now()
  			 * @requires tokenRegExp
  			 * @requires opts
  			 * @requires rng
  			 * @inner
  			 * @author julrich
  			 * @since 1.0
  			 */
  			function processNextToken() {
  				var timeElapsed = now() - lastTime; // Work-around for Firefox "bug": setTimeout can fire before the timeout
  				if (timeElapsed >= opts.delay) {
  					var match = tokenRegExp.exec(sequence);
  					if ( match !== null ) {
  						var s = match[0];
  						(localkeys[s] || $.simulate.prototype.simulateKeySequence.defaults[s] || $.simulate.prototype.simulateKeySequence.defaults.simplechar)(rng, s, opts);
  						setTimeout(processNextToken, opts.delay);
  					}
  					else {
  						sequenceFinished();
  					}
  					lastTime = now();
  				}
  				else {
  					setTimeout(processNextToken, opts.delay - timeElapsed);
  				}
  			}
  
  			if (!opts.delay || opts.delay <= 0) {
  				// Run as fast as possible
  				sequence.replace(/\{[^}]*\}|[^{]+/g, function(s){
  					(localkeys[s] || $.simulate.prototype.simulateKeySequence.defaults[s] || $.simulate.prototype.simulateKeySequence.defaults.simplechar)(rng, s, opts);
  				});
  				sequenceFinished();
  			}
  			else {
  				var tokenRegExp = /\{[^}]*\}|[^{]/g; // This matches curly bracket expressions or single characters
  				var now = Date.now || function() { return new Date().getTime(); },
  					lastTime = now();
  				
  				processNextToken();
  			}
  			
  		}
  	});
  
  	$.extend($.simulate.prototype.simulateKeySequence.prototype,
  			
  	/**
  	 * @lends $.simulate.prototype.simulateKeySequence.prototype
  	 */		
  	{
  		
  			/**
  			 * Maps special character char codes to IE key codes (covers IE and Webkit)
  			 * @author julrich
  			 * @since 1.0
  			 */
  			IEKeyCodeTable: {
  				33: 49,	// ! -> 1
  				64: 50,	// @ -> 2
  				35: 51,	// # -> 3
  				36: 52,	// $ -> 4
  				37: 53,	// % -> 5
  				94: 54,	// ^ -> 6
  				38: 55,	// & -> 7
  				42: 56,	// * -> 8
  				40: 57,	// ( -> 9
  				41: 48,	// ) -> 0
  				
  				59: 186,	// ; -> 186
  				58: 186,	// : -> 186
  				61: 187,	// = -> 187
  				43: 187,	// + -> 187
  				44: 188,	// , -> 188
  				60: 188,	// < -> 188
  				45: 189,	// - -> 189
  				95: 189,	// _ -> 189
  				46: 190,	// . -> 190
  				62: 190,	// > -> 190
  				47: 191,	// / -> 191
  				63: 191,	// ? -> 191
  				96: 192,	// ` -> 192
  				126: 192,	// ~ -> 192
  				91: 219,	// [ -> 219
  				123: 219,	// { -> 219
  				92: 220,	// \ -> 220
  				124: 220,	// | -> 220
  				93: 221,	// ] -> 221
  				125: 221,	// } -> 221
  				39: 222,	// ' -> 222
  				34: 222		// " -> 222
  			},
  			
  			/**
  			 * Tries to convert character codes to key codes.
  			 * @param {Numeric} character - A character code
  			 * @returns {Numeric} The key code corresponding to the given character code,
  			 * based on the key code table of InternetExplorer. If no corresponding key code
  			 * could be found (which will be the case for all special characters except the common
  			 * ones), the character code itself is returned. However, <code>keyCode === charCode</code>
  			 * does not imply that no key code was found because some key codes are identical to the
  			 * character codes (e.g. for uppercase characters).
  			 * @requires $.simulate.prototype.simulateKeySequence.prototype.IEKeyCodeTable
  			 * @see $.simulate.prototype.simulateKeySequence.prototype.IEKeyCodeTable
  			 * @author julrich
  			 * @since 1.0
  			 */
  			charToKeyCode: function(character) {
  				var specialKeyCodeTable = $.simulate.prototype.simulateKeySequence.prototype.IEKeyCodeTable;
  				var charCode = character.charCodeAt(0);
  		
  				if (charCode >= 64 && charCode <= 90 || charCode >= 48 && charCode <= 57) {
  					// A-Z and 0-9
  					return charCode;
  				}
  				else if (charCode >= 97 && charCode <= 122) {
  					// a-z -> A-Z
  					return character.toUpperCase().charCodeAt(0);
  				}
  				else if (specialKeyCodeTable[charCode] !== undefined) {
  					return specialKeyCodeTable[charCode];
  				}
  				else {
  					return charCode;
  				}
  			}
  	});
  
  	// add the functions publicly so they can be overridden
  	$.simulate.prototype.simulateKeySequence.defaults = {
  		
  		/**
  		 * Simulates key strokes of "normal" characters (i.e. non-special sequences).
  		 * @param {Object} rng - bililiteRange object of the simulation target element.
  		 * @param {String} s - String of (simple) characters to be simulated. 
  		 * @param {Object} opts - The key-sequence options.
  		 * @author Daniel Wachsstock, julrich
  		 * @since 1.0
  		 */
  		simplechar: function (rng, s, opts){
  			rng.text(s, 'end');
  			if (opts.triggerKeyEvents) {
  				for (var i =0; i < s.length; i += 1){
  					var charCode = s.charCodeAt(i);
  					var keyCode = $.simulate.prototype.simulateKeySequence.prototype.charToKeyCode(s.charAt(i));
  					// a bit of cheating: rng._el is the element associated with rng.
  					$(rng._el).simulate('keydown', $.extend({}, opts.eventProps, {keyCode: keyCode}));
  					$(rng._el).simulate('keypress', $.extend({}, opts.eventProps,{keyCode: charCode, which: charCode, charCode: charCode}));
  					$(rng._el).simulate('keyup', $.extend({}, opts.eventProps, {keyCode: keyCode}));
  				}
  			}
  		},
  		
  		/**
  		 * Simulates key strokes of a curly opening bracket. 
  		 * @param {Object} rng - bililiteRange object of the simulation target element.
  		 * @param {String} s - Ignored. 
  		 * @param {Object} opts - The key-sequence options.
  		 * @author Daniel Wachsstock, julrich
  		 * @since 1.0
  		 */
  		'{{}': function (rng, s, opts){
  			$.simulate.prototype.simulateKeySequence.defaults.simplechar(rng, '{', opts);
  		},
  		
  		/**
  		 * Simulates hitting the enter button.
  		 * @param {Object} rng - bililiteRange object of the simulation target element.
  		 * @param {String} s - Ignored. 
  		 * @param {Object} opts - The key-sequence options.
  		 * @author Daniel Wachsstock, julrich
  		 * @since 1.0
  		 */
  		'{enter}': function (rng, s, opts){
  			rng.insertEOL();
  			rng.select();
  			if (opts.triggerKeyEvents === true) {
  				$(rng._el).simulate('keydown', $.extend({}, opts.eventProps, {keyCode: 13}));
  				$(rng._el).simulate('keypress', $.extend({}, opts.eventProps, {keyCode: 13, which: 13, charCode: 13}));
  				$(rng._el).simulate('keyup', $.extend({}, opts.eventProps, {keyCode: 13}));
  			}
  		},
  		
  		/**
  		 * Simulates hitting the backspace button.
  		 * @param {Object} rng - bililiteRange object of the simulation target element.
  		 * @param {String} s - Ignored. 
  		 * @param {Object} opts - The key-sequence options.
  		 * @author Daniel Wachsstock, julrich
  		 * @since 1.0
  		 */
  		'{backspace}': function (rng, s, opts){
  			var b = rng.bounds();
  			if (b[0] === b[1]) { rng.bounds([b[0]-1, b[0]]); } // no characters selected; it's just an insertion point. Remove the previous character
  			rng.text('', 'end'); // delete the characters and update the selection
  			if (opts.triggerKeyEvents === true) {
  				$(rng._el).simulate('keydown', $.extend({}, opts.eventProps, {keyCode: 8}));
  				$(rng._el).simulate('keyup', $.extend({}, opts.eventProps, {keyCode: 8}));
  			}
  		},
  		
  		/**
  		 * Simulates hitting the delete button.
  		 * @param {Object} rng - bililiteRange object of the simulation target element.
  		 * @param {String} s - Ignored. 
  		 * @param {Object} opts - The key-sequence options.
  		 * @author Daniel Wachsstock, julrich
  		 * @since 1.0
  		 */
  		'{del}': function (rng, s, opts){
  			var b = rng.bounds();
  			if (b[0] === b[1]) { rng.bounds([b[0], b[0]+1]); } // no characters selected; it's just an insertion point. Remove the next character
  			rng.text('', 'end'); // delete the characters and update the selection
  			if (opts.triggerKeyEvents === true) {
  				$(rng._el).simulate('keydown', $.extend({}, opts.eventProps, {keyCode: 46}));
  				$(rng._el).simulate('keyup', $.extend({}, opts.eventProps, {keyCode: 46}));
  			}
  		},
  		
  		/**
  		 * Simulates hitting the right arrow button.
  		 * @param {Object} rng - bililiteRange object of the simulation target element.
  		 * @param {String} s - Ignored. 
  		 * @param {Object} opts - The key-sequence options.
  		 * @author Daniel Wachsstock, julrich
  		 * @since 1.0
  		 */
  		'{rightarrow}':  function (rng, s, opts){
  			var b = rng.bounds();
  			if (b[0] === b[1]) { b[1] += 1; } // no characters selected; it's just an insertion point. Move to the right
  			rng.bounds([b[1], b[1]]).select();
  			if (opts.triggerKeyEvents === true) {
  				$(rng._el).simulate('keydown', $.extend({}, opts.eventProps, {keyCode: 39}));
  				$(rng._el).simulate('keyup', $.extend({}, opts.eventProps, {keyCode: 39}));
  			}
  		},
  		
  		/**
  		 * Simulates hitting the left arrow button.
  		 * @param {Object} rng - bililiteRange object of the simulation target element.
  		 * @param {String} s - Ignored. 
  		 * @param {Object} opts - The key-sequence options.
  		 * @author Daniel Wachsstock, julrich
  		 * @since 1.0
  		 */
  		'{leftarrow}': function (rng, s, opts){
  			var b = rng.bounds();
  			if (b[0] === b[1]) { b[0] -= 1; } // no characters selected; it's just an insertion point. Move to the left
  			rng.bounds([b[0], b[0]]).select();
  			if (opts.triggerKeyEvents === true) {
  				$(rng._el).simulate('keydown', $.extend({}, opts.eventProps, {keyCode: 37}));
  				$(rng._el).simulate('keyup', $.extend({}, opts.eventProps, {keyCode: 37}));
  			}
  		},
  		
  		/**
  		 * Selects all characters in the target element.
  		 * @param {Object} rng - bililiteRange object of the simulation target element.
  		 * @author Daniel Wachsstock, julrich
  		 * @since 1.0
  		 */
  		'{selectall}' : function (rng){
  			rng.bounds('all').select();
  		}
  	};
  	
  	
  		
  	
  	//####### Quirk detection #######
  	if ($.simulate.ext_disableQuirkDetection !== true) { // Fixes issue #9 (https://github.com/j-ulrich/jquery-simulate-ext/issues/9)
  		$(document).ready(function() {
  			// delayedSpacesInNonInputGlitchToEnd
  			// See issues #6 (https://github.com/j-ulrich/jquery-simulate-ext/issues/6)
  			/* Append a div to the document (bililiteRange needs the element to be in the document), simulate
  			 * a delayed sequence containing a space in the middle and check if the space moves to the end.
  			 */
  			var $testDiv = $('<div/>').css({height: 1, width: 1, position: 'absolute', left: -1000, top: -1000}).appendTo('body');
  			$testDiv.simulate('key-sequence', {sequence: '\xA0 \xA0', delay:1, callback: function() {
  				$.simulate.prototype.quirks.delayedSpacesInNonInputGlitchToEnd = ($testDiv.text() === '\xA0\xA0 ');
  				$testDiv.remove();
  			}});
  		});
  	}
  
  })(jQuery);