(function() {

	var d = document, w = window, listener = {}, minFlashVersion = "10.1", ready = false, queue = [];

	if ("undefined" == typeof swfobject) {
		d
				.write('<s'
						+ 'cript type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></s'
						+ 'cript>');
	}

	var utils = {
		// Note: no actual guide
		// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
		guidGenerator : function() {
			var S4 = function() {
				return (((1 + Math.random()) * 0x10000) | 0).toString(16)
						.substring(1);
			};
			return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-"
					+ S4() + S4() + S4());
		},

		toArray : function(obj) {
			if (!obj) {
				return [];
			}
			if (obj.toArray) {
				return obj.toArray();
			}
			try {
				return Array.prototype.slice.call(obj, 0);
			} catch (e) {
				return [ obj ];
			}
		},

		addEventListener : function(elem, event, fn) {
			if (elem.addEventListener) {
				elem.addEventListener(event, fn, false);
			} else {
				elem.attachEvent("on" + event, fn);
			}
		},

		removeEventListener : function(elem, event, fn) {
			if (elem.removeEventListener) {
				elem.removeEventListener(event, fn, false);
			} else {
				elem.detachEvent("on" + event, fn);
			}
		},

		trim : function(str) {
			return (str || "").replace(/^\s+|\s+$/g, "");
		},

		keys : function(obj) {
			var keys = [];
			if (null != obj) {
				for ( var key in obj) {
					if (obj.hasOwnProperty(key)) {
						keys.push(key);
					}
				}
			}
			return keys;
		},

		merge : function(defaults, options) {
			var result = {}, keys = this.keys(defaults);

			if (null != options) {
				for ( var i = 0, optKeys = this.keys(options), len = optKeys.length; i < len; ++i) {
					if (!defaults.hasOwnProperty(optKeys[i])) {
						keys.push(optKeys[i]);
					}
				}
			}

			for ( var i = 0, len = keys.length; i < len; ++i) {
				result[keys[i]] = (null != options && typeof options[keys[i]] != "undefined") ? options[keys[i]]
						: defaults[keys[i]];
			}

			return result;
		},

		toQueryString : function(params) {
			var result = "";

			for ( var i = 0, keys = this.keys(params), len = keys.length; i < len; ++i) {
				result += "&" + encodeURIComponent(keys[i]) + "="
						+ encodeURIComponent(params[keys[i]]);
			}

			return result.substr(1);
		},

		toUrl : function(url, options) {
			return this.trim(url) + "?" + this.toQueryString(options);
		},

		halfPixelTimer : null,
		halfPixelElem : null,
		handlerPointer : null,

		handleHalfPixels : function(elem) {
			this.unbindHalfPixelHandler();
			this.halfPixelElem = elem;
			this.handlerPointer = function() {
				utils.fixHalfPixels();
			};
			this.addEventListener(w, "resize", this.handlerPointer);
			this.fixHalfPixels();
		},

		fixHalfPixels : function(e) {
			w.clearTimeout(this.halfPixelTimer);
			this.halfPixelTimer = w.setTimeout(function() {
				utils.fixHalfPixelsFn();
			}, 500)
		},

		fixHalfPixelsFn : function() {
			var container = this.halfPixelElem, box;

			if (container.getBoundingClientRect) {
				container.style.marginLeft = container.style.marginTop = "";
				try {
					box = container.getBoundingClientRect();
				} catch (e) {
					return;
				}
				checkOffset(box.left, "marginLeft");
				checkOffset(box.top, "marginTop");
			}

			function checkOffset(offset, style) {
				offset = Math.round(offset * 10) / 10;
				var gap = offset % Math.floor(offset);
				if (gap > 0) {
					container.style[style] = -gap + "px";
				}
			}
		},

		unbindHalfPixelHandler : function() {
			if (null != this.handlerPointer) {
				this.removeEventListener(w, "resize", this.handlerPointer);
			}
		},

		isLinux : function() {
			return this.getPlatform().indexOf("linux") > -1;
		},

		isMac : function() {
			return this.getPlatform().indexOf("mac") > -1;
		},

		getPlatform : function() {
			if (navigator && null != navigator.platform) {
				return navigator.platform.toLowerCase();
			}
			return "";
		},

		getBrowser : function() {
			if (navigator && null != navigator.userAgent) {
				return navigator.userAgent.toLowerCase();
			}
			return "";
		},

		createIframe : function(parent, params) {
			var keys = this.keys(params), ifr = d.createElement("IFRAME");
			ifr.frameBorder = 0;
			for ( var i = 0, l = keys.length; i < l; ++i) {
				ifr[keys[i]] = params[keys[i]];
			}
			parent.appendChild(ifr);
			return ifr;
		},

		loaded : function() {
			ready = true;
			utils.handleQueue();
		},

		onload : function(fn, context) {
			if (ready) {
				fn.apply(context || null);
			} else {
				queue.push(function() {
					fn.apply(context || null);
				});
			}
		},

		handleQueue : function() {
			for ( var i = 0, len = queue.length; i < len; ++i) {
				queue.shift()();
			}
		}
	};

	if (d.addEventListener) {
		utils.addEventListener(d, "DOMContentLoaded", utils.loaded);
	}
	utils.addEventListener(w, "load", utils.loaded);

	function Client() {
		this.container = null;
		this.validator = null;
		this.options = null;
	}

	;

	Client.prototype = {

		defaults : {
			locale : "en",
			flashId : "JumioStartClient",
			width : 340,
			height : 260
		},

		status : {
			INCOMPATIBLE_BROWSER : "incompatiblebrowser",
			NO_FLASH : "noflash",
			MINOR_FLASH : "minorflash",
			NO_CAMERA : "nocam",
			OK : "ok"
		},

		events : {
			LOADED : "loaded",
			TOKEN_EXPIRED : "token-expired",
			CAM_INFO : "webcam-access-info",
			CAM_DIALOG : "webcam-access-dialog",
			CAM_NOT_FOUND : "webcam-not-found",
			CAM_DENIED : "webcam-denied",
			CAM_IN_USE : "webcam-in-use",
			START : "detector-start",
			// FIRST_DETECTION: "detector-found-logo",
			FOUND : "detector-found-all",
			CVV : "cvv",
			UPLOAD : "progress",
			UPLOAD_FAILED : "upload-failed",
			COMPLETE : "complete",
			ALL : "all"
		},

		statusEvents : {
			FLASH_STATUS : "flash-status",
			CAM_STATUS : "cam-status",
			BROWSER_STATUS : "browser-status"
		},

		// // VALIDATOR

		flashCapabilities : null,

		getFlashCapabilities : function() {
			if (null == this.flashCapabilities) {
				this.flashCapabilities = {
					installed : swfobject.hasFlashPlayerVersion("1"),
					express : swfobject.hasFlashPlayerVersion("8"),
					isOutdated : false
				};

				// min flash version
				if (!swfobject.hasFlashPlayerVersion(minFlashVersion)) {
					this.flashCapabilities.isOutdated = true;
				}

				if (utils.isMac()) {
					// gtalk plugin issue; no webcam access
					if (utils.getBrowser().indexOf("chrome/") < 0
							&& !swfobject.hasFlashPlayerVersion("10.2")) {
						var list = (navigator || {}).plugins;
						if (list) {
							for ( var i = 0, len = list.length; i < len; ++i) {
								var name = (list[i].name || "").toLowerCase();
								if (name.indexOf("google") > -1
										&& name.indexOf("talk") > -1) {
									this.flashCapabilities.isOutdated = true;
									break;
								}
							}
						}
					}

					// lion access dialog issue; see
					// http://kb2.adobe.com/cps/905/cpsid_90508.html#main_Flash_Player
					if (utils.getBrowser().indexOf("10_7") > -1
							&& !swfobject.hasFlashPlayerVersion("10.3.183")) {
						this.flashCapabilities.isOutdated = true;
						this.flashCapabilities.express = false;
					}
				}
			}

			return this.flashCapabilities;
		},

		validate : function(callback) {
			this.bind(this.statusEvents.FLASH_STATUS, callback);
			this.bind(this.statusEvents.CAM_STATUS, callback);
			this.bind(this.statusEvents.BROWSER_STATUS, callback);

			if (utils.getBrowser().indexOf("chrome/") >= 0) {
				this.fire(this.statusEvents.BROWSER_STATUS,
						this.status.INCOMPATIBLE_BROWSER);
				return;
			}

			var flash = this.getFlashCapabilities();

			if (!flash.installed) {
				this.fire(this.statusEvents.FLASH_STATUS, this.status.NO_FLASH);
				return;
			} else if (flash.isOutdated) {
				this.fire(this.statusEvents.FLASH_STATUS,
						this.status.MINOR_FLASH, flash.express);
				return;
			} else if (flash.gTalkConflict) {
				this.fire(this.statusEvents.FLASH_STATUS,
						this.status.GOOGLE_PLUGIN_CONFLICT);
				return;
			}

			var id = "jumioValidator" + Math.round(Math.random() * 100000000), styles = {
				"position" : "absolute",
				"left" : -1,
				"top" : 0,
				"height" : 1,
				"width" : 1,
				"visibility" : "hidden"
			}, parent = d.createElement("DIV"), child = d.createElement("DIV");

			for ( var i = 0, keys = utils.keys(styles), len = keys.length; i < len; ++i) {
				parent.style[keys[i]] = styles[keys[i]];
			}

			this.validator = child.id = id;
			parent.appendChild(child);

			function embed() {
				d.getElementsByTagName("BODY")[0].appendChild(parent);
				this.embedSwf({
					swf : utils.trim("https://pay.jumio.com/wicket/resource/com.jumio.wicket.resources.swf.ResourcesSwfScope/jumioStreamScan/cameraValidator-ver-5F207FA4DABCD2332CB8FCEF5EA41639.swf"),
					id : id,
					width : 1,
					height : 1,
					params : {
						allowscriptaccess : "always"
					}
				});
			}

			;

			utils.onload(embed, this);
		},

		initCamera : function(exists, w, h) {
			this.fire(this.statusEvents.CAM_STATUS, exists ? this.status.OK
					: this.status.NO_CAMERA, w, h);
			try {
				if (exists) {
					var swf = d.getElementById(this.validator);
					if (null != swf) {
						var parent = swf.parentNode;
						this.removeSWF(this.validator);
						if (null != parent) {
							d.getElementsByTagName("BODY")[0]
									.removeChild(parent);
						}
					}
				}
			} catch (e) {
			}
		},

		// // EVENT PROXY

		bind : function(type, fn, scope) {
			if (typeof fn != "function") {
				return this;
			}

			if (!listener[type]) {
				listener[type] = [];
			}

			listener[type].push({
				handler : fn,
				context : (scope || null)
			});

			return this;
		},

		fire : function(type) {
			var l = utils.toArray(listener[type]).concat(
					utils.toArray(listener[this.events.ALL])), args = utils
					.toArray(arguments);
			for ( var i = 0, len = l.length; i < len; ++i) {
				l[i].handler.apply(l[i].context, args);
			}

			return this;
		},

		unbind : function(type, handler) {
			var l = listener[type];

			if (handler) {

				if (l) {
					for ( var i = 0, len = l.length; i < len; ++i) {
						if (l[i].handler === handler) {
							l.splice(i, 1);
							return this;
						}
					}
				}

			} else {
				delete l;
			}

			return this;
		},

		// // swfobject proxy

		embedSwf : function(options) {
			// proxy to swfobject framework
			var props = utils.merge({
				swf : "",
				id : "",
				width : 340,
				height : 260,
				minFlash : minFlashVersion,
				flashVars : {},
				params : {},
				attr : {},
				onReady : null
			}, options);
			swfobject.embedSWF(props.swf, props.id, props.width, props.height,
					props.minFlash, null, props.flashVars || {}, props.params
							|| {}, props.attr || {}, props.onReady);
		},

		removeSWF : function(id) {
			swfobject.removeSWF(id);
		},

		// // HELPER FUNCTIONS

		// simulates similar behavior as an HTTP redirect
		redirect : function(url) {
			window.location.replace(url);
		},

		// simulates similar behavior as an HTTP redirect
		// redirects the parent of the current windows, e.g. you want to
		// redirect whole page from an iframe
		redirectTop : function(url) {
			window.top.location.replace(url);
		},

		// simulates similar behavior as clicking on a link
		linkRedirect : function(url) {
			window.location.href = url;
		},

		// simulates similar behavior as clicking on a link
		// redirects the parent of the current windows, e.g. you want to
		// redirect whole page from an iframe
		linkRedirectTop : function(url) {
			window.top.location.href = url;
		},

		// // JUMIO CLIENT/START/SCAN

		setVars : function(options) {
			this.options = utils.merge(this.defaults, options);
			return this;
		},

		// // SCAN CLIENT

		initScan : function(container) {
			this.container = container;
			utils.onload(this.loadScan, this);
			return this;
		},

		loadScan : function() {
			this.options.clientUrl = utils.trim("https://pay.jumio.com/wicket/resource/com.jumio.wicket.resources.swf.ResourcesSwfScope/jumioStreamScan/client-ver-1E1CEAF2AAF9F4E9307B427D756BD249.swf");
			if (0 == (this.options.locale || "").length) {
				this.options.locale = "en";
			}

			return this.loadClient();
		},

		initClient : function(container) {
			this.container = container;
			utils.onload(this.loadClient, this);
			return this;
		},

		loadClient : function() {
			if ("string" == typeof this.container) {
				this.container = d.getElementById(this.container);
			}

			if (null == this.container) {
				this.error("container not found");
				return this;
			}

			if ("" == this.container.id) {
				this.container.id = "netswipe-"
						+ Math.round(Math.random() * 100000000);
			}

			var flash = this.getFlashCapabilities();

			if (!flash.installed) {
				this.container.innerHTML = '<div class="jumio-error">You need Adobe Flash to use Netswipe.<br /><br /><a href="http://www.adobe.com/go/getflashplayer" target="_blank">'
						+ '<img src="//www.adobe.com/images/shared/download_buttons/get_flash_player.gif" alt="Get Adobe Flash player" />'
						+ '</a></div>';
				return this;
			} else if (flash.isOutdated) {
				if (flash.express) {
					swfobject
							.showExpressInstall(
									{
										data : "https://pay.jumio.com/wicket/resource/com.jumio.wicket.resources.swf.ResourcesSwfScope/jumioStreamScan/expressInstall-ver-7B65FBFAEC8B2955090389AF60646E8B.swf",
										width : "500",
										height : "200"
									},
									{},
									this.container.id,
									function(result) {
										if (!result.success) {
											d.getElementById(result.id).innerHTML = '<div class="jumio-error">Flash upgrade cancelled.</div>';
										}
									});
				} else {
					this.container.innerHTML = '<div class="jumio-error">Your Adobe Flash Player is outdated. Please upgrade '
							+ 'to the latest version.<br /><br /><a href="http://www.adobe.com/go/getflashplayer" target="_blank">'
							+ '<img src="//www.adobe.com/images/shared/download_buttons/get_flash_player.gif" alt="Get Adobe Flash player" />'
							+ '</a></div>';
				}
				return this;
			}

			var params = {
				wmode : "window",
                bgcolor : "FFFFFF",
				allowscriptaccess : "always"
			};

			var opt = this.options, flashDiv = d.createElement("DIV");
			flashDiv.id = opt.flashId;

			this.container.appendChild(flashDiv);

			opt.streamLocatorUrl = utils.trim(opt.streamLocatorUrl
					|| "https://processing.jumio.com/api/recognition/v1/streamlocator");

			this.embedSwf({
				swf : utils.trim(opt.clientUrl),
				id : opt.flashId,
				width : opt.width,
				height : opt.height,
				flashVars : opt,
				params : params
			});

			utils.handleHalfPixels(this.container);

			return this;
		},

		reload : function() {
			this.removeSWF(this.options.flashId);
			this.loadClient();
		},

		pageview : function(page) {
			page = (page || "").replace("/", "").toLowerCase();
			this.fire(page);
		},

		// // JUMIO START

		initStart : function(container) {
			this.container = container;
			utils.onload(this.loadStart, this);
			return this;
		},

		startUrl : function() {
			if (0 == (this.options.locale || "").length) {
				this.options.locale = "en";
			}
			this.options.referer = w.location.href;
			return utils.toUrl("https://pay.jumio.com/widget/jumio-start/1.0/iframe", this.options);
		},

		loadStart : function() {
			if ("string" == typeof this.container) {
				this.container = d.getElementById(this.container);
			}

			if (null != this.container) {
				var url = this.startUrl();
				var ifr = utils.createIframe(this.container, {
					width : 460,
					height : 450,
					src : url
				});

				utils.handleHalfPixels(ifr);
			}

			return this;
		},

		initForm : function(container) {
			this.container = container;
			utils.onload(this.loadForm, this);
			return this;
		},

		formUrl : function() {
			if (0 == (this.options.locale || "").length) {
				this.options.locale = "en";
			}
			this.options.referer = w.location.href;
			return utils.toUrl("https://pay.jumio.com/widget/jumio-form/1.0/form/" + utils.guidGenerator(), this.options);
		},

		loadForm : function() {
			if ("string" == typeof this.container) {
				this.container = d.getElementById(this.container);
			}

			if (null != this.container) {
				var url = this.formUrl();
				var ifr = utils.createIframe(this.container, {
					width : 960,
					height : 960,
					src : url
				});

				utils.handleHalfPixels(ifr);
			}

			return this;
		},

		error : function(msg) {
			alert("Jumio Netswipe couldn't be loaded.\n\nError:   " + msg);
		}
	};

	w.JumioClient = new Client();
})();

