/*
PNotify 2.0.1 sciactive.com/pnotify/
(C) 2014 Hunter Perrin
license GPL/LGPL/MPL
*/
/*
 * ====== PNotify ======
 *
 * http://sciactive.com/pnotify/
 *
 * Copyright 2009-2014 Hunter Perrin
 *
 * Triple licensed under the GPL, LGPL, and MPL.
 * 	http://gnu.org/licenses/gpl.html
 * 	http://gnu.org/licenses/lgpl.html
 * 	http://mozilla.org/MPL/MPL-1.1.html
 */

// Uses AMD or browser globals for jQuery.
(function (factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as a module.
        define('pnotify', ['jquery'], factory);
    } else {
        // Browser globals
        factory(jQuery);
    }
}(function($){
	var default_stack = {
		dir1: "down",
		dir2: "left",
		push: "top",
		spacing1: 15,
		spacing2: 15,
		context: $("body")
	};
	var timer, // Position all timer.
		body,
		jwindow = $(window);
	// Set global variables.
	var do_when_ready = function(){
		body = $("body");
		PNotify.prototype.options.stack.context = body;
		jwindow = $(window);
		// Reposition the notices when the window resizes.
		jwindow.bind('resize', function(){
			if (timer)
				clearTimeout(timer);
			timer = setTimeout(function(){ PNotify.positionAll(true) }, 10);
		});
	};
	PNotify = function(options){
		this.parseOptions(options);
		this.init();
	};
	$.extend(PNotify.prototype, {
		// The current version of PNotify.
		version: "2.0.1",

		// === Options ===

		// Options defaults.
		options: {
			// The notice's title.
			title: false,
			// Whether to escape the content of the title. (Not allow HTML.)
			title_escape: false,
			// The notice's text.
			text: false,
			// Whether to escape the content of the text. (Not allow HTML.)
			text_escape: false,
			// What styling classes to use. (Can be either jqueryui or bootstrap.)
			styling: "bootstrap3",
			// Additional classes to be added to the notice. (For custom styling.)
			addclass: "",
			// Class to be added to the notice for corner styling.
			cornerclass: "",
			// Display the notice when it is created.
			auto_display: true,
			// Width of the notice.
			width: "290px",
			// Minimum height of the notice. It will expand to fit content.
			min_height: "16px",
			// Type of the notice. "notice", "info", "success", or "error".
			type: "notice",
			// Set icon to true to use the default icon for the selected
			// style/type, false for no icon, or a string for your own icon class.
			icon: true,
			// Opacity of the notice.
			opacity: 1,
			// The animation to use when displaying and hiding the notice. "none",
			// "show", "fade", and "slide" are built in to jQuery. Others require jQuery
			// UI. Use an object with effect_in and effect_out to use different effects.
			animation: "fade",
			// Speed at which the notice animates in and out. "slow", "def" or "normal",
			// "fast" or number of milliseconds.
			animate_speed: "slow",
			// Specify a specific duration of position animation
			position_animate_speed: 500,
			// Display a drop shadow.
			shadow: false,
			// After a delay, remove the notice.
			hide: true,
			// Delay in milliseconds before the notice is removed.
			delay: 8000,
			// Reset the hide timer if the mouse moves over the notice.
			mouse_reset: true,
			// Remove the notice's elements from the DOM after it is removed.
			remove: true,
			// Change new lines to br tags.
			insert_brs: true,
			// Whether to remove notices from the global array.
			destroy: true,
			// The stack on which the notices will be placed. Also controls the
			// direction the notices stack.
			stack: default_stack
		},

		// === Modules ===

		// This object holds all the PNotify modules. They are used to provide
		// additional functionality.
		modules: {},
		// This runs an event on all the modules.
		runModules: function(event, arg){
			var curArg;
			for (var module in this.modules) {
				curArg = ((typeof arg === "object" && module in arg) ? arg[module] : arg);
				if (typeof this.modules[module][event] === 'function')
					this.modules[module][event](this, typeof this.options[module] === 'object' ? this.options[module] : {}, curArg);
			}
		},

		// === Class Variables ===

		state: "initializing", // The state can be "initializing", "opening", "open", "closing", and "closed".
		timer: null, // Auto close timer.
		styles: null,
		elem: null,
		container: null,
		title_container: null,
		text_container: null,
		animating: false, // Stores what is currently being animated (in or out).
		timerHide: false, // Stores whether the notice was hidden by a timer.

		// === Events ===

		init: function(){
			var that = this;

			// First and foremost, we don't want our module objects all referencing the prototype.
			this.modules = {};
			$.extend(true, this.modules, PNotify.prototype.modules);

			// Get our styling object.
			if (typeof this.options.styling === "object") {
				this.styles = this.options.styling;
			} else {
				this.styles = PNotify.styling[this.options.styling];
			}

			// Create our widget.
			// Stop animation, reset the removal timer when the user mouses over.
			this.elem = $("<div />", {
				"class": "ui-pnotify "+this.options.addclass,
				"css": {"display": "none"},
				"mouseenter": function(e){
					if (that.options.mouse_reset && that.animating === "out") {
						if (!that.timerHide)
							return;
						that.cancelRemove();
					}
					// Stop the close timer.
					if (that.options.hide && that.options.mouse_reset) that.cancelRemove();
				},
				"mouseleave": function(e){
					// Start the close timer.
					if (that.options.hide && that.options.mouse_reset) that.queueRemove();
					PNotify.positionAll();
				}
			});
			// Create a container for the notice contents.
			this.container = $("<div />", {"class":
				 this.styles.container+" ui-pnotify-container "+
				   (this.options.type === "error" ? this.styles.error :
				   (this.options.type === "primary" ? this.styles.primary :
				   (this.options.type === "info" ? this.styles.info :
				   (this.options.type === "success" ? this.styles.success :
				   (this.options.type === "warning" ? this.styles.warning :
				   (this.options.type === "danger" ? this.styles.danger :
				   (this.options.type === "alert" ? this.styles.alert :
				   (this.options.type === "system" ? this.styles.system :
				   (this.options.type === "dark" ? this.styles.dark :
				 	this.options.type === "success" ? this.styles.success : this.styles.notice)))))))))})
			
			.appendTo(this.elem);
			if (this.options.cornerclass !== "")
				this.container.removeClass("ui-corner-all").addClass(this.options.cornerclass);
			// Create a drop shadow.
			if (this.options.shadow)
				this.container.addClass("ui-pnotify-shadow");

			// Add the appropriate icon.
			if (this.options.icon !== false) {
				$("<div />", {"class": "ui-pnotify-icon"})
				.append($("<span />", {"class": this.options.icon === true ? 
					(this.options.type === "error" ? this.styles.error_icon : 
					(this.options.type === "primary" ? this.styles.primary_icon : 
					(this.options.type === "info" ? this.styles.info_icon : 
					(this.options.type === "success" ? this.styles.success_icon : 
					(this.options.type === "warning" ? this.styles.warning_icon : 
					(this.options.type === "danger" ? this.styles.danger_icon : 
					(this.options.type === "alert" ? this.styles.alert_icon : 
					(this.options.type === "system" ? this.styles.system_icon : 
					(this.options.type === "dark" ? this.styles.dark_icon : this.styles.notice_icon))))))))) : this.options.icon}))
				.prependTo(this.container);
			}

			// Add a title.
			this.title_container = $("<h4 />", {
				"class": "ui-pnotify-title"
			})
			.appendTo(this.container);
			if (this.options.title === false)
				this.title_container.hide();
			else if (this.options.title_escape)
				this.title_container.text(this.options.title);
			else
				this.title_container.html(this.options.title);

			// Add text.
			this.text_container = $("<div />", {
				"class": "ui-pnotify-text"
			})
			.appendTo(this.container);
			if (this.options.text === false)
				this.text_container.hide();
			else if (this.options.text_escape)
				this.text_container.text(this.options.text);
			else
				this.text_container.html(this.options.insert_brs ? String(this.options.text).replace(/\n/g, "<br />") : this.options.text);

			// Set width and min height.
			if (typeof this.options.width === "string")
				this.elem.css("width", this.options.width);
			if (typeof this.options.min_height === "string")
				this.container.css("min-height", this.options.min_height);


			// Add the notice to the notice array.
			if (this.options.stack.push === "top")
				PNotify.notices = $.merge([this], PNotify.notices);
			else
				PNotify.notices = $.merge(PNotify.notices, [this]);
			// Now position all the notices if they are to push to the top.
			if (this.options.stack.push === "top")
				this.queuePosition(false, 1);

			// Mark the stack so it won't animate the new notice.
			this.options.stack.animation = false;

			// Run the modules.
			this.runModules('init');

			// Display the notice.
			if (this.options.auto_display)
				this.open();
			return this;
		},

		// This function is for updating the notice.
		update: function(options){
			// Save old options.
			var oldOpts = this.options;
			// Then update to the new options.
			this.parseOptions(oldOpts, options);
			// Update the corner class.
			if (this.options.cornerclass !== oldOpts.cornerclass)
				this.container.removeClass("ui-corner-all "+oldOpts.cornerclass).addClass(this.options.cornerclass);
			// Update the shadow.
			if (this.options.shadow !== oldOpts.shadow) {
				if (this.options.shadow)
					this.container.addClass("ui-pnotify-shadow");
				else
					this.container.removeClass("ui-pnotify-shadow");
			}
			// Update the additional classes.
			if (this.options.addclass === false)
				this.elem.removeClass(oldOpts.addclass);
			else if (this.options.addclass !== oldOpts.addclass)
				this.elem.removeClass(oldOpts.addclass).addClass(this.options.addclass);
			// Update the title.
			if (this.options.title === false)
				this.title_container.slideUp("fast");
			else if (this.options.title !== oldOpts.title) {
				if (this.options.title_escape)
					this.title_container.text(this.options.title);
				else
					this.title_container.html(this.options.title);
				if (oldOpts.title === false)
					this.title_container.slideDown(200)
			}
			// Update the text.
			if (this.options.text === false) {
				this.text_container.slideUp("fast");
			} else if (this.options.text !== oldOpts.text) {
				if (this.options.text_escape)
					this.text_container.text(this.options.text);
				else
					this.text_container.html(this.options.insert_brs ? String(this.options.text).replace(/\n/g, "<br />") : this.options.text);
				if (oldOpts.text === false)
					this.text_container.slideDown(200)
			}
			// Change the notice type.
			if (this.options.type !== oldOpts.type)
				this.container.removeClass(
					this.styles.error+" "+this.styles.notice+" "+this.styles.primary+" "+this.styles.info+" "+this.styles.success+" "+this.styles.warning+" "+this.styles.danger+" "+this.styles.alert+" "+this.styles.system+" "+this.styles.dark
				).addClass(
					 this.options.type === "error" ? this.styles.error :
					 (this.options.type === "primary" ? this.styles.primary :
					 (this.options.type === "info" ? this.styles.info :
					 (this.options.type === "success" ? this.styles.success :
					 (this.options.type === "warning" ? this.styles.warning :
					 (this.options.type === "danger" ? this.styles.danger :
					 (this.options.type === "alert" ? this.styles.alert :
					 (this.options.type === "system" ? this.styles.system :
					 (this.options.type === "dark" ? this.styles.dark :
													 this.styles.notice
												 ) 
											 )
										 )
									 )
								 )
							 )
						 )
					 )										     	
				);
			if (this.options.icon !== oldOpts.icon || (this.options.icon === true && this.options.type !== oldOpts.type)) {
				// Remove any old icon.
				this.container.find("div.ui-pnotify-icon").remove();
				if (this.options.icon !== false) {
					// Build the new icon.
					$("<div />", {"class": "ui-pnotify-icon"})
					.append($("<span />", {"class":
					 	 this.options.icon === true ?
						 (this.options.type === "error" ? this.styles.error_icon :
						 (this.options.type === "primary" ? this.styles.primary_icon :
						 (this.options.type === "info" ? this.styles.info_icon :
						 (this.options.type === "success" ? this.styles.success_icon :
						 (this.options.type === "warning" ? this.styles.warning_icon :
						 (this.options.type === "danger" ? this.styles.danger_icon :
						 (this.options.type === "alert" ? this.styles.alert_icon :
						 (this.options.type === "system" ? this.styles.system_icon :
						 (this.options.type === "dark" ? this.styles.dark_icon : this.styles.notice_icon))))))))) 
						 : this.options.icon}))
					.prependTo(this.container);
				}
			}
			// Update the width.
			if (this.options.width !== oldOpts.width)
				this.elem.animate({width: this.options.width});
			// Update the minimum height.
			if (this.options.min_height !== oldOpts.min_height)
				this.container.animate({minHeight: this.options.min_height});
			// Update the opacity.
			if (this.options.opacity !== oldOpts.opacity)
				this.elem.fadeTo(this.options.animate_speed, this.options.opacity);
			// Update the timed hiding.
			if (!this.options.hide)
				this.cancelRemove();
			else if (!oldOpts.hide)
				this.queueRemove();
			this.queuePosition(true);

			// Run the modules.
			this.runModules('update', oldOpts);
			return this;
		},

		// Display the notice.
		open: function(){
			this.state = "opening";
			// Run the modules.
			this.runModules('beforeOpen');

			var that = this;
			// If the notice is not in the DOM, append it.
			if (!this.elem.parent().length)
				this.elem.appendTo(this.options.stack.context ? this.options.stack.context : body);
			// Try to put it in the right position.
			if (this.options.stack.push !== "top")
				this.position(true);
			// First show it, then set its opacity, then hide it.
			if (this.options.animation === "fade" || this.options.animation.effect_in === "fade") {
				// If it's fading in, it should start at 0.
				this.elem.show().fadeTo(0, 0).hide();
			} else {
				// Or else it should be set to the opacity.
				if (this.options.opacity !== 1)
					this.elem.show().fadeTo(0, this.options.opacity).hide();
			}
			this.animateIn(function(){
				that.queuePosition(true);

				// Now set it to hide.
				if (that.options.hide)
					that.queueRemove();

				that.state = "open";

				// Run the modules.
				that.runModules('afterOpen');
			});

			return this;
		},

		// Remove the notice.
		remove: function(timer_hide) {
			this.state = "closing";
			this.timerHide = !!timer_hide; // Make sure it's a boolean.
			// Run the modules.
			this.runModules('beforeClose');

			var that = this;
			if (this.timer) {
				window.clearTimeout(this.timer);
				this.timer = null;
			}
			this.animateOut(function(){
				that.state = "closed";
				// Run the modules.
				that.runModules('afterClose');
				that.queuePosition(true);
				// If we're supposed to remove the notice from the DOM, do it.
				if (that.options.remove)
					that.elem.detach();
				// Run the modules.
				that.runModules('beforeDestroy');
				// Remove object from PNotify.notices to prevent memory leak (issue #49)
				// unless destroy is off
				if (that.options.destroy) {
					if (PNotify.notices !== null) {
						var idx = $.inArray(that,PNotify.notices);
						if (idx !== -1) {
							PNotify.notices.splice(idx,1);
						}
					}
				}
				// Run the modules.
				that.runModules('afterDestroy');
			});

			return this;
		},

		// === Class Methods ===

		// Get the DOM element.
		get: function(){ return this.elem; },

		// Put all the options in the right places.
		parseOptions: function(options, moreOptions){
			this.options = $.extend(true, {}, PNotify.prototype.options);
			// This is the only thing that *should* be copied by reference.
			this.options.stack = PNotify.prototype.options.stack;
			var optArray = [options, moreOptions], curOpts;
			for (var curIndex in optArray) {
				curOpts = optArray[curIndex];
				if (typeof curOpts == "undefined")
					break;
				if (typeof curOpts !== 'object') {
					this.options.text = curOpts;
				} else {
					for (var option in curOpts) {
						if (this.modules[option]) {
							// Avoid overwriting module defaults.
							$.extend(true, this.options[option], curOpts[option]);
						} else {
							this.options[option] = curOpts[option];
						}
					}
				}
			}
		},

		// Animate the notice in.
		animateIn: function(callback){
			// Declare that the notice is animating in. (Or has completed animating in.)
			this.animating = "in";
			var animation;
			if (typeof this.options.animation.effect_in !== "undefined")
				animation = this.options.animation.effect_in;
			else
				animation = this.options.animation;
			if (animation === "none") {
				this.elem.show();
				callback();
			} else if (animation === "show")
				this.elem.show(this.options.animate_speed, callback);
			else if (animation === "fade")
				this.elem.show().fadeTo(this.options.animate_speed, this.options.opacity, callback);
			else if (animation === "slide")
				this.elem.slideDown(this.options.animate_speed, callback);
			else if (typeof animation === "function")
				animation("in", callback, this.elem);
			else
				this.elem.show(animation, (typeof this.options.animation.options_in === "object" ? this.options.animation.options_in : {}), this.options.animate_speed, callback);
			if (this.elem.parent().hasClass('ui-effects-wrapper'))
				this.elem.parent().css({"position": "fixed", "overflow": "visible"});
			if (animation !== "slide")
				this.elem.css("overflow", "visible");
			this.container.css("overflow", "hidden");
		},

		// Animate the notice out.
		animateOut: function(callback){
			// Declare that the notice is animating out. (Or has completed animating out.)
			this.animating = "out";
			var animation;
			if (typeof this.options.animation.effect_out !== "undefined")
				animation = this.options.animation.effect_out;
			else
				animation = this.options.animation;
			if (animation === "none") {
				this.elem.hide();
				callback();
			} else if (animation === "show")
				this.elem.hide(this.options.animate_speed, callback);
			else if (animation === "fade")
				this.elem.fadeOut(this.options.animate_speed, callback);
			else if (animation === "slide")
				this.elem.slideUp(this.options.animate_speed, callback);
			else if (typeof animation === "function")
				animation("out", callback, this.elem);
			else
				this.elem.hide(animation, (typeof this.options.animation.options_out === "object" ? this.options.animation.options_out : {}), this.options.animate_speed, callback);
			if (this.elem.parent().hasClass('ui-effects-wrapper'))
				this.elem.parent().css({"position": "fixed", "overflow": "visible"});
			if (animation !== "slide")
				this.elem.css("overflow", "visible");
			this.container.css("overflow", "hidden");
		},

		// Position the notice. dont_skip_hidden causes the notice to
		// position even if it's not visible.
		position: function(dontSkipHidden){
			// Get the notice's stack.
			var s = this.options.stack,
				e = this.elem;
			if (e.parent().hasClass('ui-effects-wrapper'))
				e = this.elem.css({"left": "0", "top": "0", "right": "0", "bottom": "0"}).parent();
			if (typeof s.context === "undefined")
				s.context = body;
			if (!s) return;
			if (typeof s.nextpos1 !== "number")
				s.nextpos1 = s.firstpos1;
			if (typeof s.nextpos2 !== "number")
				s.nextpos2 = s.firstpos2;
			if (typeof s.addpos2 !== "number")
				s.addpos2 = 0;
			var hidden = e.css("display") === "none";
			// Skip this notice if it's not shown.
			if (!hidden || dontSkipHidden) {
				var curpos1, curpos2;
				// Store what will need to be animated.
				var animate = {};
				// Calculate the current pos1 value.
				var csspos1;
				switch (s.dir1) {
					case "down":
						csspos1 = "top";
						break;
					case "up":
						csspos1 = "bottom";
						break;
					case "left":
						csspos1 = "right";
						break;
					case "right":
						csspos1 = "left";
						break;
				}
				curpos1 = parseInt(e.css(csspos1).replace(/(?:\..*|[^0-9.])/g, ''));
				if (isNaN(curpos1))
					curpos1 = 0;
				// Remember the first pos1, so the first visible notice goes there.
				if (typeof s.firstpos1 === "undefined" && !hidden) {
					s.firstpos1 = curpos1;
					s.nextpos1 = s.firstpos1;
				}
				// Calculate the current pos2 value.
				var csspos2;
				switch (s.dir2) {
					case "down":
						csspos2 = "top";
						break;
					case "up":
						csspos2 = "bottom";
						break;
					case "left":
						csspos2 = "right";
						break;
					case "right":
						csspos2 = "left";
						break;
				}
				curpos2 = parseInt(e.css(csspos2).replace(/(?:\..*|[^0-9.])/g, ''));
				if (isNaN(curpos2))
					curpos2 = 0;
				// Remember the first pos2, so the first visible notice goes there.
				if (typeof s.firstpos2 === "undefined" && !hidden) {
					s.firstpos2 = curpos2;
					s.nextpos2 = s.firstpos2;
				}
				// Check that it's not beyond the viewport edge.
				if ((s.dir1 === "down" && s.nextpos1 + e.height() > (s.context.is(body) ? jwindow.height() : s.context.prop('scrollHeight')) ) ||
					(s.dir1 === "up" && s.nextpos1 + e.height() > (s.context.is(body) ? jwindow.height() : s.context.prop('scrollHeight')) ) ||
					(s.dir1 === "left" && s.nextpos1 + e.width() > (s.context.is(body) ? jwindow.width() : s.context.prop('scrollWidth')) ) ||
					(s.dir1 === "right" && s.nextpos1 + e.width() > (s.context.is(body) ? jwindow.width() : s.context.prop('scrollWidth')) ) ) {
					// If it is, it needs to go back to the first pos1, and over on pos2.
					s.nextpos1 = s.firstpos1;
					s.nextpos2 += s.addpos2 + (typeof s.spacing2 === "undefined" ? 25 : s.spacing2);
					s.addpos2 = 0;
				}
				// Animate if we're moving on dir2.
				if (s.animation && s.nextpos2 < curpos2) {
					switch (s.dir2) {
						case "down":
							animate.top = s.nextpos2+"px";
							break;
						case "up":
							animate.bottom = s.nextpos2+"px";
							break;
						case "left":
							animate.right = s.nextpos2+"px";
							break;
						case "right":
							animate.left = s.nextpos2+"px";
							break;
					}
				} else {
					if(typeof s.nextpos2 === "number")
						e.css(csspos2, s.nextpos2+"px");
				}
				// Keep track of the widest/tallest notice in the column/row, so we can push the next column/row.
				switch (s.dir2) {
					case "down":
					case "up":
						if (e.outerHeight(true) > s.addpos2)
							s.addpos2 = e.height();
						break;
					case "left":
					case "right":
						if (e.outerWidth(true) > s.addpos2)
							s.addpos2 = e.width();
						break;
				}
				// Move the notice on dir1.
				if (typeof s.nextpos1 === "number") {
					// Animate if we're moving toward the first pos.
					if (s.animation && (curpos1 > s.nextpos1 || animate.top || animate.bottom || animate.right || animate.left)) {
						switch (s.dir1) {
							case "down":
								animate.top = s.nextpos1+"px";
								break;
							case "up":
								animate.bottom = s.nextpos1+"px";
								break;
							case "left":
								animate.right = s.nextpos1+"px";
								break;
							case "right":
								animate.left = s.nextpos1+"px";
								break;
						}
					} else
						e.css(csspos1, s.nextpos1+"px");
				}
				// Run the animation.
				if (animate.top || animate.bottom || animate.right || animate.left)
					e.animate(animate, {duration: this.options.position_animate_speed, queue: false});
				// Calculate the next dir1 position.
				switch (s.dir1) {
					case "down":
					case "up":
						s.nextpos1 += e.height() + (typeof s.spacing1 === "undefined" ? 25 : s.spacing1);
						break;
					case "left":
					case "right":
						s.nextpos1 += e.width() + (typeof s.spacing1 === "undefined" ? 25 : s.spacing1);
						break;
				}
			}
			return this;
		},
		// Queue the position all function so it doesn't run repeatedly and
		// use up resources.
		queuePosition: function(animate, milliseconds){
			if (timer)
				clearTimeout(timer);
			if (!milliseconds)
				milliseconds = 10;
			timer = setTimeout(function(){ PNotify.positionAll(animate) }, milliseconds);
			return this;
		},


		// Cancel any pending removal timer.
		cancelRemove: function(){
			if (this.timer)
				window.clearTimeout(this.timer);
			if (this.state === "closing") {
				// If it's animating out, animate back in really quickly.
				this.elem.stop(true);
				this.state = "open";
				this.animating = "in";
				this.elem.css("height", "auto").animate({"width": this.options.width, "opacity": this.options.opacity}, "fast");
			}
			return this;
		},
		// Queue a removal timer.
		queueRemove: function(){
			var that = this;
			// Cancel any current removal timer.
			this.cancelRemove();
			this.timer = window.setTimeout(function(){
				that.remove(true);
			}, (isNaN(this.options.delay) ? 0 : this.options.delay));
			return this;
		}
	});
	// These functions affect all notices.
	$.extend(PNotify, {
		// This holds all the notices.
		notices: [],
		removeAll: function () {
			$.each(PNotify.notices, function(){
				if (this.remove)
					this.remove();
			});
		},
		positionAll: function (animate) {
			// This timer is used for queueing this function so it doesn't run
			// repeatedly.
			if (timer)
				clearTimeout(timer);
			timer = null;
			// Reset the next position data.
			$.each(PNotify.notices, function(){
				var s = this.options.stack;
				if (!s) return;
				s.nextpos1 = s.firstpos1;
				s.nextpos2 = s.firstpos2;
				s.addpos2 = 0;
				s.animation = animate;
			});
			$.each(PNotify.notices, function(){
				this.position();
			});
		},
		styling: {
			jqueryui: {
				container: "ui-widget ui-widget-content ui-corner-all",
				notice: "ui-state-highlight",
				// (The actual jQUI notice icon looks terrible.)
				notice_icon: "ui-icon ui-icon-info",
				info: "",
				info_icon: "ui-icon ui-icon-info",
				success: "ui-state-default",
				success_icon: "ui-icon ui-icon-circle-check",
				error: "ui-state-error",
				error_icon: "ui-icon ui-icon-alert"
			},
			bootstrap2: {
				container: "alert",
				notice: "",
				notice_icon: "icon-exclamation-sign",
				info: "alert-info",
				info_icon: "icon-info-sign",
				success: "alert-success",
				success_icon: "icon-ok-sign",
				error: "alert-error",
				error_icon: "icon-warning-sign"
			},
			bootstrap3: {
				container: "alert",
				notice: "alert-default",
				notice_icon: "glyphicon glyphicon-exclamation-sign",
				error: "alert-danger",
				error_icon: "glyphicon glyphicon-warning-sign",
				primary: "alert-primary",
				primary_icon: "glyphicon glyphicon-exclamation-sign",
				info: "alert-info",
				info_icon: "glyphicon glyphicon-info-sign",
				success: "alert-success",
				success_icon: "glyphicon glyphicon-ok-sign",
				warning: "alert-warning",
				warning_icon: "glyphicon glyphicon-ok-sign",
				danger: "alert-danger",
				danger_icon: "glyphicon glyphicon-warning-sign",
				alert: "alert-alert",
				alert_icon: "glyphicon glyphicon-warning-sign",
				system: "alert-system",
				system_icon: "glyphicon glyphicon-warning-sign",
				dark: "alert-dark",
				dark_icon: "glyphicon glyphicon-warning-sign",
			}
		}
	});
	/*
	 * uses icons from http://fontawesome.io/
	 * version 4.0.3
	 */
	PNotify.styling.fontawesome = $.extend({}, PNotify.styling.bootstrap3);
	$.extend(PNotify.styling.fontawesome, {
		notice_icon: "fa fa-exclamation-circle",
		error_icon: "fa fa-warning",
		primary_icon: "fa fa-info",
		info_icon: "fa fa-info",
		success_icon: "fa fa-check",
		warning_icon: "fa fa-check",
		danger_icon: "fa fa-info",
		alert_icon: "fa fa-check",
		system_icon: "fa fa-warning",
		dark_icon: "fa fa-warning",
	});

	if (document.body)
		do_when_ready();
	else
		$(do_when_ready);
	return PNotify;
}));
// Buttons
// Uses AMD or browser globals for jQuery.
(function (factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as a module.
        define('pnotify.buttons', ['jquery', 'pnotify'], factory);
    } else {
        // Browser globals
        factory(jQuery, PNotify);
    }
}(function($, PNotify){
	PNotify.prototype.options.buttons = {
		// Provide a button for the user to manually close the notice.
		closer: true,
		// Only show the closer button on hover.
		closer_hover: true,
		// Provide a button for the user to manually stick the notice.
		sticker: true,
		// Only show the sticker button on hover.
		sticker_hover: true,
		// The various displayed text, helps facilitating internationalization.
		labels: {
			close: "Close",
			stick: "Stick"
		}
	};
	PNotify.prototype.modules.buttons = {
		// This lets us update the options available in the closures.
		myOptions: null,

		closer: null,
		sticker: null,

		init: function(notice, options){
			var that = this;
			this.myOptions = options;
			notice.elem.on({
				"mouseenter": function(e){
					// Show the buttons.
					if (that.myOptions.sticker && !(notice.options.nonblock && notice.options.nonblock.nonblock)) that.sticker.trigger("pnotify_icon").css("visibility", "visible");
					if (that.myOptions.closer && !(notice.options.nonblock && notice.options.nonblock.nonblock)) that.closer.css("visibility", "visible");
				},
				"mouseleave": function(e){
					// Hide the buttons.
					if (that.myOptions.sticker_hover)
						that.sticker.css("visibility", "hidden");
					if (that.myOptions.closer_hover)
						that.closer.css("visibility", "hidden");
				}
			});

			// Provide a button to stick the notice.
			this.sticker = $("<div />", {
				"class": "ui-pnotify-sticker",
				"css": {"cursor": "pointer", "visibility": options.sticker_hover ? "hidden" : "visible"},
				"click": function(){
					notice.options.hide = !notice.options.hide;
					if (notice.options.hide)
						notice.queueRemove();
					else
						notice.cancelRemove();
					$(this).trigger("pnotify_icon");
				}
			})
			.bind("pnotify_icon", function(){
				$(this).children().removeClass(notice.styles.pin_up+" "+notice.styles.pin_down).addClass(notice.options.hide ? notice.styles.pin_up : notice.styles.pin_down);
			})
			.append($("<span />", {"class": notice.styles.pin_up, "title": options.labels.stick}))
			.prependTo(notice.container);
			if (!options.sticker || (notice.options.nonblock && notice.options.nonblock.nonblock))
				this.sticker.css("display", "none");

			// Provide a button to close the notice.
			this.closer = $("<div />", {
				"class": "ui-pnotify-closer",
				"css": {"cursor": "pointer", "visibility": options.closer_hover ? "hidden" : "visible"},
				"click": function(){
					notice.remove(false);
					that.sticker.css("visibility", "hidden");
					that.closer.css("visibility", "hidden");
				}
			})
			.append($("<span />", {"class": notice.styles.closer, "title": options.labels.close}))
			.prependTo(notice.container);
			if (!options.closer || (notice.options.nonblock && notice.options.nonblock.nonblock))
				this.closer.css("display", "none");
		},
		update: function(notice, options){
			this.myOptions = options;
			// Update the sticker and closer buttons.
			if (!options.closer || (notice.options.nonblock && notice.options.nonblock.nonblock))
				this.closer.css("display", "none");
			else if (options.closer)
				this.closer.css("display", "block");
			if (!options.sticker || (notice.options.nonblock && notice.options.nonblock.nonblock))
				this.sticker.css("display", "none");
			else if (options.sticker)
				this.sticker.css("display", "block");
			// Update the sticker icon.
			this.sticker.trigger("pnotify_icon");
			// Update the hover status of the buttons.
			if (options.sticker_hover)
				this.sticker.css("visibility", "hidden");
			else if (!(notice.options.nonblock && notice.options.nonblock.nonblock))
				this.sticker.css("visibility", "visible");
			if (options.closer_hover)
				this.closer.css("visibility", "hidden");
			else if (!(notice.options.nonblock && notice.options.nonblock.nonblock))
				this.closer.css("visibility", "visible");
		}
	};
	$.extend(PNotify.styling.jqueryui, {
		closer: "ui-icon ui-icon-close",
		pin_up: "ui-icon ui-icon-pin-w",
		pin_down: "ui-icon ui-icon-pin-s"
	});
	$.extend(PNotify.styling.bootstrap2, {
		closer: "icon-remove",
		pin_up: "icon-pause",
		pin_down: "icon-play"
	});
	$.extend(PNotify.styling.bootstrap3, {
		closer: "glyphicon glyphicon-remove",
		pin_up: "glyphicon glyphicon-pause",
		pin_down: "glyphicon glyphicon-play"
	});
	$.extend(PNotify.styling.fontawesome, {
		closer: "fa fa-times",
		pin_up: "fa fa-pause",
		pin_down: "fa fa-play"
	});
}));
// Confirm
// Uses AMD or browser globals for jQuery.
(function (factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as a module.
        define('pnotify.confirm', ['jquery', 'pnotify'], factory);
    } else {
        // Browser globals
        factory(jQuery, PNotify);
    }
}(function($, PNotify){
	PNotify.prototype.options.confirm = {
		// Make a confirmation box.
		confirm: false,
		// Make a prompt.
		prompt: false,
		// Classes to add to the input element of the prompt.
		prompt_class: "",
		// The default value of the prompt.
		prompt_default: "",
		// Whether the prompt should accept multiple lines of text.
		prompt_multi_line: false,
		// Where to align the buttons. (right, center, left, justify)
		align: "right",
		// The buttons to display, and their callbacks.
		buttons: [
			{
				text: "Ok",
				addClass: "",
				// Whether to trigger this button when the user hits enter in a single line prompt.
				promptTrigger: true,
				click: function(notice, value){
					notice.remove();
					notice.get().trigger("pnotify.confirm", [notice, value]);
				}
			},
			{
				text: "Cancel",
				addClass: "",
				click: function(notice){
					notice.remove();
					notice.get().trigger("pnotify.cancel", notice);
				}
			}
		]
	};
	PNotify.prototype.modules.confirm = {
		// The div that contains the buttons.
		container: null,
		// The input element of a prompt.
		prompt: null,

		init: function(notice, options){
			this.container = $('<div style="margin-top:5px;clear:both;" />').css('text-align', options.align).appendTo(notice.container);

			if (options.confirm || options.prompt)
				this.makeDialog(notice, options);
			else
				this.container.hide();
		},

		update: function(notice, options){
			if (options.confirm) {
				this.makeDialog(notice, options);
				this.container.show();
			} else {
				this.container.hide().empty();
			}
		},

		afterOpen: function(notice, options){
			if (options.prompt)
				this.prompt.focus();
		},

		makeDialog: function(notice, options) {
			var already = false, that = this, btn, elem;
			this.container.empty();
			if (options.prompt) {
				this.prompt = $('<'+(options.prompt_multi_line ? 'textarea rows="5"' : 'input type="text"')+' style="margin-bottom:5px;clear:both;" />')
				.addClass(notice.styles.input+' '+options.prompt_class)
				.val(options.prompt_default)
				.appendTo(this.container);
			}
			for (var i in options.buttons) {
				btn = options.buttons[i];
				if (already)
					this.container.append(' ');
				else
					already = true;
				elem = $('<button type="button" />')
				.addClass(notice.styles.btn+' '+btn.addClass)
				.text(btn.text)
				.appendTo(this.container)
				.on("click", (function(btn){ return function(){
					if (typeof btn.click == "function") {
						btn.click(notice, options.prompt ? that.prompt.val() : null);
					}
				}})(btn));
				if (options.prompt && !options.prompt_multi_line && btn.promptTrigger)
					this.prompt.keypress((function(elem){ return function(e){
						if (e.keyCode == 13)
							elem.click();
					}})(elem));
				if (notice.styles.text) {
					elem.wrapInner('<span class="'+notice.styles.text+'"></span>');
				}
				if (notice.styles.btnhover) {
					elem.hover((function(elem){ return function(){
						elem.addClass(notice.styles.btnhover);
					}})(elem), (function(elem){ return function(){
						elem.removeClass(notice.styles.btnhover);
					}})(elem));
				}
				if (notice.styles.btnactive) {
					elem.on("mousedown", (function(elem){ return function(){
						elem.addClass(notice.styles.btnactive);
					}})(elem)).on("mouseup", (function(elem){ return function(){
						elem.removeClass(notice.styles.btnactive);
					}})(elem));
				}
				if (notice.styles.btnfocus) {
					elem.on("focus", (function(elem){ return function(){
						elem.addClass(notice.styles.btnfocus);
					}})(elem)).on("blur", (function(elem){ return function(){
						elem.removeClass(notice.styles.btnfocus);
					}})(elem));
				}
			}
		}
	};
	$.extend(PNotify.styling.jqueryui, {
		btn: "ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only",
		btnhover: "ui-state-hover",
		btnactive: "ui-state-active",
		btnfocus: "ui-state-focus",
		input: "",
		text: "ui-button-text"
	});
	$.extend(PNotify.styling.bootstrap2, {
		btn: "btn",
		input: ""
	});
	$.extend(PNotify.styling.bootstrap3, {
		btn: "btn btn-default",
		input: "form-control"
	});
	$.extend(PNotify.styling.fontawesome, {
		btn: "btn btn-default",
		input: "form-control"
	});
}));
// Desktop
// Uses AMD or browser globals for jQuery.
(function (factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as a module.
        define('pnotify.desktop', ['jquery', 'pnotify'], factory);
    } else {
        // Browser globals
        factory(jQuery, PNotify);
    }
}(function($, PNotify){
	var permission;
	var notify = function(title, options){
		// Memoize based on feature detection.
		if ("Notification" in window) {
			notify = function (title, options) {
				return new Notification(title, options);
			};
		} else if ("mozNotification" in navigator) {
			notify = function (title, options) {
				// Gecko < 22
				return navigator.mozNotification
					.createNotification(title, options.body, options.icon)
					.show();
			};
		} else if ("webkitNotifications" in window) {
			notify = function (title, options) {
				return window.webkitNotifications.createNotification(
					options.icon,
					title,
					options.body
				);
			};
		} else {
			notify = function (title, options) {
				return null;
			};
		}
		return notify(title, options);
	};


	PNotify.prototype.options.desktop = {
		// Display the notification as a desktop notification.
		desktop: false,
		// The URL of the icon to display. If false, no icon will show. If null, a default icon will show.
		icon: null,
		// Using a tag lets you update an existing notice, or keep from duplicating notices between tabs.
		// If you leave tag null, one will be generated, facilitating the "update" function.
		// see: http://www.w3.org/TR/notifications/#tags-example
		tag: null
	};
	PNotify.prototype.modules.desktop = {
		tag: null,
		icon: null,
		genNotice: function(notice, options){
			if (options.icon === null) {
				this.icon = "http://sciactive.com/pnotify/includes/desktop/"+notice.options.type+".png";
			} else if (options.icon === false) {
				this.icon = null;
			} else {
				this.icon = options.icon;
			}
			if (this.tag === null || options.tag !== null) {
				this.tag = options.tag === null ? "PNotify-"+Math.round(Math.random() * 1000000) : options.tag;
			}
			notice.desktop = notify(notice.options.title, {
				icon: this.icon,
				body: notice.options.text,
				tag: this.tag
			});
			if (!("close" in notice.desktop)) {
				notice.desktop.close = function(){
					notice.desktop.cancel();
				};
			}
			notice.desktop.onclick = function(){
				notice.elem.trigger("click");
			};
			notice.desktop.onclose = function(){
				if (notice.state !== "closing" && notice.state !== "closed") {
					notice.remove();
				}
			};
		},
		init: function(notice, options){
			if (!options.desktop)
				return;
			permission = PNotify.desktop.checkPermission();
			if (permission != 0)
				return;
			this.genNotice(notice, options);
		},
		update: function(notice, options, oldOpts){
			if (permission != 0 || !options.desktop)
				return;
			this.genNotice(notice, options);
		},
		beforeOpen: function(notice, options){
			if (permission != 0 || !options.desktop)
				return;
			notice.elem.css({'left': '-10000px', 'display': 'none'});
		},
		afterOpen: function(notice, options){
			if (permission != 0 || !options.desktop)
				return;
			notice.elem.css({'left': '-10000px', 'display': 'none'});
			if ("show" in notice.desktop) {
				notice.desktop.show();
			}
		},
		beforeClose: function(notice, options){
			if (permission != 0 || !options.desktop)
				return;
			notice.elem.css({'left': '-10000px', 'display': 'none'});
		},
		afterClose: function(notice, options){
			if (permission != 0 || !options.desktop)
				return;
			notice.elem.css({'left': '-10000px', 'display': 'none'});
			notice.desktop.close();
		}
	};
	PNotify.desktop = {
		permission: function(){
			if (typeof Notification !== "undefined" && "requestPermission" in Notification) {
				Notification.requestPermission();
			} else if ("webkitNotifications" in window) {
				window.webkitNotifications.requestPermission();
			}
		},
		checkPermission: function(){
			if (typeof Notification !== "undefined" && "permission" in Notification) {
				return (Notification.permission == "granted" ? 0 : 1);
			} else if ("webkitNotifications" in window) {
				return window.webkitNotifications.checkPermission();
			} else {
				return 1;
			}
		}
	};
	permission = PNotify.desktop.checkPermission();
}));
