/**
 * jquery.mask.js
 *
 * @version: v1.14.16
 * @author: Igor Escobar
 *
 * Created by Igor Escobar on 2012-03-10. Please report any bug at github.com/igorescobar/jQuery-Mask-Plugin
 *
 * Copyright (c) 2012 Igor Escobar http://igorescobar.com
 *
 * The MIT License (http://www.opensource.org/licenses/mit-license.php)
 *
 * 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.
 */

/* jshint laxbreak: true */
/* jshint maxcomplexity:17 */
/* global define */

// UMD (Universal Module Definition) patterns for JavaScript modules that work everywhere.
// https://github.com/umdjs/umd/blob/master/templates/jqueryPlugin.js
// reformatted code by themeComplete
( function( factory, jQuery, Zepto ) {
	'use strict';

	if ( typeof define === 'function' && define.amd ) {
		define( [ 'jquery' ], factory );
	} else if ( typeof exports === 'object' && typeof Meteor === 'undefined' ) {
		window.module.exports = factory( window.require( 'jquery' ) );
	} else {
		factory( jQuery || Zepto );
	}
}(
	function( $ ) {
		'use strict';

		var JSON = window.JSON;
		var globals;
		var Mask = function( el, mask, options ) {
			var jMask = this;
			var regexMask;
			var oldValue;

			var p = {
				invalid: [],
				getCaret: function() {
					var sel,
						pos = 0,
						ctrl,
						dSel,
						cSelStart;
					try {
						ctrl = el.get( 0 );
						dSel = document.selection;
						cSelStart = ctrl.selectionStart;

						// IE Support
						if ( dSel && navigator.appVersion.indexOf( 'MSIE 10' ) === -1 ) {
							sel = dSel.createRange();
							sel.moveStart( 'character', -p.val().length );
							pos = sel.text.length;
						} else if ( cSelStart || cSelStart === '0' ) { // Firefox support
							pos = cSelStart;
						}

						return pos;
					} catch ( err ) {
						window.console.log( err );
					}
				},
				setCaret: function( pos ) {
					var range;
					var ctrl;
					try {
						if ( el.is( ':focus' ) ) {
							ctrl = el.get( 0 );

							// Firefox, WebKit, etc..
							if ( ctrl.setSelectionRange ) {
								ctrl.setSelectionRange( pos, pos );
							} else {
								// IE
								range = ctrl.createTextRange();
								range.collapse( true );
								range.moveEnd( 'character', pos );
								range.moveStart( 'character', pos );
								range.select();
							}
						}
					} catch ( err ) {
						window.console.log( err );
					}
				},
				events: function() {
					el.on( 'keydown.mask', function( e ) {
						el.data( 'mask-keycode', e.keyCode || e.which );
						el.data( 'mask-previus-value', el.val() );
						el.data( 'mask-previus-caret-pos', p.getCaret() );
						p.maskDigitPosMapOld = p.maskDigitPosMap;
					} )
						.on( $.jMaskGlobals.useInput ? 'input.mask' : 'keyup.mask', p.behaviour )
						.on( 'paste.mask drop.mask', function() {
							setTimeout( function() {
								el.keydown().keyup();
							}, 100 );
						} )
						.on( 'change.mask', function() {
							el.data( 'changed', true );
						} )
						.on( 'blur.mask', function() {
							if ( oldValue !== p.val() && ! el.data( 'changed' ) ) {
								el.trigger( 'change' );
							}
							el.data( 'changed', false );
						} )
						// it's very important that this callback remains in this position
						// otherwhise oldValue it's going to work buggy
						.on( 'blur.mask', function() {
							oldValue = p.val();
						} )
						// select all text on focus
						.on( 'focus.mask', function( e ) {
							if ( options.selectOnFocus === true ) {
								$( e.target ).select();
							}
						} )
						// clear the value if it not complete the mask
						.on( 'focusout.mask', function() {
							if ( options.clearIfNotMatch && ! regexMask.test( p.val() ) ) {
								p.val( '' );
							}
						} );
				},
				getRegexMask: function() {
					var maskChunks = [],
						translation,
						pattern,
						optional,
						recursive,
						oRecursive,
						r;
					var i;

					for ( i = 0; i < mask.length; i += 1 ) {
						translation = jMask.translation[ mask.charAt( i ) ];

						if ( translation ) {
							pattern = translation.pattern.toString().replace( /.{1}$|^.{1}/g, '' );
							optional = translation.optional;
							recursive = translation.recursive;

							if ( recursive ) {
								maskChunks.push( mask.charAt( i ) );
								oRecursive = {
									digit: mask.charAt( i ),
									pattern: pattern
								};
							} else {
								maskChunks.push( ! optional && ! recursive ? pattern : pattern + '?' );
							}
						} else {
							// eslint-disable-next-line no-useless-escape
							maskChunks.push( mask.charAt( i ).replace( /[-\/\\^$*+?.()|[\]{}]/g, '\\$&' ) );
						}
					}

					r = maskChunks.join( '' );

					if ( oRecursive ) {
						r = r.replace( new RegExp( '(' + oRecursive.digit + '(.*' + oRecursive.digit + ')?)' ), '($1)?' ).replace( new RegExp( oRecursive.digit, 'g' ), oRecursive.pattern );
					}

					return new RegExp( r );
				},
				destroyEvents: function() {
					el.off( [ 'input', 'keydown', 'keyup', 'paste', 'drop', 'blur', 'focusout', '' ].join( '.mask ' ) );
				},
				val: function( v ) {
					var isInput = el.is( 'input' ),
						method = isInput ? 'val' : 'text',
						r;

					if ( arguments.length > 0 ) {
						if ( el[ method ]() !== v ) {
							el[ method ]( v );
						}
						r = el;
					} else {
						r = el[ method ]();
					}

					return r;
				},
				calculateCaretPosition: function( oldVal ) {
					var newVal = p.getMasked(),
						caretPosNew = p.getCaret();
					var caretPosOld,
						newValL,
						oldValL,
						maskDigitsBeforeCaret = 0,
						maskDigitsAfterCaret = 0,
						maskDigitsBeforeCaretAll = 0,
						maskDigitsBeforeCaretAllOld = 0,
						i = 0;
					var caretPos;
					if ( oldVal !== newVal ) {
						caretPosOld = el.data( 'mask-previus-caret-pos' ) || 0;
						newValL = newVal.length;
						oldValL = oldVal.length;

						for ( i = caretPosNew; i < newValL; i += 1 ) {
							if ( ! p.maskDigitPosMap[ i ] ) {
								break;
							}
							maskDigitsAfterCaret = maskDigitsAfterCaret + 1;
						}

						for ( i = caretPosNew - 1; i >= 0; i -= 1 ) {
							if ( ! p.maskDigitPosMap[ i ] ) {
								break;
							}
							maskDigitsBeforeCaret = maskDigitsBeforeCaret + 1;
						}

						for ( i = caretPosNew - 1; i >= 0; i -= 1 ) {
							if ( p.maskDigitPosMap[ i ] ) {
								maskDigitsBeforeCaretAll = maskDigitsBeforeCaretAll + 1;
							}
						}

						for ( i = caretPosOld - 1; i >= 0; i -= 1 ) {
							if ( p.maskDigitPosMapOld[ i ] ) {
								maskDigitsBeforeCaretAllOld = maskDigitsBeforeCaretAllOld + 1;
							}
						}

						// if the cursor is at the end keep it there
						if ( caretPosNew > oldValL ) {
							caretPosNew = newValL * 10;
						} else if ( caretPosOld >= caretPosNew && caretPosOld !== oldValL ) {
							if ( ! p.maskDigitPosMapOld[ caretPosNew ] ) {
								caretPos = caretPosNew;
								caretPosNew -= maskDigitsBeforeCaretAllOld - maskDigitsBeforeCaretAll;
								caretPosNew -= maskDigitsBeforeCaret;
								if ( p.maskDigitPosMap[ caretPosNew ] ) {
									caretPosNew = caretPos;
								}
							}
						} else if ( caretPosNew > caretPosOld ) {
							caretPosNew += maskDigitsBeforeCaretAll - maskDigitsBeforeCaretAllOld;
							caretPosNew += maskDigitsAfterCaret;
						}
					}
					return caretPosNew;
				},
				behaviour: function( e ) {
					var keyCode = el.data( 'mask-keycode' );
					var newVal;
					var oldVal;
					var caretPos;
					e = e || window.event;
					p.invalid = [];

					if ( $.inArray( keyCode, jMask.byPassKeys ) === -1 ) {
						newVal = p.getMasked();
						caretPos = p.getCaret();
						oldVal = el.data( 'mask-previus-value' ) || '';

						// this is a compensation to devices/browsers that don't compensate
						// caret positioning the right way
						setTimeout( function() {
							p.setCaret( p.calculateCaretPosition( oldVal ) );
						}, $.jMaskGlobals.keyStrokeCompensation );

						p.val( newVal );
						p.setCaret( caretPos );
						return p.callbacks( e );
					}
				},
				getMasked: function( skipMaskChars, val ) {
					var buf = [],
						value = val === undefined ? p.val() : val + '',
						m = 0,
						maskLen = mask.length,
						v = 0,
						valLen = value.length,
						offset = 1,
						addMethod = 'push',
						resetPos = -1,
						maskDigitCount = 0,
						maskDigitPosArr = [],
						lastMaskChar,
						check;
					var lastUntranslatedMaskChar;
					var maskDigit;
					var valDigit;
					var translation;
					var lastMaskCharDigit;
					var newVal;
					if ( options.reverse ) {
						addMethod = 'unshift';
						offset = -1;
						lastMaskChar = 0;
						m = maskLen - 1;
						v = valLen - 1;
						check = function() {
							return m > -1 && v > -1;
						};
					} else {
						lastMaskChar = maskLen - 1;
						check = function() {
							return m < maskLen && v < valLen;
						};
					}

					while ( check() ) {
						maskDigit = mask.charAt( m );
						valDigit = value.charAt( v );
						translation = jMask.translation[ maskDigit ];

						if ( translation ) {
							if ( valDigit.match( translation.pattern ) ) {
								buf[ addMethod ]( valDigit );
								if ( translation.recursive ) {
									if ( resetPos === -1 ) {
										resetPos = m;
									} else if ( m === lastMaskChar && m !== resetPos ) {
										m = resetPos - offset;
									}

									if ( lastMaskChar === resetPos ) {
										m -= offset;
									}
								}
								m += offset;
							} else if ( valDigit === lastUntranslatedMaskChar ) {
								// matched the last untranslated (raw) mask character that we encountered
								// likely an insert offset the mask character from the last entry; fall
								// through and only increment v
								maskDigitCount = maskDigitCount - 1;
								lastUntranslatedMaskChar = undefined;
							} else if ( translation.optional ) {
								m += offset;
								v -= offset;
							} else if ( translation.fallback ) {
								buf[ addMethod ]( translation.fallback );
								m += offset;
								v -= offset;
							} else {
								p.invalid.push( {
									p: v,
									v: valDigit,
									e: translation.pattern
								} );
							}
							v += offset;
						} else {
							if ( ! skipMaskChars ) {
								buf[ addMethod ]( maskDigit );
							}

							if ( valDigit === maskDigit ) {
								maskDigitPosArr.push( v );
								v += offset;
							} else {
								lastUntranslatedMaskChar = maskDigit;
								maskDigitPosArr.push( v + maskDigitCount );
								maskDigitCount = maskDigitCount + 1;
							}

							m += offset;
						}
					}

					lastMaskCharDigit = mask.charAt( lastMaskChar );
					if ( maskLen === valLen + 1 && ! jMask.translation[ lastMaskCharDigit ] ) {
						buf.push( lastMaskCharDigit );
					}

					newVal = buf.join( '' );
					p.mapMaskdigitPositions( newVal, maskDigitPosArr, valLen );
					return newVal;
				},
				mapMaskdigitPositions: function( newVal, maskDigitPosArr, valLen ) {
					var maskDiff = options.reverse ? newVal.length - valLen : 0;
					var i;
					p.maskDigitPosMap = {};
					for ( i = 0; i < maskDigitPosArr.length; i += 1 ) {
						p.maskDigitPosMap[ maskDigitPosArr[ i ] + maskDiff ] = 1;
					}
				},
				callbacks: function( e ) {
					var val = p.val(),
						changed = val !== oldValue,
						defaultArgs = [ val, e, el, options ],
						callback = function( name, criteria, args ) {
							if ( typeof options[ name ] === 'function' && criteria ) {
								options[ name ].apply( this, args );
							}
						};

					callback( 'onChange', changed === true, defaultArgs );
					callback( 'onKeyPress', changed === true, defaultArgs );
					callback( 'onComplete', val.length === mask.length, defaultArgs );
					callback( 'onInvalid', p.invalid.length > 0, [ val, e, el, p.invalid, options ] );
				}
			};

			el = $( el );

			oldValue = p.val();

			mask = typeof mask === 'function' ? mask( p.val(), undefined, el, options ) : mask;

			// public methods
			jMask.mask = mask;
			jMask.options = options;
			jMask.remove = function() {
				var caret = p.getCaret();
				if ( jMask.options.placeholder ) {
					el.removeAttr( 'placeholder' );
				}
				if ( el.data( 'mask-maxlength' ) ) {
					el.removeAttr( 'maxlength' );
				}
				p.destroyEvents();
				p.val( jMask.getCleanVal() );
				p.setCaret( caret );
				return el;
			};

			// get value without mask
			jMask.getCleanVal = function() {
				return p.getMasked( true );
			};

			// get masked value without the value being in the input or element
			jMask.getMaskedVal = function( val ) {
				return p.getMasked( false, val );
			};

			jMask.init = function( onlyMask ) {
				var i;
				var translation;
				var caret;
				var maxlength;

				onlyMask = onlyMask || false;
				options = options || {};

				jMask.clearIfNotMatch = $.jMaskGlobals.clearIfNotMatch;
				jMask.byPassKeys = $.jMaskGlobals.byPassKeys;
				jMask.translation = $.extend( {}, $.jMaskGlobals.translation, options.translation );

				jMask = $.extend( true, {}, jMask, options );

				regexMask = p.getRegexMask();

				if ( onlyMask ) {
					p.events();
					p.val( p.getMasked() );
				} else {
					if ( options.placeholder ) {
						el.attr( 'placeholder', options.placeholder );
					}

					// this is necessary, otherwise if the user submit the form
					// and then press the "back" button, the autocomplete will erase
					// the data. Works fine on IE9+, FF, Opera, Safari.
					if ( el.data( 'mask' ) ) {
						el.attr( 'autocomplete', 'off' );
					}

					// detect if is necessary let the user type freely.
					// for is a lot faster than forEach.
					for ( i = 0, maxlength = true; i < mask.length; i += 1 ) {
						translation = jMask.translation[ mask.charAt( i ) ];
						if ( translation && translation.recursive ) {
							maxlength = false;
							break;
						}
					}

					if ( maxlength ) {
						el.attr( 'maxlength', mask.length ).data( 'mask-maxlength', true );
					}

					p.destroyEvents();
					p.events();

					caret = p.getCaret();
					p.val( p.getMasked() );
					p.setCaret( caret );
				}
			};

			jMask.init( ! el.is( 'input' ) );
		};

		var notSameMaskObject = function( field, mask, options ) {
				var maskObject = $( field ).data( 'mask' ),
					stringify = JSON.stringify,
					value = $( field ).val() || $( field ).text();
				options = options || {};
				try {
					if ( typeof mask === 'function' ) {
						mask = mask( value );
					}
					return typeof maskObject !== 'object' || stringify( maskObject.options ) !== stringify( options ) || maskObject.mask !== mask;
				} catch ( err ) {
					window.console.log( err );
				}
			},
			HTMLAttributes = function() {
				var input = $( this ),
					options = {},
					prefix = 'data-mask-',
					mask = input.attr( 'data-mask' );

				if ( input.attr( prefix + 'reverse' ) ) {
					options.reverse = true;
				}

				if ( input.attr( prefix + 'clearifnotmatch' ) ) {
					options.clearIfNotMatch = true;
				}

				if ( input.attr( prefix + 'selectonfocus' ) === 'true' ) {
					options.selectOnFocus = true;
				}

				if ( notSameMaskObject( input, mask, options ) ) {
					return input.data( 'mask', new Mask( this, mask, options ) );
				}
			},
			eventSupported = function( eventName ) {
				var el = document.createElement( 'div' ),
					isSupported;

				eventName = 'on' + eventName;
				isSupported = eventName in el;

				if ( ! isSupported ) {
					el.setAttribute( eventName, 'return;' );
					isSupported = typeof el[ eventName ] === 'function';
				}
				el = null;

				return isSupported;
			};

		$.maskWatchers = {};

		$.fn.mask = function( mask, options ) {
			var selector = this.selector,
				maskGlobals = $.jMaskGlobals,
				interval = maskGlobals.watchInterval,
				watchInputs,
				maskFunction;
			options = options || {};

			watchInputs = options.watchInputs || maskGlobals.watchInputs;
			maskFunction = function() {
				if ( notSameMaskObject( this, mask, options ) ) {
					return $( this ).data( 'mask', new Mask( this, mask, options ) );
				}
			};

			$( this ).each( maskFunction );

			if ( selector && selector !== '' && watchInputs ) {
				clearInterval( $.maskWatchers[ selector ] );
				$.maskWatchers[ selector ] = setInterval( function() {
					$( document ).find( selector ).each( maskFunction );
				}, interval );
			}
			return this;
		};

		$.fn.masked = function( val ) {
			return this.data( 'mask' ).getMaskedVal( val );
		};

		$.fn.unmask = function() {
			clearInterval( $.maskWatchers[ this.selector ] );
			delete $.maskWatchers[ this.selector ];
			return this.each( function() {
				var dataMask = $( this ).data( 'mask' );
				if ( dataMask ) {
					dataMask.remove().removeData( 'mask' );
				}
			} );
		};

		$.fn.cleanVal = function() {
			return this.data( 'mask' ).getCleanVal();
		};

		$.applyDataMask = function( selector ) {
			var $selector;
			selector = selector || $.jMaskGlobals.maskElements;
			$selector = selector instanceof $ ? selector : $( selector );
			$selector.filter( $.jMaskGlobals.dataMaskAttr ).each( HTMLAttributes );
		};

		globals = {
			maskElements: 'input,td,span,div',
			dataMaskAttr: '*[data-mask]',
			dataMask: true,
			watchInterval: 300,
			watchInputs: true,
			keyStrokeCompensation: 10,
			// old versions of chrome dont work great with input event
			useInput: ! /Chrome\/[2-4][0-9]|SamsungBrowser/.test( window.navigator.userAgent ) && eventSupported( 'input' ),
			watchDataMask: false,
			byPassKeys: [ 9, 16, 17, 18, 36, 37, 38, 39, 40, 91 ],
			translation: {
				0: { pattern: /\d/ },
				9: { pattern: /\d/, optional: true },
				'#': { pattern: /\d/, recursive: true },
				A: { pattern: /[a-zA-Z0-9]/ },
				S: { pattern: /[a-zA-Z]/ }
			}
		};

		$.jMaskGlobals = $.jMaskGlobals || {};
		globals = $.jMaskGlobals = $.extend( true, {}, globals, $.jMaskGlobals );

		// looking for inputs with data-mask attribute
		if ( globals.dataMask ) {
			$.applyDataMask();
		}

		setInterval( function() {
			if ( $.jMaskGlobals.watchDataMask ) {
				$.applyDataMask();
			}
		}, globals.watchInterval );
	},
	window.jQuery,
	window.Zepto
) );
