/*! jQuery UI Virtual Keyboard Typing Simulator v1.11.1 *//*
 * for Keyboard v1.18+ only (3/15/2017)
 *
 * By Rob Garrison (aka Mottie)
 * Licensed under the MIT License
 *
 * Use this extension with the Virtual Keyboard to simulate
 * typing for tutorials or whatever else use you can find
 *
 * Requires:
 *  jQuery
 *  Keyboard plugin : https://github.com/Mottie/Keyboard
 *
 * Setup:
 *  $('.ui-keyboard-input')
 *   .keyboard(options)
 *   .addTyping(typing-options);
 *
 *  // or if targeting a specific keyboard
 *  $('#keyboard1')
 *   .keyboard(options)
 *   .addTyping(typing-options);
 *
 * Basic Usage:
 *  // To disable manual typing on the virtual keyboard, just set "showTyping"
 *  // option to false
 *  $('#keyboard-input').keyboard(options).addTyping({ showTyping: false });
 *
 *  // Change the default typing delay (time the virtual keyboard highlights the
 *  // manually typed key) - default = 250 milliseconds
 *  $('#keyboard-input').keyboard(options).addTyping({ delay: 500 });
 *
 *  // get keyboard object, open it, then start typing simulation
 *  $('#keyboard-input').getkeyboard().reveal().typeIn('Hello World', 700);
 *
 *  // get keyboard object, open it, type in "This is a test" with 700ms delay
 *  // between types, then accept & close the keyboard
 *  $('#keyboard-input')
 *    .getkeyboard()
 *    .reveal()
 *    .typeIn('This is a test', 700, function(keyboard) {
 *      keyboard.accept();
 *    });
 */
/* More Examples:
 * $('#inter').getkeyboard().reveal().typeIn('\tHello \b\n\tWorld', 500);
 * $('#meta')
 *  .getkeyboard().reveal()
 *  .typeIn('abCDd11123\u2648\u2649\u264A\u264B', 700, function() {
 *    alert('all done!');
 *  });
*/
/* jshint browser:true, jquery:true, unused:false */
/* global require:false, define:false, module:false */
;( function( factory ) {
	if ( typeof define === 'function' && define.amd ) {
		define( ['jquery'], factory );
	} else if (
			typeof module === 'object' &&
			typeof module.exports === 'object'
	) {
		module.exports = factory( require( 'jquery' ) );
	} else {
		factory( jQuery );
	}
}( function( $ ) {
	$.fn.addTyping = function( options ) {
		//Set the default values, use comma to separate the settings, example:
		var defaults = {
			showTyping : true,
			lockTypeIn : false,
			delay      : 250,
			hoverDelay : 250
		},
		$keyboard = $.keyboard;
		return this.each( function() {
			// make sure a keyboard is attached
			var o, base = $( this ).data( 'keyboard' );
			if ( !base ) {
				return;
			}

			// variables
			o = base.typing_options = $.extend( {}, defaults, options );
			base.typing_keymap = {
				' '   : 'space',
				'"'   : '34',
				"'"   : '39',
				'&nbsp;' : 'space',
				'\b'  : 'bksp', // delete character to the left
				'{b}' : 'bksp',
				'{d}' : 'del',  // delete character to the right
				'{l}' : 'left', // move caret left
				'{r}' : 'right', // move caret right
				'\n'  : 'enter',
				'\r'  : 'enter',
				'{e}' : 'enter',
				'\t'  : 'tab',
				'{t}' : 'tab'
			};
			base.typing_xref = {
				8  : 'bksp',
				9  : 'tab',
				13 : 'enter',
				32 : 'space',
				37 : 'left',
				39 : 'right',
				46 : 'del'
			};
			base.typing_event = false;
			base.typing_namespace = base.namespace + 'typing';
			base.extensionNamespace.push( base.typing_namespace );
			// save lockInput setting
			o.savedLockInput = base.options.lockInput;

			base.typing_setup_reset = function() {
				var kbevents = $keyboard.events,
					namespace = base.typing_namespace,
					events = [ kbevents.kbHidden, kbevents.kbInactive, '' ]
						.join( namespace + ' ' );
				// reset "typeIn" when keyboard is closed
				base.$el
					.unbind( namespace )
					.bind( events, function() {
						base.typing_reset();
					});
				base
					.unbindButton( namespace )
					.bindButton( 'mousedown' + namespace, function() {
						base.typing_reset();
					});
			};

			base.typing_setup = function() {
				var namespace = base.typing_namespace;
				base.typing_setup_reset();
				base.$el
					.bind( $keyboard.events.kbBeforeVisible + namespace, function() {
						base.typing_setup();
					});
				base.$preview
					.unbind( namespace )
					.bind( 'keyup' + namespace, function( e ) {
						if ( o.init && o.lockTypeIn || !o.showTyping ) {
							return false;
						}
						if ( e.which >= 37 && e.which <=40 ) {
							return; // ignore arrow keys
						}
						if ( e.which === 16 ) {
							base.shiftActive = false;
						}
						if ( e.which === 18 ) {
							base.altActive = false;
						}
						if ( e.which === 16 || e.which === 18 ) {
							base.showSet();
							// Alt key will shift focus to the menu - doesn't work in Windows
							setTimeout( function() {
								base.$preview.focus();
							}, 200 );
							return;
						}
					})
					// change keyset when either shift or alt is held down
					.bind( 'keydown' + namespace, function( e ) {
						if ( o.init && o.lockTypeIn || !o.showTyping ) {
							return false;
						}
						e.temp = false; // prevent repetitive calls while keydown repeats.
						if ( e.which === 16 ) {
							e.temp = !base.shiftActive; base.shiftActive = true;
						}
						// it should be ok to reset e.temp, since both alt and shift will
						// call this function separately
						if ( e.which === 18 ) {
							e.temp = !base.altActive; base.altActive = true;
						}
						if ( e.temp ) {
							base.showSet();
							base.$preview.focus(); // Alt shift focus to the menu
						}
						base.typing_event = true;
						// Simulate key press for tab and backspace since they don't fire
						// the keypress event
						if ( base.typing_xref[ e.which ] ) {
							base.typing_findKey( '', e ); // pass event object
						}

					})
					.bind( 'keypress' + namespace, function( e ) {
						if ( o.init && o.lockTypeIn ) {
							return false;
						}
						// Simulate key press on virtual keyboard
						if ( base.typing_event && !base.options.lockInput ) {
							base.typing_reset();
							base.typing_event = true;
							base.typing_findKey( '', e ); // pass event object
						}
					});
			};

			base.typing_reset = function() {
				base.typing_event = o.init = false;
				o.text = '';
				o.len = o.current = 0;
				base.options.lockInput = o.savedLockInput;
				// clearTimeout( base.typing_timer );
			};

			// Store typing text
			base.typeIn = function( txt, delay, callback, e ) {
				if ( !base.isVisible() ) {
					// keyboard was closed
					clearTimeout( base.typing_timer );
					base.typing_reset();
					return;
				}
				if ( !base.typing_event ) {

					if ( o.init !== true ) {
						o.init = true;
						base.options.lockInput = o.lockTypeIn;
						o.text = txt || o.text || '';
						o.len = o.text.length;
						o.delay = delay || o.delay;
						o.current = 0; // position in text string
						if ( callback ) {
							o.callback = callback;
						}
					}
					// function that loops through and types each character
					txt = o.text.substring( o.current, ++o.current );
					// add support for curly-wrapped single character: {l}, {r}, {d}, etc.
					if (
						txt === '{' &&
						o.text.substring( o.current + 1, o.current + 2 ) === '}'
					) {
						txt += o.text.substring( o.current, o.current += 2 );
					}
					base.typing_findKey( txt, e );
				} else if ( typeof txt === 'undefined' ) {
					// typeIn called by user input
					base.typing_event = false;
					base.options.lockInput = o.savedLockInput;
					return;
				}

			};

			base.typing_findKey = function( txt, e ) {
				var tar, m, n, k, key, ks, meta, set,
					kbcss = $keyboard.css,
					mappedKeys = $keyboard.builtLayouts[base.layout].mappedKeys;
				// stop if keyboard is closed
				if ( !base.isOpen || !base.$keyboard.length ) {
					return;
				}
				ks = base.$keyboard.find( '.' + kbcss.keySet );
				k = txt in base.typing_keymap ? base.typing_keymap[ txt ] : txt;
				// typing_event is true when typing on the actual keyboard - look for
				// actual key; All of this breaks when the CapLock is on... unable to
				// find a cross-browser method that works.
				tar = '.' + kbcss.keyButton + '[data-action="' + k + '"]';
				if ( base.typing_event && e ) {
					// xref used for keydown
					// ( 46 = delete in keypress & period on keydown )
					if (
						e.type !== 'keypress' &&
						base.typing_xref.hasOwnProperty( e.keyCode || e.which )
					) {
						// special named keys: bksp, tab and enter
						tar = '.' +
							kbcss.keyPrefix +
							base.processName( base.typing_xref[ e.keyCode || e.which ] );
					} else {
						m = String.fromCharCode( e.charCode || e.which );
						tar = ( mappedKeys.hasOwnProperty( m ) ) ?
							'.' + kbcss.keyButton + '[data-value="' +
								mappedKeys[ m ].replace(/"/g, '\\"') + '"]' :
							'.' + kbcss.keyPrefix + base.processName( m );
					}
				}
				// find key
				key = ks.filter( ':visible' ).find( tar );
				if ( key.length ) {
					// key is visible, simulate typing
					base.typing_simulateKey( key, txt, e );
				} else {
					// key not found, check if it is in the keymap
					// (tab, space, enter, etc)
					if ( base.typing_event ) {
						key = ks.find( tar );
					} else {
						// key not found, check if it is in the keymap
						// (tab, space, enter, etc)
						n = txt in base.typing_keymap ?
							base.typing_keymap[ txt ] :
							base.processName( txt );
						// find actual key on keyboard
						key = ks.find( '.' + kbcss.keyPrefix + n );
					}
					// find the keyset
					set = key.closest( '.' + kbcss.keySet );
					// figure out which keyset the key is in then simulate clicking on
					// that meta key, then on the key
					if ( set.attr('name' ) ) {
						if ( o.showTyping ) {
							// get meta key name
							meta = set.attr( 'name' );
							// show correct key set
							base.shiftActive = /shift/.test( meta );
							base.altActive = /alt/.test( meta );
							base.metaActive = base.last.keyset[ 2 ] = /\bmeta/.test(meta) ?
								( meta ).match(/meta[\w-]+/)[0] : false;
							base.showSet( base.metaActive );
						}
						// Add the key
						base.typing_simulateKey( key, txt, e );
					} else {
						if ( !base.typing_event ) {
							// Key doesn't exist on the keyboard, so just enter it
							if (
								txt in base.typing_keymap &&
								base.typing_keymap[txt] in $keyboard.keyaction
							) {
								$keyboard.keyaction[ base.typing_keymap[ txt ] ]( base, key, e );
							} else {
								base.insertText( txt );
							}
							base.checkCombos();
							base.$el.trigger( $keyboard.events.kbChange, [ base, base.el ] );
						}
					}
				}

				if ( o.current <= o.len && o.len !== 0 ) {
					if ( !base.isVisible() ) {
						return; // keyboard was closed, abort!!
					}
					base.typing_timer = setTimeout( function() {
						base.typeIn();
					}, o.delay );
				} else if ( o.len !== 0 ) {
					// o.len is zero when the user typed on the actual keyboard during
					// simulation
					base.typing_reset();
					if ( $.isFunction( o.callback ) ) {
						// ensure all typing animation is done before the callback
						base.typing_timer = setTimeout( function() {
							// if the user typed during the key simulation, the "o" variable
							// may sometimes be undefined
							if ( $.isFunction( o.callback ) ) {
								o.callback( base );
							}
						}, o.delay );
					}
					return;
				} else {
					base.typing_reset();
				}
			};

			// mouseover the key, add the text directly, then mouseout on the key
			base.typing_simulateKey = function( el, txt, e ) {
				var len = el.length;
				if ( !base.isVisible() ) {
					return;
				}
				if ( o.showTyping && len ) {
					el.filter( ':visible' ).trigger( 'mouseenter' + base.namespace );
					if ( o.showTyping && len ) {
						setTimeout( function() {
							el.trigger( 'mouseleave' + base.namespace );
						}, Math.min( o.hoverDelay, o.delay ) );
					}
				}
				if ( !base.typing_event ) {
					// delay required or initial tab does not get added
					// in the main demo (international keyboard)
					setTimeout( function() {
						if (
							txt in base.typing_keymap &&
							base.typing_keymap[ txt ] in $keyboard.keyaction
						) {
							e = e || $.Event( 'keypress' );
							e.target = el; // "Enter" checks for the e.target
							$keyboard.keyaction[ base.typing_keymap[ txt ] ]( base, el, e );
						} else {
							base.insertText( txt );
						}
						base.checkCombos();
						base.$el.trigger( $keyboard.events.kbChange, [ base, base.el ] );
				}, o.delay/3 );
				}
			};

			// visible event is fired before this extension is initialized, so check!
			if ( o.showTyping && base.options.alwaysOpen && base.isVisible() ) {
				base.typing_setup();
			} else {
				// capture and simulate typing
				base.$el
					.unbind( $keyboard.events.kbBeforeVisible + base.typing_namespace )
					.bind( $keyboard.events.kbBeforeVisible + base.typing_namespace, function() {
						if ( o.showTyping ) {
							base.typing_setup();
						} else {
							base.typing_setup_reset();
						}
					});
			}
		});
	};

}));
